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:
Leon Serfaty
2026-08-26 10:25:39 -04:00
co-authored by Claude Opus 5
parent 4a1122a7c9
commit eb36b81dc9
28 changed files with 7936 additions and 5763 deletions
+32 -1
View File
@@ -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,
});
}