Some checks are pending
CI / Lint bridge (Biome) (push) Waiting to run
CI / Type-check bridge (push) Blocked by required conditions
CI / Tests unit bridge (push) Blocked by required conditions
CI / Tests integration bridge (push) Blocked by required conditions
CI / Security scan (push) Waiting to run
CI / Docker build + healthcheck (push) Blocked by required conditions
- Support JWT OIDC Authentik via jose + JWKS (cache 10min) - Lookup Personne via PersonneRepo.findByEmail + cache Redis 60s - Mapping groups Authentik + roles formation-hub vers scopes - Mode OIDC active uniquement si AUTHENTIK_ISSUER + JWKS_URI + AUDIENCE set - Service tokens brg_* inchanges, restent voie principale en local
62 lines
2.1 KiB
TypeScript
62 lines
2.1 KiB
TypeScript
/**
|
|
* Erreurs metier typees pour le bridge.
|
|
* Chaque erreur a un code stable + statut HTTP + details serializables.
|
|
*/
|
|
|
|
export type ErrorCode =
|
|
| 'AUTH_REQUIRED'
|
|
| 'AUTH_INVALID'
|
|
| 'FORBIDDEN'
|
|
| 'FORBIDDEN_SCOPE'
|
|
| 'NOT_FOUND'
|
|
| 'VALIDATION_ERROR'
|
|
| 'RG_VIOLATION'
|
|
| 'CONFLICT'
|
|
| 'RATE_LIMITED'
|
|
| 'BASEROW_UNAVAILABLE'
|
|
| 'DOCMOST_UNAVAILABLE'
|
|
| 'INTERNAL';
|
|
|
|
export class BridgeError extends Error {
|
|
constructor(
|
|
public readonly code: ErrorCode,
|
|
public readonly status: number,
|
|
message: string,
|
|
public readonly details?: Record<string, unknown>,
|
|
) {
|
|
super(message);
|
|
this.name = 'BridgeError';
|
|
}
|
|
|
|
toJSON() {
|
|
return {
|
|
error: {
|
|
code: this.code,
|
|
message: this.message,
|
|
...(this.details ? { details: this.details } : {}),
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
export const errors = {
|
|
authRequired: () => new BridgeError('AUTH_REQUIRED', 401, 'Token absent'),
|
|
authInvalid: () => new BridgeError('AUTH_INVALID', 401, 'Token invalide'),
|
|
forbidden: (scope: string) =>
|
|
new BridgeError('FORBIDDEN_SCOPE', 403, `Scope requis : ${scope}`, { scope }),
|
|
forbiddenIdentity: (reason: string, details?: Record<string, unknown>) =>
|
|
new BridgeError('FORBIDDEN', 403, reason, details),
|
|
notFound: (entity: string, id: string | number) =>
|
|
new BridgeError('NOT_FOUND', 404, `${entity} introuvable`, { entity, id }),
|
|
validation: (issues: unknown) =>
|
|
new BridgeError('VALIDATION_ERROR', 400, 'Body invalide', { issues }),
|
|
rgViolation: (rule: string, message: string, details?: Record<string, unknown>) =>
|
|
new BridgeError('RG_VIOLATION', 422, message, { rule, ...details }),
|
|
conflict: (message: string, details?: Record<string, unknown>) =>
|
|
new BridgeError('CONFLICT', 409, message, details),
|
|
rateLimited: (retryAfter: number) =>
|
|
new BridgeError('RATE_LIMITED', 429, 'Too many requests', { retry_after: retryAfter }),
|
|
baserowDown: () => new BridgeError('BASEROW_UNAVAILABLE', 502, 'Baserow API unreachable'),
|
|
docmostDown: () => new BridgeError('DOCMOST_UNAVAILABLE', 502, 'Docmost API unreachable'),
|
|
internal: (message: string) => new BridgeError('INTERNAL', 500, message),
|
|
};
|