@nodefony/security 10.0.0-beta.5 → 10.0.0-beta.6

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.
Files changed (73) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorate.js +1 -1
  2. package/dist/index.js +60 -60
  3. package/dist/nodefony/config/config.js +13 -5
  4. package/dist/nodefony/contracts/IWebhookPayload.js +1 -0
  5. package/dist/nodefony/errors/index.js +4 -4
  6. package/dist/nodefony/keycloak/keycloakAdmin.js +3 -3
  7. package/dist/nodefony/keycloak/security-keycloak-realm.js +1 -1
  8. package/dist/nodefony/service/accessTokenVerifier.js +5 -5
  9. package/dist/nodefony/service/apiKeys.js +8 -4
  10. package/dist/nodefony/service/authFlow.js +3 -3
  11. package/dist/nodefony/service/authorization.js +1 -1
  12. package/dist/nodefony/service/firewall.js +233 -41
  13. package/dist/nodefony/service/keycloak.js +1 -1
  14. package/dist/nodefony/service/oauth2.js +8 -4
  15. package/dist/nodefony/service/securityHeaders.js +340 -47
  16. package/dist/nodefony/service/tokenService.js +8 -8
  17. package/dist/nodefony/service/totp.js +3 -3
  18. package/dist/nodefony/service/webAuthn.js +1 -1
  19. package/dist/nodefony/service/webhooks.js +11 -4
  20. package/dist/nodefony/src/admin/SecurityAdminApi.js +19 -0
  21. package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +1 -1
  22. package/dist/nodefony/src/authenticator/ExternalSubjectResolver.js +31 -7
  23. package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +62 -20
  24. package/dist/nodefony/src/authenticator/JwtAuthenticator.js +5 -2
  25. package/dist/nodefony/src/authenticator/SessionAuthenticator.js +1 -1
  26. package/dist/nodefony/src/authenticator/authenticatorRegistry.js +1 -1
  27. package/dist/nodefony/src/csp.js +265 -10
  28. package/dist/nodefony/src/cspReports.js +307 -0
  29. package/dist/nodefony/src/oauth/clientCredentials.js +1 -1
  30. package/dist/nodefony/src/oauth/metadata.js +1 -1
  31. package/dist/nodefony/src/oauth/oauthProviderRegistry.js +17 -4
  32. package/dist/nodefony/src/oauth/providers/github.js +1 -1
  33. package/dist/nodefony/src/realtime/UserRealtimeToken.js +59 -2
  34. package/dist/nodefony/src/realtime/frameAuthorizer.js +6 -5
  35. package/dist/nodefony/src/token/RemoteJwtVerifier.js +8 -2
  36. package/dist/nodefony/src/token/tokenTimeClaims.js +34 -0
  37. package/dist/nodefony/src/totp/totpOperations.js +1 -1
  38. package/dist/nodefony/src/voter/RoleVoter.js +1 -1
  39. package/dist/nodefony/src/webhook/WebhookDispatcher.js +71 -17
  40. package/dist/nodefony/src/webhook/webhookDelivery.js +3 -3
  41. package/dist/nodefony/testing/index.js +26 -36
  42. package/dist/types/index.d.ts +1 -0
  43. package/dist/types/nodefony/config/config.d.ts +2 -0
  44. package/dist/types/nodefony/contracts/IApiKey.d.ts +8 -2
  45. package/dist/types/nodefony/contracts/IFirewall.d.ts +15 -2
  46. package/dist/types/nodefony/contracts/ITokenStore.d.ts +7 -3
  47. package/dist/types/nodefony/contracts/IWebhookEndpoint.d.ts +13 -3
  48. package/dist/types/nodefony/contracts/IWebhookPayload.d.ts +24 -0
  49. package/dist/types/nodefony/service/apiKeys.d.ts +3 -0
  50. package/dist/types/nodefony/service/firewall.d.ts +28 -7
  51. package/dist/types/nodefony/service/securityHeaders.d.ts +82 -11
  52. package/dist/types/nodefony/service/webhooks.d.ts +7 -1
  53. package/dist/types/nodefony/src/admin/SecurityAdminApi.d.ts +3 -0
  54. package/dist/types/nodefony/src/authenticator/ExternalSubjectResolver.d.ts +11 -0
  55. package/dist/types/nodefony/src/csp.d.ts +142 -0
  56. package/dist/types/nodefony/src/cspReports.d.ts +73 -0
  57. package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +15 -0
  58. package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +35 -1
  59. package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +25 -17
  60. package/dist/types/nodefony/src/token/tokenTimeClaims.d.ts +32 -0
  61. package/dist/types/nodefony/src/webhook/WebhookDispatcher.d.ts +36 -1
  62. package/dist/types/nodefony/testing/index.d.ts +6 -0
  63. package/docs/api-keys.md +3 -1
  64. package/docs/authenticators.md +6 -4
  65. package/docs/csrf.md +3 -2
  66. package/docs/external-jwt.md +5 -1
  67. package/docs/firewall.md +3 -2
  68. package/docs/headers.md +427 -71
  69. package/docs/login-page.md +4 -3
  70. package/docs/totp.md +2 -2
  71. package/docs/webauthn.md +2 -2
  72. package/docs/webhooks.md +48 -11
  73. package/package.json +14 -14
@@ -1,4 +1,4 @@
1
- // \0@oxc-project+runtime@0.155.0/helpers/esm/decorate.js
1
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
package/dist/index.js CHANGED
@@ -1,94 +1,94 @@
1
- import { JwtKeystore } from "./nodefony/src/token/JwtKeystore.js";
2
1
  import config from "./nodefony/config/config.js";
3
- import { SecuredArea } from "./nodefony/src/SecuredArea.js";
4
- import { LoginThrottler } from "./nodefony/src/throttle/LoginThrottler.js";
2
+ import { Firewall } from "./nodefony/service/firewall.js";
3
+ import { AuthFlow } from "./nodefony/service/authFlow.js";
4
+ import { TokenService } from "./nodefony/service/tokenService.js";
5
5
  import { InsecureTransportError } from "./nodefony/errors/InsecureTransportError.js";
6
- import { CredentialTransportPolicy, shouldEnforceCredentialTransport } from "./nodefony/src/transport/CredentialTransportPolicy.js";
7
- import { RoleHierarchyWalker } from "./nodefony/src/RoleHierarchyWalker.js";
8
- import { CsrfTokenManager } from "./nodefony/src/csrfToken.js";
6
+ import { AccessTokenVerifierService } from "./nodefony/service/accessTokenVerifier.js";
7
+ import { Authorization } from "./nodefony/service/authorization.js";
8
+ import { WebAuthnService } from "./nodefony/service/webAuthn.js";
9
9
  import { CsrfError } from "./nodefony/errors/CsrfError.js";
10
- import { Csrf } from "./nodefony/service/csrf.js";
11
- import { Cors } from "./nodefony/service/cors.js";
12
- import { SecurityHeaders } from "./nodefony/service/securityHeaders.js";
10
+ import { OAuth2Service } from "./nodefony/service/oauth2.js";
11
+ import { ApiKeyService } from "./nodefony/service/apiKeys.js";
12
+ import AuditService from "./nodefony/service/auditService.js";
13
13
  import { AuthenticationError } from "./nodefony/errors/AuthenticationError.js";
14
14
  import { ThrottledError } from "./nodefony/errors/ThrottledError.js";
15
15
  import { UnverifiableTokenError } from "./nodefony/errors/UnverifiableTokenError.js";
16
+ import { TotpService } from "./nodefony/service/totp.js";
17
+ import { WebhookService } from "./nodefony/service/webhooks.js";
18
+ import { KeycloakService } from "./nodefony/service/keycloak.js";
19
+ import SecuritySecrets from "./nodefony/command/security-secrets.js";
20
+ import SecurityUserAdd from "./nodefony/command/security-user-add.js";
21
+ import SecurityUserList from "./nodefony/command/security-user-list.js";
22
+ import SecurityUserDelete from "./nodefony/command/security-user-delete.js";
23
+ import SecurityUserPassword from "./nodefony/command/security-user-password.js";
24
+ import { publicRoutesNotice } from "./nodefony/src/boot/publicRoutesNotice.js";
25
+ import SecurityMcpToken from "./nodefony/command/security-mcp-token.js";
26
+ import SecurityKeycloakRealm from "./nodefony/keycloak/security-keycloak-realm.js";
27
+ import SecurityOAuthDoctor from "./nodefony/command/security-oauth-doctor.js";
28
+ import { createSecurityAdminApi, parseAuditQuery, registerSecurityAdminApi } from "./nodefony/src/admin/SecurityAdminApi.js";
29
+ import { registerUserRevocationCascade, usersEventBusNotice } from "./nodefony/src/admin/userRevocationCascade.js";
16
30
  import { defineSecurityConfig, securityConfigJsonSchema } from "./nodefony/config/defineModuleConfig.js";
31
+ import __decorate from "./_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.js";
32
+ import { RemoteJwtVerifier } from "./nodefony/src/token/RemoteJwtVerifier.js";
33
+ import { amrForFactor } from "./nodefony/src/sessionAuthentication.js";
34
+ import { ApiKeyError } from "./nodefony/errors/ApiKeyError.js";
35
+ import { InvalidTargetError } from "./nodefony/errors/InvalidTargetError.js";
36
+ import { SecuredArea } from "./nodefony/src/SecuredArea.js";
37
+ import { Csrf } from "./nodefony/service/csrf.js";
38
+ import { CsrfTokenManager } from "./nodefony/src/csrfToken.js";
39
+ import { Cors } from "./nodefony/service/cors.js";
40
+ import { SecurityHeaders } from "./nodefony/service/securityHeaders.js";
41
+ import { RoleHierarchyWalker } from "./nodefony/src/RoleHierarchyWalker.js";
42
+ import { VoterVote } from "./nodefony/contracts/IAccessVoter.js";
17
43
  import { AnonymousToken } from "./nodefony/src/token/AnonymousToken.js";
18
- import { AnonymousAuthenticator } from "./nodefony/src/authenticator/AnonymousAuthenticator.js";
19
44
  import { UserToken } from "./nodefony/src/token/UserToken.js";
20
- import { SessionAuthenticator } from "./nodefony/src/authenticator/SessionAuthenticator.js";
45
+ import { RoleVoter } from "./nodefony/src/voter/RoleVoter.js";
46
+ import { ScopeVoter } from "./nodefony/src/voter/ScopeVoter.js";
47
+ import { listVoterFactories, registerVoterFactory } from "./nodefony/src/voter/voterRegistry.js";
48
+ import { AnonymousAuthenticator } from "./nodefony/src/authenticator/AnonymousAuthenticator.js";
21
49
  import { UserPasswordAuthenticator } from "./nodefony/src/authenticator/UserPasswordAuthenticator.js";
22
- import peekIssuer from "./nodefony/src/authenticator/peekIssuer.js";
50
+ import { SessionAuthenticator } from "./nodefony/src/authenticator/SessionAuthenticator.js";
23
51
  import { JwtAuthenticator } from "./nodefony/src/authenticator/JwtAuthenticator.js";
24
- import { generateApiKey, hashApiKey, looksLikeApiKey, parseApiKey } from "./nodefony/src/apikey/apiKeyFormat.js";
25
52
  import { ApiKeyAuthenticator } from "./nodefony/src/authenticator/ApiKeyAuthenticator.js";
26
53
  import { ExternalJwtAuthenticator } from "./nodefony/src/authenticator/ExternalJwtAuthenticator.js";
27
- import { resolveJwtRuntime } from "./nodefony/src/token/jwtRuntime.js";
54
+ import peekIssuer from "./nodefony/src/authenticator/peekIssuer.js";
55
+ import { generateApiKey, hashApiKey, looksLikeApiKey, parseApiKey } from "./nodefony/src/apikey/apiKeyFormat.js";
28
56
  import { getAuthenticatorFactory, listAuthenticatorFactories, registerAuthenticatorFactory } from "./nodefony/src/authenticator/authenticatorRegistry.js";
29
- import { recordAudit } from "./nodefony/src/audit/recordAudit.js";
30
- import { readAuditContext } from "./nodefony/src/audit/readAuditContext.js";
31
- import { Firewall } from "./nodefony/service/firewall.js";
32
- import { amrForFactor } from "./nodefony/src/sessionAuthentication.js";
33
- import { AuthFlow } from "./nodefony/service/authFlow.js";
34
- import { InvalidTargetError } from "./nodefony/errors/InvalidTargetError.js";
57
+ import { MemoryTokenStore } from "./nodefony/src/token/MemoryTokenStore.js";
35
58
  import { TOKEN_DEFAULT_ORDER, TOKEN_SORTABLE_FIELDS } from "./nodefony/src/token/tokenSort.js";
59
+ import { tokenStatusCriteria } from "./nodefony/src/token/tokenCriteria.js";
36
60
  import { matchesTokenStatus, tokenStatusOf } from "./nodefony/src/token/tokenStatus.js";
37
- import { MemoryTokenStore } from "./nodefony/src/token/MemoryTokenStore.js";
61
+ import { TOKEN_FACETS } from "./nodefony/src/token/tokenFilters.js";
62
+ import { JwtKeystore } from "./nodefony/src/token/JwtKeystore.js";
63
+ import { resolveJwtRuntime } from "./nodefony/src/token/jwtRuntime.js";
38
64
  import { getTokenStoreFactory, listTokenStores, registerTokenStore } from "./nodefony/src/token/tokenStoreRegistry.js";
39
- import { TokenService } from "./nodefony/service/tokenService.js";
40
- import { RemoteJwtVerifier } from "./nodefony/src/token/RemoteJwtVerifier.js";
41
- import { AccessTokenVerifierService } from "./nodefony/service/accessTokenVerifier.js";
42
- import { VoterVote } from "./nodefony/contracts/IAccessVoter.js";
43
- import { RoleVoter } from "./nodefony/src/voter/RoleVoter.js";
44
- import { ScopeVoter } from "./nodefony/src/voter/ScopeVoter.js";
45
- import { listVoterFactories, registerVoterFactory } from "./nodefony/src/voter/voterRegistry.js";
46
- import { Authorization } from "./nodefony/service/authorization.js";
47
65
  import { MemoryWebAuthnCredentialStore } from "./nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js";
48
66
  import { getWebAuthnStoreFactory, listWebAuthnStores, registerWebAuthnStore } from "./nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js";
49
- import { WebAuthnService } from "./nodefony/service/webAuthn.js";
50
- import { KeycloakAdminClient, KeycloakAdminError, keycloakAdminTarget, keycloakLoginThemeCheck, readKeycloakLoginTheme } from "./nodefony/keycloak/keycloakAdmin.js";
67
+ import { getOAuthProviderFactory, listOAuthProviders, oauthProviderRequiresIssuer, registerOAuthProvider } from "./nodefony/src/oauth/oauthProviderRegistry.js";
51
68
  import { OAuth2Client, OAuth2RequestError, OAuth2Tokens, createCodeChallenge, generateCodeVerifier, generateState } from "./nodefony/src/oauth/oauth2Client.js";
52
69
  import { discoverAuthorizationServer } from "./nodefony/src/oauth/metadata.js";
70
+ import { requestClientCredentialsToken } from "./nodefony/src/oauth/clientCredentials.js";
71
+ import { diagnoseOAuthProvider } from "./nodefony/src/oauth/providerDiagnosis.js";
72
+ import { KeycloakAdminClient, KeycloakAdminError, keycloakAdminTarget, keycloakLoginThemeCheck, readKeycloakLoginTheme } from "./nodefony/keycloak/keycloakAdmin.js";
53
73
  import { createDiscoveredOidcProvider, createOidcProvider, readLogoutTokenClaims } from "./nodefony/src/oauth/providers/oidc.js";
74
+ import { SsrfError } from "./nodefony/errors/SsrfError.js";
54
75
  import { createGithubProvider } from "./nodefony/src/oauth/providers/github.js";
55
- import { getOAuthProviderFactory, listOAuthProviders, oauthProviderRequiresIssuer, registerOAuthProvider } from "./nodefony/src/oauth/oauthProviderRegistry.js";
56
- import { diagnoseOAuthProvider } from "./nodefony/src/oauth/providerDiagnosis.js";
57
- import { OAuth2Service } from "./nodefony/service/oauth2.js";
58
- import { TOKEN_FACETS } from "./nodefony/src/token/tokenFilters.js";
59
- import { ApiKeyError } from "./nodefony/errors/ApiKeyError.js";
60
- import { ApiKeyService } from "./nodefony/service/apiKeys.js";
61
76
  import { MemoryAuditStore } from "./nodefony/src/audit/MemoryAuditStore.js";
62
77
  import { getAuditStoreFactory, listAuditStores, registerAuditStore } from "./nodefony/src/audit/auditStoreRegistry.js";
63
- import AuditService from "./nodefony/service/auditService.js";
78
+ import { recordAudit } from "./nodefony/src/audit/recordAudit.js";
79
+ import { readAuditContext } from "./nodefony/src/audit/readAuditContext.js";
64
80
  import { MemoryTotpSecretStore } from "./nodefony/src/totp/MemoryTotpSecretStore.js";
65
81
  import { getTotpStoreFactory, listTotpStores, registerTotpStore } from "./nodefony/src/totp/totpSecretStoreRegistry.js";
66
- import { decryptSecret, deriveKey, encryptSecret, generateEphemeralKey } from "./nodefony/src/crypto/secretCipher.js";
67
- import { deriveTotpKey } from "./nodefony/src/totp/totpCipher.js";
68
- import { base32Decode, totpCode } from "./nodefony/src/totp/totpCrypto.js";
69
82
  import { beginTotpEnrollment, confirmTotpEnrollment, disableTotp, totpStatus, verifyTotpLogin } from "./nodefony/src/totp/totpOperations.js";
70
- import { TotpService } from "./nodefony/service/totp.js";
71
- import { WEBHOOK_DEFAULT_ORDER, WEBHOOK_SORTABLE_FIELDS } from "./nodefony/src/webhook/webhookSort.js";
83
+ import { base32Decode, totpCode } from "./nodefony/src/totp/totpCrypto.js";
84
+ import { deriveTotpKey } from "./nodefony/src/totp/totpCipher.js";
72
85
  import { MemoryWebhookStore } from "./nodefony/src/webhook/MemoryWebhookStore.js";
86
+ import { WEBHOOK_DEFAULT_ORDER, WEBHOOK_SORTABLE_FIELDS } from "./nodefony/src/webhook/webhookSort.js";
73
87
  import { getWebhookStoreFactory, listWebhookStores, registerWebhookStore } from "./nodefony/src/webhook/webhookStoreRegistry.js";
74
- import { SsrfError } from "./nodefony/errors/SsrfError.js";
75
88
  import { assertPublicUrl, isBlockedAddress } from "./nodefony/src/net/ssrfGuard.js";
76
- import { WebhookService } from "./nodefony/service/webhooks.js";
77
- import { KeycloakService } from "./nodefony/service/keycloak.js";
78
- import SecuritySecrets from "./nodefony/command/security-secrets.js";
79
- import SecurityUserAdd from "./nodefony/command/security-user-add.js";
80
- import SecurityUserList from "./nodefony/command/security-user-list.js";
81
- import SecurityUserDelete from "./nodefony/command/security-user-delete.js";
82
- import SecurityUserPassword from "./nodefony/command/security-user-password.js";
83
- import { publicRoutesNotice } from "./nodefony/src/boot/publicRoutesNotice.js";
84
- import SecurityMcpToken from "./nodefony/command/security-mcp-token.js";
85
- import SecurityKeycloakRealm from "./nodefony/keycloak/security-keycloak-realm.js";
86
- import SecurityOAuthDoctor from "./nodefony/command/security-oauth-doctor.js";
87
- import { createSecurityAdminApi, parseAuditQuery, registerSecurityAdminApi } from "./nodefony/src/admin/SecurityAdminApi.js";
88
- import { registerUserRevocationCascade, usersEventBusNotice } from "./nodefony/src/admin/userRevocationCascade.js";
89
- import __decorate from "./_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
90
- import { tokenStatusCriteria } from "./nodefony/src/token/tokenCriteria.js";
91
- import { requestClientCredentialsToken } from "./nodefony/src/oauth/clientCredentials.js";
89
+ import { decryptSecret, deriveKey, encryptSecret, generateEphemeralKey } from "./nodefony/src/crypto/secretCipher.js";
90
+ import { LoginThrottler } from "./nodefony/src/throttle/LoginThrottler.js";
91
+ import { CredentialTransportPolicy, shouldEnforceCredentialTransport } from "./nodefony/src/transport/CredentialTransportPolicy.js";
92
92
  import { AccessDeniedError } from "./nodefony/errors/AccessDeniedError.js";
93
93
  import "./nodefony/errors/index.js";
94
94
  import "./nodefony/contracts/index.js";
@@ -96,20 +96,28 @@ const csrfSchema = z.strictObject({
96
96
  secret: z.string().min(16).optional().describe("Secret HMAC du token synchronizer (`@CsrfProtect`, défense en profondeur opt-in). PROD : fixer via env (≥16 car.) — DOIT être PARTAGÉ entre process (cluster) sinon les tokens d'un pod sont rejetés par un autre. DEV : si absent, un secret éphémère est généré au boot (re-généré à chaque restart → invalide les tokens en cours). Sans valeur, `@CsrfProtect` fonctionne en dev mais N'EST PAS sûr en cluster.")
97
97
  }).describe("Cross-Site Request Forgery — Fetch Metadata d'abord (OWASP 2025) ; token synchronizer = opt-in @CsrfProtect.");
98
98
  const headersSchema = z.strictObject({
99
- enabled: z.boolean().default(true).describe("En-têtes de sécurité HTTP (natif, sans la lib helmet)."),
99
+ enabled: z.boolean().default(true).describe("Couche APPLICATIVE des en-têtes de sécurité : CSP, Referrer-Policy, COOP/COEP/CORP, Origin-Agent-Cluster, Permissions-Policy. `false` les coupe tous. Le transport (X-Content-Type-Options, X-Frame-Options, HSTS) n'en dépend pas : il se règle dans `http.securityHeaders`. Couper la seule CSP : `csp: \"\"`."),
100
100
  hsts: z.boolean().default(true).meta({
101
101
  reserved: true,
102
- description: "INERTE ICI — l'en-tête est posé par @nodefony/http à l'entrée brute (couvre statics, erreurs et serveur nu ; une seule source par en-tête). Pour le régler : `http.securityHeaders.strictTransportSecurity` (`null` pour ne pas émettre l'en-tête)."
102
+ description: "INERTE ICI — l'en-tête est posé par @nodefony/http sur toute réponse (couvre statics, erreurs et serveur nu ; une seule source par en-tête). Pour le régler : `http.securityHeaders.strictTransportSecurity` (`null` pour ne pas émettre l'en-tête)."
103
103
  }),
104
104
  hstsMaxAgeS: z.number().int().default(31536e3).meta({
105
105
  reserved: true,
106
106
  description: "INERTE ICI — pour régler la durée : `http.securityHeaders.strictTransportSecurity.maxAge`."
107
107
  }),
108
- csp: z.string().default("default-src 'self'; script-src 'self' 'nonce-{{nonce}}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://www.gravatar.com https://*.googleusercontent.com https://avatars.githubusercontent.com; font-src 'self' data:; connect-src 'self'; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; form-action 'self'").describe("Content-Security-Policy « secure-but-usable ». Seul `script-src` est strict (self + `{{nonce}}` substitué par requête si `cspNonces`) = défense XSS ; le reste couvre les besoins réels (CSS-in-JS, img/font inline, blobs, fetch/WS same-origin, workers) + durcissements (object 'none', base-uri/form-action 'self')."),
108
+ csp: z.string().default("default-src 'self'; script-src 'nonce-{{nonce}}' 'strict-dynamic' 'self'; style-src 'self' 'unsafe-inline' 'nonce-{{nonce}}'; style-src-attr 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests").describe("Content-Security-Policy « secure-but-usable ». `script-src` : `{{nonce}}` (substitué par requête si `cspNonces`) et `'strict-dynamic'` — un navigateur CSP3 ignore alors `'self'`, gardé en repli CSP2. `style-src` : le nonce ferme l'exfiltration par `<style>` injecté ; `'unsafe-inline'` n'y joue que là où le nonce est retiré (CSP2+ l'ignore en présence d'un nonce), et `style-src-attr 'unsafe-inline'` tolère les attributs `style=` (ADR-0024 §C2) ; un document servi tel quel (fichier statique) reçoit la politique sans nonce ni `'strict-dynamic'` (ADR-0024 §C1) = défense XSS ; le reste couvre les besoins réels (img/font inline, blobs, fetch/WS same-origin, workers) + durcissements (object 'none', base-uri/form-action 'self'). `upgrade-insecure-requests` n’est posé que sur une page servie en https (proxy de confiance compris) ; le retirer de cette chaîne le coupe partout."),
109
+ cspSources: z.record(z.string(), z.record(z.string(), z.array(z.string()))).default({}).describe("Sources AJOUTÉES à la politique `csp` sans la réécrire, par contribution nommée : `{ cdn: { \"img-src\": [\"https://cdn.example.com\"] } }`. Indexé par NOM et non par directive : la fusion de configuration remplace un tableau case par case, deux contributions nommées coexistent (une application et un module, par exemple) ; un même nom écrit deux fois est un remplacement. Fusionné par la même règle que les contributions des modules ; une contribution malformée est ignorée et journalisée, une source ouverte à tout hôte est gardée (choix de l'application) et dite. Sans effet quand `csp` vaut `\"\"`."),
109
110
  cspNonces: z.boolean().default(true).describe("Nonce CSP par requête (bloque l'inline non signé)."),
111
+ cspReport: z.union([z.boolean(), z.string()]).default(true).describe("Rapports de violation CSP (ADR-0024 §C7). `true` : la politique les demande (`report-uri` + `report-to`, en-tête `Reporting-Endpoints`) et l'application les reçoit sur `/nodefony/security/api/csp-report` — corps borné, quota par adresse, URL expurgées, mémoire seule —, lus par `GET /nodefony/security/api/csp-reports` (administrateur). Une chaîne : l'URL de votre propre collecteur (chemin absolu ou https). `false` : aucun rapport. Sans effet quand `csp` vaut `\"\"` ou déclare déjà `report-uri`/`report-to`. Une URL malformée est retirée et journalisée."),
112
+ /**
113
+ * @deprecated Inerte : régler `http.securityHeaders.frameOptions`, qui pilote
114
+ * `X-Frame-Options` ET `frame-ancestors` (ADR-0024 §C5). Retrait à la
115
+ * prochaine version majeure.
116
+ */
110
117
  frameguard: z.enum(["deny", "sameorigin"]).default("deny").meta({
111
118
  reserved: true,
112
- description: "INERTE ICI — l'en-tête anti-clickjacking est posé par @nodefony/http (il couvre aussi les statics et les erreurs). Pour le régler : `http.securityHeaders.frameOptions` (`DENY` | `SAMEORIGIN` | `null`)."
119
+ deprecated: true,
120
+ description: "DÉPRÉCIÉ, INERTE — remplacé par `http.securityHeaders.frameOptions` (`DENY` | `SAMEORIGIN` | `null`), qui pilote `X-Frame-Options` ET `frame-ancestors` et couvre aussi les statiques et les erreurs. Retrait à la prochaine version majeure."
113
121
  }),
114
122
  noSniff: z.boolean().default(true).meta({
115
123
  reserved: true,
@@ -260,7 +268,7 @@ const trustedIssuerSchema = z.strictObject({
260
268
  "EdDSA"
261
269
  ]).describe("Algorithmes de signature acceptés — allowlist SERVEUR (RFC 8725 §3.1) : l'algorithme n'est jamais déduit de l'en-tête du jeton. Restreindre à ce que l'émetteur utilise réellement."),
262
270
  typ: z.string().optional().describe("Valeur exigée de l'en-tête `typ` — « at+jwt » pour un émetteur conforme RFC 9068. Non exigé par défaut : le parc réel est inégal, et la séparation entre jetons est déjà portée par l'audience, qui n'est pas facultative."),
263
- requiredClaims: z.array(z.string()).default([]).describe("Claims dont la PRÉSENCE est exigée, en plus de `iss`/`aud` (toujours obligatoires). Ex. `[\"sub\"]` pour refuser un jeton sans sujet identifiable."),
271
+ requiredClaims: z.array(z.string()).default([]).describe("Claims dont la PRÉSENCE est exigée, en plus de `iss`, `aud`, `exp` et `iat` (toujours obligatoires, RFC 9068 §2.2 ; un `iat` daté du futur est refusé). Ex. `[\"sub\"]` pour refuser un jeton sans sujet identifiable."),
264
272
  subjectMapping: z.enum(["prefixed", "subject"]).default("prefixed").describe("Comment le sujet (`sub`) de CET émetteur devient un identifiant dans l'espace de noms local. « prefixed » (défaut) : l'identifiant cherché est « <issuer>#<sub> » — deux émetteurs ne peuvent pas se disputer un compte, et surtout aucun sujet étranger ne peut tomber par hasard sur un identifiant local existant. « subject » : le `sub` est cherché TEL QUEL, l'ancien comportement — à ne déclarer que si l'on maîtrise l'espace de noms de cet émetteur, typiquement parce qu'il EST cette application (jetons maison) ou parce que ses sujets sont déjà des identifiants locaux. 🔴 Le défaut n'est pas un réglage de confort : un annuaire où l'utilisateur choisit son identifiant permet de présenter `sub: \"admin\"`, et « subject » rattache alors le porteur au compte local du même nom. `sub` n'est unique que dans l'espace de son émetteur (OIDC Core §2) — l'identité est la paire, jamais le sujet seul.")
265
273
  });
266
274
  const resourceServerSchema = z.strictObject({
@@ -0,0 +1 @@
1
+ export {};
@@ -1,10 +1,10 @@
1
- import { InsecureTransportError } from "./InsecureTransportError.js";
2
- import { CsrfError } from "./CsrfError.js";
3
1
  import { AuthenticationError } from "./AuthenticationError.js";
2
+ import { AccessDeniedError } from "./AccessDeniedError.js";
4
3
  import { ThrottledError } from "./ThrottledError.js";
5
4
  import { UnverifiableTokenError } from "./UnverifiableTokenError.js";
6
5
  import { InvalidTargetError } from "./InvalidTargetError.js";
7
- import { WebAuthnError } from "./WebAuthnError.js";
6
+ import { CsrfError } from "./CsrfError.js";
8
7
  import { SsrfError } from "./SsrfError.js";
9
- import { AccessDeniedError } from "./AccessDeniedError.js";
8
+ import { WebAuthnError } from "./WebAuthnError.js";
9
+ import { InsecureTransportError } from "./InsecureTransportError.js";
10
10
  export { AccessDeniedError, AuthenticationError, CsrfError, InsecureTransportError, InvalidTargetError, SsrfError, ThrottledError, UnverifiableTokenError, WebAuthnError };
@@ -1,4 +1,5 @@
1
1
  import { isNodefonyKeycloakTheme, keycloakLoginTheme, realmNameFromIssuer } from "./keycloakRealmInput.js";
2
+ import { ownField } from "nodefony";
2
3
  // nodefony/keycloak/keycloakAdmin.ts
3
4
  /** Refus ou échec d'un appel ; le message nomme la cause et le geste suivant. */
4
5
  var KeycloakAdminError = class extends Error {
@@ -183,9 +184,8 @@ var KeycloakAdminClient = class {
183
184
  throw new KeycloakAdminError(`compte administrateur refusé par Keycloak (HTTP ${response.status}, realm master) — vérifier NF_KEYCLOAK_ADMIN_USERNAME et NF_KEYCLOAK_ADMIN_PASSWORD`, response.status);
184
185
  }
185
186
  const payload = await response.json();
186
- const field = (key) => typeof payload === "object" && payload !== null ? Object.getOwnPropertyDescriptor(payload, key)?.value : void 0;
187
- const accessToken = field("access_token");
188
- const expiresIn = field("expires_in");
187
+ const accessToken = ownField(payload, "access_token");
188
+ const expiresIn = ownField(payload, "expires_in");
189
189
  if (typeof accessToken !== "string" || accessToken === "") throw new KeycloakAdminError("Keycloak n'a rendu aucun jeton d'administration");
190
190
  const lifetimeMs = typeof expiresIn === "number" ? expiresIn * 1e3 : 0;
191
191
  this.#token = {
@@ -1,5 +1,5 @@
1
- import { KeycloakRealmError } from "./keycloakRealmInput.js";
2
1
  import { KeycloakAdminError } from "./keycloakAdmin.js";
2
+ import { KeycloakRealmError } from "./keycloakRealmInput.js";
3
3
  import { Command } from "nodefony";
4
4
  import path from "node:path";
5
5
  // nodefony/keycloak/security-keycloak-realm.ts
@@ -1,11 +1,11 @@
1
- import { AuthenticationError } from "../errors/AuthenticationError.js";
2
1
  import { defineSecurityConfig } from "../config/defineModuleConfig.js";
3
- import { UserToken } from "../src/token/UserToken.js";
4
- import peekIssuer from "../src/authenticator/peekIssuer.js";
2
+ import { RemoteJwtVerifier } from "../src/token/RemoteJwtVerifier.js";
5
3
  import { JwtAuthenticator } from "../src/authenticator/JwtAuthenticator.js";
6
- import { ExternalSubjectResolver, externalSubjectOptions } from "../src/authenticator/ExternalSubjectResolver.js";
4
+ import peekIssuer from "../src/authenticator/peekIssuer.js";
7
5
  import { resolveJwtRuntime } from "../src/token/jwtRuntime.js";
8
- import { RemoteJwtVerifier } from "../src/token/RemoteJwtVerifier.js";
6
+ import { UserToken } from "../src/token/UserToken.js";
7
+ import { ExternalSubjectResolver, externalSubjectOptions } from "../src/authenticator/ExternalSubjectResolver.js";
8
+ import { AuthenticationError } from "../errors/AuthenticationError.js";
9
9
  import { ACCESS_ACCOUNT_RESOLVER, ACCESS_TOKEN_VERIFIER, Service } from "nodefony";
10
10
  // nodefony/service/accessTokenVerifier.ts
11
11
  const serviceName = ACCESS_TOKEN_VERIFIER;
@@ -1,10 +1,10 @@
1
- import { ScopeGrantPolicy, refusedScopes } from "../src/token/scopeGrants.js";
1
+ import { TOKEN_FACETS } from "../src/token/tokenFilters.js";
2
2
  import { defineSecurityConfig } from "../config/defineModuleConfig.js";
3
- import { ACCOUNT_ID_KEY, CREDENTIALS_VERSION_KEY, credentialsVersionOf } from "../src/accountBinding.js";
3
+ import { ApiKeyError } from "../errors/ApiKeyError.js";
4
4
  import { generateApiKey } from "../src/apikey/apiKeyFormat.js";
5
+ import { ScopeGrantPolicy, refusedScopes } from "../src/token/scopeGrants.js";
5
6
  import { recordAudit } from "../src/audit/recordAudit.js";
6
- import { TOKEN_FACETS } from "../src/token/tokenFilters.js";
7
- import { ApiKeyError } from "../errors/ApiKeyError.js";
7
+ import { ACCOUNT_ID_KEY, CREDENTIALS_VERSION_KEY, credentialsVersionOf } from "../src/accountBinding.js";
8
8
  import { Service, countFacets } from "nodefony";
9
9
  import { randomUUID } from "node:crypto";
10
10
  // nodefony/service/apiKeys.ts
@@ -97,6 +97,9 @@ var ApiKeyService = class extends Service {
97
97
  * @param roles - rôles du porteur tels que son compte les porte ; omis =
98
98
  * aucun rôle, donc tout scope réservé est refusé (fail-closed).
99
99
  * @throws ApiKeyError 400 — nom vide/trop long, scope hors catalogue, expiry invalide.
100
+ * @throws ApiKeyError 400 — `tenantId` rempli : le périmètre d'un locataire n'est
101
+ * pas encore appliqué (aucun lecteur ne cloisonne sur lui) — l'accepter rendrait
102
+ * la clé trompeuse, et l'appliquer un jour lui retirerait des accès (rupture).
100
103
  * @throws ApiKeyError 403 — scope réservé à des rôles que le porteur n'a pas.
101
104
  * @throws ApiKeyError 409 — plafond `maxPerSubject` atteint.
102
105
  * @throws ApiKeyError 403 — aucun compte sous cet identifiant (la clé est liée
@@ -104,6 +107,7 @@ var ApiKeyService = class extends Service {
104
107
  * @throws ApiKeyError 503 — store ou annuaire `users` indisponible.
105
108
  */
106
109
  async createForSubject(subjectId, subjectType, opts, roles = []) {
110
+ if (opts.tenantId != null) throw new ApiKeyError("apiKeys: tenantId n'est pas encore pris en charge — le multi-tenant n'existe pas, et une clé rattachée à un locataire verrait tout. Laisser tenantId absent ou null.", 400);
107
111
  const store = this.#resolveStore();
108
112
  const name = this.#normalizeName(opts.name);
109
113
  const scopes = this.#normalizeScopes(opts.scopes);
@@ -1,12 +1,12 @@
1
- import { securityConfigSchema } from "../config/config.js";
2
- import { InsecureTransportError } from "../errors/InsecureTransportError.js";
3
1
  import { AuthenticationError } from "../errors/AuthenticationError.js";
4
2
  import { ThrottledError } from "../errors/ThrottledError.js";
5
- import { ACCOUNT_ID_KEY, CREDENTIALS_VERSION_KEY, credentialsVersionOf } from "../src/accountBinding.js";
3
+ import { InsecureTransportError } from "../errors/InsecureTransportError.js";
6
4
  import { resolveBoundSessionIdentity, resolveSessionIdentity } from "../src/sessionIdentity.js";
5
+ import { ACCOUNT_ID_KEY, CREDENTIALS_VERSION_KEY, credentialsVersionOf } from "../src/accountBinding.js";
7
6
  import { recordAudit } from "../src/audit/recordAudit.js";
8
7
  import { readAuditContext } from "../src/audit/readAuditContext.js";
9
8
  import { amrForFactor } from "../src/sessionAuthentication.js";
9
+ import { securityConfigSchema } from "../config/config.js";
10
10
  import { LOGIN_PAGE_SKIN_SPECS, MFA_METHOD_TOTP, SESSION_AMR_KEY, SESSION_AUTH_AT_KEY, Service } from "nodefony";
11
11
  import { requestUser } from "@nodefony/user";
12
12
  // nodefony/service/authFlow.ts
@@ -1,6 +1,6 @@
1
- import { recordAudit } from "../src/audit/recordAudit.js";
2
1
  import "../contracts/IAccessVoter.js";
3
2
  import { listVoterFactories } from "../src/voter/voterRegistry.js";
3
+ import { recordAudit } from "../src/audit/recordAudit.js";
4
4
  import { Service, logColor } from "nodefony";
5
5
  // nodefony/service/authorization.ts
6
6
  const serviceName = "authorization";