"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 { 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 { 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 { 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 { 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 { 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 { 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 { 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)); }