Superadmin impersonation, hardened auth/upload paths, dependency updates
- Add superadmin impersonation: sessions.impersonated_by (migration 0003) is stamped onto audit rows so impersonated actions are attributable, with a persistent ImpersonationBanner in the app shell. - Harden auth and upload handling across routes (safe redirect targets, filename sanitization, checkout grant handling). - Update dependencies: Sentry 8 -> 10, @fastify/static 8 -> 10, react-router-dom 6.30.6; add find-my-way / fast-uri overrides. - Add tests for safe-next, checkout-grant, and upload-filename. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4a1122a7c9
commit
eb36b81dc9
@@ -1,3 +1,4 @@
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import { getDb, auditLog } from '@lawdesk/db';
|
||||
|
||||
export interface AuditEntry {
|
||||
@@ -6,14 +7,44 @@ export interface AuditEntry {
|
||||
action: string;
|
||||
meta?: unknown;
|
||||
ip?: string | null;
|
||||
/** Superadmin id when the acting session is an impersonation. */
|
||||
impersonatedBy?: string | null;
|
||||
}
|
||||
|
||||
export async function logAudit(entry: AuditEntry): Promise<void> {
|
||||
// The impersonating admin is folded into `meta` rather than a dedicated column so existing
|
||||
// rows stay valid: an entry written during impersonation records BOTH the user the action
|
||||
// appears to come from and the admin who actually performed it.
|
||||
const meta =
|
||||
entry.impersonatedBy != null
|
||||
? { ...(typeof entry.meta === 'object' && entry.meta !== null ? entry.meta : { value: entry.meta }), impersonatedBy: entry.impersonatedBy }
|
||||
: entry.meta;
|
||||
|
||||
await getDb().insert(auditLog).values({
|
||||
userId: entry.userId ?? null,
|
||||
firmId: entry.firmId ?? null,
|
||||
action: entry.action,
|
||||
meta: entry.meta == null ? null : JSON.stringify(entry.meta),
|
||||
meta: meta == null ? null : JSON.stringify(meta),
|
||||
ip: entry.ip ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Audit an action performed by the current request's user. Carries the impersonating admin
|
||||
* through automatically, so a support session can never write an audit trail that looks like
|
||||
* the customer acted alone.
|
||||
*/
|
||||
export async function logAuditFromRequest(
|
||||
req: FastifyRequest,
|
||||
action: string,
|
||||
extra: { firmId?: string | null; meta?: unknown } = {},
|
||||
): Promise<void> {
|
||||
await logAudit({
|
||||
userId: req.user?.id ?? null,
|
||||
firmId: extra.firmId !== undefined ? extra.firmId : (req.user?.firmId ?? null),
|
||||
action,
|
||||
meta: extra.meta,
|
||||
ip: req.ip,
|
||||
impersonatedBy: req.user?.impersonatedBy ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user