feat(phase1c): audit sign-in / sign-out via better-auth after hook
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
import { createAuthMiddleware } from "better-auth/api";
|
||||
import { prisma } from "./db";
|
||||
|
||||
const AUDITED_PATHS = new Set(["/sign-in/email", "/sign-out"]);
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: prismaAdapter(prisma, { provider: "postgresql" }),
|
||||
secret: process.env.BETTER_AUTH_SECRET,
|
||||
@@ -21,4 +24,34 @@ export const auth = betterAuth({
|
||||
cookiePrefix: "sp",
|
||||
useSecureCookies: process.env.NODE_ENV === "production",
|
||||
},
|
||||
hooks: {
|
||||
after: createAuthMiddleware(async (ctx) => {
|
||||
if (!AUDITED_PATHS.has(ctx.path)) return;
|
||||
const status = ctx.context.returned ? 200 : 400;
|
||||
const userId =
|
||||
(ctx.context.returned as { user?: { id?: string } } | undefined)?.user?.id ??
|
||||
ctx.context.session?.user.id ??
|
||||
null;
|
||||
const ip =
|
||||
ctx.request?.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
|
||||
ctx.request?.headers.get("x-real-ip") ??
|
||||
null;
|
||||
const ua = ctx.request?.headers.get("user-agent") ?? null;
|
||||
try {
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
actorUserId: userId,
|
||||
projectKey: "panel",
|
||||
endpoint: ctx.path,
|
||||
method: ctx.method ?? "POST",
|
||||
responseStatus: status,
|
||||
sourceIp: ip,
|
||||
userAgent: ua,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
// never break auth on audit failure
|
||||
}
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user