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

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 (165) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/index.js +6 -8
  3. package/dist/nodefony/command/{security-token.js → security-mcp-token.js} +18 -11
  4. package/dist/nodefony/command/security-oauth-doctor.js +5 -3
  5. package/dist/nodefony/command/security-secrets.js +10 -4
  6. package/dist/nodefony/command/security-user-add.js +9 -3
  7. package/dist/nodefony/command/security-user-delete.js +5 -3
  8. package/dist/nodefony/command/security-user-list.js +5 -3
  9. package/dist/nodefony/command/security-user-password.js +5 -3
  10. package/dist/nodefony/config/config.js +20 -4
  11. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  12. package/dist/nodefony/contracts/IAccessVoter.js +1 -2
  13. package/dist/nodefony/errors/AccessDeniedError.js +1 -2
  14. package/dist/nodefony/errors/ApiKeyError.js +3 -2
  15. package/dist/nodefony/errors/AuthenticationError.js +1 -2
  16. package/dist/nodefony/errors/CsrfError.js +1 -2
  17. package/dist/nodefony/errors/InsecureTransportError.js +1 -2
  18. package/dist/nodefony/errors/InvalidTargetError.js +1 -2
  19. package/dist/nodefony/errors/SsrfError.js +1 -2
  20. package/dist/nodefony/errors/ThrottledError.js +1 -2
  21. package/dist/nodefony/errors/UnverifiableTokenError.js +1 -2
  22. package/dist/nodefony/errors/WebAuthnError.js +1 -2
  23. package/dist/nodefony/keycloak/keycloakAdmin.js +1 -2
  24. package/dist/nodefony/keycloak/keycloakRealm.js +1 -2
  25. package/dist/nodefony/keycloak/keycloakRealmInput.js +1 -2
  26. package/dist/nodefony/keycloak/scaffold.js +3 -3
  27. package/dist/nodefony/keycloak/security-keycloak-realm.js +5 -3
  28. package/dist/nodefony/scaffold/contribute.js +1 -2
  29. package/dist/nodefony/service/accessTokenVerifier.js +101 -34
  30. package/dist/nodefony/service/apiKeys.js +45 -5
  31. package/dist/nodefony/service/auditService.js +4 -5
  32. package/dist/nodefony/service/authFlow.js +10 -8
  33. package/dist/nodefony/service/authorization.js +1 -2
  34. package/dist/nodefony/service/cors.js +1 -2
  35. package/dist/nodefony/service/csrf.js +1 -2
  36. package/dist/nodefony/service/firewall.js +5 -4
  37. package/dist/nodefony/service/keycloak.js +1 -2
  38. package/dist/nodefony/service/oauth2.js +29 -7
  39. package/dist/nodefony/service/securityHeaders.js +1 -2
  40. package/dist/nodefony/service/tokenService.js +38 -22
  41. package/dist/nodefony/service/totp.js +4 -5
  42. package/dist/nodefony/service/webAuthn.js +4 -5
  43. package/dist/nodefony/service/webhooks.js +4 -5
  44. package/dist/nodefony/src/RoleHierarchyWalker.js +1 -2
  45. package/dist/nodefony/src/SecuredArea.js +1 -2
  46. package/dist/nodefony/src/accountBinding.js +96 -0
  47. package/dist/nodefony/src/admin/SecurityAdminApi.js +1 -2
  48. package/dist/nodefony/src/admin/WebhookAdminApi.js +23 -7
  49. package/dist/nodefony/src/admin/adminAudit.js +1 -2
  50. package/dist/nodefony/src/admin/userRevocationCascade.js +79 -15
  51. package/dist/nodefony/src/apikey/apiKeyFormat.js +1 -2
  52. package/dist/nodefony/src/audit/MemoryAuditStore.js +1 -2
  53. package/dist/nodefony/src/audit/auditBridge.js +1 -2
  54. package/dist/nodefony/src/audit/auditFilters.js +1 -2
  55. package/dist/nodefony/src/audit/auditStoreRegistry.js +1 -2
  56. package/dist/nodefony/src/audit/readAuditContext.js +1 -2
  57. package/dist/nodefony/src/audit/recordAudit.js +1 -2
  58. package/dist/nodefony/src/authenticator/AnonymousAuthenticator.js +1 -2
  59. package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +3 -2
  60. package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +8 -113
  61. package/dist/nodefony/src/authenticator/ExternalSubjectResolver.js +180 -0
  62. package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +42 -4
  63. package/dist/nodefony/src/authenticator/JwtAuthenticator.js +91 -16
  64. package/dist/nodefony/src/authenticator/SessionAuthenticator.js +9 -5
  65. package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +1 -2
  66. package/dist/nodefony/src/authenticator/authenticatorRegistry.js +3 -17
  67. package/dist/nodefony/src/authenticator/externalSubject.js +1 -2
  68. package/dist/nodefony/src/authenticator/peekIssuer.js +1 -2
  69. package/dist/nodefony/src/boot/publicRoutesNotice.js +1 -2
  70. package/dist/nodefony/src/crypto/secretCipher.js +1 -2
  71. package/dist/nodefony/src/csp.js +1 -2
  72. package/dist/nodefony/src/csrfToken.js +1 -2
  73. package/dist/nodefony/src/entryPoint.js +1 -2
  74. package/dist/nodefony/src/net/ssrfGuard.js +1 -2
  75. package/dist/nodefony/src/net/transportFailure.js +57 -0
  76. package/dist/nodefony/src/oauth/clientCredentials.js +1 -2
  77. package/dist/nodefony/src/oauth/httpJson.js +3 -3
  78. package/dist/nodefony/src/oauth/metadata.js +5 -16
  79. package/dist/nodefony/src/oauth/oauth2Client.js +1 -2
  80. package/dist/nodefony/src/oauth/oauthProviderRegistry.js +1 -2
  81. package/dist/nodefony/src/oauth/providerDiagnosis.js +5 -6
  82. package/dist/nodefony/src/oauth/providerRoles.js +1 -2
  83. package/dist/nodefony/src/oauth/providers/github.js +16 -6
  84. package/dist/nodefony/src/oauth/providers/oidc.js +9 -6
  85. package/dist/nodefony/src/openToSignup.js +1 -2
  86. package/dist/nodefony/src/realtime/UserRealtimeToken.js +17 -3
  87. package/dist/nodefony/src/realtime/frameAuthorizer.js +2 -3
  88. package/dist/nodefony/src/rolesWithin.js +57 -0
  89. package/dist/nodefony/src/sessionAuthentication.js +1 -2
  90. package/dist/nodefony/src/sessionIdentity.js +29 -3
  91. package/dist/nodefony/src/throttle/LoginThrottler.js +1 -2
  92. package/dist/nodefony/src/token/AnonymousToken.js +1 -2
  93. package/dist/nodefony/src/token/JwtKeystore.js +14 -5
  94. package/dist/nodefony/src/token/MemoryTokenStore.js +1 -2
  95. package/dist/nodefony/src/token/RemoteJwtVerifier.js +6 -3
  96. package/dist/nodefony/src/token/UserToken.js +1 -2
  97. package/dist/nodefony/src/token/jwtRuntime.js +1 -2
  98. package/dist/nodefony/src/token/scopeGrants.js +109 -0
  99. package/dist/nodefony/src/token/secretFile.js +1 -2
  100. package/dist/nodefony/src/token/tokenCriteria.js +1 -2
  101. package/dist/nodefony/src/token/tokenFilters.js +1 -2
  102. package/dist/nodefony/src/token/tokenSort.js +1 -2
  103. package/dist/nodefony/src/token/tokenStatus.js +1 -2
  104. package/dist/nodefony/src/token/tokenStoreRegistry.js +1 -2
  105. package/dist/nodefony/src/totp/MemoryTotpSecretStore.js +1 -2
  106. package/dist/nodefony/src/totp/totpCipher.js +1 -2
  107. package/dist/nodefony/src/totp/totpCrypto.js +1 -2
  108. package/dist/nodefony/src/totp/totpOperations.js +1 -2
  109. package/dist/nodefony/src/totp/totpSecretStoreRegistry.js +1 -2
  110. package/dist/nodefony/src/transport/CredentialTransportPolicy.js +1 -2
  111. package/dist/nodefony/src/voter/RoleVoter.js +1 -2
  112. package/dist/nodefony/src/voter/ScopeVoter.js +1 -2
  113. package/dist/nodefony/src/voter/voterRegistry.js +1 -2
  114. package/dist/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js +1 -2
  115. package/dist/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js +1 -2
  116. package/dist/nodefony/src/webhook/MemoryWebhookStore.js +1 -2
  117. package/dist/nodefony/src/webhook/WebhookDispatcher.js +1 -2
  118. package/dist/nodefony/src/webhook/webhookCipher.js +1 -2
  119. package/dist/nodefony/src/webhook/webhookDelivery.js +1 -2
  120. package/dist/nodefony/src/webhook/webhookFilters.js +1 -2
  121. package/dist/nodefony/src/webhook/webhookSignature.js +1 -2
  122. package/dist/nodefony/src/webhook/webhookSort.js +1 -2
  123. package/dist/nodefony/src/webhook/webhookStoreRegistry.js +1 -2
  124. package/dist/nodefony/testing/index.js +108 -0
  125. package/dist/types/nodefony/command/{security-token.d.ts → security-mcp-token.d.ts} +7 -6
  126. package/dist/types/nodefony/config/config.d.ts +1 -0
  127. package/dist/types/nodefony/errors/ApiKeyError.d.ts +3 -1
  128. package/dist/types/nodefony/service/accessTokenVerifier.d.ts +35 -14
  129. package/dist/types/nodefony/service/apiKeys.d.ts +13 -2
  130. package/dist/types/nodefony/service/oauth2.d.ts +20 -0
  131. package/dist/types/nodefony/src/accountBinding.d.ts +82 -0
  132. package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +23 -5
  133. package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +2 -44
  134. package/dist/types/nodefony/src/authenticator/ExternalSubjectResolver.d.ts +99 -0
  135. package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +4 -1
  136. package/dist/types/nodefony/src/authenticator/JwtAuthenticator.d.ts +37 -1
  137. package/dist/types/nodefony/src/net/transportFailure.d.ts +22 -0
  138. package/dist/types/nodefony/src/oauth/httpJson.d.ts +0 -8
  139. package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +7 -1
  140. package/dist/types/nodefony/src/realtime/frameAuthorizer.d.ts +2 -2
  141. package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +6 -0
  142. package/dist/types/nodefony/src/rolesWithin.d.ts +27 -0
  143. package/dist/types/nodefony/src/sessionIdentity.d.ts +22 -0
  144. package/dist/types/nodefony/src/token/scopeGrants.d.ts +77 -0
  145. package/dist/types/nodefony/testing/index.d.ts +51 -0
  146. package/docs/api-keys.md +181 -174
  147. package/docs/audit.md +140 -140
  148. package/docs/authenticators.md +101 -101
  149. package/docs/authorization.md +116 -69
  150. package/docs/cors.md +64 -64
  151. package/docs/csrf.md +74 -71
  152. package/docs/external-jwt.md +22 -12
  153. package/docs/firewall.md +85 -82
  154. package/docs/headers.md +81 -81
  155. package/docs/index.md +4 -4
  156. package/docs/keycloak.md +95 -78
  157. package/docs/login-page.md +23 -23
  158. package/docs/oauth2.md +157 -146
  159. package/docs/obtenir-un-jeton.md +116 -26
  160. package/docs/tokens.md +128 -122
  161. package/docs/totp.md +136 -136
  162. package/docs/webauthn.md +153 -153
  163. package/docs/webhooks.md +136 -124
  164. package/package.json +19 -13
  165. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
@@ -1,9 +1,8 @@
1
- //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorate.js
1
+ // \0@oxc-project+runtime@0.155.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);
5
5
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  }
8
- //#endregion
9
8
  export { __decorate as default };
package/dist/index.js CHANGED
@@ -81,22 +81,21 @@ import SecurityUserList from "./nodefony/command/security-user-list.js";
81
81
  import SecurityUserDelete from "./nodefony/command/security-user-delete.js";
82
82
  import SecurityUserPassword from "./nodefony/command/security-user-password.js";
83
83
  import { publicRoutesNotice } from "./nodefony/src/boot/publicRoutesNotice.js";
84
- import SecurityToken from "./nodefony/command/security-token.js";
84
+ import SecurityMcpToken from "./nodefony/command/security-mcp-token.js";
85
85
  import SecurityKeycloakRealm from "./nodefony/keycloak/security-keycloak-realm.js";
86
86
  import SecurityOAuthDoctor from "./nodefony/command/security-oauth-doctor.js";
87
87
  import { createSecurityAdminApi, parseAuditQuery, registerSecurityAdminApi } from "./nodefony/src/admin/SecurityAdminApi.js";
88
88
  import { registerUserRevocationCascade, usersEventBusNotice } from "./nodefony/src/admin/userRevocationCascade.js";
89
- import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js";
90
- import __decorate from "./_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
89
+ import __decorate from "./_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
91
90
  import { tokenStatusCriteria } from "./nodefony/src/token/tokenCriteria.js";
92
91
  import { requestClientCredentialsToken } from "./nodefony/src/oauth/clientCredentials.js";
93
92
  import { AccessDeniedError } from "./nodefony/errors/AccessDeniedError.js";
94
93
  import "./nodefony/errors/index.js";
95
94
  import "./nodefony/contracts/index.js";
96
- import { Kernel, Module, services } from "nodefony";
95
+ import { Module, services } from "nodefony";
97
96
  import { fileURLToPath } from "node:url";
98
97
  import { registerUserAdminApi } from "@nodefony/user";
99
- //#region index.ts
98
+ // index.ts
100
99
  /**
101
100
  * `@nodefony/security` — couche de sécurité de Nodefony (refonte 2026, P6).
102
101
  *
@@ -120,7 +119,7 @@ let Security = class Security extends Module {
120
119
  this.addCommand(SecurityUserList);
121
120
  this.addCommand(SecurityUserDelete);
122
121
  this.addCommand(SecurityUserPassword);
123
- this.addCommand(SecurityToken);
122
+ this.addCommand(SecurityMcpToken);
124
123
  this.addCommand(SecurityKeycloakRealm);
125
124
  this.addCommand(SecurityOAuthDoctor);
126
125
  }
@@ -185,7 +184,6 @@ Security = __decorate([services([
185
184
  TotpService,
186
185
  WebhookService,
187
186
  KeycloakService
188
- ]), __decorateMetadata("design:paramtypes", [typeof Kernel === "undefined" ? Object : Kernel])], Security);
187
+ ])], Security);
189
188
  var security_default = Security;
190
- //#endregion
191
189
  export { AccessDeniedError, AccessTokenVerifierService, AnonymousAuthenticator, AnonymousToken, ApiKeyAuthenticator, ApiKeyError, ApiKeyService, AuditService, AuthFlow, AuthenticationError, Authorization, Cors, CredentialTransportPolicy, Csrf, CsrfError, CsrfTokenManager, ExternalJwtAuthenticator, Firewall, InsecureTransportError, InvalidTargetError, JwtAuthenticator, JwtKeystore, KeycloakAdminClient, KeycloakAdminError, KeycloakService, LoginThrottler, MemoryAuditStore, MemoryTokenStore, MemoryTotpSecretStore, MemoryWebAuthnCredentialStore, MemoryWebhookStore, OAuth2Client, OAuth2RequestError, OAuth2Service, OAuth2Tokens, RemoteJwtVerifier, RoleHierarchyWalker, RoleVoter, ScopeVoter, SecuredArea, SecurityHeaders, SessionAuthenticator, SsrfError, TOKEN_DEFAULT_ORDER, TOKEN_FACETS, TOKEN_SORTABLE_FIELDS, ThrottledError, TokenService, TotpService, UnverifiableTokenError, UserPasswordAuthenticator, UserToken, VoterVote, WEBHOOK_DEFAULT_ORDER, WEBHOOK_SORTABLE_FIELDS, WebAuthnService, WebhookService, amrForFactor, assertPublicUrl, base32Decode, beginTotpEnrollment, confirmTotpEnrollment, createCodeChallenge, createDiscoveredOidcProvider, createGithubProvider, createOidcProvider, createSecurityAdminApi, decryptSecret, security_default as default, defineSecurityConfig, deriveKey, deriveTotpKey, diagnoseOAuthProvider, disableTotp, discoverAuthorizationServer, encryptSecret, generateApiKey, generateCodeVerifier, generateEphemeralKey, generateState, getAuditStoreFactory, getAuthenticatorFactory, getOAuthProviderFactory, getTokenStoreFactory, getTotpStoreFactory, getWebAuthnStoreFactory, getWebhookStoreFactory, hashApiKey, isBlockedAddress, keycloakAdminTarget, keycloakLoginThemeCheck, listAuditStores, listAuthenticatorFactories, listOAuthProviders, listTokenStores, listTotpStores, listVoterFactories, listWebAuthnStores, listWebhookStores, looksLikeApiKey, matchesTokenStatus, oauthProviderRequiresIssuer, parseApiKey, parseAuditQuery, peekIssuer, readAuditContext, readKeycloakLoginTheme, readLogoutTokenClaims, recordAudit, registerAuditStore, registerAuthenticatorFactory, registerOAuthProvider, registerSecurityAdminApi, registerTokenStore, registerTotpStore, registerVoterFactory, registerWebAuthnStore, registerWebhookStore, requestClientCredentialsToken, resolveJwtRuntime, securityConfigJsonSchema, shouldEnforceCredentialTransport, tokenStatusCriteria, tokenStatusOf, totpCode, totpStatus, verifyTotpLogin };
@@ -3,7 +3,7 @@ import { ADMIN_SCOPE_READ, ADMIN_SCOPE_WRITE, AGENT_TARGETS, CONSOLE_DATA_RUN_PR
3
3
  import path from "node:path";
4
4
  import { existsSync } from "node:fs";
5
5
  import { spawnSync } from "node:child_process";
6
- //#region nodefony/command/security-token.ts
6
+ // nodefony/command/security-mcp-token.ts
7
7
  const options = {
8
8
  runProfile: CONSOLE_DATA_RUN_PROFILE,
9
9
  helpGroup: "COMPTES ET SECRETS",
@@ -74,7 +74,7 @@ const DIM = "\x1B[2m";
74
74
  const BOLD = "\x1B[1m";
75
75
  const RESET = "\x1B[0m";
76
76
  /**
77
- * `nodefony security:token` — émet un jeton d'accès pour une porte de cette
77
+ * `nodefony security:mcp:token` — émet un jeton d'accès pour une porte de cette
78
78
  * application (la porte MCP par défaut).
79
79
  *
80
80
  * **Pourquoi une commande, et pas un `curl`.** Le jeton s'obtenait par un appel
@@ -88,18 +88,26 @@ const RESET = "\x1B[0m";
88
88
  * raison d'être de la liaison d'audience. La commande la vise d'elle-même,
89
89
  * `--resource` ne sert qu'à en viser une autre.
90
90
  *
91
- * Suit `security:secrets` : `--write` pose la valeur là où l'AGENT la lit
92
- * (gitignoré), jamais dans le `.env` commité, et ne remplace jamais une valeur
93
- * existante — une rotation est un geste explicite.
91
+ * `--write` pose la valeur là où l'AGENT la lit (gitignoré), jamais dans le
92
+ * `.env` de l'application. Relancée, elle REMPLACE le jeton chez les agents qui
93
+ * le portent déjà — la rotation est le geste le plus fréquent — et n'en câble
94
+ * aucun nouveau sans le proposer.
94
95
  */
95
- var SecurityToken = class extends Command {
96
+ var SecurityMcpToken = class extends Command {
96
97
  constructor(cli) {
97
- super("security:token", `émet un jeton d'accès pour la porte MCP`, cli, options);
98
+ super("security:mcp:token", `émet un jeton d'accès pour la porte MCP`, cli, {
99
+ ...options,
100
+ examples: [
101
+ "nodefony security:mcp:token",
102
+ "nodefony security:mcp:token --write",
103
+ "nodefony security:mcp:token alice --scope admin:read --ttl 60"
104
+ ]
105
+ });
98
106
  this.addArgument("[identifier]", "compte porteur du jeton (défaut : admin)");
99
107
  this.addOption("-s, --scope <scopes>", `scopes demandés, séparés par des espaces (défaut : « ${ADMIN_SCOPE_READ} » ; ajouter « ${ADMIN_SCOPE_WRITE} » pour les mutations)`);
100
108
  this.addOption("-r, --resource <uri>", "audience visée (défaut : la porte MCP de cette application)");
101
109
  this.addOption("-a, --agent <noms>", "agents à servir, séparés par des virgules (défaut : ceux détectés ; « none » pour aucun)");
102
- this.addOption("-t, --ttl <duree>", "durée de validité, en minutes (défaut : 8 h avec --write, sinon celle de la config, 15 min)");
110
+ this.addOption("-t, --ttl <minutes>", "durée de validité, en minutes (défaut : 8 h avec --write, sinon celle de la config, 15 min)");
103
111
  this.addOption("-w, --write", `pose ${MCP_TOKEN_ENV} dans la configuration des agents présents`);
104
112
  this.addOption("-j, --json", "sortie JSON (scripts/CI)");
105
113
  }
@@ -300,7 +308,7 @@ var SecurityToken = class extends Command {
300
308
  porteur obtiendrait un jeton pour la ressource de son choix :
301
309
 
302
310
  use("@nodefony/security", {
303
- jwt: { audiences: ["${resource}"] },\n })\n\n → puis npm run build (le runtime lit le dist)\n` : ` La porte est servie par un module de DÉVELOPPEMENT, absent en\n « ${env} » : un jeton pour une porte absente n'aurait personne\n pour l'accepter.\n\n → NODE_ENV=development nodefony security:token${opts.write ? " --write" : ""}\n`) + ` → ou vise une autre porte : --resource <uri>`, "ERROR");
311
+ jwt: { audiences: ["${resource}"] },\n })\n\n → puis npm run build (le runtime lit le dist)\n` : ` La porte est servie par un module de DÉVELOPPEMENT, absent en\n « ${env} » : un jeton pour une porte absente n'aurait personne\n pour l'accepter.\n\n → NODE_ENV=development nodefony security:mcp:token${opts.write ? " --write" : ""}\n`) + ` → ou vise une autre porte : --resource <uri>`, "ERROR");
304
312
  process.exitCode = 1;
305
313
  return this;
306
314
  }
@@ -363,5 +371,4 @@ var SecurityToken = class extends Command {
363
371
  return this;
364
372
  }
365
373
  };
366
- //#endregion
367
- export { SecurityToken as default, tokenTtlSeconds, ttlSeconds };
374
+ export { SecurityMcpToken as default, tokenTtlSeconds, ttlSeconds };
@@ -1,5 +1,5 @@
1
1
  import { Command } from "nodefony";
2
- //#region nodefony/command/security-oauth-doctor.ts
2
+ // nodefony/command/security-oauth-doctor.ts
3
3
  const options = {
4
4
  helpGroup: "COMPTES ET SECRETS",
5
5
  showBanner: false,
@@ -44,7 +44,10 @@ function mark(check) {
44
44
  */
45
45
  var SecurityOAuthDoctor = class extends Command {
46
46
  constructor(cli) {
47
- super("security:oauth:doctor", "diagnostique le branchement des fournisseurs OAuth", cli, options);
47
+ super("security:oauth:doctor", "diagnostique le branchement des fournisseurs OAuth", cli, {
48
+ ...options,
49
+ examples: ["nodefony security:oauth:doctor", "nodefony security:oauth:doctor --provider github --json"]
50
+ });
48
51
  this.addOption("-p, --provider <nom>", "un seul fournisseur de oauth2.providers");
49
52
  this.addOption("-j, --json", "sortie JSON (scripts/CI)");
50
53
  }
@@ -62,5 +65,4 @@ var SecurityOAuthDoctor = class extends Command {
62
65
  return this;
63
66
  }
64
67
  };
65
- //#endregion
66
68
  export { SecurityOAuthDoctor as default };
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import { appendFileSync } from "node:fs";
6
6
  import { randomBytes } from "node:crypto";
7
7
  import { spawnSync } from "node:child_process";
8
- //#region nodefony/command/security-secrets.ts
8
+ // nodefony/command/security-secrets.ts
9
9
  const options = {
10
10
  helpGroup: "COMPTES ET SECRETS",
11
11
  showBanner: false,
@@ -69,7 +69,14 @@ const ROLES = {
69
69
  */
70
70
  var SecuritySecrets = class extends Command {
71
71
  constructor(cli) {
72
- super("security:secrets", "engendre les clés de chiffrement du module security", cli, options);
72
+ super("security:secrets", "engendre les clés de chiffrement du module security", cli, {
73
+ ...options,
74
+ examples: [
75
+ "nodefony security:secrets",
76
+ "nodefony security:secrets --write",
77
+ "nodefony security:secrets --jwt-keyset"
78
+ ]
79
+ });
73
80
  this.addOption("-j, --json", "sortie JSON (scripts/CI)");
74
81
  this.addOption("-w, --write", "écrit les clés manquantes dans le .env du projet (jamais de remplacement)");
75
82
  this.addOption("-e, --env", "les 4 secrets en lignes CLÉ=valeur (docker run --env-file, gestionnaire de secrets)");
@@ -144,7 +151,7 @@ var SecuritySecrets = class extends Command {
144
151
  w(`\n${BOLD}🔐 Secrets du module security${RESET} ${DIM}— 4 secrets, 3 fichiers${RESET}\n\n`);
145
152
  for (const [name, role] of Object.entries(ROLES)) w(` ${(name === "NF_JWT_KEYSET" ? cfgTs.includes(name) : new RegExp(`^\\s*${name}\\s*=`, "m").test(dotenv)) ? "\x1B[32m✓" : "\x1B[33m○"}${RESET} ${BOLD}${name.padEnd(16)}${RESET}${DIM}${role.protected}${RESET}\n ${DIM}sans → ${role.without}${RESET}\n`);
146
153
  const tokenStillThere = /^\s*NF_MCP_TOKEN\s*=/m.test(dotenv);
147
- w(`\n${DIM} · NF_MCP_TOKEN n'est PAS un secret de cette application, et n'a rien à\n faire dans cette liste : c'est un jeton qu'elle ÉMET, présenté par un\n agent pour entrer. Aucun code d'ici ne le lit. Il se pose chez l'agent\n qui le porte — nodefony security:token --write.${RESET}\n`);
154
+ w(`\n${DIM} · NF_MCP_TOKEN n'est PAS un secret de cette application, et n'a rien à\n faire dans cette liste : c'est un jeton qu'elle ÉMET, présenté par un\n agent pour entrer. Aucun code d'ici ne le lit. Il se pose chez l'agent\n qui le porte — nodefony security:mcp:token --write.${RESET}\n`);
148
155
  if (tokenStillThere) w(`${YELLOW} ⚠ une ligne NF_MCP_TOKEN traîne encore dans .env — rien ne la lit,\n tu peux la retirer.${RESET}\n`);
149
156
  w(`\n${YELLOW}⚠ rien ne se tape dans le terminal : chaque bloc se colle dans le fichier indiqué.${RESET}\n\n`);
150
157
  w(`${BOLD}1. Fichier ${CYAN}.env${RESET}${BOLD} — les valeurs${RESET} ${DIM}(valeurs du poste, gitignoré ; la notice est .env.example)${RESET}\n`);
@@ -175,5 +182,4 @@ var SecuritySecrets = class extends Command {
175
182
  return this;
176
183
  }
177
184
  };
178
- //#endregion
179
185
  export { SecuritySecrets as default };
@@ -1,6 +1,6 @@
1
1
  import { AUTH_LOGIN_PATH, CONSOLE_DATA_RUN_PROFILE, Command, askPasswordMasked } from "nodefony";
2
2
  import { WeakPasswordError } from "@nodefony/user";
3
- //#region nodefony/command/security-user-add.ts
3
+ // nodefony/command/security-user-add.ts
4
4
  const options = {
5
5
  runProfile: CONSOLE_DATA_RUN_PROFILE,
6
6
  helpGroup: "COMPTES ET SECRETS",
@@ -42,7 +42,14 @@ const ROLE_BASE = "ROLE_USER";
42
42
  */
43
43
  var SecurityUserAdd = class extends Command {
44
44
  constructor(cli) {
45
- super("security:user:add", "crée un compte (mot de passe demandé masqué)", cli, options);
45
+ super("security:user:add", "crée un compte (mot de passe demandé masqué)", cli, {
46
+ ...options,
47
+ examples: [
48
+ "nodefony security:user:add alice",
49
+ "nodefony security:user:add admin --admin",
50
+ "nodefony security:user:add bob --roles ROLE_USER,ROLE_DEV"
51
+ ]
52
+ });
46
53
  this.addArgument("[identifier]", "identifiant (login) du compte");
47
54
  this.addOption("-p, --password <password>", "mot de passe (sinon : prompt masqué en TTY)");
48
55
  this.addOption("-r, --roles <roles>", "rôles CSV (ex: ROLE_USER,ROLE_DEV) — défaut ROLE_USER");
@@ -146,5 +153,4 @@ var SecurityUserAdd = class extends Command {
146
153
  return this;
147
154
  }
148
155
  };
149
- //#endregion
150
156
  export { SecurityUserAdd as default };
@@ -1,5 +1,5 @@
1
1
  import { CONSOLE_DATA_RUN_PROFILE, Command } from "nodefony";
2
- //#region nodefony/command/security-user-delete.ts
2
+ // nodefony/command/security-user-delete.ts
3
3
  const options = {
4
4
  runProfile: CONSOLE_DATA_RUN_PROFILE,
5
5
  helpGroup: "COMPTES ET SECRETS",
@@ -37,7 +37,10 @@ const ADMIN_ROLE = "ROLE_NODEFONY_ADMIN";
37
37
  */
38
38
  var SecurityUserDelete = class extends Command {
39
39
  constructor(cli) {
40
- super("security:user:delete", "supprime un compte, après confirmation", cli, options);
40
+ super("security:user:delete", "supprime un compte, après confirmation", cli, {
41
+ ...options,
42
+ examples: ["nodefony security:user:delete alice", "nodefony security:user:delete alice --yes"]
43
+ });
41
44
  this.addArgument("[identifier]", "identifiant (login) du compte à retirer");
42
45
  this.addOption("-y, --yes", "ne pas demander confirmation (obligatoire hors terminal)");
43
46
  }
@@ -100,5 +103,4 @@ var SecurityUserDelete = class extends Command {
100
103
  return this;
101
104
  }
102
105
  };
103
- //#endregion
104
106
  export { SecurityUserDelete as default };
@@ -1,5 +1,5 @@
1
1
  import { CONSOLE_DATA_RUN_PROFILE, Command } from "nodefony";
2
- //#region nodefony/command/security-user-list.ts
2
+ // nodefony/command/security-user-list.ts
3
3
  const options = {
4
4
  runProfile: CONSOLE_DATA_RUN_PROFILE,
5
5
  helpGroup: "COMPTES ET SECRETS",
@@ -31,7 +31,10 @@ const DEFAULT_LIMIT = 50;
31
31
  */
32
32
  var SecurityUserList = class extends Command {
33
33
  constructor(cli) {
34
- super("security:user:list", "liste les comptes : identifiant, rôles, état", cli, options);
34
+ super("security:user:list", "liste les comptes : identifiant, rôles, état", cli, {
35
+ ...options,
36
+ examples: ["nodefony security:user:list", "nodefony security:user:list --role ROLE_ADMIN --json"]
37
+ });
35
38
  this.addOption("-q, --query <texte>", "filtre sur l'identifiant (sous-chaîne, insensible à la casse)");
36
39
  this.addOption("-r, --role <role>", "n'affiche que les porteurs d'un rôle");
37
40
  this.addOption("-l, --limit <n>", `nombre maximum de comptes (défaut ${DEFAULT_LIMIT})`);
@@ -74,5 +77,4 @@ var SecurityUserList = class extends Command {
74
77
  return this;
75
78
  }
76
79
  };
77
- //#endregion
78
80
  export { SecurityUserList as default };
@@ -1,5 +1,5 @@
1
1
  import { AUTH_LOGIN_PATH, CONSOLE_DATA_RUN_PROFILE, Command, askPasswordMasked } from "nodefony";
2
- //#region nodefony/command/security-user-password.ts
2
+ // nodefony/command/security-user-password.ts
3
3
  const options = {
4
4
  runProfile: CONSOLE_DATA_RUN_PROFILE,
5
5
  helpGroup: "COMPTES ET SECRETS",
@@ -40,7 +40,10 @@ const RESET = "\x1B[0m";
40
40
  */
41
41
  var SecurityUserPassword = class extends Command {
42
42
  constructor(cli) {
43
- super("security:user:password", "change le mot de passe d'un compte", cli, options);
43
+ super("security:user:password", "change le mot de passe d'un compte", cli, {
44
+ ...options,
45
+ examples: ["nodefony security:user:password alice"]
46
+ });
44
47
  this.addArgument("[identifier]", "identifiant (login) du compte");
45
48
  this.addOption("-p, --password <password>", "nouveau mot de passe (sinon : prompt masqué en TTY)");
46
49
  }
@@ -103,5 +106,4 @@ var SecurityUserPassword = class extends Command {
103
106
  return this;
104
107
  }
105
108
  };
106
- //#endregion
107
109
  export { SecurityUserPassword as default };
@@ -1,8 +1,9 @@
1
1
  import { parseKeySet } from "../src/token/JwtKeystore.js";
2
+ import { scopeGrantKeyProblem } from "../src/token/scopeGrants.js";
2
3
  import { PLATFORM_ROLE_PREFIX, isPlatformRole } from "../src/oauth/providerRoles.js";
3
4
  import { DEFAULT_LOGIN_PAGE_SKIN, LOGIN_PAGE_LAYOUTS, LOGIN_PAGE_PATH, LOGIN_PAGE_SKINS, parseDiscoveryUrl, safeRedirectPath } from "nodefony";
4
5
  import { z } from "zod";
5
- //#region nodefony/config/config.ts
6
+ // nodefony/config/config.ts
6
7
  /**
7
8
  * ════════════════════════════════════════════════════════════════════════════
8
9
  * @nodefony/security — CONFIGURATION DU MODULE (schéma Zod = source unique)
@@ -263,7 +264,7 @@ const trustedIssuerSchema = z.strictObject({
263
264
  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.")
264
265
  });
265
266
  const resourceServerSchema = z.strictObject({
266
- issuers: z.array(trustedIssuerSchema).default([]).describe("Émetteurs dont l'application accepte les jetons. VIDE (défaut) = aucun jeton tiers n'est vérifiable, et le service `accessTokenVerifier` n'est pas posé — une porte protégée refusera alors de servir plutôt que d'accepter des porteurs qu'elle ne sait pas lire. Un seul réglage commande le rôle : pas de drapeau `enabled` qui permettrait « activé sans émetteur »."),
267
+ issuers: z.array(trustedIssuerSchema).default([]).describe("Émetteurs TIERS dont l'application accepte les jetons. Les jetons signés par l'application elle-même n'ont pas à y figurer : dès que `jwt.enabled`, le service `accessTokenVerifier` les reconnaît, et les juge par les règles de l'authentificateur `jwt` (révocation, compte actif). VIDE (défaut) = aucun jeton tiers n'est vérifiable ; avec `jwt.enabled: false`, le service n'est pas posé et une porte protégée refusera de servir plutôt que d'accepter des porteurs qu'elle ne sait pas lire. Une liste INVALIDE n'en pose aucun, jetons locaux compris."),
267
268
  subjectPolicy: z.enum(["require", "ephemeral"]).default("require").describe("Comment le sujet d'un jeton tiers (`sub`) devient un utilisateur de CETTE application. « require » (défaut) : le sujet doit correspondre à un compte local (`loadUserByIdentifier`) — absent, désactivé ou verrouillé, l'accès est refusé. « ephemeral » : aucun compte local n'est exigé ni créé, l'appelant vit le temps de la requête avec les rôles de `ephemeralRoles` — c'est le mode de l'appelant PUREMENT machine (agent, service). Le défaut est « require » parce qu'un jeton d'un annuaire d'entreprise vaut pour des milliers de comptes : l'accepter d'office ferait de cet annuaire l'unique autorité d'accès à l'application, et supprimerait la seconde décision — locale — qui est la raison d'être du pare-feu."),
268
269
  ephemeralRoles: z.array(z.string().min(1)).default([]).describe("Rôles accordés à un appelant accepté en mode « ephemeral ». VIDE par défaut, et ce n'est pas une omission : sans rôle, l'appelant ne passe aucun `@IsGranted` et n'est autorisé que par ses SCOPES (`@RequireScope`), qui viennent du jeton et sont bornés par le serveur d'autorisation. Y écrire un rôle revient à accorder un pouvoir local à quiconque détient un jeton valide pour cette ressource — à faire sciemment, jamais par confort."),
269
270
  timeoutMs: z.number().int().min(100).default(5e3).describe("Délai maximal d'une requête vers l'émetteur (découverte + clés)."),
@@ -363,7 +364,7 @@ const oauthProviderSchema = z.strictObject({
363
364
  defaultRoles: z.array(z.string()).optional().describe("Rôles du Shadow User à la création — surcharge le global pour CE fournisseur."),
364
365
  roleMapping: z.record(z.string().min(1), z.string().min(1)).optional().describe("Rôles du FOURNISSEUR traduits en rôles de l'application, recalculés à chaque connexion ET à chaque jeton présenté à l'API : `{ \"app-admin\": \"ROLE_ADMIN\" }`. Un rôle du fournisseur absent de la table est ignoré, jamais recopié. Les rôles donnés à la main sont conservés. OMIS = les rôles restent entièrement locaux (posés à la création, puis la base fait foi). Un rôle `ROLE_NODEFONY_*` refuse le démarrage, sauf `allowPlatformRoles`."),
365
366
  audiences: z.array(z.string().min(1)).optional().describe("Ressources pour lesquelles CE fournisseur émet des jetons à l'application — la mention « valable pour » (`aud`) qu'il doit imprimer. Chaque entrée est la `resource` d'une zone `external-jwt`. Lue par `security:keycloak:realm`, qui en écrit les mappers d'audience ; sans effet sur la vérification, que la `resource` de la zone porte seule. OMIS = la `resource` de TOUTES les zones `external-jwt`. À écrire dès qu'une zone existe pour REFUSER les jetons de ce fournisseur : la déduire ferait imprimer son adresse."),
366
- allowPlatformRoles: z.boolean().default(false).describe("Autorise `roleMapping` à accorder des rôles de PLATEFORME (`ROLE_NODEFONY_*` : console d'administration, génération de code, secrets). OMIS = refusé au démarrage. L'ouvrir fait de l'administrateur de l'annuaire un administrateur de l'instance, et d'un annuaire compromis une prise de contrôle complète — un avertissement le rappelle à chaque démarrage."),
367
+ allowPlatformRoles: z.boolean().default(false).describe("Autorise `roleMapping` à accorder des rôles de PLATEFORME (`ROLE_NODEFONY_*` : console d'administration, génération de code, secrets). OMIS = refusé au démarrage. L'ouvrir fait de l'administrateur de l'annuaire un administrateur de l'instance, et d'un annuaire compromis une prise de contrôle complète — rappelé à chaque démarrage, en avertissement en production, en information ailleurs."),
367
368
  rolesSource: z.array(z.enum([
368
369
  "client",
369
370
  "realm",
@@ -396,6 +397,21 @@ const oauth2Schema = z.strictObject({
396
397
  providers: z.record(z.string(), oauthProviderSchema).default({}).describe("Fournisseurs activés par nom (doit correspondre au registre : builtins google/keycloak/oidc/github ; tout autre serveur OpenID Connect en enregistrant une fabrique, décrit par son seul émetteur).")
397
398
  }).describe("Social login OAuth 2.0 — Authorization Code + PKCE, session BFF.");
398
399
  /**
400
+ * Qui peut OBTENIR un scope d'application — appliqué par CHAQUE émetteur de
401
+ * l'application (jeton d'accès, clé API), jamais par une zone : un jeton ne
402
+ * sait pas quelle zone le lira, et `security:mcp:token` signe sans requête.
403
+ */
404
+ const grantableScopesSchema = z.record(z.string(), z.array(z.string().min(1)).min(1, "au moins un rôle — pour n'accorder ce scope à personne, ne pas l'exposer")).superRefine((grants, ctx) => {
405
+ for (const key of Object.keys(grants)) {
406
+ const problem = scopeGrantKeyProblem(key);
407
+ if (problem !== null) ctx.addIssue({
408
+ code: "custom",
409
+ path: [key],
410
+ message: `« ${key} » ${problem}`
411
+ });
412
+ }
413
+ }).default({}).describe("Qui peut OBTENIR un scope d'application. Clé : un scope exact (`vitrine:write`) ou une famille (`shop:*`) ; valeur : les rôles dont UN suffit, hiérarchie de rôles comprise. Une clé exacte l'emporte sur une famille, la famille la plus longue sur une plus courte. Un scope qu'aucune clé ne couvre reste accordé à tout compte connecté. Appliqué par chaque émetteur de CETTE application : un jeton d'accès est émis sans les scopes refusés (RFC 6749 §3.3, nommés au journal), une clé API demandée avec l'un d'eux est refusée (403). Ne borne PAS les jetons d'un émetteur tiers (Keycloak…), qui signe selon sa propre politique. `admin:read`, `admin:write` et `admin:*` sont refusés au démarrage : réservés au framework. Un module peut compléter cette section par `module-security` ; un préfixe = un propriétaire, car la fusion de configuration fusionne les listes case par case (`[\"A\",\"B\"]` surchargé par `[\"C\"]` donne `[\"C\",\"B\"]`).");
414
+ /**
399
415
  * Règle d'autorisation d'un **namespace de canaux WebSocket** (subscribe/inbound)
400
416
  * par préfixe. Contraintes cumulatives ; un champ absent = pas de contrainte sur
401
417
  * cet axe. Étend OU surcharge les défauts système (placée AVANT eux à l'évaluation).
@@ -409,6 +425,7 @@ const realtimeChannelRuleSchema = z.strictObject({
409
425
  const securityConfigSchema = z.strictObject({
410
426
  encoders: z.record(z.string(), encoderSchema).default(() => ({ default: encoderSchema.parse({ type: "argon2id" }) })).describe("Chaîne d'encodeurs de mot de passe. 1re entrée = principal (hash à la création), suivantes = legacy en lecture seule (migration au login). Défaut : un argon2id sûr — le firewall en dérive le service `passwordEncoder`."),
411
427
  roleHierarchy: z.record(z.string(), z.array(z.string())).default({}).describe("Hiérarchie de rôles (ROLE_ADMIN: ['ROLE_USER'])."),
428
+ grantableScopes: grantableScopesSchema,
412
429
  areas: z.record(z.string(), areaSchema).default({}).describe("Zones sécurisées (firewall) par nom."),
413
430
  realtimeChannels: z.array(realtimeChannelRuleSchema).default([]).describe("Politiques d'autorisation par préfixe sur le temps réel. Couvre les canaux WS (subscribe/inbound) **ET les actions RPC** : le nom de méthode d'une frame entrante est soumis aux mêmes règles de préfixe — une règle `admin:` garde donc aussi l'action `admin:purge`. Étend/surcharge les défauts système (syslog:/orm:/node:/dashboard:/debugbar:/realtime:/cluster: + :health/:stats = ROLE_NODEFONY_ADMIN, l'échelle PLATEFORME : ces canaux exposent l'état du processus, pas celui d'une application). Ce qui n'est couvert par aucune règle reste libre, sauf gating `@RealtimeChannel` côté controller."),
414
431
  cors: corsSchema.default(() => corsSchema.parse({})),
@@ -441,5 +458,4 @@ const securityConfigSchema = z.strictObject({
441
458
  * construction ; passés au `super(..., config)` du Module class.
442
459
  */
443
460
  const config = securityConfigSchema.parse({});
444
- //#endregion
445
461
  export { config as default, securityConfigSchema };
@@ -1,7 +1,7 @@
1
1
  import { securityConfigSchema } from "./config.js";
2
2
  import { BootConfigurationError, parseModuleConfig } from "nodefony";
3
3
  import { z } from "zod";
4
- //#region nodefony/config/defineModuleConfig.ts
4
+ // nodefony/config/defineModuleConfig.ts
5
5
  /**
6
6
  * Valide + normalise + gèle la configuration de sécurité.
7
7
  *
@@ -31,5 +31,4 @@ function detectConflicts(areas) {
31
31
  seen.set(area.pattern, name);
32
32
  }
33
33
  }
34
- //#endregion
35
34
  export { defineSecurityConfig, securityConfigJsonSchema };
@@ -1,4 +1,4 @@
1
- //#region nodefony/contracts/IAccessVoter.ts
1
+ // nodefony/contracts/IAccessVoter.ts
2
2
  /**
3
3
  * Vote d'un {@link IAccessVoter}. Stratégie firewall = affirmative + DENY veto :
4
4
  * un seul DENY bloque ; sinon un GRANT suffit ; tous ABSTAIN → DENY (Zero Trust).
@@ -9,5 +9,4 @@ let VoterVote = /* @__PURE__ */ function(VoterVote) {
9
9
  VoterVote["ABSTAIN"] = "ABSTAIN";
10
10
  return VoterVote;
11
11
  }({});
12
- //#endregion
13
12
  export { VoterVote };
@@ -1,5 +1,5 @@
1
1
  import { nodefonyError } from "nodefony";
2
- //#region nodefony/errors/AccessDeniedError.ts
2
+ // nodefony/errors/AccessDeniedError.ts
3
3
  /**
4
4
  * Accès refusé — `code = 403`. Levée par l'autorisation (un `@IsGranted` non
5
5
  * satisfait, un voter DENY) : l'utilisateur EST authentifié mais n'a pas le droit.
@@ -10,5 +10,4 @@ var AccessDeniedError = class extends nodefonyError {
10
10
  super(message, 403);
11
11
  }
12
12
  };
13
- //#endregion
14
13
  export { AccessDeniedError, AccessDeniedError as default };
@@ -1,11 +1,13 @@
1
1
  import { nodefonyError } from "nodefony";
2
- //#region nodefony/errors/ApiKeyError.ts
2
+ // nodefony/errors/ApiKeyError.ts
3
3
  /**
4
4
  * Erreur de **gestion** d'une clé API (création/révocation) — porte un `code`
5
5
  * HTTP que l'adaptateur framework mappe par duck-typing (il n'importe jamais les
6
6
  * classes de `@nodefony/security`).
7
7
  *
8
8
  * - `400` — entrée invalide (nom vide, scope hors catalogue) ;
9
+ * - `403` — scope demandé que le porteur ne peut pas obtenir (`grantableScopes`,
10
+ * ou `admin:*` sans le rôle d'administration) ;
9
11
  * - `409` — plafond `apiKeys.maxPerSubject` atteint ;
10
12
  * - `503` — store de jetons indisponible (clés activées mais non provisionnées).
11
13
  *
@@ -17,5 +19,4 @@ var ApiKeyError = class extends nodefonyError {
17
19
  super(message, code);
18
20
  }
19
21
  };
20
- //#endregion
21
22
  export { ApiKeyError, ApiKeyError as default };
@@ -1,5 +1,5 @@
1
1
  import { nodefonyError } from "nodefony";
2
- //#region nodefony/errors/AuthenticationError.ts
2
+ // nodefony/errors/AuthenticationError.ts
3
3
  /**
4
4
  * Échec d'authentification — `code = 401`. Levée par un {@link IAuthenticator}
5
5
  * quand le credential est absent/invalide, ou par le firewall en Zero Trust
@@ -10,5 +10,4 @@ var AuthenticationError = class extends nodefonyError {
10
10
  super(message, 401);
11
11
  }
12
12
  };
13
- //#endregion
14
13
  export { AuthenticationError, AuthenticationError as default };
@@ -1,5 +1,5 @@
1
1
  import { nodefonyError } from "nodefony";
2
- //#region nodefony/errors/CsrfError.ts
2
+ // nodefony/errors/CsrfError.ts
3
3
  /**
4
4
  * Mutation cross-site bloquée — `code = 403` (RFC 9110 §15.5.4 : le serveur a
5
5
  * compris la requête mais refuse de l'honorer).
@@ -19,5 +19,4 @@ var CsrfError = class extends nodefonyError {
19
19
  super(message, 403);
20
20
  }
21
21
  };
22
- //#endregion
23
22
  export { CsrfError, CsrfError as default };
@@ -1,5 +1,5 @@
1
1
  import { INSECURE_TRANSPORT_MESSAGE, nodefonyError } from "nodefony";
2
- //#region nodefony/errors/InsecureTransportError.ts
2
+ // nodefony/errors/InsecureTransportError.ts
3
3
  /**
4
4
  * Secret refusé parce qu'il arrive en clair — `code = 403` (RFC 9110 §15.5.4 :
5
5
  * le serveur a compris la requête mais refuse de l'honorer).
@@ -20,5 +20,4 @@ var InsecureTransportError = class extends nodefonyError {
20
20
  super(INSECURE_TRANSPORT_MESSAGE, 403);
21
21
  }
22
22
  };
23
- //#endregion
24
23
  export { InsecureTransportError, InsecureTransportError as default };
@@ -1,5 +1,5 @@
1
1
  import { nodefonyError } from "nodefony";
2
- //#region nodefony/errors/InvalidTargetError.ts
2
+ // nodefony/errors/InvalidTargetError.ts
3
3
  /**
4
4
  * La ressource demandée à l'émission ne peut pas être servie — `code = 400`,
5
5
  * code d'erreur OAuth `invalid_target` (RFC 8707 §2).
@@ -35,5 +35,4 @@ var InvalidTargetError = class extends nodefonyError {
35
35
  this.requested = requested;
36
36
  }
37
37
  };
38
- //#endregion
39
38
  export { InvalidTargetError, InvalidTargetError as default };
@@ -1,5 +1,5 @@
1
1
  import { nodefonyError } from "nodefony";
2
- //#region nodefony/errors/SsrfError.ts
2
+ // nodefony/errors/SsrfError.ts
3
3
  /**
4
4
  * URL rejetée par la protection SSRF — `code = 422`. Levée quand une URL sortante
5
5
  * (endpoint webhook, fetch applicatif…) est syntaxiquement valide mais cible une
@@ -13,5 +13,4 @@ var SsrfError = class extends nodefonyError {
13
13
  super(message, 422);
14
14
  }
15
15
  };
16
- //#endregion
17
16
  export { SsrfError, SsrfError as default };
@@ -1,5 +1,5 @@
1
1
  import { nodefonyError } from "nodefony";
2
- //#region nodefony/errors/ThrottledError.ts
2
+ // nodefony/errors/ThrottledError.ts
3
3
  /**
4
4
  * Tentatives de login trop rapprochées — `code = 429` (RFC 6585 §4).
5
5
  *
@@ -17,5 +17,4 @@ var ThrottledError = class extends nodefonyError {
17
17
  this.retryAfterS = retryAfterS;
18
18
  }
19
19
  };
20
- //#endregion
21
20
  export { ThrottledError, ThrottledError as default };
@@ -1,5 +1,5 @@
1
1
  import { nodefonyError } from "nodefony";
2
- //#region nodefony/errors/UnverifiableTokenError.ts
2
+ // nodefony/errors/UnverifiableTokenError.ts
3
3
  /**
4
4
  * Le jeton n'a pas pu être VÉRIFIÉ — `code = 503`, et surtout **pas 401**.
5
5
  *
@@ -38,5 +38,4 @@ var UnverifiableTokenError = class extends nodefonyError {
38
38
  this.detail = detail;
39
39
  }
40
40
  };
41
- //#endregion
42
41
  export { UnverifiableTokenError, UnverifiableTokenError as default };
@@ -1,5 +1,5 @@
1
1
  import { nodefonyError } from "nodefony";
2
- //#region nodefony/errors/WebAuthnError.ts
2
+ // nodefony/errors/WebAuthnError.ts
3
3
  /**
4
4
  * Erreur de **gestion** d'un credential WebAuthn (enrôlement) — porte un `code`
5
5
  * HTTP que l'adaptateur framework mappe par duck-typing (il n'importe jamais les
@@ -17,5 +17,4 @@ var WebAuthnError = class extends nodefonyError {
17
17
  super(message, code);
18
18
  }
19
19
  };
20
- //#endregion
21
20
  export { WebAuthnError, WebAuthnError as default };
@@ -1,5 +1,5 @@
1
1
  import { isNodefonyKeycloakTheme, keycloakLoginTheme, realmNameFromIssuer } from "./keycloakRealmInput.js";
2
- //#region nodefony/keycloak/keycloakAdmin.ts
2
+ // nodefony/keycloak/keycloakAdmin.ts
3
3
  /** Refus ou échec d'un appel ; le message nomme la cause et le geste suivant. */
4
4
  var KeycloakAdminError = class extends Error {
5
5
  status;
@@ -251,5 +251,4 @@ function keycloakLoginThemeCheck(skin) {
251
251
  };
252
252
  };
253
253
  }
254
- //#endregion
255
254
  export { KeycloakAdminClient, KeycloakAdminError, keycloakAdminTarget, keycloakLoginThemeCheck, readKeycloakLoginTheme };
@@ -1,4 +1,4 @@
1
- //#region nodefony/keycloak/keycloakRealm.ts
1
+ // nodefony/keycloak/keycloakRealm.ts
2
2
  /** Le type du mapper qui ajoute une audience au jeton d'accès. */
3
3
  const AUDIENCE_MAPPER = "oidc-audience-mapper";
4
4
  /**
@@ -256,5 +256,4 @@ function renderValue(value, indent, lead) {
256
256
  function renderKeycloakRealm(realm) {
257
257
  return `${renderValue(realm, "", 0)}\n`;
258
258
  }
259
- //#endregion
260
259
  export { buildKeycloakRealm, keycloakDefaultRole, mergeKeycloakRealm, renderKeycloakRealm };
@@ -1,6 +1,6 @@
1
1
  import { KEYCLOAK_THEME_NAME } from "./scaffold.js";
2
2
  import { DEFAULT_LOGIN_PAGE_SKIN } from "nodefony";
3
- //#region nodefony/keycloak/keycloakRealmInput.ts
3
+ // nodefony/keycloak/keycloakRealmInput.ts
4
4
  /** Erreur de dérivation : la config ne permet pas d'écrire un realm. */
5
5
  var KeycloakRealmError = class extends Error {
6
6
  name = "KeycloakRealmError";
@@ -120,5 +120,4 @@ function deriveKeycloakRealmInput(ctx) {
120
120
  warnings
121
121
  };
122
122
  }
123
- //#endregion
124
123
  export { KeycloakRealmError, deriveKeycloakRealmInput, isNodefonyKeycloakTheme, keycloakLoginTheme, realmNameFromIssuer };
@@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url";
3
3
  import path from "node:path";
4
4
  import { existsSync, readFileSync, readdirSync } from "node:fs";
5
5
  import { randomUUID } from "node:crypto";
6
- //#region nodefony/keycloak/scaffold.ts
6
+ // nodefony/keycloak/scaffold.ts
7
7
  /**
8
8
  * Le décor Keycloak de développement que `@nodefony/security` livre à une
9
9
  * application : realm d'import et thème aux couleurs du framework.
@@ -54,7 +54,8 @@ function packageRoot() {
54
54
  for (;;) {
55
55
  const manifest = path.join(dir, "package.json");
56
56
  if (existsSync(manifest)) {
57
- if (JSON.parse(readFileSync(manifest, "utf8")).name === PACKAGE_NAME) return dir;
57
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
58
+ if (typeof parsed === "object" && parsed !== null && "name" in parsed && parsed.name === PACKAGE_NAME) return dir;
58
59
  }
59
60
  const parent = path.dirname(dir);
60
61
  if (parent === dir) throw new Error(`racine du paquet ${PACKAGE_NAME} introuvable`);
@@ -227,5 +228,4 @@ function contributeKeycloak(context) {
227
228
  context.write(APP_KEYCLOAK_REALM_FILE, renderKeycloakRealm(realm));
228
229
  copyKeycloakTheme(context);
229
230
  }
230
- //#endregion
231
231
  export { APP_KEYCLOAK_REALM_FILE, APP_KEYCLOAK_THEMES_DIR, APP_KEYCLOAK_THEME_DIR, APP_PARTNER_DEV_RESOURCE, KEYCLOAK_THEME_NAME, appDevRealmInput, contributeKeycloak, copyKeycloakTheme, deliveredThemeTypes, devClientSecret, devMachineSecret, keycloakThemeSource, keycloakThemesSource };