From 3964346db0d54524a43dbf555464015b3b5f5d6f Mon Sep 17 00:00:00 2001 From: Lea Date: Thu, 18 Jan 2024 16:53:17 +0100 Subject: [PATCH] Add audit log --- .env | 1 + src/lib/actions.ts | 20 +++++++++++++++----- src/lib/audit.ts | 31 +++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 src/lib/audit.ts diff --git a/.env b/.env index 75aa822..f34729f 100644 --- a/.env +++ b/.env @@ -1,3 +1,4 @@ NEXTAUTH_SECRET=changeme CREDENTIALS_DB_PATH=/home/lea/git/maddy-admin/data/credentials.db ALIASES_DB_PATH=/home/lea/git/maddy-admin/data/aliases.db +AUDIT_FILE_PATH=/home/lea/git/maddy-admin/data/panel.log diff --git a/src/lib/actions.ts b/src/lib/actions.ts index 1d141eb..33dfffe 100644 --- a/src/lib/actions.ts +++ b/src/lib/actions.ts @@ -3,6 +3,7 @@ import { getServerSession } from "next-auth"; import { AliasEntry, approveAliasEntry, createAliasEntry, createUserEntry, database, deleteAliasEntry, getAlias, getAllAliases, getUserAliases, setUserPassword } from "./db"; import { aliasesNeedApproval, isAdmin } from "./util"; +import { auditLog } from "./audit"; export async function fetchAllUsers(): Promise { return new Promise(async (resolve, reject) => { @@ -23,6 +24,7 @@ export async function changeOwnPassword(newPass: string) { const session = await getServerSession(); if (!session?.user?.email) throw new Error("Unauthenticated"); await setUserPassword(session.user.email, newPass); + auditLog("changeOwnPassword"); } export async function fetchOwnAliases() { @@ -75,13 +77,15 @@ export async function createAlias(user: string, alias: string): Promise { @@ -90,14 +94,17 @@ export async function createAliasSelf(alias: string): Promise { const pending = aliasesNeedApproval(session); if (!await aliasAvailable(alias)) throw new Error("Alias unavailable"); - const id = await createAliasEntry(session.user.email, alias.toLowerCase(), pending); - return { + const id = await createAliasEntry(session.user.email, alias.toLowerCase(), pending); + const res = { id: id, address: session.user.email, alias: alias, pending: pending }; + + auditLog('requestAlias', res); + return res; } export async function deleteAlias(alias: string) { @@ -109,6 +116,7 @@ export async function deleteAlias(alias: string) { } await deleteAliasEntry(alias); + auditLog('deleteAlias', alias); } export async function approveAlias(alias: string) { @@ -117,6 +125,7 @@ export async function approveAlias(alias: string) { if (!isAdmin(session)) throw new Error("Unauthorized"); await approveAliasEntry(alias); + auditLog('approveAlias', alias); } export async function createUser(email: string, password: string) { @@ -133,4 +142,5 @@ export async function createUser(email: string, password: string) { } await createUserEntry(email, password); + auditLog('createUser', email); } diff --git a/src/lib/audit.ts b/src/lib/audit.ts new file mode 100644 index 0000000..ba9a20f --- /dev/null +++ b/src/lib/audit.ts @@ -0,0 +1,31 @@ +import { Session, getServerSession } from "next-auth"; +import fs from "fs/promises"; + +export type AuditLogAction = 'login' | 'changeOwnPassword' | 'requestAlias' | 'deleteAlias' // Unprivileged + | 'createUser' | 'createAlias' | 'approveAlias' | 'deleteAlias'; // Privileged + +export function auditLog(action: AuditLogAction, data?: any) { + (async () => { + try { + const session = await getServerSession(); + + const log = { + user: session?.user?.email, + ts: Date.now(), + action: action, + data: data, + }; + + console.log("Audit event:", log); + + if (process.env.AUDIT_FILE_PATH) { + await fs.appendFile( + process.env.AUDIT_FILE_PATH, + JSON.stringify(log) + "\n", + ); + } + } catch (e) { + console.error("Failed to write to log file:", e); + } + })(); +}