mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 12:41:04 +00:00
Merge branch 'dev'
This commit is contained in:
@@ -20,7 +20,39 @@ interface LoggedRequest {
|
||||
headers: Record<string, string | string[] | undefined>;
|
||||
ip?: string;
|
||||
query?: Record<string, unknown>;
|
||||
user?: Record<string, unknown> | null;
|
||||
/** Set by the IAM JwtGuard AFTER this middleware runs — read at emit time. */
|
||||
user?: AuthenticatedUser | null;
|
||||
currentUnitId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The subset of `TCurrentUser` (@tria-plc/api-common) the log line reads.
|
||||
* Everything here is an identifier, a key or a status — the personal fields on
|
||||
* that type (name, email, username, phoneNumber) are deliberately absent so
|
||||
* they cannot be picked up by accident.
|
||||
*/
|
||||
interface AuthenticatedUser {
|
||||
id?: string;
|
||||
sub?: string;
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
userType?: string;
|
||||
status?: string;
|
||||
roles?: { key?: string }[];
|
||||
permissions?: unknown[];
|
||||
employee?: {
|
||||
id?: string;
|
||||
organizationId?: string;
|
||||
unitId?: string;
|
||||
position?: {
|
||||
id?: string;
|
||||
key?: string;
|
||||
employeePositionId?: string;
|
||||
isDelegate?: boolean;
|
||||
delegatorId?: string;
|
||||
positionType?: { key?: string };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface LoggedResponse {
|
||||
@@ -35,13 +67,48 @@ const header = (req: LoggedRequest, name: string): string | undefined => {
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
};
|
||||
|
||||
const userId = (req: LoggedRequest): string | undefined => {
|
||||
const userId = (req: LoggedRequest): string | undefined =>
|
||||
req.user?.id ?? req.user?.sub ?? req.user?.userId;
|
||||
|
||||
/**
|
||||
* Who the caller was acting as — WITHOUT any personal data. Ids, role/position
|
||||
* keys and statuses only: enough to answer "which desk did this", "was it a
|
||||
* delegate", "which tenant", and to spot an authorization problem, with nothing
|
||||
* that identifies the human behind the account beyond the opaque user id.
|
||||
*
|
||||
* `authenticated: false` with `hasBearer: true` is the signature of a rejected
|
||||
* token (expired session, bad signature) as opposed to a missing one.
|
||||
*/
|
||||
const authContext = (req: LoggedRequest): Record<string, unknown> => {
|
||||
const user = req.user;
|
||||
if (!user) return undefined;
|
||||
const id = user.id ?? user.sub ?? user.userId;
|
||||
return typeof id === "string" || typeof id === "number"
|
||||
? String(id)
|
||||
: undefined;
|
||||
const position = user?.employee?.position;
|
||||
return {
|
||||
authenticated: Boolean(user),
|
||||
hasBearer: header(req, "authorization")?.startsWith("Bearer ") ?? false,
|
||||
// Which frontend called — /auth/login rejects cross-audience credentials on it.
|
||||
clientApp: header(req, "x-client-app"),
|
||||
userId: userId(req),
|
||||
sessionId: user?.sessionId,
|
||||
userType: user?.userType,
|
||||
userStatus: user?.status,
|
||||
roles: user?.roles?.map((role) => role.key).filter(Boolean),
|
||||
// Count only: the full grant list is hundreds of keys and would dwarf the line.
|
||||
permissionCount: user?.permissions?.length,
|
||||
employeeId: user?.employee?.id,
|
||||
organizationId: user?.employee?.organizationId,
|
||||
unitId: user?.employee?.unitId ?? req.currentUnitId,
|
||||
positionId: position?.id,
|
||||
positionKey: position?.key,
|
||||
positionType: position?.positionType?.key,
|
||||
employeePositionId: position?.employeePositionId,
|
||||
// Acting on someone else's behalf — the first thing to check when a staff
|
||||
// action lands under an unexpected desk.
|
||||
isDelegate: position?.isDelegate,
|
||||
delegatorId: position?.delegatorId,
|
||||
// Tenant/scope headers the frontends send alongside the token.
|
||||
projectId:
|
||||
header(req, "current-project-id") ?? header(req, "x-current-project-id"),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -101,6 +168,9 @@ export class RequestLogMiddleware implements NestMiddleware {
|
||||
status,
|
||||
durationMs,
|
||||
userId: userId(req),
|
||||
// Read at emit time on purpose: the guard populates req.user long
|
||||
// after this middleware handed control on.
|
||||
auth: authContext(req),
|
||||
ip: req.ip,
|
||||
userAgent: header(req, "user-agent"),
|
||||
query:
|
||||
|
||||
@@ -937,10 +937,19 @@ export interface ClearanceView {
|
||||
importReleaseGranted?: boolean;
|
||||
}
|
||||
|
||||
/** Company an invoice is billed to (minimal projection). */
|
||||
/**
|
||||
* Company an invoice is billed to. `findById` returns the full `Company`
|
||||
* relation, not a stripped projection — these extra fields are what the
|
||||
* backoffice invoice detail page's recipient card shows.
|
||||
*/
|
||||
export interface IInvoiceCompany {
|
||||
id: string;
|
||||
name: string;
|
||||
tin?: string | null;
|
||||
vatNumber?: string | null;
|
||||
phone?: string | null;
|
||||
email?: string | null;
|
||||
address?: string | null;
|
||||
}
|
||||
|
||||
/** Company profile (importer/exporter/forwarder/…) an invoice is billed to. */
|
||||
|
||||
Reference in New Issue
Block a user