@nodefony/security 10.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +544 -0
- package/README.md +182 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/index.js +151 -0
- package/dist/nodefony/command/security-secrets.js +158 -0
- package/dist/nodefony/command/security-token.js +335 -0
- package/dist/nodefony/command/security-user-add.js +131 -0
- package/dist/nodefony/command/security-user-delete.js +102 -0
- package/dist/nodefony/command/security-user-list.js +77 -0
- package/dist/nodefony/config/config.js +366 -0
- package/dist/nodefony/config/defineModuleConfig.js +35 -0
- package/dist/nodefony/contracts/IAccessVoter.js +13 -0
- package/dist/nodefony/contracts/IApiKey.js +1 -0
- package/dist/nodefony/contracts/IAuditEvent.js +1 -0
- package/dist/nodefony/contracts/IAuditStore.js +1 -0
- package/dist/nodefony/contracts/IAuthenticator.js +1 -0
- package/dist/nodefony/contracts/IAuthorizationService.js +1 -0
- package/dist/nodefony/contracts/IFirewall.js +1 -0
- package/dist/nodefony/contracts/IFirewallDescription.js +1 -0
- package/dist/nodefony/contracts/IJwtKeystore.js +1 -0
- package/dist/nodefony/contracts/IOAuthProvider.js +1 -0
- package/dist/nodefony/contracts/ISecuredArea.js +1 -0
- package/dist/nodefony/contracts/IToken.js +1 -0
- package/dist/nodefony/contracts/ITokenStore.js +1 -0
- package/dist/nodefony/contracts/ITotpSecret.js +1 -0
- package/dist/nodefony/contracts/ITotpSecretStore.js +1 -0
- package/dist/nodefony/contracts/IWebAuthnCredential.js +1 -0
- package/dist/nodefony/contracts/IWebAuthnCredentialStore.js +1 -0
- package/dist/nodefony/contracts/IWebhookEndpoint.js +1 -0
- package/dist/nodefony/contracts/IWebhookStore.js +1 -0
- package/dist/nodefony/contracts/index.js +2 -0
- package/dist/nodefony/errors/AccessDeniedError.js +14 -0
- package/dist/nodefony/errors/ApiKeyError.js +21 -0
- package/dist/nodefony/errors/AuthenticationError.js +14 -0
- package/dist/nodefony/errors/CsrfError.js +23 -0
- package/dist/nodefony/errors/InvalidTargetError.js +39 -0
- package/dist/nodefony/errors/SsrfError.js +17 -0
- package/dist/nodefony/errors/ThrottledError.js +21 -0
- package/dist/nodefony/errors/UnverifiableTokenError.js +42 -0
- package/dist/nodefony/errors/WebAuthnError.js +21 -0
- package/dist/nodefony/errors/index.js +9 -0
- package/dist/nodefony/service/accessTokenVerifier.js +77 -0
- package/dist/nodefony/service/apiKeys.js +310 -0
- package/dist/nodefony/service/auditService.js +145 -0
- package/dist/nodefony/service/authFlow.js +332 -0
- package/dist/nodefony/service/authorization.js +95 -0
- package/dist/nodefony/service/cors.js +81 -0
- package/dist/nodefony/service/csrf.js +97 -0
- package/dist/nodefony/service/firewall.js +699 -0
- package/dist/nodefony/service/oauth2.js +153 -0
- package/dist/nodefony/service/securityHeaders.js +80 -0
- package/dist/nodefony/service/tokenService.js +486 -0
- package/dist/nodefony/service/totp.js +209 -0
- package/dist/nodefony/service/webAuthn.js +343 -0
- package/dist/nodefony/service/webhooks.js +539 -0
- package/dist/nodefony/src/RoleHierarchyWalker.js +77 -0
- package/dist/nodefony/src/SecuredArea.js +51 -0
- package/dist/nodefony/src/admin/SecurityAdminApi.js +495 -0
- package/dist/nodefony/src/admin/WebhookAdminApi.js +378 -0
- package/dist/nodefony/src/admin/adminAudit.js +37 -0
- package/dist/nodefony/src/admin/userRevocationCascade.js +40 -0
- package/dist/nodefony/src/apikey/apiKeyFormat.js +107 -0
- package/dist/nodefony/src/audit/MemoryAuditStore.js +121 -0
- package/dist/nodefony/src/audit/auditBridge.js +82 -0
- package/dist/nodefony/src/audit/auditFilters.js +60 -0
- package/dist/nodefony/src/audit/auditStoreRegistry.js +25 -0
- package/dist/nodefony/src/audit/readAuditContext.js +24 -0
- package/dist/nodefony/src/audit/recordAudit.js +16 -0
- package/dist/nodefony/src/authenticator/AnonymousAuthenticator.js +36 -0
- package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +164 -0
- package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +224 -0
- package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +174 -0
- package/dist/nodefony/src/authenticator/JwtAuthenticator.js +176 -0
- package/dist/nodefony/src/authenticator/SessionAuthenticator.js +92 -0
- package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +95 -0
- package/dist/nodefony/src/authenticator/authenticatorRegistry.js +63 -0
- package/dist/nodefony/src/authenticator/bearer.js +2 -0
- package/dist/nodefony/src/authenticator/externalSubject.js +36 -0
- package/dist/nodefony/src/authenticator/peekIssuer.js +56 -0
- package/dist/nodefony/src/crypto/secretCipher.js +79 -0
- package/dist/nodefony/src/csp.js +54 -0
- package/dist/nodefony/src/csrfToken.js +65 -0
- package/dist/nodefony/src/net/ssrfGuard.js +130 -0
- package/dist/nodefony/src/oauth/oauthProviderRegistry.js +37 -0
- package/dist/nodefony/src/oauth/providers/github.js +65 -0
- package/dist/nodefony/src/oauth/providers/oidc.js +48 -0
- package/dist/nodefony/src/realtime/UserRealtimeToken.js +94 -0
- package/dist/nodefony/src/realtime/frameAuthorizer.js +279 -0
- package/dist/nodefony/src/realtime/realtimeContracts.js +1 -0
- package/dist/nodefony/src/sessionIdentity.js +35 -0
- package/dist/nodefony/src/throttle/LoginThrottler.js +97 -0
- package/dist/nodefony/src/token/AnonymousToken.js +40 -0
- package/dist/nodefony/src/token/JwtKeystore.js +160 -0
- package/dist/nodefony/src/token/MemoryTokenStore.js +236 -0
- package/dist/nodefony/src/token/RemoteJwtVerifier.js +231 -0
- package/dist/nodefony/src/token/UserToken.js +67 -0
- package/dist/nodefony/src/token/jwtRuntime.js +19 -0
- package/dist/nodefony/src/token/secretFile.js +134 -0
- package/dist/nodefony/src/token/tokenCriteria.js +35 -0
- package/dist/nodefony/src/token/tokenFilters.js +72 -0
- package/dist/nodefony/src/token/tokenSort.js +40 -0
- package/dist/nodefony/src/token/tokenStatus.js +35 -0
- package/dist/nodefony/src/token/tokenStoreRegistry.js +25 -0
- package/dist/nodefony/src/totp/MemoryTotpSecretStore.js +97 -0
- package/dist/nodefony/src/totp/totpCipher.js +30 -0
- package/dist/nodefony/src/totp/totpCrypto.js +226 -0
- package/dist/nodefony/src/totp/totpOperations.js +129 -0
- package/dist/nodefony/src/totp/totpSecretStoreRegistry.js +18 -0
- package/dist/nodefony/src/voter/RoleVoter.js +32 -0
- package/dist/nodefony/src/voter/ScopeVoter.js +52 -0
- package/dist/nodefony/src/voter/voterRegistry.js +20 -0
- package/dist/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js +121 -0
- package/dist/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js +18 -0
- package/dist/nodefony/src/webhook/MemoryWebhookStore.js +87 -0
- package/dist/nodefony/src/webhook/WebhookDispatcher.js +208 -0
- package/dist/nodefony/src/webhook/webhookCipher.js +27 -0
- package/dist/nodefony/src/webhook/webhookDelivery.js +102 -0
- package/dist/nodefony/src/webhook/webhookFilters.js +56 -0
- package/dist/nodefony/src/webhook/webhookSignature.js +51 -0
- package/dist/nodefony/src/webhook/webhookSort.js +48 -0
- package/dist/nodefony/src/webhook/webhookStoreRegistry.js +18 -0
- package/dist/types/index.d.ts +157 -0
- package/dist/types/nodefony/command/security-secrets.d.ts +24 -0
- package/dist/types/nodefony/command/security-token.d.ts +44 -0
- package/dist/types/nodefony/command/security-user-add.d.ts +28 -0
- package/dist/types/nodefony/command/security-user-delete.d.ts +25 -0
- package/dist/types/nodefony/command/security-user-list.d.ts +28 -0
- package/dist/types/nodefony/config/config.d.ts +295 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +27 -0
- package/dist/types/nodefony/contracts/IAccessVoter.d.ts +23 -0
- package/dist/types/nodefony/contracts/IApiKey.d.ts +75 -0
- package/dist/types/nodefony/contracts/IAuditEvent.d.ts +94 -0
- package/dist/types/nodefony/contracts/IAuditStore.d.ts +80 -0
- package/dist/types/nodefony/contracts/IAuthenticator.d.ts +66 -0
- package/dist/types/nodefony/contracts/IAuthorizationService.d.ts +28 -0
- package/dist/types/nodefony/contracts/IFirewall.d.ts +64 -0
- package/dist/types/nodefony/contracts/IFirewallDescription.d.ts +120 -0
- package/dist/types/nodefony/contracts/IJwtKeystore.d.ts +40 -0
- package/dist/types/nodefony/contracts/IOAuthProvider.d.ts +51 -0
- package/dist/types/nodefony/contracts/ISecuredArea.d.ts +57 -0
- package/dist/types/nodefony/contracts/IToken.d.ts +41 -0
- package/dist/types/nodefony/contracts/ITokenStore.d.ts +240 -0
- package/dist/types/nodefony/contracts/ITotpSecret.d.ts +41 -0
- package/dist/types/nodefony/contracts/ITotpSecretStore.d.ts +88 -0
- package/dist/types/nodefony/contracts/IWebAuthnCredential.d.ts +56 -0
- package/dist/types/nodefony/contracts/IWebAuthnCredentialStore.d.ts +118 -0
- package/dist/types/nodefony/contracts/IWebhookEndpoint.d.ts +82 -0
- package/dist/types/nodefony/contracts/IWebhookStore.d.ts +85 -0
- package/dist/types/nodefony/contracts/index.d.ts +9 -0
- package/dist/types/nodefony/errors/AccessDeniedError.d.ts +10 -0
- package/dist/types/nodefony/errors/ApiKeyError.d.ts +17 -0
- package/dist/types/nodefony/errors/AuthenticationError.d.ts +10 -0
- package/dist/types/nodefony/errors/CsrfError.d.ts +19 -0
- package/dist/types/nodefony/errors/InvalidTargetError.d.ts +34 -0
- package/dist/types/nodefony/errors/SsrfError.d.ts +13 -0
- package/dist/types/nodefony/errors/ThrottledError.d.ts +16 -0
- package/dist/types/nodefony/errors/UnverifiableTokenError.d.ts +37 -0
- package/dist/types/nodefony/errors/WebAuthnError.d.ts +17 -0
- package/dist/types/nodefony/errors/index.d.ts +8 -0
- package/dist/types/nodefony/service/accessTokenVerifier.d.ts +29 -0
- package/dist/types/nodefony/service/apiKeys.d.ts +103 -0
- package/dist/types/nodefony/service/auditService.d.ts +30 -0
- package/dist/types/nodefony/service/authFlow.d.ts +123 -0
- package/dist/types/nodefony/service/authorization.d.ts +33 -0
- package/dist/types/nodefony/service/cors.d.ts +48 -0
- package/dist/types/nodefony/service/csrf.d.ts +57 -0
- package/dist/types/nodefony/service/firewall.d.ts +148 -0
- package/dist/types/nodefony/service/oauth2.d.ts +66 -0
- package/dist/types/nodefony/service/securityHeaders.d.ts +66 -0
- package/dist/types/nodefony/service/tokenService.d.ts +103 -0
- package/dist/types/nodefony/service/totp.d.ts +58 -0
- package/dist/types/nodefony/service/webAuthn.d.ts +123 -0
- package/dist/types/nodefony/service/webhooks.d.ts +160 -0
- package/dist/types/nodefony/src/RoleHierarchyWalker.d.ts +21 -0
- package/dist/types/nodefony/src/SecuredArea.d.ts +31 -0
- package/dist/types/nodefony/src/admin/SecurityAdminApi.d.ts +82 -0
- package/dist/types/nodefony/src/admin/WebhookAdminApi.d.ts +30 -0
- package/dist/types/nodefony/src/admin/adminAudit.d.ts +27 -0
- package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +31 -0
- package/dist/types/nodefony/src/apikey/apiKeyFormat.d.ts +43 -0
- package/dist/types/nodefony/src/audit/MemoryAuditStore.d.ts +33 -0
- package/dist/types/nodefony/src/audit/auditBridge.d.ts +49 -0
- package/dist/types/nodefony/src/audit/auditFilters.d.ts +56 -0
- package/dist/types/nodefony/src/audit/auditStoreRegistry.d.ts +37 -0
- package/dist/types/nodefony/src/audit/readAuditContext.d.ts +17 -0
- package/dist/types/nodefony/src/audit/recordAudit.d.ts +13 -0
- package/dist/types/nodefony/src/authenticator/AnonymousAuthenticator.d.ts +26 -0
- package/dist/types/nodefony/src/authenticator/ApiKeyAuthenticator.d.ts +74 -0
- package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +132 -0
- package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +78 -0
- package/dist/types/nodefony/src/authenticator/JwtAuthenticator.d.ts +69 -0
- package/dist/types/nodefony/src/authenticator/SessionAuthenticator.d.ts +70 -0
- package/dist/types/nodefony/src/authenticator/UserPasswordAuthenticator.d.ts +53 -0
- package/dist/types/nodefony/src/authenticator/authenticatorRegistry.d.ts +39 -0
- package/dist/types/nodefony/src/authenticator/bearer.d.ts +22 -0
- package/dist/types/nodefony/src/authenticator/externalSubject.d.ts +27 -0
- package/dist/types/nodefony/src/authenticator/peekIssuer.d.ts +31 -0
- package/dist/types/nodefony/src/crypto/secretCipher.d.ts +31 -0
- package/dist/types/nodefony/src/csp.d.ts +39 -0
- package/dist/types/nodefony/src/csrfToken.d.ts +36 -0
- package/dist/types/nodefony/src/net/ssrfGuard.d.ts +43 -0
- package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +45 -0
- package/dist/types/nodefony/src/oauth/providers/github.d.ts +9 -0
- package/dist/types/nodefony/src/oauth/providers/oidc.d.ts +35 -0
- package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +62 -0
- package/dist/types/nodefony/src/realtime/frameAuthorizer.d.ts +171 -0
- package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +139 -0
- package/dist/types/nodefony/src/sessionIdentity.d.ts +20 -0
- package/dist/types/nodefony/src/throttle/LoginThrottler.d.ts +68 -0
- package/dist/types/nodefony/src/token/AnonymousToken.d.ts +23 -0
- package/dist/types/nodefony/src/token/JwtKeystore.d.ts +43 -0
- package/dist/types/nodefony/src/token/MemoryTokenStore.d.ts +66 -0
- package/dist/types/nodefony/src/token/RemoteJwtVerifier.d.ts +149 -0
- package/dist/types/nodefony/src/token/UserToken.d.ts +41 -0
- package/dist/types/nodefony/src/token/jwtRuntime.d.ts +28 -0
- package/dist/types/nodefony/src/token/secretFile.d.ts +70 -0
- package/dist/types/nodefony/src/token/tokenCriteria.d.ts +20 -0
- package/dist/types/nodefony/src/token/tokenFilters.d.ts +76 -0
- package/dist/types/nodefony/src/token/tokenSort.d.ts +33 -0
- package/dist/types/nodefony/src/token/tokenStatus.d.ts +38 -0
- package/dist/types/nodefony/src/token/tokenStoreRegistry.d.ts +38 -0
- package/dist/types/nodefony/src/totp/MemoryTotpSecretStore.d.ts +43 -0
- package/dist/types/nodefony/src/totp/totpCipher.d.ts +9 -0
- package/dist/types/nodefony/src/totp/totpCrypto.d.ts +164 -0
- package/dist/types/nodefony/src/totp/totpOperations.d.ts +73 -0
- package/dist/types/nodefony/src/totp/totpSecretStoreRegistry.d.ts +27 -0
- package/dist/types/nodefony/src/voter/RoleVoter.d.ts +25 -0
- package/dist/types/nodefony/src/voter/ScopeVoter.d.ts +30 -0
- package/dist/types/nodefony/src/voter/voterRegistry.d.ts +33 -0
- package/dist/types/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.d.ts +39 -0
- package/dist/types/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.d.ts +26 -0
- package/dist/types/nodefony/src/webhook/MemoryWebhookStore.d.ts +37 -0
- package/dist/types/nodefony/src/webhook/WebhookDispatcher.d.ts +69 -0
- package/dist/types/nodefony/src/webhook/webhookCipher.d.ts +8 -0
- package/dist/types/nodefony/src/webhook/webhookDelivery.d.ts +28 -0
- package/dist/types/nodefony/src/webhook/webhookFilters.d.ts +64 -0
- package/dist/types/nodefony/src/webhook/webhookSignature.d.ts +20 -0
- package/dist/types/nodefony/src/webhook/webhookSort.d.ts +39 -0
- package/dist/types/nodefony/src/webhook/webhookStoreRegistry.d.ts +31 -0
- package/docs/api-keys.md +691 -0
- package/docs/audit.md +751 -0
- package/docs/authenticators.md +487 -0
- package/docs/authorization.md +497 -0
- package/docs/cors.md +497 -0
- package/docs/csrf.md +392 -0
- package/docs/external-jwt.md +181 -0
- package/docs/firewall.md +546 -0
- package/docs/headers.md +616 -0
- package/docs/index.md +207 -0
- package/docs/lexique.md +190 -0
- package/docs/oauth2.md +575 -0
- package/docs/obtenir-un-jeton.md +225 -0
- package/docs/tokens.md +520 -0
- package/docs/totp.md +804 -0
- package/docs/webauthn.md +733 -0
- package/docs/webhooks.md +1016 -0
- package/package.json +83 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Container } from "nodefony";
|
|
2
|
+
import type { IAdminApi, IAdminRegistry } from "nodefony";
|
|
3
|
+
import type { ITokenListQuery } from "../../contracts/ITokenStore.js";
|
|
4
|
+
import type { IAuditListQuery } from "../../contracts/IAuditStore.js";
|
|
5
|
+
/**
|
|
6
|
+
* Traduit la query string admin en {@link ITokenListQuery} bornée (`limit` par
|
|
7
|
+
* défaut 50, cap 200 ; pagination, tri et filtres de {@link TOKEN_FILTERS}).
|
|
8
|
+
*
|
|
9
|
+
* **UN SEUL traducteur par dimension, jamais deux.** Pour la page et le tri,
|
|
10
|
+
* `parsePageQuery` ; pour les filtres, `parseFilters`. En appeler un second sans
|
|
11
|
+
* son allowlist ferait refuser en 400 ce que le premier venait d'accepter, et
|
|
12
|
+
* aucun test unitaire ne le verrait (chaque appel est correct isolément).
|
|
13
|
+
*
|
|
14
|
+
* Un filtre inconnu ou mal formé est désormais **refusé** (400) au lieu d'être
|
|
15
|
+
* ignoré : `?status=revoqué` rendait la liste ENTIÈRE, que la console affichait
|
|
16
|
+
* comme le résultat du filtre demandé.
|
|
17
|
+
*
|
|
18
|
+
* @param query - `request.query` du broker admin.
|
|
19
|
+
* @param sortable - champs que le backend branché sait trier ; un `?order=`
|
|
20
|
+
* portant autre chose est refusé en 400 par le traducteur. Liste vide (store à
|
|
21
|
+
* curseur, store absent) ⇒ tout tri est refusé, ce qui est la vérité du backend.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseTokenListQuery(query: Readonly<Record<string, string | string[]>>, sortable: readonly string[]): ITokenListQuery;
|
|
24
|
+
/**
|
|
25
|
+
* Traduit la query string admin en {@link IAuditListQuery} typée — pagination
|
|
26
|
+
* par curseur, plus les filtres de {@link AUDIT_FILTERS}.
|
|
27
|
+
*
|
|
28
|
+
* Un filtre inconnu ou mal formé est **refusé** (400). Il était auparavant
|
|
29
|
+
* ignoré, au nom de la robustesse de la console : mais un journal d'audit rendu
|
|
30
|
+
* ENTIER à qui demandait `?outcome=deneid` n'est pas robuste — c'est la pire
|
|
31
|
+
* réponse possible à un auditeur, qui lit l'absence de refus comme l'absence
|
|
32
|
+
* d'incident. Le typage suit la même source : les valeurs viennent de la spec,
|
|
33
|
+
* il n'y a plus de `as AuditCategory` à écrire ici.
|
|
34
|
+
*
|
|
35
|
+
* Le `limit` est **toujours posé** (défaut {@link AUDIT_DEFAULT_LIMIT}, cap du
|
|
36
|
+
* traducteur) : le contrat de page n'admet pas « tout » ; le store applique en
|
|
37
|
+
* plus son propre plafond, l'appelant ne peut donc pas s'en servir pour tirer un
|
|
38
|
+
* journal entier.
|
|
39
|
+
*
|
|
40
|
+
* @param query - `request.query` du broker admin.
|
|
41
|
+
* @returns filtre prêt pour `auditService.listPage`.
|
|
42
|
+
* @throws `PageQueryError` (400) sur un filtre inconnu ou mal formé.
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseAuditQuery(query: Readonly<Record<string, string | string[]>>): IAuditListQuery;
|
|
45
|
+
/**
|
|
46
|
+
* Producteur admin (`IAdminApi`) du module sécurité — data plane consommé par
|
|
47
|
+
* Studio (section Sécurité, P6.15) :
|
|
48
|
+
*
|
|
49
|
+
* - `GET /nodefony/security/api/audit/events` — page filtrée du journal d'audit
|
|
50
|
+
* (P6.14 Lot 3 ; `?category&outcome&actor&action&requestId&since&until&limit&cursor`),
|
|
51
|
+
* du plus récent au plus ancien, pagination par curseur (`cursor` / `nextCursor`).
|
|
52
|
+
* - `GET /nodefony/security/api/firewall` — introspection du firewall (zones,
|
|
53
|
+
* authenticators montés, défenses) — état RUNTIME, secrets exclus.
|
|
54
|
+
* - `GET /nodefony/security/api/roleHierarchy` — hiérarchie de rôles + résolution.
|
|
55
|
+
* - `GET /nodefony/security/api/apikeys` — toutes les clés API (gouvernance),
|
|
56
|
+
* `GET …/apikeys/status` — backend du token store (« où on écrit »),
|
|
57
|
+
* `POST …/apikeys/{id}/revoke` — révocation par id (réponse à incident).
|
|
58
|
+
* - `GET …/users/{id}/passkeys` + `DELETE …/users/{id}/passkeys/{credentialId}`
|
|
59
|
+
* — passkeys d'un utilisateur (vue admin sans clé publique) + révocation
|
|
60
|
+
* owner-scopée (reset facteur fort, audité).
|
|
61
|
+
* - `GET …/users/{id}/totp` + `POST …/users/{id}/totp/disable` — état du 2FA
|
|
62
|
+
* TOTP + désactivation (reset facteur fort, audité). Pas d'enrôlement
|
|
63
|
+
* cross-user (le secret se scanne sur l'appareil de l'utilisateur).
|
|
64
|
+
*
|
|
65
|
+
* **RBAC `ROLE_NODEFONY_ADMIN`** (appliqué par le broker, 403 sinon) — la console
|
|
66
|
+
* sécurité ne se consulte qu'en administrateur. Handlers **lazy** : ils résolvent
|
|
67
|
+
* `auditService`/`firewall` à la requête (service désactivable → 503), jamais au
|
|
68
|
+
* montage. Le namespace `"security"` est distinct des routes classiques
|
|
69
|
+
* `/nodefony/security/api/keys` (P6.12) — paths disjoints, zéro collision.
|
|
70
|
+
*
|
|
71
|
+
* @param container - container du kernel (résolution lazy des services).
|
|
72
|
+
*/
|
|
73
|
+
export declare function createSecurityAdminApi(container: Container): IAdminApi;
|
|
74
|
+
/**
|
|
75
|
+
* Enregistre le producteur admin sécurité sur le broker — **idempotent** (no-op
|
|
76
|
+
* si déjà monté). À appeler au `onKernelBoot` du module (avant le montage des
|
|
77
|
+
* routes par framework à `onKernelReady`). Calque `registerOrmAdminApi`.
|
|
78
|
+
*
|
|
79
|
+
* @param registry - broker admin (`container.get("adminBroker")`).
|
|
80
|
+
* @param container - container du kernel (capturé par le handler lazy).
|
|
81
|
+
*/
|
|
82
|
+
export declare function registerSecurityAdminApi(registry: IAdminRegistry, container: Container): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Container, IAdminEndpoint } from "nodefony";
|
|
2
|
+
import type { IWebhookListQuery } from "../../contracts/IWebhookStore.js";
|
|
3
|
+
/**
|
|
4
|
+
* Traduit la query string admin en {@link IWebhookListQuery} **bornée**
|
|
5
|
+
* (`limit` défaut 50, cap 200 ; pagination, tri, et les filtres de
|
|
6
|
+
* {@link WEBHOOK_FILTERS}). Pagination **offset uniquement** : les trois stores
|
|
7
|
+
* webhook (memory/drizzle/mongoose) sont offset-pur — aucun curseur ici.
|
|
8
|
+
*
|
|
9
|
+
* **UN SEUL traducteur par dimension, jamais deux** : `parsePageQuery` pour la
|
|
10
|
+
* page et le tri, `parseFilters` pour les filtres. En appeler un second sans son
|
|
11
|
+
* allowlist ferait refuser en 400 ce que le premier venait d'accepter, et aucun
|
|
12
|
+
* test unitaire ne le verrait.
|
|
13
|
+
*
|
|
14
|
+
* Un filtre inconnu ou mal formé est **refusé** (400) et non plus ignoré :
|
|
15
|
+
* `?enabled=oui` rendait la liste entière, lue comme « aucun endpoint désactivé ».
|
|
16
|
+
*
|
|
17
|
+
* @param query - `request.query` du broker admin.
|
|
18
|
+
* @param sortable - champs que le backend branché sait trier ; un `?order=`
|
|
19
|
+
* portant autre chose est refusé en 400 par le traducteur.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseWebhookListQuery(query: Readonly<Record<string, string | string[]>>, sortable: readonly string[]): IWebhookListQuery;
|
|
22
|
+
/**
|
|
23
|
+
* Construit les endpoints admin webhook, à **spreader** dans les
|
|
24
|
+
* `adminEndpoints()` du producteur `security`. Les handlers résolvent le service
|
|
25
|
+
* `webhooks` **lazy** (à la requête) → un service désactivé/absent rend 503 (ou
|
|
26
|
+
* un état honnête en lecture), jamais une erreur au montage.
|
|
27
|
+
*
|
|
28
|
+
* @param container - container du kernel (capturé par les handlers lazy).
|
|
29
|
+
*/
|
|
30
|
+
export declare function webhookAdminEndpoints(container: Container): IAdminEndpoint[];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Container } from "nodefony";
|
|
2
|
+
import type { IAuditEventDraft } from "../../contracts/IAuditEvent.js";
|
|
3
|
+
/**
|
|
4
|
+
* Helpers d'audit PARTAGÉS par les producteurs admin du module sécurité
|
|
5
|
+
* (`SecurityAdminApi`, `WebhookAdminApi`…). Extraits dans leur propre fichier
|
|
6
|
+
* pour être consommés par plusieurs producteurs SANS créer de cycle d'import
|
|
7
|
+
* (un producteur composé ne ré-importe pas le producteur qui le compose).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Identité de l'admin appelant (label d'audit) — duck-typing prudent sur
|
|
11
|
+
* l'`IUser` projeté dans `IAdminRequest.user` (ALS du firewall). Repli
|
|
12
|
+
* `"admin"` (libellé d'audit, jamais une décision d'autorisation).
|
|
13
|
+
*
|
|
14
|
+
* @param user - `request.user` du broker admin.
|
|
15
|
+
* @returns un libellé d'identité stable, jamais un secret.
|
|
16
|
+
*/
|
|
17
|
+
export declare function adminActor(user: unknown): string;
|
|
18
|
+
/**
|
|
19
|
+
* Émet un événement d'audit pour une mutation admin (best-effort,
|
|
20
|
+
* fire-and-forget) — l'audit ne doit jamais bloquer ni faire échouer l'action.
|
|
21
|
+
* No-op si le service `auditService` est absent. Couplage structurel : `record`
|
|
22
|
+
* lu défensivement (jamais d'import de la classe concrète).
|
|
23
|
+
*
|
|
24
|
+
* @param container - container du kernel.
|
|
25
|
+
* @param draft - événement (sans `id`/`ts`, posés par le service).
|
|
26
|
+
*/
|
|
27
|
+
export declare function auditAdmin(container: Container, draft: IAuditEventDraft): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Container } from "nodefony";
|
|
2
|
+
import { type IUserRevokedEvent } from "@nodefony/user";
|
|
3
|
+
/** Bus d'événements minimal (kernel) capable d'abonner un handler. */
|
|
4
|
+
interface KernelListenerLike {
|
|
5
|
+
on(event: string, handler: (...args: unknown[]) => void): unknown;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Cascade de révocation déclenchée par {@link USER_REVOKED_EVENT} : éjecte
|
|
9
|
+
* **immédiatement** les artefacts d'accès du porteur — ses **sessions** (http,
|
|
10
|
+
* `destroyByUser`) et ses **jetons/PAT** (`tokenStore.revokeAllForSubject`,
|
|
11
|
+
* seuil `invalidBefore`). Best-effort par brique (une indispo n'empêche pas
|
|
12
|
+
* l'autre) — l'accès était DÉJÀ neutralisé par le re-fetch des authenticators,
|
|
13
|
+
* cette cascade est de la **propreté + défense en profondeur**, jamais l'unique
|
|
14
|
+
* rempart. `tenantId` du payload est réservé (scoping non câblé en mono-tenant).
|
|
15
|
+
*
|
|
16
|
+
* @param container - container du kernel (résolution lazy de `sessions`/`tokenStore`).
|
|
17
|
+
* @param event - charge utile de l'événement (porteur + raison).
|
|
18
|
+
* @param now - horloge (epoch ms) injectable pour les tests.
|
|
19
|
+
*/
|
|
20
|
+
export declare function cascadeUserRevocation(container: Container, event: IUserRevokedEvent, now?: number): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Abonne la cascade au bus kernel. À appeler au `onKernelBoot` d'un module
|
|
23
|
+
* bootable (ici `@nodefony/security`). **Extensible** : tout autre module
|
|
24
|
+
* (webhooks…) peut s'abonner au MÊME `USER_REVOKED_EVENT` pour ses propres
|
|
25
|
+
* artefacts, sans toucher à ce fichier.
|
|
26
|
+
*
|
|
27
|
+
* @param kernel - bus d'événements (kernel) exposant `on`.
|
|
28
|
+
* @param container - container capturé par le handler.
|
|
29
|
+
*/
|
|
30
|
+
export declare function registerUserRevocationCascade(kernel: KernelListenerLike, container: Container): void;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Hash au repos d'une clé présentée (token entier) — `sha256` hex. */
|
|
2
|
+
export declare function hashApiKey(token: string): string;
|
|
3
|
+
/** Résultat d'une génération de clé — le `token` n'est disponible qu'ICI (1×). */
|
|
4
|
+
export interface IGeneratedApiKey {
|
|
5
|
+
/** Token complet EN CLAIR — à afficher une seule fois, jamais re-dérivable. */
|
|
6
|
+
token: string;
|
|
7
|
+
/** Identifiant public (8 car.) — composant de {@link IGeneratedApiKey.publicPrefix}. */
|
|
8
|
+
pubid: string;
|
|
9
|
+
/** Préfixe public affichable (`<prefix>_<pubid>`) → `record.prefix`. */
|
|
10
|
+
publicPrefix: string;
|
|
11
|
+
/** Hash au repos (`sha256` hex) → `record.secretHash`. */
|
|
12
|
+
secretHash: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Génère une nouvelle clé API cryptographiquement aléatoire.
|
|
16
|
+
*
|
|
17
|
+
* @param prefix - marque applicative (`apiKeys.prefix`).
|
|
18
|
+
* @returns le token clair + ses dérivés publics/persistants.
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateApiKey(prefix: string): IGeneratedApiKey;
|
|
21
|
+
/** Décomposition validée d'une clé présentée. */
|
|
22
|
+
export interface IParsedApiKey {
|
|
23
|
+
pubid: string;
|
|
24
|
+
publicPrefix: string;
|
|
25
|
+
/** Hash de lookup (`findByHash`) — `sha256` du token entier présenté. */
|
|
26
|
+
secretHash: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Test **bon marché** (préfixe seul) — discrimine un PAT d'un JWT à l'`supports()`
|
|
30
|
+
* de l'authenticator, sans calculer le checksum.
|
|
31
|
+
*/
|
|
32
|
+
export declare function looksLikeApiKey(token: string, prefix: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Valide la **forme** d'une clé présentée et en dérive le hash de lookup —
|
|
35
|
+
* **sans aucun accès au store**. Rejette (→ `null`) un préfixe absent, une
|
|
36
|
+
* longueur incorrecte, un charset non base64url ou un **CRC invalide** : autant
|
|
37
|
+
* de requêtes qui n'atteignent jamais la base (anti-DoS).
|
|
38
|
+
*
|
|
39
|
+
* @param token - valeur brute présentée (after `Bearer `).
|
|
40
|
+
* @param prefix - marque applicative attendue.
|
|
41
|
+
* @returns la décomposition + le `secretHash` de lookup, ou `null` si malformée.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseApiKey(token: string, prefix: string): IParsedApiKey | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { IPage } from "nodefony";
|
|
2
|
+
import type { IAuditEvent } from "../../contracts/IAuditEvent.js";
|
|
3
|
+
import type { IAuditListQuery, IAuditStore } from "../../contracts/IAuditStore.js";
|
|
4
|
+
/** Instantané sérialisable du journal mémoire (persistance fichier + tests). */
|
|
5
|
+
export interface AuditStoreSnapshot {
|
|
6
|
+
events: IAuditEvent[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Journal d'audit **en mémoire** — implémentation de référence d'{@link IAuditStore}.
|
|
10
|
+
*
|
|
11
|
+
* 0 dépendance, idéal pour le dev mono-process et les **tests**. **Volatile**
|
|
12
|
+
* (perdu au redémarrage) et **non partagé** (per-pod) → en prod multi-process,
|
|
13
|
+
* brancher un backend ORM/Redis. Le volume est **borné** (`maxEntries`, FIFO : au
|
|
14
|
+
* delà, le plus ancien tombe) pour ne JAMAIS fuir, doublé d'une purge par âge
|
|
15
|
+
* ({@link MemoryAuditStore.gc}, rétention). Append-only : aucune mutation d'un
|
|
16
|
+
* événement déjà journalisé.
|
|
17
|
+
*
|
|
18
|
+
* Horloge injectable (`now`) pour des tests déterministes (pattern `MemoryTokenStore`).
|
|
19
|
+
*/
|
|
20
|
+
export declare class MemoryAuditStore implements IAuditStore {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(now?: () => number, retentionMs?: number, // 365 jours
|
|
23
|
+
maxEntries?: number);
|
|
24
|
+
append(event: IAuditEvent): Promise<void>;
|
|
25
|
+
listPage(query: IAuditListQuery): Promise<IPage<IAuditEvent>>;
|
|
26
|
+
gc(now?: number): Promise<number>;
|
|
27
|
+
/** Nombre d'événements actuellement retenus (introspection / tests). */
|
|
28
|
+
get size(): number;
|
|
29
|
+
/** Instantané sérialisable de l'état courant. */
|
|
30
|
+
snapshot(): AuditStoreSnapshot;
|
|
31
|
+
/** Remplace l'état par celui d'un instantané. */
|
|
32
|
+
restore(snapshot: AuditStoreSnapshot): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { IAuditEvent } from "../../contracts/IAuditEvent.js";
|
|
2
|
+
/**
|
|
3
|
+
* Canal WS du flux live d'audit (P6.14 lot 4). Le préfixe `security:` le place
|
|
4
|
+
* sous le plancher `SECURITY_CHANNEL_POLICY` (ROLE_NODEFONY_ADMIN) du verrou de
|
|
5
|
+
* frame — un user lambda ne peut pas s'y abonner (refus audité `frame.denied`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const SECURITY_AUDIT_CHANNEL: "nodefony:audit";
|
|
8
|
+
/** Source d'événements live — sous-ensemble de `IAuditSink` (slot `subscribe`). */
|
|
9
|
+
export interface IAuditEventSource {
|
|
10
|
+
subscribe(listener: (event: IAuditEvent) => void): () => void;
|
|
11
|
+
}
|
|
12
|
+
/** Charge poussée sur le canal `nodefony:audit` — batch coalescé + omis. */
|
|
13
|
+
export interface IAuditBatch {
|
|
14
|
+
events: IAuditEvent[];
|
|
15
|
+
dropped: number;
|
|
16
|
+
}
|
|
17
|
+
/** Options de coalescing du pont d'audit. */
|
|
18
|
+
export interface AuditBridgeOptions {
|
|
19
|
+
/** Fenêtre d'agrégation : 1 frame WS au plus toutes les `flushMs`. Défaut 250. */
|
|
20
|
+
flushMs?: number;
|
|
21
|
+
/** Cap d'un batch (ring buffer) : au-delà, on garde les + récents et on compte
|
|
22
|
+
* les omis. Borne la mémoire ET le nb d'événements envoyés au front. Défaut 200. */
|
|
23
|
+
maxBatch?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Pont journal d'audit → canal `nodefony:audit`, **coalescé** (P6.14 lot 4).
|
|
27
|
+
*
|
|
28
|
+
* Calque {@link createSyslogBridge} (studio) : au lieu de 1 frame WS par
|
|
29
|
+
* événement (un pic d'`auth.failure` sous brute-force noierait la console
|
|
30
|
+
* auditeur), on accumule dans un **ring buffer borné** et on flush **1 frame
|
|
31
|
+
* agrégée toutes les `flushMs`** : `{ events, dropped }`. Sous surcharge, le ring
|
|
32
|
+
* écrase les plus vieux et `dropped` indique combien ont été omis → la console
|
|
33
|
+
* affiche un récap au lieu de se figer (budget borné, dégradable — règle
|
|
34
|
+
* observabilité « superviser ≠ tomber la prod »).
|
|
35
|
+
*
|
|
36
|
+
* **Lazy par construction** (créé par le hub au 1ᵉʳ abonné, `dispose` au dernier) :
|
|
37
|
+
* tant qu'aucun auditeur n'écoute `nodefony:audit`, ce pont N'EXISTE PAS — aucun
|
|
38
|
+
* listener sur l'`AuditService`, aucun timer. Au repos avec auditeur connecté mais
|
|
39
|
+
* sans événement : ring `null`, 0 timer (armé au 1ᵉʳ événement, `unref`).
|
|
40
|
+
*
|
|
41
|
+
* @param source - l'`AuditService` (slot `subscribe`).
|
|
42
|
+
* @param publish - publication hub (le canal est fourni par la factory).
|
|
43
|
+
* @param channel - canal de publication (`nodefony:audit`).
|
|
44
|
+
* @returns dispose() — détache le listener `AuditService` ET désarme le timer.
|
|
45
|
+
* OBLIGATOIRE (aucun listener/timer sans cleanup, sinon fuite à chaque
|
|
46
|
+
* dernier désabonnement).
|
|
47
|
+
*/
|
|
48
|
+
export declare function createAuditBridge(source: IAuditEventSource, publish: (channel: string, payload: unknown) => void, channel: string, opts?: AuditBridgeOptions): () => void;
|
|
49
|
+
export default createAuditBridge;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AuditCategory, AuditOutcome } from "../../contracts/IAuditEvent.js";
|
|
2
|
+
/**
|
|
3
|
+
* **Le vocabulaire de filtre du journal d'audit**, en noms PUBLICS — ceux qu'un
|
|
4
|
+
* auditeur écrit dans l'URL (`?category=authz&outcome=denied&since=…`).
|
|
5
|
+
*
|
|
6
|
+
* Les deux énumérations y sont écrites en toutes lettres, et
|
|
7
|
+
* {@link AUDIT_FILTER_VOCABULARY_IS_COMPLETE} vérifie **à la compilation**
|
|
8
|
+
* qu'elles couvrent exactement `AuditCategory` et `AuditOutcome`.
|
|
9
|
+
*
|
|
10
|
+
* Ce contrôle n'est pas décoratif : la liste qu'il remplace avait DÉJÀ dérivé.
|
|
11
|
+
* Un `Set` recopié à la main dans le data plane portait dix catégories quand le
|
|
12
|
+
* type en déclarait onze — `?category=config` tombait donc hors de l'allowlist,
|
|
13
|
+
* était ignoré en silence, et l'auditeur recevait le journal ENTIER en croyant
|
|
14
|
+
* lire les seules mutations de configuration. Une liste recopiée ne diverge
|
|
15
|
+
* jamais bruyamment.
|
|
16
|
+
*
|
|
17
|
+
* `actor`, `action` et `requestId` restent des chaînes libres : ce sont des
|
|
18
|
+
* identifiants produits à l'exécution, aucune allowlist ne peut les connaître.
|
|
19
|
+
* `since`/`until` sont des horodatages en millisecondes (bornes incluses).
|
|
20
|
+
*/
|
|
21
|
+
export declare const AUDIT_FILTERS: {
|
|
22
|
+
/** Famille d'événement — la liste EST le type `AuditCategory`. */
|
|
23
|
+
readonly category: readonly ["auth", "authz", "token", "session", "oauth", "webauthn", "csrf", "cors", "ws", "webhook", "config"];
|
|
24
|
+
/** Issue — `denied` est le signal d'accès non autorisé. */
|
|
25
|
+
readonly outcome: readonly ["success", "failure", "denied"];
|
|
26
|
+
/** Identité de l'acteur (égalité stricte). */
|
|
27
|
+
readonly actor: "string";
|
|
28
|
+
/** Nom de l'action auditée (égalité stricte). */
|
|
29
|
+
readonly action: "string";
|
|
30
|
+
/** Corrèle toutes les traces d'une même requête. */
|
|
31
|
+
readonly requestId: "string";
|
|
32
|
+
/** Borne basse, horodatage en millisecondes. */
|
|
33
|
+
readonly since: "int";
|
|
34
|
+
/** Borne haute, horodatage en millisecondes. */
|
|
35
|
+
readonly until: "int";
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Vrai si les deux ensembles sont EXACTEMENT les mêmes, `never` sinon — donc
|
|
39
|
+
* inassignable depuis `true`, donc erreur de compilation.
|
|
40
|
+
*
|
|
41
|
+
* Les deux sens comptent, et pour des raisons différentes : une valeur en trop
|
|
42
|
+
* dans la liste ouvrirait un filtre qu'aucun store ne sait honorer ; une valeur
|
|
43
|
+
* manquante ferait refuser en 400 une catégorie parfaitement légitime — le
|
|
44
|
+
* contraire du silence d'origine, mais tout aussi faux.
|
|
45
|
+
*/
|
|
46
|
+
type SameValues<A, B> = [A] extends [B] ? [B] extends [A] ? true : never : never;
|
|
47
|
+
/**
|
|
48
|
+
* Preuve **à la compilation** que le vocabulaire ci-dessus est exactement celui
|
|
49
|
+
* des types du contrat. Elle remplace la discipline humaine « penser à mettre
|
|
50
|
+
* les deux à jour », qui avait échoué en silence.
|
|
51
|
+
*/
|
|
52
|
+
export declare const AUDIT_FILTER_VOCABULARY_IS_COMPLETE: [
|
|
53
|
+
SameValues<(typeof AUDIT_FILTERS.category)[number], AuditCategory>,
|
|
54
|
+
SameValues<(typeof AUDIT_FILTERS.outcome)[number], AuditOutcome>
|
|
55
|
+
];
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Container } from "nodefony";
|
|
2
|
+
import type { ISecurityConfig } from "../../config/defineModuleConfig.js";
|
|
3
|
+
import type { IAuditStore } from "../../contracts/IAuditStore.js";
|
|
4
|
+
/**
|
|
5
|
+
* Registre de **fabriques de stores d'audit** — résout le nom configuré
|
|
6
|
+
* (`security.audit.store`) vers une instance d'{@link IAuditStore}, SANS coupler
|
|
7
|
+
* le cœur à un backend en dur.
|
|
8
|
+
*
|
|
9
|
+
* Pourquoi : le journal d'audit est pluggable par contrat (mémoire/ORM/Redis/Loki) ;
|
|
10
|
+
* un `if (name === "drizzle")` dans le service trahirait cette promesse. Le builtin
|
|
11
|
+
* sans dépendance (`memory`) s'enregistre au chargement de ce module ; les adapters
|
|
12
|
+
* lourds (`drizzle`, `mongoose`, `redis`) s'enregistrent depuis LEUR module
|
|
13
|
+
* (inversion de dépendance : ils importent `import type { IAuditStore }`, effacé à
|
|
14
|
+
* la compilation → 0 cycle). Convention-frère : `tokenStoreRegistry`,
|
|
15
|
+
* `webAuthnCredentialStoreRegistry`, `ormRegistry`.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Contexte passé à une fabrique de store d'audit : de quoi se construire
|
|
19
|
+
* (résolutions coûteuses en lazy à l'intérieur de l'instance).
|
|
20
|
+
*/
|
|
21
|
+
export interface IAuditStoreFactoryContext {
|
|
22
|
+
/** Container DI — résolution de services (ORM, redis...). */
|
|
23
|
+
readonly container: Container;
|
|
24
|
+
/** Config sécurité validée + gelée. */
|
|
25
|
+
readonly config: ISecurityConfig;
|
|
26
|
+
}
|
|
27
|
+
/** Fabrique d'un store d'audit pour un nom donné. */
|
|
28
|
+
export type AuditStoreFactory = (ctx: IAuditStoreFactoryContext) => IAuditStore;
|
|
29
|
+
/**
|
|
30
|
+
* Enregistre (ou remplace) la fabrique d'un store d'audit. Appelée par le builtin
|
|
31
|
+
* `memory` au chargement, et par les adapters (drizzle/mongoose/redis) pour les leurs.
|
|
32
|
+
*/
|
|
33
|
+
export declare function registerAuditStore(name: string, factory: AuditStoreFactory): void;
|
|
34
|
+
/** Fabrique d'un store par nom, ou `undefined` si inconnu. */
|
|
35
|
+
export declare function getAuditStoreFactory(name: string): AuditStoreFactory | undefined;
|
|
36
|
+
/** Noms enregistrés (validation boot, introspection Studio, tests). */
|
|
37
|
+
export declare function listAuditStores(): string[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IAuditEventFlags } from "../../contracts/IAuditEvent.js";
|
|
2
|
+
/** Métadonnées de provenance extraites d'un contexte, prêtes à enrichir un événement. */
|
|
3
|
+
export interface AuditContextInfo {
|
|
4
|
+
ip: string | null;
|
|
5
|
+
userAgent: string | null;
|
|
6
|
+
requestId: string | null;
|
|
7
|
+
flags: IAuditEventFlags;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Extrait IP / User-Agent / requestId + drapeaux de présence (jamais la valeur)
|
|
11
|
+
* d'un contexte de requête — pour enrichir un événement d'audit avec la
|
|
12
|
+
* provenance (« d'où vient cette tentative »). Calque {@link JsonAuditLogger}.
|
|
13
|
+
*
|
|
14
|
+
* @param context - contexte HTTP/WS courant (typé `unknown` à la frontière).
|
|
15
|
+
* @returns provenance normalisée ; champs `null` si l'info est absente.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readAuditContext(context: unknown): AuditContextInfo;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Container } from "nodefony";
|
|
2
|
+
import type { IAuditEventDraft } from "../../contracts/IAuditEvent.js";
|
|
3
|
+
/**
|
|
4
|
+
* Émet un événement d'audit **si** le service est présent — no-op sinon. La
|
|
5
|
+
* résolution se fait par le container (`auditService`) sur le **cold-path**
|
|
6
|
+
* (login, refus, révocation) : le coût d'un `Map.get` y est négligeable, et le
|
|
7
|
+
* journal reste **découplé** (module audit absent ou désactivé → aucun effet,
|
|
8
|
+
* jamais d'exception qui remonterait dans le flux métier).
|
|
9
|
+
*
|
|
10
|
+
* @param container - container du service émetteur (`this.container`).
|
|
11
|
+
* @param event - brouillon d'événement (l'`AuditService` pose `id` + `ts`).
|
|
12
|
+
*/
|
|
13
|
+
export declare function recordAudit(container: Container | null | undefined, event: IAuditEventDraft): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ContextType } from "@nodefony/http";
|
|
2
|
+
import type { IAuthenticator } from "../../contracts/IAuthenticator.js";
|
|
3
|
+
import type { IToken } from "../../contracts/IToken.js";
|
|
4
|
+
/**
|
|
5
|
+
* Acceptation EXPLICITE de l'anonymat dans une zone — le seul authenticator
|
|
6
|
+
* autorisé à produire un token non authentifié sans déclencher le Zero Trust.
|
|
7
|
+
*
|
|
8
|
+
* Ne le lister que volontairement : une zone `authenticators: ["jwt", "anonymous"]`
|
|
9
|
+
* (mode `first`) signifie « identifié si preuve présente, sinon visiteur anonyme
|
|
10
|
+
* accepté ». Sans lui, zone protégée + aucune preuve → 401. En mode `all` il
|
|
11
|
+
* reste utile en DERNIER : « le canal doit être prouvé (ex. mtls), l'identité
|
|
12
|
+
* utilisateur est optionnelle ».
|
|
13
|
+
*
|
|
14
|
+
* Zéro coût : `supports()` accepte tout, le token porte le singleton gelé
|
|
15
|
+
* `anonymousUser` (aucune allocation d'utilisateur).
|
|
16
|
+
*/
|
|
17
|
+
export declare class AnonymousAuthenticator implements IAuthenticator {
|
|
18
|
+
readonly name = "anonymous";
|
|
19
|
+
supports(): boolean;
|
|
20
|
+
createToken(): Promise<IToken>;
|
|
21
|
+
/** Toujours un succès — accepter l'anonymat ne vérifie rien. */
|
|
22
|
+
authenticate(token: IToken): Promise<IToken>;
|
|
23
|
+
onSuccess(_context: ContextType, _token: IToken): Promise<void>;
|
|
24
|
+
onFailure(_context: ContextType, _error: Error): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export default AnonymousAuthenticator;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Container } from "nodefony";
|
|
2
|
+
import type { ContextType } from "@nodefony/http";
|
|
3
|
+
import type { IAuthenticator } from "../../contracts/IAuthenticator.js";
|
|
4
|
+
import type { IToken } from "../../contracts/IToken.js";
|
|
5
|
+
/** Paramètres effectifs d'un {@link ApiKeyAuthenticator} (dérivés de la config). */
|
|
6
|
+
export interface IApiKeyAuthenticatorRuntime {
|
|
7
|
+
/** Marque des clés (`apiKeys.prefix`) — discrimine un PAT d'un JWT. */
|
|
8
|
+
prefix: string;
|
|
9
|
+
/** Coalescence d'écriture `lastUsedAt` (s) — 0 = écrit à chaque usage. */
|
|
10
|
+
lastUsedThrottleS: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Authentification par **clé API personnelle (PAT, P6.12)** présentée en
|
|
14
|
+
* `Authorization: Bearer <prefix>_…` (RFC 6750). Réservée API/CI/scripts — le web
|
|
15
|
+
* utilise la session BFF.
|
|
16
|
+
*
|
|
17
|
+
* Un PAT est un **bearer opaque** (≠ JWT auto-porté) : sa vérité vit côté serveur
|
|
18
|
+
* (`ITokenStore`), donc il est **révocable immédiatement**. Discrimination du
|
|
19
|
+
* JWT : le PAT porte le préfixe `<prefix>_` (le JWT a la structure compacte
|
|
20
|
+
* `a.b.c`) → les deux authenticators cohabitent dans une même zone.
|
|
21
|
+
*
|
|
22
|
+
* Défenses :
|
|
23
|
+
* - **forme + CRC validés AVANT tout accès au store** ({@link parseApiKey}) →
|
|
24
|
+
* une valeur malformée n'atteint jamais la base (anti-DoS) ;
|
|
25
|
+
* - lookup par **hash** (`sha256`) — le secret n'existe nulle part au repos ;
|
|
26
|
+
* - **révocation** immédiate (`revokedAt`) + **expiration** (`expiresAt`) +
|
|
27
|
+
* **ban en masse** du porteur (`invalidBefore` vs `createdAt`) ;
|
|
28
|
+
* - **sujet revérifié** à chaque requête (`loadUserByIdentifier` → disparu/
|
|
29
|
+
* inactif/verrouillé = rejet) — rôles **frais** (révocation effective) ;
|
|
30
|
+
* - message d'échec **uniforme** (anti-énumération).
|
|
31
|
+
*
|
|
32
|
+
* Dépendances (store, userProvider) résolues **paresseusement** du container.
|
|
33
|
+
*/
|
|
34
|
+
export declare class ApiKeyAuthenticator implements IAuthenticator {
|
|
35
|
+
#private;
|
|
36
|
+
readonly name = "apikey";
|
|
37
|
+
/**
|
|
38
|
+
* @param container - container DI (résolution lazy de `tokenStore`/`users`).
|
|
39
|
+
* @param runtime - préfixe + throttle effectifs (dérivés de `config.apiKeys`).
|
|
40
|
+
*/
|
|
41
|
+
constructor(container: Container, runtime: IApiKeyAuthenticatorRuntime);
|
|
42
|
+
/** La requête porte-t-elle un `Authorization: Bearer <prefix>_…` ? (test bon marché) */
|
|
43
|
+
supports(context: ContextType): boolean;
|
|
44
|
+
/** Extrait la valeur brute (non vérifiée) → portée par un `UserToken` type `"apikey"`. */
|
|
45
|
+
createToken(context: ContextType): Promise<IToken>;
|
|
46
|
+
/**
|
|
47
|
+
* Valide la clé (forme+CRC, puis store) et résout le sujet — ou lève un 401 au
|
|
48
|
+
* message uniforme.
|
|
49
|
+
*
|
|
50
|
+
* @throws AuthenticationError (401) — clé malformée/inconnue/révoquée/expirée,
|
|
51
|
+
* ou sujet disparu/banni.
|
|
52
|
+
* @throws Error (câblage : store/users absents) — loggée ERROR par le firewall
|
|
53
|
+
* puis 401 fail-closed (rien ne fuite au client).
|
|
54
|
+
*/
|
|
55
|
+
authenticate(token: IToken): Promise<IToken>;
|
|
56
|
+
/**
|
|
57
|
+
* Inscrit la trace d'usage de la clé — horodatage, IP et agent.
|
|
58
|
+
*
|
|
59
|
+
* C'est ici, et pas dans `authenticate()`, parce que c'est ici qu'on reçoit
|
|
60
|
+
* le contexte. La provenance se lit par les ACCESSEURS proxy-aware des
|
|
61
|
+
* contextes concrets (`getRemoteAddress()` dépouille `X-Forwarded-For` selon
|
|
62
|
+
* `trustProxy`), absents du type de base — duck-typing optionnel, même
|
|
63
|
+
* approche que `AuthFlow.#openSession()`.
|
|
64
|
+
*
|
|
65
|
+
* Rien n'est écrit si `authenticate()` n'a pas posé le marqueur : la fenêtre
|
|
66
|
+
* de throttle n'était pas dépassée, et le hot path reste sans écriture.
|
|
67
|
+
*/
|
|
68
|
+
onSuccess(context: ContextType, token: IToken): Promise<void>;
|
|
69
|
+
/** Slot audit (P6.14) — le 401 + challenge sont posés par le firewall. */
|
|
70
|
+
onFailure(_context: ContextType, _error: Error): Promise<void>;
|
|
71
|
+
/** Challenge RFC 6750/7235 posé par le firewall sur les 401 de la zone. */
|
|
72
|
+
challenge(): string;
|
|
73
|
+
}
|
|
74
|
+
export default ApiKeyAuthenticator;
|