@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
@@ -3,50 +3,8 @@ import type { ContextType } from "@nodefony/http";
3
3
  import type { IAuthenticator } from "../../contracts/IAuthenticator.js";
4
4
  import type { ISecuredArea } from "../../contracts/ISecuredArea.js";
5
5
  import type { IToken } from "../../contracts/IToken.js";
6
- import { type ExternalSubjectMapping } from "./externalSubject.js";
7
- import { type IProviderRoleMapping } from "../oauth/providerRoles.js";
8
- /** Comment le sujet d'un jeton devient un utilisateur de cette application. */
9
- export type ExternalSubjectPolicy = "require" | "ephemeral";
10
- /** Un émetteur reconnu, et la façon dont ses sujets entrent chez nous. */
11
- export interface IExternalIssuerBinding {
12
- /** Émetteur de confiance, sous sa forme canonique. */
13
- issuer: string;
14
- /** Comment le `sub` de CET émetteur devient un identifiant local. */
15
- subjectMapping: ExternalSubjectMapping;
16
- }
17
- /** Ce que la fabrique doit fournir à l'authenticator. */
18
- export interface IExternalJwtAuthenticatorOptions {
19
- /**
20
- * Émetteurs de confiance, et leur politique de sujet.
21
- *
22
- * La liste sert à deux choses, qu'il ne faut pas confondre : reconnaître les
23
- * jetons qui relèvent de cet authenticator (aiguillage — elle n'accorde
24
- * rien, le vérificateur refait le contrôle sur sa propre liste), et savoir
25
- * dans quel espace de noms lire le sujet de chacun.
26
- */
27
- issuers: readonly IExternalIssuerBinding[];
28
- /** Politique de rattachement du sujet à un utilisateur local. */
29
- subjectPolicy: ExternalSubjectPolicy;
30
- /** Rôles accordés en mode `ephemeral`. */
31
- ephemeralRoles: readonly string[];
32
- /**
33
- * Fournisseurs de CONNEXION (`oauth2.providers`) qui déclarent leur émetteur.
34
- *
35
- * Quand l'émetteur d'un jeton est celui d'un fournisseur de connexion, la
36
- * même personne a pu se connecter par le navigateur : son compte local porte
37
- * alors le lien `(fournisseur, sub)`. Le chercher d'abord évite qu'un jeton
38
- * d'API désigne un AUTRE compte que la session de son porteur.
39
- */
40
- oauthProviders?: readonly {
41
- name: string;
42
- issuer: string;
43
- /**
44
- * Table de correspondance des rôles du fournisseur, compilée — absente ou
45
- * `null` : les rôles du compte lié restent locaux.
46
- */
47
- roleMapping?: IProviderRoleMapping | null;
48
- }[];
49
- }
6
+ import { type IExternalJwtAuthenticatorOptions } from "./ExternalSubjectResolver.js";
7
+ export type { ExternalSubjectPolicy, IExternalIssuerBinding, IExternalJwtAuthenticatorOptions, } from "./ExternalSubjectResolver.js";
50
8
  /**
51
9
  * Authentification par **jeton d'accès émis par un serveur d'autorisation
52
10
  * TIERS** (Keycloak, Auth0, Entra, ou l'émetteur d'une flotte d'agents).
@@ -0,0 +1,99 @@
1
+ import { type Container } from "nodefony";
2
+ import { type IUser } from "@nodefony/user";
3
+ import type { ISecuredArea } from "../../contracts/ISecuredArea.js";
4
+ import { type ExternalSubjectMapping } from "./externalSubject.js";
5
+ import type { ISecurityConfig } from "../../config/defineModuleConfig.js";
6
+ import { type IProviderRoleMapping } from "../oauth/providerRoles.js";
7
+ /** Comment le sujet d'un jeton devient un utilisateur de cette application. */
8
+ export type ExternalSubjectPolicy = "require" | "ephemeral";
9
+ /** Un émetteur reconnu, et la façon dont ses sujets entrent chez nous. */
10
+ export interface IExternalIssuerBinding {
11
+ /** Émetteur de confiance, sous sa forme canonique. */
12
+ issuer: string;
13
+ /** Comment le `sub` de CET émetteur devient un identifiant local. */
14
+ subjectMapping: ExternalSubjectMapping;
15
+ }
16
+ /** Ce que la fabrique doit fournir à l'authenticator. */
17
+ export interface IExternalJwtAuthenticatorOptions {
18
+ /**
19
+ * Émetteurs de confiance, et leur politique de sujet.
20
+ *
21
+ * La liste sert à deux choses, qu'il ne faut pas confondre : reconnaître les
22
+ * jetons qui relèvent de cet authenticator (aiguillage — elle n'accorde
23
+ * rien, le vérificateur refait le contrôle sur sa propre liste), et savoir
24
+ * dans quel espace de noms lire le sujet de chacun.
25
+ */
26
+ issuers: readonly IExternalIssuerBinding[];
27
+ /** Politique de rattachement du sujet à un utilisateur local. */
28
+ subjectPolicy: ExternalSubjectPolicy;
29
+ /** Rôles accordés en mode `ephemeral`. */
30
+ ephemeralRoles: readonly string[];
31
+ /**
32
+ * Fournisseurs de CONNEXION (`oauth2.providers`) qui déclarent leur émetteur.
33
+ *
34
+ * Quand l'émetteur d'un jeton est celui d'un fournisseur de connexion, la
35
+ * même personne a pu se connecter par le navigateur : son compte local porte
36
+ * alors le lien `(fournisseur, sub)`. Le chercher d'abord évite qu'un jeton
37
+ * d'API désigne un AUTRE compte que la session de son porteur.
38
+ */
39
+ oauthProviders?: readonly {
40
+ name: string;
41
+ issuer: string;
42
+ /**
43
+ * Table de correspondance des rôles du fournisseur, compilée — absente ou
44
+ * `null` : les rôles du compte lié restent locaux.
45
+ */
46
+ roleMapping?: IProviderRoleMapping | null;
47
+ }[];
48
+ }
49
+ /**
50
+ * Les options de rattachement des sujets externes, lues dans la configuration
51
+ * de sécurité — UNE lecture, pour la zone `external-jwt` comme pour le
52
+ * résolveur de compte d'une ressource protégée.
53
+ *
54
+ * @param config - configuration de sécurité validée
55
+ * @returns émetteurs, politique de sujet, fournisseurs de connexion liables
56
+ */
57
+ export declare function externalSubjectOptions(config: ISecurityConfig): IExternalJwtAuthenticatorOptions;
58
+ /**
59
+ * Établit l'utilisateur applicatif que désigne le sujet d'un jeton TIERS —
60
+ * la décision qui transforme « un annuaire a authentifié ce sujet » en « ce
61
+ * compte agit chez nous ».
62
+ *
63
+ * UNE implémentation, deux consommateurs : la zone `external-jwt` du pare-feu
64
+ * (`ExternalJwtAuthenticator`) et le résolveur de compte d'une ressource
65
+ * protégée (porte MCP). Recopiée, la règle d'espace de noms aurait divergé dès
66
+ * le premier émetteur ajouté — et c'est elle qui empêche un annuaire tiers de
67
+ * réclamer le compte local `admin`.
68
+ */
69
+ export declare class ExternalSubjectResolver {
70
+ #private;
71
+ /**
72
+ * @param container - container DI (résolution lazy de `users`)
73
+ * @param options - émetteurs reconnus et politique de rattachement
74
+ */
75
+ constructor(container: Container, options: IExternalJwtAuthenticatorOptions);
76
+ /**
77
+ * L'émetteur revendiqué est-il l'un des nôtres ? Ne lève JAMAIS : la valeur
78
+ * vient d'un jeton que n'importe qui peut forger.
79
+ *
80
+ * @param issuer - émetteur lu dans le jeton, non vérifié
81
+ */
82
+ recognizes(issuer: string): boolean;
83
+ /**
84
+ * Établit l'utilisateur applicatif à partir du sujet d'un jeton VÉRIFIÉ.
85
+ *
86
+ * Les deux politiques répondent à deux questions différentes : « qui, chez
87
+ * nous, est cette personne ? » et « quel pouvoir accorde-t-on à une machine
88
+ * que l'annuaire a authentifiée ? ».
89
+ *
90
+ * @param issuer - émetteur canonique, tel que le vérificateur l'a établi
91
+ * @param subject - sujet brut (`sub`) chez cet émetteur
92
+ * @param claims - revendications du jeton (rôles d'annuaire)
93
+ * @param area - zone qui SURCHARGE la politique, s'il y en a une
94
+ * @throws AuthenticationError (401) — sujet sans compte local utilisable
95
+ * @throws UnverifiableTokenError (503) — émetteur vérifié mais absent de la
96
+ * table de rattachement : configuration incohérente
97
+ */
98
+ resolve(issuer: string, subject: string, claims: Readonly<Record<string, unknown>> | undefined, area?: ISecuredArea): Promise<IUser>;
99
+ }
@@ -1,3 +1,4 @@
1
+ import type { IUserProvider } from "@nodefony/user";
1
2
  import type { IRealtimeAuthenticator, IRealtimeHandshake, IRealtimeToken } from "../realtime/realtimeContracts.js";
2
3
  /**
3
4
  * Authenticator realtime des identités résolues par le **firewall** — équivalent
@@ -50,8 +51,10 @@ export declare class FirewallRealtimeAuthenticator implements IRealtimeAuthentic
50
51
  * @param resolveStore - fournit le store de révocation des jetons (le firewall
51
52
  * passe une closure sur son container). Omis → mode dégradé documenté :
52
53
  * seules les bornes portées par le jeton lui-même sont vérifiables.
54
+ * @param resolveUsers - fournit l'annuaire des comptes (`users`). Omis → pas
55
+ * de confirmation du compte avant `api.request`.
53
56
  */
54
- constructor(resolveStore?: (() => IRealtimeRevocationStore | null) | null);
57
+ constructor(resolveStore?: (() => IRealtimeRevocationStore | null) | null, resolveUsers?: (() => IUserProvider | null) | null);
55
58
  /** Une identité authentifiée a-t-elle été résolue (par le firewall) au handshake ? */
56
59
  supports(_handshake: IRealtimeHandshake): boolean;
57
60
  /**
@@ -1,5 +1,6 @@
1
- import type { Container } from "nodefony";
1
+ import type { Container, IAccessPrincipal } from "nodefony";
2
2
  import type { ContextType } from "@nodefony/http";
3
+ import type { IUser } from "@nodefony/user";
3
4
  import type { IAuthenticator } from "../../contracts/IAuthenticator.js";
4
5
  import type { IToken } from "../../contracts/IToken.js";
5
6
  import type { IJwtRuntime } from "../token/jwtRuntime.js";
@@ -59,6 +60,41 @@ export declare class JwtAuthenticator implements IAuthenticator {
59
60
  * firewall puis 401 fail-closed (rien ne fuite au client).
60
61
  */
61
62
  authenticate(token: IToken): Promise<IToken>;
63
+ /**
64
+ * Vérifie un jeton émis par CETTE application pour une ressource protégée
65
+ * nommée par l'appelant — la porte d'une ressource (RFC 8707), pas une zone.
66
+ *
67
+ * Les MÊMES règles que {@link authenticate}, appliquées par la même fonction :
68
+ * signature par le keyset local, `iss`, `typ: at+jwt`, révocation ciblée
69
+ * (`jti`) et en masse (`invalidBefore`), compte encore actif. Seule
70
+ * l'audience diffère : c'est celle de la ressource qui appelle, et elle est
71
+ * exigée. Un jeton révoqué, ou celui d'un compte désactivé, n'ouvre donc pas
72
+ * plus une porte MCP qu'une route — ce que la vérification par le seul jeu de
73
+ * clés publiques laissait passer jusqu'à l'expiration.
74
+ *
75
+ * @param raw - le jeton présenté, brut
76
+ * @param audience - l'URI canonique de la ressource qui le reçoit
77
+ * @returns le principal établi, ou `null` pour tout refus (contrat
78
+ * `IAccessTokenVerifier` : un refus n'est jamais une exception)
79
+ * @throws Error si le câblage manque (keystore, store, annuaire) — la porte
80
+ * le traduit en `503`, jamais en jeton accepté
81
+ */
82
+ verifyForResource(raw: string, audience: string): Promise<IAccessPrincipal | null>;
83
+ /**
84
+ * Recharge le compte que désigne un principal établi par
85
+ * {@link verifyForResource} — pour une ressource protégée qui fait agir un
86
+ * outil au nom du propriétaire du jeton.
87
+ *
88
+ * La MÊME règle que la zone ({@link loadBoundAccount}), rejouée sur les
89
+ * revendications du principal : le vérificateur ne garde pas le compte qu'il
90
+ * a lu, et une seconde lecture de l'annuaire est le coût accepté d'un outil
91
+ * qui la demande — les autres ne la paient pas.
92
+ *
93
+ * @param principal - principal d'un jeton émis par CETTE application
94
+ * @returns le compte, ou `null` s'il n'est pas (ou plus) celui du jeton
95
+ * @throws Error si l'annuaire des comptes n'est pas câblé
96
+ */
97
+ resolveAccount(principal: IAccessPrincipal): Promise<IUser | null>;
62
98
  /** Slot audit (J4b). */
63
99
  onSuccess(_context: ContextType, _token: IToken): Promise<void>;
64
100
  /** Slot audit (J4b) — le 401 + challenge sont posés par le firewall. */
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Rend un échec de transport LISIBLE : `fetch` (undici) ne dit que
3
+ * `fetch failed` et range la vraie raison dans `cause` — certificat refusé,
4
+ * connexion refusée, nom introuvable.
5
+ *
6
+ * @param error - ce que `fetch` (ou une bibliothèque qui l'appelle) a levé
7
+ * @returns `message (CODE : raison)`, ou le message seul faute de cause
8
+ */
9
+ export declare function describeTransportFailure(error: unknown): string;
10
+ /**
11
+ * Le remède d'un certificat signé par une autorité que le processus ne
12
+ * reconnaît pas — `null` pour tout autre échec.
13
+ *
14
+ * En développement, Nodefony reconnaît d'office l'autorité qu'il a générée ; en
15
+ * production jamais, et c'est voulu. Sans ce remède, le journal ne montrait que
16
+ * `SELF_SIGNED_CERT_IN_CHAIN` : la variable qui le règle n'était écrite que dans
17
+ * un commentaire du `.env`, fichier qu'un serveur de production n'a pas.
18
+ *
19
+ * @param error - ce que `fetch` (ou une bibliothèque qui l'appelle) a levé
20
+ * @returns une phrase d'action, ou `null`
21
+ */
22
+ export declare function untrustedAuthorityHint(error: unknown): string | null;
@@ -1,11 +1,3 @@
1
- /**
2
- * Lecture BORNÉE d'un corps JSON — la brique de transport commune aux trois
3
- * appels sortants du social login (découverte, point de jeton, API d'un
4
- * fournisseur non-OIDC).
5
- *
6
- * Elle existe parce qu'une borne posée APRÈS `response.text()` ne protège plus
7
- * rien : le corps est déjà entièrement en mémoire quand on mesure sa longueur.
8
- */
9
1
  /**
10
2
  * Lit un corps JSON en refusant de dépasser une taille — la borne est vérifiée
11
3
  * PENDANT la lecture, pas après : un corps déjà entièrement en mémoire ne se
@@ -31,8 +31,9 @@ export declare class UserRealtimeToken implements IRealtimeToken {
31
31
  * @param revalidate - preuve de vie de l'identité, adaptée à son mode.
32
32
  * @param type - mode d'authentification réel (défaut `"session"`).
33
33
  * @param scopes - scopes délégués du jeton d'origine (défaut aucun).
34
+ * @param confirmAccount - relecture du compte, payée sur `api.request` seul.
34
35
  */
35
- constructor(user: IUser, revalidate?: ((nowMs?: number) => Promise<boolean>) | null, type?: string, scopes?: readonly string[]);
36
+ constructor(user: IUser, revalidate?: ((nowMs?: number) => Promise<boolean>) | null, type?: string, scopes?: readonly string[], confirmAccount?: (() => Promise<boolean>) | null);
36
37
  /**
37
38
  * Zero Trust : l'identité qui a ouvert cette socket est-elle TOUJOURS vivante ?
38
39
  *
@@ -46,6 +47,11 @@ export declare class UserRealtimeToken implements IRealtimeToken {
46
47
  * sans dépendre de l'horloge de la machine.
47
48
  */
48
49
  isValid(nowMs?: number): Promise<boolean>;
50
+ /**
51
+ * Le compte qui a ouvert la socket existe-t-il toujours, actif, et sous le
52
+ * même `id` immuable ? `true` si aucune confirmation n'est câblée.
53
+ */
54
+ confirmAccount(): Promise<boolean>;
49
55
  getUserIdentifier(): string;
50
56
  isAuthenticated(): boolean;
51
57
  getRoles(): string[];
@@ -39,7 +39,7 @@ export interface ISystemChannelRule {
39
39
  * hot-path WS. Le firewall fournit l'implémentation (closure sur son container) ;
40
40
  * absent par défaut (verrou pur, testable sans audit).
41
41
  *
42
- * @param surface - cible refusée : pont API souverain (`api.request`) ou canal
42
+ * @param surface - cible refusée : pont socket (`api.request`) ou canal
43
43
  * (subscribe/inbound).
44
44
  * @param target - pathname (api.request) ou nom de canal refusé.
45
45
  * @param reason - raison machine stable (`zone_protected` | `channel_policy`).
@@ -159,7 +159,7 @@ export declare function buildSystemRules(prefixes: readonly string[]): readonly
159
159
  * (api.request) ou la politique du canal (subscribe/inbound).
160
160
  *
161
161
  * Trois surfaces gardées :
162
- * - `api.request {path}` (pont API souverain) : re-match de zone HTTP → zone
162
+ * - `api.request {path}` (pont socket) : re-match de zone HTTP → zone
163
163
  * protégée + anonyme = refus (autorisation de ZONE, identique à `GET {path}` ;
164
164
  * la méthode/action est gardée en aval par le router + `@IsGranted`).
165
165
  * - `subscribe {channel}` : politique de canal (système plancher + déclaration
@@ -32,6 +32,12 @@ export interface IRealtimeToken {
32
32
  * (absent = valide). Appelé par le pont `api.request` avant l'action data plane.
33
33
  */
34
34
  isValid?(nowMs?: number): boolean | Promise<boolean>;
35
+ /**
36
+ * Le compte de la socket existe-t-il toujours, actif, sous le même `id` ?
37
+ * (miroir de `IRealtimeToken.confirmAccount` realtime). Optionnel (absent =
38
+ * confirmé). Relit l'annuaire : payé sur `api.request` seul, jamais au tick.
39
+ */
40
+ confirmAccount?(): Promise<boolean>;
35
41
  }
36
42
  /**
37
43
  * Données de handshake WS (miroir de `IRealtimeHandshake`) — DTO neutre passé à
@@ -0,0 +1,27 @@
1
+ import type { IRolesWithin } from "nodefony";
2
+ import type { IUser } from "@nodefony/user";
3
+ import type { RoleHierarchyWalker } from "./RoleHierarchyWalker.js";
4
+ /**
5
+ * Vue de l'annuaire PLUS permissive que `IUserProvider` (qui promet un
6
+ * `IUser`) : un fournisseur d'application peut rendre `null` ou `undefined`
7
+ * au lieu de lever — sans garde, zéro rôle vaudrait accord. Tout
8
+ * `IUserProvider` la satisfait.
9
+ */
10
+ export interface IAccountDirectory {
11
+ loadUserByIdentifier(identifier: string): Promise<IUser | null | undefined>;
12
+ }
13
+ /**
14
+ * La règle « jouer un autre compte » : les rôles de `target`, hiérarchie
15
+ * comprise, sont-ils TOUS atteints par ceux de `owner` ? Jouer un compte ne
16
+ * monte jamais en droits.
17
+ *
18
+ * Fermée en cas de doute : un annuaire absent, un compte introuvable — y
19
+ * compris un sujet externe éphémère, qui n'a pas de compte local — rendent un
20
+ * refus, jamais un accord. L'annuaire est résolu à l'appel : le service `users`
21
+ * peut être posé après le pare-feu.
22
+ *
23
+ * @param resolveUsers - rend l'annuaire des comptes (`users`), ou `null`
24
+ * @param hierarchy - la hiérarchie de rôles du pare-feu
25
+ * @returns la règle, à poser au conteneur sous `ROLES_WITHIN`
26
+ */
27
+ export declare function rolesWithin(resolveUsers: () => IAccountDirectory | null, hierarchy: RoleHierarchyWalker): IRolesWithin;
@@ -18,3 +18,25 @@ import type { IUser, IUserProvider } from "@nodefony/user";
18
18
  * compte verrouillé ou désactivé.
19
19
  */
20
20
  export declare function resolveSessionIdentity(provider: IUserProvider, identifier: string): Promise<IUser>;
21
+ /**
22
+ * Résout l'identité d'une session DÉJÀ ouverte, en exigeant qu'elle ait été
23
+ * ouverte pour CE compte — {@link resolveSessionIdentity} plus la liaison à
24
+ * l'identifiant immuable (`isBoundToAccount`).
25
+ *
26
+ * La session range son principal sous l'`identifier`, qu'un compte recréé
27
+ * (suppression hors framework, restauration, import) peut reprendre : sans la
28
+ * liaison, la session de l'ancien titulaire ouvrirait le compte du nouveau.
29
+ * Une session sans liaison est refusée au même titre.
30
+ *
31
+ * @param provider - source d'identité (`UserService` via le container).
32
+ * @param identifier - identifiant fonctionnel stocké en session.
33
+ * @param boundAccountId - identifiant immuable inscrit à l'ouverture
34
+ * (`metaBag`, clé `ACCOUNT_ID_KEY`).
35
+ * @param boundCredentialsVersion - version des identifiants inscrite à
36
+ * l'ouverture (`metaBag`, clé `CREDENTIALS_VERSION_KEY`).
37
+ * @returns l'utilisateur actif, titulaire de la session.
38
+ * @throws AuthenticationError (401, message uniforme) — identifiant inconnu,
39
+ * compte verrouillé ou désactivé, session ouverte pour un autre compte ou
40
+ * avant le dernier changement de mot de passe.
41
+ */
42
+ export declare function resolveBoundSessionIdentity(provider: IUserProvider, identifier: string, boundAccountId: unknown, boundCredentialsVersion: unknown): Promise<IUser>;
@@ -0,0 +1,77 @@
1
+ import type { RoleHierarchyWalker } from "../RoleHierarchyWalker.js";
2
+ /**
3
+ * Qui peut OBTENIR un scope d'application : clé = un scope exact ou une
4
+ * famille `préfixe:*`, valeur = les rôles dont UN suffit.
5
+ */
6
+ export type IScopeGrants = Readonly<Record<string, readonly string[]>>;
7
+ /**
8
+ * Un scope demandé et refusé, avec la règle qui l'a refusé.
9
+ *
10
+ * `admin` = réservé au plan d'administration du framework
11
+ * ({@link refusedAdminScopes}) ; `application` = déclaré dans
12
+ * `grantableScopes` pour des rôles que le porteur n'a pas.
13
+ */
14
+ export interface IScopeRefusal {
15
+ scope: string;
16
+ rule: "admin" | "application";
17
+ }
18
+ /**
19
+ * Clés que `grantableScopes` refuse : le plan d'administration est jugé par le
20
+ * framework, et une déclaration ici serait soit sans effet, soit un
21
+ * assouplissement — deux pièges que rien ne signalerait.
22
+ */
23
+ export declare const RESERVED_SCOPE_GRANT_KEYS: readonly string[];
24
+ /**
25
+ * Dit pourquoi une clé de `grantableScopes` est invalide, ou `null` si elle
26
+ * est acceptable.
27
+ *
28
+ * Une clé est un `scope-token` (RFC 6749 §3.3) ; un `*` n'y est admis qu'en
29
+ * dernière position, juste après `:` (une famille `préfixe:*`).
30
+ *
31
+ * @param key - la clé déclarée.
32
+ * @returns le motif du refus, ou `null`.
33
+ */
34
+ export declare function scopeGrantKeyProblem(key: string): string | null;
35
+ /**
36
+ * Politique d'attribution des scopes d'application, compilée une fois au boot.
37
+ *
38
+ * Résolution : une clé EXACTE l'emporte sur une famille, et la famille la plus
39
+ * longue sur une plus courte (`shop:admin:*` > `shop:*`). Le résultat ne dépend
40
+ * donc pas de l'ordre des clés. Un scope qu'aucune clé ne couvre reste accordé
41
+ * à tout porteur (comportement historique).
42
+ */
43
+ export declare class ScopeGrantPolicy {
44
+ #private;
45
+ /**
46
+ * @param grants - la section `grantableScopes`, déjà validée par le schéma.
47
+ */
48
+ constructor(grants?: IScopeGrants);
49
+ /** `true` si aucune clé n'est déclarée — tout scope d'application est accordé. */
50
+ get isEmpty(): boolean;
51
+ /**
52
+ * Les rôles dont UN suffit pour obtenir ce scope.
53
+ *
54
+ * @param scope - un scope demandé.
55
+ * @returns les rôles de la clé la plus spécifique, ou `null` si aucune clé ne
56
+ * le couvre (scope libre).
57
+ */
58
+ rolesFor(scope: string): readonly string[] | null;
59
+ }
60
+ /**
61
+ * Les scopes demandés que ce porteur ne peut pas obtenir, avec la règle qui
62
+ * refuse chacun.
63
+ *
64
+ * Seul juge des DEUX familles, appelé par chaque émetteur de l'application
65
+ * (jeton d'accès, clé API) : la règle d'administration du framework
66
+ * ({@link refusedAdminScopes}, jugée sur les rôles PLATS du compte, comme le
67
+ * plan d'administration) et la politique de l'application
68
+ * ({@link ScopeGrantPolicy}, jugée hiérarchie de rôles comprise, comme un
69
+ * `@IsGranted`).
70
+ *
71
+ * @param requested - scopes demandés (RFC 6749 §3.3).
72
+ * @param roles - rôles du porteur, tels que son compte les porte.
73
+ * @param policy - politique compilée de `grantableScopes`.
74
+ * @param hierarchy - hiérarchie de rôles ; absente, seuls les rôles plats comptent.
75
+ * @returns les refus, dans l'ordre de la demande — vide si tout est accordé.
76
+ */
77
+ export declare function refusedScopes(requested: readonly string[], roles: readonly string[], policy: ScopeGrantPolicy, hierarchy: RoleHierarchyWalker | null): IScopeRefusal[];
@@ -0,0 +1,51 @@
1
+ import { type IChannelPolicyResolver } from "../src/realtime/frameAuthorizer.js";
2
+ import type { FrameAuthorizer, IRealtimeToken } from "../src/realtime/realtimeContracts.js";
3
+ /** L'identité d'essai : qui se connecte, avec quels droits. */
4
+ export interface ITestIdentityOptions {
5
+ /** Identifiant de l'utilisateur (défaut `"alice"`). */
6
+ readonly identifier?: string;
7
+ /** Rôles portés (défaut `["ROLE_USER"]`) — ceux qu'aurait résolus le pare-feu. */
8
+ readonly roles?: readonly string[];
9
+ /** Scopes portés (défaut aucun) — l'axe des jetons d'API, pas de la session. */
10
+ readonly scopes?: readonly string[];
11
+ /**
12
+ * L'utilisateur rendu par `@CurrentUser()` et `RequestContext.getUser()`
13
+ * (défaut `{ identifier, roles }`). Passer l'entité de l'application quand
14
+ * l'action en lit d'autres champs.
15
+ */
16
+ readonly user?: unknown;
17
+ }
18
+ /**
19
+ * Une identité de connexion AUTHENTIFIÉE, telle que le pare-feu l'aurait posée
20
+ * au handshake.
21
+ *
22
+ * Pour éprouver un visiteur anonyme, ne rien passer au harnais : il pose alors
23
+ * le jeton anonyme, comme le repli Zero Trust du runtime.
24
+ *
25
+ * @param options - identifiant, rôles, scopes, utilisateur.
26
+ * @returns un jeton à passer en `identity` au harnais temps réel.
27
+ */
28
+ export declare function createTestIdentity(options?: ITestIdentityOptions): IRealtimeToken;
29
+ /** Écarts au pare-feu d'essai. */
30
+ export interface ITestFrameAuthorizerOptions {
31
+ /**
32
+ * Hiérarchie de rôles, à la forme de `roleHierarchy` de la configuration de
33
+ * sécurité (défaut : aucune). La reprendre de l'application quand un rôle en
34
+ * couvre un autre — `{ ROLE_ADMIN: ["ROLE_USER"] }`.
35
+ */
36
+ readonly roleHierarchy?: Record<string, readonly string[]>;
37
+ }
38
+ /**
39
+ * Le VRAI verrou de frame du pare-feu, pour le harnais temps réel.
40
+ *
41
+ * C'est lui qui applique `@RealtimeChannel(nom, { roles })`,
42
+ * `@RealtimeAction(nom, { roles })` et les canaux système : sans lui, une
43
+ * politique déclarée n'est appliquée par personne — en test comme au runtime.
44
+ *
45
+ * @param channels - ce qui connaît les politiques déclarées : le hub temps réel
46
+ * (`getRealtimeHub()`), lu à CHAQUE frame — les politiques que le controller
47
+ * enregistre à sa construction y sont donc vues.
48
+ * @param options - hiérarchie de rôles de l'application.
49
+ * @returns le verrou à passer en `frameAuthorizer` au harnais.
50
+ */
51
+ export declare function createTestFrameAuthorizer(channels: IChannelPolicyResolver, options?: ITestFrameAuthorizerOptions): FrameAuthorizer;