maddy-admin/src/lib/actions.ts
2024-06-16 23:35:53 +02:00

281 lines
8.5 KiB
TypeScript

"use server";
import crypto from "crypto";
import fs from "fs/promises";
import { auth } from "@/auth";
import { AuditLog, auditLog } from "./audit";
import { AliasEntry, AliasRequestEntry, ApiKeyEntry, approveAliasEntry, createAliasEntry, createApiKeyEntry, createTempAliasRequestEntry, createUserEntry, database, deleteAliasEntry, deleteApiKey, deleteTempAliasRequestEntry, getAlias, getAllAliases, getApiKeyById, getTempAliasRequestEntry, getUserAliases, getUserApiKeys, isAliasAvailable, setUserPassword } from "./db";
import { aliasesNeedApproval, anonymizeApiKey, isAdmin } from "./util";
import { generateAliasEmail } from "./util-server";
export async function fetchAllUsers(): Promise<string[]> {
return new Promise(async (resolve, reject) => {
const session = await auth();
if (!isAdmin(session)) return reject("Unauthenticated");
const db = database('credentials');
db.all("SELECT key FROM passwords", (err, res: any) => {
if (err) return reject(err);
resolve(res.map((row: any) => row.key));
});
});
}
export async function changeOwnPassword(newPass: string) {
if (newPass.length < 8) throw new Error("Invalid password");
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
await setUserPassword(session.user.email, newPass);
auditLog("changeOwnPassword");
}
export async function fetchOwnAliases(tempAliases?: boolean) {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
return await getUserAliases(session.user.email, tempAliases);
}
export async function fetchUserAliases(email: string) {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
if (!isAdmin(session)) throw new Error("Unauthorized");
return await getUserAliases(email);
}
export async function fetchAllAliases() {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
if (!isAdmin(session)) throw new Error("Unauthorized");
return await getAllAliases();
}
export async function aliasAvailable(email: string, searchTempRequests: boolean = false) {
const session = await auth();
if (!session?.user) throw new Error("Unauthenticated");
return await isAliasAvailable(email, searchTempRequests);
}
export async function createAlias(user: string, alias: string): Promise<AliasEntry> {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
if (!isAdmin(session)) throw new Error("Unauthenticated");
if (!await isAliasAvailable(alias)) throw new Error("Alias unavailable");
const id = await createAliasEntry(user, alias.toLowerCase(), false);
const res = {
id: id,
address: user,
alias: alias,
pending: false,
temporary: false,
};
auditLog('createAlias', res);
return res;
}
export async function createAliasSelf(alias: string): Promise<AliasEntry> {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
const pending = aliasesNeedApproval(session);
if (!await isAliasAvailable(alias)) throw new Error("Alias unavailable");
const id = await createAliasEntry(session.user.email, alias.toLowerCase(), pending);
const res = {
id: id,
address: session.user.email,
alias: alias,
pending: pending,
temporary: false,
};
auditLog('requestAlias', res);
return res;
}
export async function requestTemporaryAlias(
label: string,
labelAtEnd: boolean,
style: 'words' | 'random',
oldToken?: string,
): Promise<AliasRequestEntry> {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
if (!label.length || label.length > 16) throw new Error("Malformed request");
let email = await generateAliasEmail(label, style, labelAtEnd);
const request: AliasRequestEntry = {
key: crypto.randomBytes(12).toString('base64'),
address: session.user.email,
alias: email,
expires: Math.floor(Date.now() / 1000) + (60 * 60 * 4), // In 4 hours
};
await createTempAliasRequestEntry(
request.key,
request.address,
request.alias,
request.expires,
);
if (oldToken) {
await deleteTempAliasRequestEntry(oldToken);
}
return request;
}
export async function claimTemporaryAlias(key: string): Promise<AliasEntry> {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
const data = await getTempAliasRequestEntry(key);
if (!data || data.address != session.user.email) throw new Error("Unknown alias key");
await deleteTempAliasRequestEntry(key);
if (data.expires < Math.floor(Date.now() / 1000)) throw new Error("Alias request expired");
const id = await createAliasEntry(data.address, data.alias, false, true);
const alias: AliasEntry = {
id: id,
address: data.address,
alias: data.alias,
pending: false,
temporary: true,
};
auditLog('createTempAlias', alias);
return alias;
}
export async function disposeTempAliasRequest(key: string) {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
const data = await getTempAliasRequestEntry(key);
if (!data) return;
if (data.address != session.user.email) throw new Error("Unauthorized");
await deleteTempAliasRequestEntry(key);
}
export async function deleteAlias(alias: string) {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
if (!isAdmin(session) && (await getAlias(alias))?.address != session.user.email) {
throw new Error("Unauthorized");
}
await deleteAliasEntry(alias);
auditLog('deleteAlias', alias);
}
export async function approveAlias(alias: string) {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
if (!isAdmin(session)) throw new Error("Unauthorized");
await approveAliasEntry(alias);
auditLog('approveAlias', alias);
}
export async function createUser(email: string, password: string) {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
if (!isAdmin(session)) throw new Error("Unauthorized");
if (!await isAliasAvailable(email.toLowerCase())) {
throw new Error("Alias unavailable");
}
if (password.length < 8 || !email.includes("@")) {
throw new Error("Validation failed");
}
await createUserEntry(email, password);
auditLog('createUser', email);
}
export async function fetchAuditLog(page: number): Promise<{ page: number, perPage: number, totalItems: number, items: (AuditLog & { index: number })[] }> {
const itemsPerPage = 10;
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
if (!isAdmin(session)) throw new Error("Unauthorized");
if (!process.env.AUDIT_FILE_PATH) return { page: page, perPage: itemsPerPage, totalItems: 0, items: [] };
const lines = (await fs.readFile(process.env.AUDIT_FILE_PATH))
.toString("utf8")
.trim()
.split("\n")
.reverse();
if (page > Math.floor(lines.length / itemsPerPage)) {
page = Math.ceil(lines.length / itemsPerPage) - 1;
} else if (page < 0) {
page = 0;
}
return {
page: page,
perPage: itemsPerPage,
totalItems: lines.length,
items: lines
.slice(itemsPerPage * page, itemsPerPage * (page + 1))
.map((item, i) => ({ ...JSON.parse(item), index: page * itemsPerPage + i })),
};
}
export async function createApiKey(label: string): Promise<ApiKeyEntry> {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
if (!label.length || label.length > 64) throw new Error("Malformed label");
const token = crypto.randomBytes(48).toString("base64");
const id = await createApiKeyEntry(session.user.email, label, token);
const key: ApiKeyEntry = {
id: id,
address: session.user.email,
label: label,
token: token,
};
auditLog('createApiKey', anonymizeApiKey(key));
return key;
}
export async function fetchOwnApiKeys(): Promise<ApiKeyEntry[]> {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
const keys = await getUserApiKeys(session.user.email);
return keys.map(anonymizeApiKey);
}
export async function deleteOwnApikey(id: number) {
const session = await auth();
if (!session?.user?.email) throw new Error("Unauthenticated");
const key = await getApiKeyById(id);
if (!key || key.address != session.user.email) throw new Error("Unauthorized");
await deleteApiKey(id);
auditLog('deleteApiKey', anonymizeApiKey(key));
}