281 lines
8.5 KiB
TypeScript
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));
|
|
}
|