mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 14:50:57 +00:00
- Implemented utility to calculate wagon usage metrics for train schedules. - Created for sending wagons to maintenance with optional notes. - Added unit tests for train builder maintenance functionalities, including formatting train run labels and building maintenance notes. - Developed component for merging train schedules with detailed previews and reasons for merging. - Introduced component for selecting wagons with search functionality and selection limits. - Created for displaying and filtering audit logs, including detailed views of individual log entries. - Added for handling API interactions related to audit logs, including fetching logs and entity types.
85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
/**
|
|
* Who acted, and does this API audit them?
|
|
*
|
|
* The staff/customer split reuses the exact discriminator the permission guards
|
|
* already apply (`freight-permission.guard.ts`): `userType === 'employee'` is
|
|
* backoffice, `individual` / `external_organization` are customers. Restating
|
|
* the rule instead of importing it would let the two drift apart silently.
|
|
*/
|
|
|
|
const EMPLOYEE_USER_TYPE = 'employee';
|
|
const SUPER_ADMIN_ROLE = 'super_admin';
|
|
|
|
/** The subset of the JWT payload this module reads. */
|
|
export interface AuditActorSource {
|
|
id?: string;
|
|
sub?: string;
|
|
userType?: string;
|
|
username?: string;
|
|
name?: string | { en?: string; am?: string };
|
|
firstName?: string;
|
|
lastName?: string;
|
|
email?: string;
|
|
roles?: { key?: string; name?: string }[];
|
|
employee?: unknown;
|
|
}
|
|
|
|
export interface AuditActor {
|
|
userId: string | null;
|
|
userName: string | null;
|
|
userRole: string | null;
|
|
}
|
|
|
|
function isSuperAdmin(user: AuditActorSource): boolean {
|
|
return Boolean(user.roles?.some((role) => role.key === SUPER_ADMIN_ROLE));
|
|
}
|
|
|
|
/**
|
|
* Is this caller a backoffice user whose actions are audited?
|
|
*
|
|
* Only employees qualify. Customers are excluded by request, and unauthenticated
|
|
* callers are excluded too — which means failed logins, OTP sends and password
|
|
* resets produce no audit rows. That was a deliberate call: those endpoints are
|
|
* not backoffice actions. Note the trade-off, since failed-auth attempts are
|
|
* often what an incident review looks for first.
|
|
*/
|
|
export function isAuditableActor(user: AuditActorSource | null | undefined): boolean {
|
|
if (!user) return false;
|
|
// Super admins may not carry an `employee` userType on every token, but are
|
|
// unambiguously staff — the permission guards treat them the same way.
|
|
return user.userType === EMPLOYEE_USER_TYPE || isSuperAdmin(user);
|
|
}
|
|
|
|
/** Best-effort display name, tolerating the several shapes tokens use. */
|
|
function resolveUserName(user: AuditActorSource): string | null {
|
|
if (typeof user.name === 'string' && user.name.trim()) return user.name.trim();
|
|
|
|
if (user.name && typeof user.name === 'object') {
|
|
const localized = user.name.en ?? user.name.am;
|
|
if (localized?.trim()) return localized.trim();
|
|
}
|
|
|
|
const composed = [user.firstName, user.lastName].filter(Boolean).join(' ').trim();
|
|
if (composed) return composed;
|
|
|
|
return user.username?.trim() || user.email?.trim() || null;
|
|
}
|
|
|
|
/**
|
|
* Snapshot the actor at the moment of the action.
|
|
*
|
|
* Name and role are copied, never referenced: resolving them from IAM at read
|
|
* time would rewrite history whenever someone is renamed, changes role or is
|
|
* deleted. An audit row from last year must still say who acted and with what
|
|
* authority *then*.
|
|
*/
|
|
export function resolveAuditActor(user: AuditActorSource): AuditActor {
|
|
const roleKey = user.roles?.[0]?.key ?? user.roles?.[0]?.name ?? null;
|
|
|
|
return {
|
|
userId: user.id ?? user.sub ?? null,
|
|
userName: resolveUserName(user),
|
|
userRole: roleKey,
|
|
};
|
|
}
|