feat(privacy): WP-41 — persisted, queryable authz/PII-reveal audit
Persist the security-relevant events (authz denials + BIG-nummer reveal/step-up) into a data-minimised EF table (AuthzAuditEntry: At/Action/Resource/Decision/Role/CorrelationId — never a name/BSN/value), extending the DocumentStore AuditEntry pattern (migration AuthzAudit). AuditAuthz now persists via AuthzAuditStore.Record alongside its log line. GET /admin/audit (admin-gated by the existing CasesAdmin) returns the trail newest-first. +3 backend tests incl. a schema-carries-no-PII reflection test. Typed client regenerated (audit() + AuthzAuditDto); no FE consumer yet (a future audit view must add the ROLE_AWARE prefix). Finishes WP-42's audit half. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1120,6 +1120,48 @@ export class ApiClient {
|
||||
return Promise.resolve<void>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OK
|
||||
*/
|
||||
audit(): Promise<AuthzAuditDto[]> {
|
||||
let url_ = this.baseUrl + "/api/v1/admin/audit";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||
return this.processAudit(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processAudit(response: Response): Promise<AuthzAuditDto[]> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as AuthzAuditDto[];
|
||||
return result200;
|
||||
});
|
||||
} else if (status === 403) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result403: any = null;
|
||||
result403 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as ProblemDetails;
|
||||
return throwException("Forbidden", status, _responseText, _headers, result403);
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<AuthzAuditDto[]>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OK
|
||||
*/
|
||||
@@ -1765,6 +1807,15 @@ export interface ApplicationSummaryDto {
|
||||
owner?: string | undefined;
|
||||
}
|
||||
|
||||
export interface AuthzAuditDto {
|
||||
at?: string | undefined;
|
||||
action?: string | undefined;
|
||||
resource?: string | undefined;
|
||||
decision?: string | undefined;
|
||||
role?: string | undefined;
|
||||
correlationId?: string | undefined;
|
||||
}
|
||||
|
||||
export interface BriefDecisionsDto {
|
||||
canEdit?: boolean;
|
||||
canApprove?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user