Fundament: Audit mit IP/User-Agent, Mail-Absender je Mandant
- Migration audit_request_context: ip_address, user_agent an audit_logs (Spec §26) - writeAuditLog/writePlatformAudit erfassen IP (X-Forwarded-For) und User-Agent aus dem Request; außerhalb eines Requests (Worker/Skripte) null - Audit-Viewer liest die neuen Spalten statt Heuristik aus before/after - deliverMail nutzt Anzeigename und Reply-To aus TenantSettings (Spec §33.2); Absenderadresse bleibt Plattform-Domain (SPF/DKIM), Header-Injection bereinigt Gate: tsc, lint, build, 24/24 Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -100,19 +100,6 @@ export function diffAudit(before: unknown, after: unknown): DiffRow[] {
|
||||
});
|
||||
}
|
||||
|
||||
/** Extract request metadata if a writer stored it (writeAuditLog does not capture it yet). */
|
||||
function requestMeta(...sources: unknown[]): { ip: string | null; userAgent: string | null } {
|
||||
for (const s of sources) {
|
||||
if (s && typeof s === "object") {
|
||||
const o = s as Record<string, unknown>;
|
||||
const ip = typeof o.ip === "string" ? o.ip : null;
|
||||
const userAgent = typeof o.userAgent === "string" ? o.userAgent : null;
|
||||
if (ip || userAgent) return { ip, userAgent };
|
||||
}
|
||||
}
|
||||
return { ip: null, userAgent: null };
|
||||
}
|
||||
|
||||
export async function getAuditEntry(ctx: ServiceCtx, id: unknown) {
|
||||
assertCan(ctx, "audit:read");
|
||||
const entryId = z.string().min(1).max(64).parse(id);
|
||||
@@ -123,6 +110,8 @@ export async function getAuditEntry(ctx: ServiceCtx, id: unknown) {
|
||||
...row,
|
||||
actorName: actor?.name ?? null,
|
||||
diff: diffAudit(row.before ?? undefined, row.after ?? undefined),
|
||||
...requestMeta(row.after, row.before),
|
||||
// captured by writeAuditLog from the request (spec §26); null for worker/script writes
|
||||
ip: row.ipAddress,
|
||||
userAgent: row.userAgent,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user