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

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 (160) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorate.js +1 -1
  2. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateMetadata.js +1 -1
  3. package/dist/index.js +44 -10
  4. package/dist/nodefony/command/security-oauth-doctor.js +66 -0
  5. package/dist/nodefony/command/security-secrets.js +42 -25
  6. package/dist/nodefony/command/security-token.js +46 -15
  7. package/dist/nodefony/command/security-user-delete.js +5 -4
  8. package/dist/nodefony/command/security-user-password.js +4 -11
  9. package/dist/nodefony/config/config.js +80 -5
  10. package/dist/nodefony/contracts/index.js +1 -2
  11. package/dist/nodefony/errors/InsecureTransportError.js +24 -0
  12. package/dist/nodefony/errors/index.js +2 -1
  13. package/dist/nodefony/keycloak/keycloakAdmin.js +255 -0
  14. package/dist/nodefony/keycloak/keycloakRealm.js +260 -0
  15. package/dist/nodefony/keycloak/keycloakRealmInput.js +124 -0
  16. package/dist/nodefony/keycloak/scaffold.js +231 -0
  17. package/dist/nodefony/keycloak/security-keycloak-realm.js +128 -0
  18. package/dist/nodefony/scaffold/contribute.js +20 -0
  19. package/dist/nodefony/service/authFlow.js +111 -11
  20. package/dist/nodefony/service/firewall.js +111 -21
  21. package/dist/nodefony/service/keycloak.js +209 -0
  22. package/dist/nodefony/service/oauth2.js +379 -26
  23. package/dist/nodefony/service/tokenService.js +57 -6
  24. package/dist/nodefony/service/totp.js +51 -6
  25. package/dist/nodefony/service/webAuthn.js +20 -2
  26. package/dist/nodefony/src/SecuredArea.js +14 -3
  27. package/dist/nodefony/src/admin/SecurityAdminApi.js +70 -40
  28. package/dist/nodefony/src/admin/adminAudit.js +0 -1
  29. package/dist/nodefony/src/admin/userRevocationCascade.js +79 -15
  30. package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +66 -6
  31. package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +38 -15
  32. package/dist/nodefony/src/authenticator/JwtAuthenticator.js +1 -0
  33. package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +13 -3
  34. package/dist/nodefony/src/authenticator/authenticatorRegistry.js +9 -2
  35. package/dist/nodefony/src/authenticator/bearer.js +1 -2
  36. package/dist/nodefony/src/boot/publicRoutesNotice.js +27 -0
  37. package/dist/nodefony/src/entryPoint.js +39 -0
  38. package/dist/nodefony/src/oauth/clientCredentials.js +40 -0
  39. package/dist/nodefony/src/oauth/metadata.js +36 -9
  40. package/dist/nodefony/src/oauth/oauth2Client.js +60 -33
  41. package/dist/nodefony/src/oauth/oauthProviderRegistry.js +39 -6
  42. package/dist/nodefony/src/oauth/providerDiagnosis.js +283 -0
  43. package/dist/nodefony/src/oauth/providerRoles.js +79 -0
  44. package/dist/nodefony/src/oauth/providers/github.js +32 -6
  45. package/dist/nodefony/src/oauth/providers/oidc.js +82 -3
  46. package/dist/nodefony/src/openToSignup.js +47 -0
  47. package/dist/nodefony/src/sessionAuthentication.js +26 -0
  48. package/dist/nodefony/src/token/JwtKeystore.js +78 -46
  49. package/dist/nodefony/src/token/RemoteJwtVerifier.js +40 -13
  50. package/dist/nodefony/src/token/secretFile.js +59 -2
  51. package/dist/nodefony/src/transport/CredentialTransportPolicy.js +51 -0
  52. package/dist/types/index.d.ts +26 -6
  53. package/dist/types/nodefony/command/security-oauth-doctor.d.ts +23 -0
  54. package/dist/types/nodefony/command/security-secrets.d.ts +2 -0
  55. package/dist/types/nodefony/command/security-token.d.ts +9 -0
  56. package/dist/types/nodefony/command/security-user-delete.d.ts +4 -3
  57. package/dist/types/nodefony/command/security-user-password.d.ts +4 -4
  58. package/dist/types/nodefony/config/config.d.ts +63 -1
  59. package/dist/types/nodefony/contracts/IAuthenticator.d.ts +1 -1
  60. package/dist/types/nodefony/contracts/IFirewallDescription.d.ts +42 -1
  61. package/dist/types/nodefony/contracts/IOAuthProvider.d.ts +48 -0
  62. package/dist/types/nodefony/contracts/ISecuredArea.d.ts +22 -0
  63. package/dist/types/nodefony/contracts/ITotpSecret.d.ts +4 -1
  64. package/dist/types/nodefony/contracts/ITotpSecretStore.d.ts +7 -1
  65. package/dist/types/nodefony/contracts/IWebAuthnCredential.d.ts +4 -1
  66. package/dist/types/nodefony/contracts/IWebAuthnCredentialStore.d.ts +6 -0
  67. package/dist/types/nodefony/errors/InsecureTransportError.d.ts +20 -0
  68. package/dist/types/nodefony/errors/index.d.ts +1 -0
  69. package/dist/types/nodefony/keycloak/keycloakAdmin.d.ts +154 -0
  70. package/dist/types/nodefony/keycloak/keycloakRealm.d.ts +138 -0
  71. package/dist/types/nodefony/keycloak/keycloakRealmInput.d.ts +92 -0
  72. package/dist/types/nodefony/keycloak/scaffold.d.ts +98 -0
  73. package/dist/types/nodefony/keycloak/security-keycloak-realm.d.ts +43 -0
  74. package/dist/types/nodefony/scaffold/contribute.d.ts +15 -0
  75. package/dist/types/nodefony/service/authFlow.d.ts +35 -3
  76. package/dist/types/nodefony/service/firewall.d.ts +17 -3
  77. package/dist/types/nodefony/service/keycloak.d.ts +115 -0
  78. package/dist/types/nodefony/service/oauth2.d.ts +125 -9
  79. package/dist/types/nodefony/service/tokenService.d.ts +7 -1
  80. package/dist/types/nodefony/service/totp.d.ts +28 -4
  81. package/dist/types/nodefony/service/webAuthn.d.ts +23 -1
  82. package/dist/types/nodefony/src/SecuredArea.d.ts +4 -0
  83. package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +54 -12
  84. package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +18 -0
  85. package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +13 -0
  86. package/dist/types/nodefony/src/authenticator/UserPasswordAuthenticator.d.ts +11 -3
  87. package/dist/types/nodefony/src/boot/publicRoutesNotice.d.ts +16 -0
  88. package/dist/types/nodefony/src/entryPoint.d.ts +37 -0
  89. package/dist/types/nodefony/src/oauth/clientCredentials.d.ts +54 -0
  90. package/dist/types/nodefony/src/oauth/metadata.d.ts +24 -1
  91. package/dist/types/nodefony/src/oauth/oauth2Client.d.ts +50 -1
  92. package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +46 -2
  93. package/dist/types/nodefony/src/oauth/providerDiagnosis.d.ts +117 -0
  94. package/dist/types/nodefony/src/oauth/providerRoles.d.ts +58 -0
  95. package/dist/types/nodefony/src/oauth/providers/oidc.d.ts +34 -1
  96. package/dist/types/nodefony/src/openToSignup.d.ts +36 -0
  97. package/dist/types/nodefony/src/sessionAuthentication.d.ts +14 -0
  98. package/dist/types/nodefony/src/token/JwtKeystore.d.ts +47 -5
  99. package/dist/types/nodefony/src/token/RemoteJwtVerifier.d.ts +29 -2
  100. package/dist/types/nodefony/src/token/secretFile.d.ts +22 -0
  101. package/dist/types/nodefony/src/transport/CredentialTransportPolicy.d.ts +47 -0
  102. package/docs/api-keys.md +17 -17
  103. package/docs/audit.md +16 -16
  104. package/docs/authenticators.md +60 -27
  105. package/docs/authorization.md +16 -16
  106. package/docs/cors.md +29 -29
  107. package/docs/csrf.md +35 -33
  108. package/docs/external-jwt.md +66 -4
  109. package/docs/firewall.md +57 -24
  110. package/docs/headers.md +30 -30
  111. package/docs/index.md +12 -4
  112. package/docs/keycloak.md +818 -0
  113. package/docs/login-page.md +361 -0
  114. package/docs/oauth2.md +236 -122
  115. package/docs/obtenir-un-jeton.md +18 -11
  116. package/docs/tokens.md +136 -85
  117. package/docs/totp.md +105 -64
  118. package/docs/webauthn.md +90 -87
  119. package/docs/webhooks.md +3 -3
  120. package/keycloak/themes/nodefony/account/resources/css/nodefony.css +66 -0
  121. package/keycloak/themes/nodefony/account/resources/img/favicon.ico +0 -0
  122. package/keycloak/themes/nodefony/account/resources/img/logo.png +0 -0
  123. package/keycloak/themes/nodefony/account/theme.properties +14 -0
  124. package/keycloak/themes/nodefony/admin/resources/css/nodefony.css +66 -0
  125. package/keycloak/themes/nodefony/admin/resources/img/favicon.ico +0 -0
  126. package/keycloak/themes/nodefony/admin/resources/img/logo.png +0 -0
  127. package/keycloak/themes/nodefony/admin/theme.properties +12 -0
  128. package/keycloak/themes/nodefony/email/html/template.ftl +73 -0
  129. package/keycloak/themes/nodefony/email/messages/messages_en.properties +2 -0
  130. package/keycloak/themes/nodefony/email/messages/messages_fr.properties +3 -0
  131. package/keycloak/themes/nodefony/email/resources/img/logo.png +0 -0
  132. package/keycloak/themes/nodefony/email/theme.properties +8 -0
  133. package/keycloak/themes/nodefony/login/messages/messages_en.properties +22 -0
  134. package/keycloak/themes/nodefony/login/messages/messages_fr.properties +25 -0
  135. package/keycloak/themes/nodefony/login/resources/css/login.css +1141 -0
  136. package/keycloak/themes/nodefony/login/resources/css/nodefony.css +688 -0
  137. package/keycloak/themes/nodefony/login/resources/css/skins/blueprint.css +70 -0
  138. package/keycloak/themes/nodefony/login/resources/css/skins/console.css +57 -0
  139. package/keycloak/themes/nodefony/login/resources/css/skins/dots.css +67 -0
  140. package/keycloak/themes/nodefony/login/resources/css/skins/enterprise.css +84 -0
  141. package/keycloak/themes/nodefony/login/resources/css/skins/horizon.css +158 -0
  142. package/keycloak/themes/nodefony/login/resources/css/skins/ledger.css +224 -0
  143. package/keycloak/themes/nodefony/login/resources/css/skins/minimal.css +63 -0
  144. package/keycloak/themes/nodefony/login/resources/css/skins/photo-card.css +72 -0
  145. package/keycloak/themes/nodefony/login/resources/img/favicon.ico +0 -0
  146. package/keycloak/themes/nodefony/login/resources/img/logo.png +0 -0
  147. package/keycloak/themes/nodefony/login/template.ftl +423 -0
  148. package/keycloak/themes/nodefony/login/theme.properties +17 -0
  149. package/keycloak/themes/nodefony/welcome/resources/css/nodefony.css +32 -0
  150. package/keycloak/themes/nodefony/welcome/resources/img/logo.png +0 -0
  151. package/keycloak/themes/nodefony/welcome/theme.properties +11 -0
  152. package/keycloak/themes/nodefony-blueprint/login/theme.properties +12 -0
  153. package/keycloak/themes/nodefony-console/login/theme.properties +12 -0
  154. package/keycloak/themes/nodefony-dots/login/theme.properties +12 -0
  155. package/keycloak/themes/nodefony-enterprise/login/theme.properties +12 -0
  156. package/keycloak/themes/nodefony-horizon/login/theme.properties +12 -0
  157. package/keycloak/themes/nodefony-ledger/login/theme.properties +12 -0
  158. package/keycloak/themes/nodefony-minimal/login/theme.properties +12 -0
  159. package/keycloak/themes/nodefony-photo-card/login/theme.properties +12 -0
  160. package/package.json +17 -15
@@ -25,7 +25,16 @@ export declare class OAuth2RequestError extends Error {
25
25
  readonly code: string;
26
26
  /** Description lisible fournie par le serveur, ou `null`. */
27
27
  readonly description: string | null;
28
- constructor(code: string, description: string | null);
28
+ /**
29
+ * Statut HTTP de la réponse, ou `null` s'il n'est pas connu.
30
+ *
31
+ * Le code `error` ne suffit pas toujours à dire QUI est refusé : la RFC 6749
32
+ * §5.2 réserve `401` à l'échec d'authentification du client, et Keycloak
33
+ * répond `unauthorized_client` en `401` sur un secret faux — le même code
34
+ * qu'en `400` pour un flux que le client n'a pas le droit d'employer.
35
+ */
36
+ readonly status: number | null;
37
+ constructor(code: string, description: string | null, status?: number | null);
29
38
  }
30
39
  /**
31
40
  * Jetons rendus par le point de jeton (RFC 6749 §5.1), enveloppés pour qu'un champ
@@ -88,6 +97,16 @@ export interface IAuthorizationRequest {
88
97
  readonly codeVerifier: string | null;
89
98
  /** Portées demandées ; aucune n'est ajoutée d'office. */
90
99
  readonly scopes: readonly string[];
100
+ /**
101
+ * Thème d'affichage que l'application a rendu (clair ou sombre), pour que
102
+ * l'écran du fournisseur le reprenne.
103
+ *
104
+ * Aucun paramètre normalisé ne le porte (OIDC n'a que `ui_locales`, pour la
105
+ * langue) : seul un fournisseur qui sait le lire le traduit — Keycloak, en
106
+ * `nf_theme` que lit le thème livré. Les autres l'ignorent, et ne le
107
+ * reçoivent jamais : un paramètre inconnu peut être refusé.
108
+ */
109
+ readonly theme?: "light" | "dark";
91
110
  /**
92
111
  * Paramètres versés TELS QUELS dans la requête d'autorisation.
93
112
  *
@@ -172,3 +191,33 @@ export declare class OAuth2Client {
172
191
  */
173
192
  validateAuthorizationCode(request: ITokenRequest): Promise<OAuth2Tokens>;
174
193
  }
194
+ /** Ce qu'il faut savoir du client pour parler au point de jeton. */
195
+ export interface ITokenEndpointClient {
196
+ /** Point de jeton (RFC 6749 §3.2). */
197
+ readonly tokenEndpoint: string;
198
+ /** Identifiant client délivré par le serveur d'autorisation. */
199
+ readonly clientId: string;
200
+ /** Secret client — vide seulement pour `none`. */
201
+ readonly clientSecret: string;
202
+ /** Comment le client s'authentifie (RFC 6749 §2.3). */
203
+ readonly clientAuthMethod: OAuth2ClientAuthMethod;
204
+ /** Implémentation de `fetch` (banc sans réseau). */
205
+ readonly fetch?: typeof globalThis.fetch | undefined;
206
+ /** Délai d'attente, en millisecondes. */
207
+ readonly timeoutMs?: number | undefined;
208
+ }
209
+ /**
210
+ * Présente une requête au point de jeton et rend les jetons (RFC 6749 §5.1).
211
+ *
212
+ * SEULE implémentation de l'authentification du client et de la lecture de la
213
+ * réponse : l'échange de code et le flux `client_credentials` y passent tous
214
+ * deux, pour qu'une règle (méthode déclarée, redirection refusée, réponse
215
+ * bornée, refus lu sur `error`) ne diverge jamais entre deux flux.
216
+ *
217
+ * @param client - point de jeton et identité du client.
218
+ * @param body - corps de la requête, `grant_type` compris ; l'identité du
219
+ * client y est ajoutée selon la méthode déclarée.
220
+ * @throws OAuth2RequestError - le serveur a refusé, en nommant la cause (RFC 6749 §5.2).
221
+ * @throws Error - réponse inintelligible, hors gabarit, ou serveur injoignable.
222
+ */
223
+ export declare function postTokenRequest(client: ITokenEndpointClient, body: URLSearchParams): Promise<OAuth2Tokens>;
@@ -14,7 +14,11 @@ import type { OAuth2ClientAuthMethod } from "./oauth2Client.js";
14
14
  * en une ligne dans l'application :
15
15
  *
16
16
  * ```ts
17
- * registerOAuthProvider("azure", (ctx) => createDiscoveredOidcProvider("azure", ctx));
17
+ * registerOAuthProvider(
18
+ * "azure",
19
+ * (ctx) => createDiscoveredOidcProvider("azure", ctx),
20
+ * { requiresIssuer: true }, // un émetteur oublié refuse le démarrage
21
+ * );
18
22
  * ```
19
23
  */
20
24
  /** Contexte de construction d'un fournisseur (secrets et URL issus de la config). */
@@ -42,6 +46,11 @@ export interface IOAuthProviderContext {
42
46
  * l'émetteur est connu d'avance (Google) ou qui n'en publient pas (GitHub).
43
47
  */
44
48
  readonly issuer?: string | undefined;
49
+ /**
50
+ * Adresse où lire les métadonnées de l'émetteur quand elle diffère de lui
51
+ * (service interne d'un cluster) — cf `IDiscoveryOptions.discoveryUrl`.
52
+ */
53
+ readonly discoveryUrl?: string | undefined;
45
54
  }
46
55
  /**
47
56
  * Fabrique d'un fournisseur OAuth pour un nom donné.
@@ -51,12 +60,47 @@ export interface IOAuthProviderContext {
51
60
  * (le service mémoïse le fournisseur résolu).
52
61
  */
53
62
  export type OAuthProviderFactory = (ctx: IOAuthProviderContext) => IOAuthProvider | Promise<IOAuthProvider>;
63
+ /**
64
+ * Ce qu'une fabrique EXIGE de la configuration — lu au démarrage, avant que le
65
+ * moindre utilisateur ne clique.
66
+ *
67
+ * @remarks La fabrique est une fonction opaque : rien, à sa seule lecture, ne
68
+ * dit qu'elle lèvera faute d'émetteur. Sans déclaration, l'oubli ne se
69
+ * découvrait qu'au premier login.
70
+ */
71
+ export interface IOAuthProviderRegistration {
72
+ /**
73
+ * `true` si le fournisseur ne peut se construire sans `issuer` dans sa
74
+ * configuration — typiquement un serveur OpenID Connect découvert par son
75
+ * émetteur. Absent : l'émetteur est facultatif (connu d'avance, ou inutile).
76
+ */
77
+ readonly requiresIssuer?: boolean;
78
+ }
54
79
  /**
55
80
  * Enregistre (ou remplace) la fabrique d'un fournisseur OAuth. Appelée par les
56
81
  * builtins au chargement, et par une application pour ses fournisseurs.
82
+ *
83
+ * @param name - nom sous lequel le fournisseur se configure (`oauth2.providers.<name>`).
84
+ * @param factory - fabrique du fournisseur.
85
+ * @param registration - exigences de configuration, vérifiées au démarrage.
57
86
  */
58
- export declare function registerOAuthProvider(name: string, factory: OAuthProviderFactory): void;
87
+ export declare function registerOAuthProvider(name: string, factory: OAuthProviderFactory, registration?: IOAuthProviderRegistration): void;
59
88
  /** Fabrique d'un fournisseur par nom, ou `undefined` si inconnu. */
60
89
  export declare function getOAuthProviderFactory(name: string): OAuthProviderFactory | undefined;
90
+ /** `true` si le fournisseur nommé a déclaré exiger un émetteur en configuration. */
91
+ export declare function oauthProviderRequiresIssuer(name: string): boolean;
61
92
  /** Noms enregistrés (validation boot, introspection Studio, tests). */
62
93
  export declare function listOAuthProviders(): string[];
94
+ /**
95
+ * Paramètre d'autorisation que lit le thème Keycloak livré (`template.ftl`)
96
+ * pour afficher l'écran dans le thème de l'application (`light` | `dark`).
97
+ */
98
+ export declare const KEYCLOAK_THEME_PARAMETER = "nf_theme";
99
+ /**
100
+ * Ajoute le thème demandé ({@link IAuthorizationRequest.theme}) à l'URL
101
+ * d'autorisation d'un fournisseur Keycloak, sous {@link KEYCLOAK_THEME_PARAMETER}.
102
+ *
103
+ * @param provider - fournisseur OIDC découvert, inchangé pour tout le reste
104
+ * @returns le même fournisseur, dont l'URL d'autorisation porte le thème
105
+ */
106
+ export declare function withKeycloakThemeHint(provider: IOAuthProvider): IOAuthProvider;
@@ -0,0 +1,117 @@
1
+ import type { IOAuthProvider } from "../../contracts/IOAuthProvider.js";
2
+ import { type OAuth2ClientAuthMethod } from "./oauth2Client.js";
3
+ /**
4
+ * **Diagnostic d'un fournisseur OAuth sans connexion humaine** — dit POURQUOI un
5
+ * branchement échoue, là où le journal ne montre qu'un 401 ou une page d'erreur
6
+ * du fournisseur.
7
+ *
8
+ * Trois sondes, dans l'ordre où l'une éclaire l'autre :
9
+ *
10
+ * 1. **découverte** — le fournisseur se construit comme au premier login
11
+ * (métadonnées, émetteur identique RFC 8414 §3.3, PKCE S256 annoncé) ;
12
+ * 2. **point d'autorisation** — la requête que le bouton enverrait, sans la
13
+ * suivre : le serveur accepte-t-il ce client et cette URL de retour ?
14
+ * 3. **point de jeton** — un code INVENTÉ présenté avec le vrai secret :
15
+ * `invalid_grant` prouve que le client s'est authentifié (seul le code est
16
+ * refusé), un refus d'authentification prouve le contraire (RFC 6749 §5.2).
17
+ *
18
+ * 🔴 **Rien n'est réécrit ici.** Les sondes passent par le fournisseur que la
19
+ * fabrique construit pour le login — mêmes points d'entrée, même méthode
20
+ * d'authentification, même URL de retour. Un diagnostic qui reconstruirait sa
21
+ * propre requête prouverait que SA requête passe, pas celle du login.
22
+ *
23
+ * @remarks Chemin froid (une commande, un écran d'administration). La sonde
24
+ * d'autorisation ouvre côté fournisseur une session de connexion vide, qui
25
+ * expire seule ; aucun compte n'est touché, aucun jeton n'est émis.
26
+ */
27
+ /**
28
+ * Les sondes, dans leur ordre. `theme` n'existe que si l'appelant fournit un
29
+ * {@link IOAuthDiagnosisInput.pageCheck} ET que la page d'autorisation s'y
30
+ * prête (Keycloak).
31
+ */
32
+ export type OAuthCheckName = "discovery" | "authorization" | "token" | "theme";
33
+ /**
34
+ * Verdict d'une sonde. `inconclusive` = le serveur a répondu quelque chose
35
+ * qu'on ne sait pas lire — ni un quitus, ni un manquement ; `skipped` = une
36
+ * sonde précédente a rendu celle-ci impossible.
37
+ */
38
+ export type OAuthCheckStatus = "ok" | "failed" | "inconclusive" | "skipped";
39
+ /** La cause d'un échec, lisible par une machine (rapport, console, tests). */
40
+ export type OAuthFailureKind =
41
+ /** Le document de métadonnées se déclare d'un autre émetteur (RFC 8414 §3.3). */
42
+ "issuer-mismatch"
43
+ /** Émetteur injoignable, document absent ou incomplet, PKCE S256 non annoncé. */
44
+ | "discovery-failed"
45
+ /** Le point d'autorisation refuse l'URL de retour (non enregistrée). */
46
+ | "redirect-uri-rejected"
47
+ /** Le point d'autorisation refuse la demande pour une autre raison. */
48
+ | "authorization-refused"
49
+ /** Le point d'autorisation n'a pas répondu. */
50
+ | "authorization-unreachable"
51
+ /** Le client existe (le point d'autorisation l'accepte), son secret est refusé. */
52
+ | "secret-rejected"
53
+ /** Authentification du client refusée : client inconnu, ou secret refusé. */
54
+ | "client-rejected"
55
+ /** Le point de jeton refuse pour une autre raison (flux non autorisé…). */
56
+ | "token-refused"
57
+ /** Le point de jeton n'a pas répondu. */
58
+ | "token-unreachable"
59
+ /** La page du fournisseur ne porte pas l'habillage de `/login`. */
60
+ | "login-theme-mismatch";
61
+ /** Le résultat d'une sonde. */
62
+ export interface IOAuthCheck {
63
+ readonly name: OAuthCheckName;
64
+ readonly status: OAuthCheckStatus;
65
+ /** Présent si et seulement si `status === "failed"`. */
66
+ readonly kind?: OAuthFailureKind;
67
+ /** Phrase destinée à l'opérateur : le constat, puis le geste. */
68
+ readonly message: string;
69
+ }
70
+ /** Le diagnostic d'un fournisseur. */
71
+ export interface IOAuthDiagnosis {
72
+ /** Nom du fournisseur (`oauth2.providers.<name>`). */
73
+ readonly provider: string;
74
+ /** `true` si aucune sonde n'a échoué. Une sonde non concluante ne l'empêche pas. */
75
+ readonly ok: boolean;
76
+ readonly checks: readonly IOAuthCheck[];
77
+ }
78
+ /** Ce que le diagnostic d'un fournisseur doit savoir. */
79
+ export interface IOAuthDiagnosisInput {
80
+ /** Nom du fournisseur. */
81
+ readonly name: string;
82
+ /** Construit le fournisseur comme pour un login — découverte comprise. */
83
+ readonly build: () => IOAuthProvider | Promise<IOAuthProvider>;
84
+ /** Identifiant du client, tel que configuré. */
85
+ readonly clientId: string;
86
+ /** URL de retour configurée — celle que le fournisseur doit connaître. */
87
+ readonly redirectUri: string;
88
+ /** Méthode d'authentification déclarée ; `"none"` = client public. */
89
+ readonly clientAuthMethod?: OAuth2ClientAuthMethod | undefined;
90
+ /** Portées configurées ; vides = celles du fournisseur. */
91
+ readonly scopes?: readonly string[] | undefined;
92
+ /**
93
+ * Transport de la sonde d'autorisation. Celle du point de jeton passe par le
94
+ * fournisseur, donc par le transport que SA fabrique a reçu.
95
+ */
96
+ readonly fetch?: typeof globalThis.fetch | undefined;
97
+ /** Délai d'attente de la sonde d'autorisation, en millisecondes. */
98
+ readonly timeoutMs?: number | undefined;
99
+ /**
100
+ * Juge la page de connexion que le point d'autorisation sert (ses premiers
101
+ * octets). Rend une sonde de plus, ou `null` quand la page ne s'y prête
102
+ * pas. Le diagnostic reste générique : c'est le kit Keycloak qui sait lire
103
+ * le thème d'une page de Keycloak.
104
+ */
105
+ readonly pageCheck?: ((html: string) => IOAuthCheck | null) | undefined;
106
+ }
107
+ /**
108
+ * Diagnostique un fournisseur OAuth sans connexion humaine.
109
+ *
110
+ * Ne lève jamais : un serveur muet ou hostile devient une sonde en échec, et
111
+ * les sondes que l'échec rend impossibles sont DITES sautées — un silence ne
112
+ * vaut pas quitus.
113
+ *
114
+ * @param input - fournisseur à construire et configuration à confronter.
115
+ * @returns les trois sondes et leur verdict.
116
+ */
117
+ export declare function diagnoseOAuthProvider(input: IOAuthDiagnosisInput): Promise<IOAuthDiagnosis>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Traduction des rôles d'un fournisseur d'identité (Keycloak, tout serveur
3
+ * OpenID Connect) en rôles de l'application.
4
+ *
5
+ * Logique PURE, sans réseau ni base : la même fonction sert la connexion par
6
+ * navigateur (claims du jeton reçu du point de jeton) et l'API (claims d'un
7
+ * jeton dont la signature vient d'être vérifiée). Deux implémentations
8
+ * divergeraient — un rôle retiré dans l'annuaire disparaîtrait d'un côté et
9
+ * survivrait de l'autre.
10
+ */
11
+ /** Où lire les rôles dans les claims du fournisseur. */
12
+ export type ProviderRoleSource = "client" | "realm" | "groups";
13
+ /** Source par défaut : les rôles du CLIENT — ceux que l'annuaire attribue à CETTE application. */
14
+ export declare const DEFAULT_ROLE_SOURCES: readonly ProviderRoleSource[];
15
+ /**
16
+ * Préfixe des rôles de PLATEFORME (console d'administration, plan
17
+ * d'administration) — jamais accordés par un annuaire externe.
18
+ */
19
+ export declare const PLATFORM_ROLE_PREFIX = "ROLE_NODEFONY_";
20
+ /** `true` si le rôle relève de la plateforme (`ROLE_NODEFONY_*`). */
21
+ export declare function isPlatformRole(role: string): boolean;
22
+ /** Table de correspondance compilée d'UN fournisseur. */
23
+ export interface IProviderRoleMapping {
24
+ /** Client dont on lit les rôles (`resource_access.<clientId>.roles`). */
25
+ readonly clientId: string;
26
+ /** Sources lues, dans l'ordre. */
27
+ readonly sources: readonly ProviderRoleSource[];
28
+ /** Rôle du fournisseur → rôle de l'application (objet sans prototype). */
29
+ readonly table: Readonly<Record<string, string>>;
30
+ }
31
+ /** Ce que la configuration d'un fournisseur apporte à la correspondance. */
32
+ export interface IProviderRoleConfig {
33
+ readonly clientId: string;
34
+ readonly roleMapping?: Readonly<Record<string, string>> | undefined;
35
+ readonly rolesSource?: readonly ProviderRoleSource[] | undefined;
36
+ }
37
+ /**
38
+ * Compile la table d'un fournisseur — `null` quand il n'en déclare aucune, ce
39
+ * qui laisse les rôles du compte entièrement locaux (comportement historique).
40
+ *
41
+ * @param config - configuration du fournisseur.
42
+ * @returns la table compilée, ou `null`.
43
+ */
44
+ export declare function compileProviderRoleMapping(config: IProviderRoleConfig): IProviderRoleMapping | null;
45
+ /**
46
+ * Rôles de l'application accordés par le fournisseur, d'après ses claims.
47
+ *
48
+ * Un rôle du fournisseur absent de la table est IGNORÉ — jamais recopié tel
49
+ * quel : l'annuaire ne fabrique pas de rôle que l'application n'a pas déclaré.
50
+ * Plusieurs jeux de claims (jeton d'accès, jeton d'identité) se cumulent : ils
51
+ * viennent du même émetteur, chacun peut porter une partie des rôles selon
52
+ * les « mappers » configurés chez lui.
53
+ *
54
+ * @param mapping - table compilée du fournisseur.
55
+ * @param claimSets - claims à lire ; `null`/`undefined` ignorés.
56
+ * @returns rôles de l'application, triés et sans doublon.
57
+ */
58
+ export declare function mapProviderRoles(mapping: IProviderRoleMapping, ...claimSets: (Readonly<Record<string, unknown>> | null | undefined)[]): string[];
@@ -1,4 +1,4 @@
1
- import type { IOAuthProvider } from "../../../contracts/IOAuthProvider.js";
1
+ import type { ILogoutTokenClaims, IOAuthProvider } from "../../../contracts/IOAuthProvider.js";
2
2
  import type { IOAuthProviderContext } from "../oauthProviderRegistry.js";
3
3
  import { type IAuthorizationRequest, type ITokenRequest, type OAuth2Tokens } from "../oauth2Client.js";
4
4
  import { type IDiscoveryOptions } from "../metadata.js";
@@ -30,7 +30,40 @@ export interface IOidcProviderOptions {
30
30
  readonly decodeIdToken: (idToken: string) => object | Promise<object>;
31
31
  /** Scopes par défaut si la config n'en précise aucun. */
32
32
  readonly defaultScopes?: string[];
33
+ /**
34
+ * Point de déconnexion du fournisseur (`end_session_endpoint`). Absent ou
35
+ * `null` : le fournisseur n'offre pas `createLogoutURL`.
36
+ */
37
+ readonly endSessionEndpoint?: string | null;
38
+ /**
39
+ * Vérifie la SIGNATURE d'un jeton de déconnexion (émetteur, audience,
40
+ * algorithme, `exp`/`iat`) et rend ses claims, ou `null` s'il est refusé ;
41
+ * lève sur une panne. Absente : le fournisseur n'offre pas
42
+ * `verifyLogoutToken`. Le sens du jeton (§2.4) est contrôlé ensuite par
43
+ * {@link readLogoutTokenClaims}, jamais par cette fonction.
44
+ */
45
+ readonly verifyLogoutTokenSignature?: (logoutToken: string) => Promise<{
46
+ issuer: string;
47
+ payload: Record<string, unknown>;
48
+ } | null>;
33
49
  }
50
+ /**
51
+ * Lit ce qu'un jeton de déconnexion DÉSIGNE, une fois sa signature vérifiée —
52
+ * les points 5 à 7 de la validation (OpenID Connect Back-Channel Logout 1.0
53
+ * §2.6) : `sub` ou `sid` présent, `events` qui le déclare jeton de
54
+ * déconnexion, AUCUN `nonce`.
55
+ *
56
+ * @remarks Ces gardes ferment la confusion entre jetons (§4.1) : un ID token
57
+ * du même émetteur, pour le même client, porte une signature tout aussi
58
+ * valide — c'est `events` qui le distingue, et l'absence de `nonce` qui
59
+ * empêche le chemin inverse. Fonction PURE, éprouvable sans clé ni réseau.
60
+ *
61
+ * @param issuer - émetteur canonique rendu par la vérification de signature.
62
+ * @param payload - claims vérifiés.
63
+ * @returns les claims utiles, ou `null` si le jeton n'est pas un jeton de
64
+ * déconnexion valide.
65
+ */
66
+ export declare function readLogoutTokenClaims(issuer: string, payload: Record<string, unknown>): ILogoutTokenClaims | null;
34
67
  /**
35
68
  * Fabrique un {@link IOAuthProvider} **générique OIDC** — couvre TOUT fournisseur
36
69
  * OpenID Connect sans code spécifique : le profil se lit toujours pareil (claims
@@ -0,0 +1,36 @@
1
+ import type { ISecurityConfig } from "../config/config.js";
2
+ /** Une zone que la connexion OAuth ouvre à quiconque obtient un compte. */
3
+ export interface IOpenToSignupZone {
4
+ /** Nom de la zone (`areas.<nom>`). */
5
+ zone: string;
6
+ /** Fournisseurs OAuth qui créent les comptes à la volée, triés. */
7
+ providers: readonly string[];
8
+ /** Le constat, en une phrase — la MÊME au démarrage, dans la console et dans `doctor`. */
9
+ message: string;
10
+ /** Les deux gestes qui le soldent, prêts à taper. */
11
+ action: string;
12
+ }
13
+ /**
14
+ * Les zones protégées qui n'exigent aucun rôle alors que la connexion OAuth
15
+ * crée les comptes à la volée — c'est-à-dire ouvertes à tout compte que le
16
+ * fournisseur délivre.
17
+ *
18
+ * Une zone sans `roles` laisse passer toute identité authentifiée : c'est un
19
+ * défaut voulu. Il devient un piège quand `oauth2.allowSignup` vaut `true`
20
+ * (le défaut) : avec Google ou GitHub, « authentifié » veut dire « n'importe
21
+ * qui sur Internet ». Une zone est donc retenue quand TOUT ceci est vrai :
22
+ * protégée, sans `anonymous`, sans `roles`, avec `session` (c'est par la
23
+ * session qu'un compte OAuth entre), et `oauth2` actif avec au moins un
24
+ * fournisseur et `allowSignup`.
25
+ *
26
+ * Le remède est une DÉCLARATION : `roles: ["ROLE_USER"]` ne change rien au
27
+ * comportement (un compte créé reçoit ce rôle) mais écrit l'intention.
28
+ *
29
+ * Calculée au démarrage et dans `describe()`, jamais par requête. C'est la
30
+ * SEULE implémentation de la règle : `doctor` lit son résultat par le plan
31
+ * d'administration, il ne la recalcule pas.
32
+ *
33
+ * @param config - la configuration de sécurité validée.
34
+ * @returns les zones concernées, dans l'ordre de déclaration ; vide sinon.
35
+ */
36
+ export declare function findZonesOpenToSignup(config: Pick<ISecurityConfig, "areas" | "oauth2">): IOpenToSignupZone[];
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Facteurs d'authentification de Nodefony → méthodes RFC 8176, posées dans la
3
+ * session à son ouverture. Les clés et leur lecteur sont au cœur
4
+ * (`readSessionAuthentication`, `nodefony`) ; seule la POLITIQUE de
5
+ * correspondance vit ici.
6
+ */
7
+ /**
8
+ * Valeurs RFC 8176 d'un facteur nommé par un chemin d'authentification.
9
+ *
10
+ * @param factor - le facteur tel que l'audit le nomme (`password`, `totp`,
11
+ * `recovery`, `webauthn`, `oauth`…)
12
+ * @returns les méthodes vérifiées localement ; vide pour un facteur délégué
13
+ */
14
+ export declare function amrForFactor(factor: string): readonly string[];
@@ -1,4 +1,4 @@
1
- import type { JSONWebKeySet } from "jose";
1
+ import type { JSONWebKeySet, JWK } from "jose";
2
2
  import type { IJwtKeystore, IJwtSigningKey } from "../../contracts/IJwtKeystore.js";
3
3
  /** Journalisation injectée (le keystore n'est pas un Service — il reçoit un log). */
4
4
  type LogFn = (message: string, severity: string) => void;
@@ -29,6 +29,43 @@ interface KeystoreSource {
29
29
  * @returns le message à journaliser, ou `null` quand le dossier est nettoyé.
30
30
  */
31
31
  export declare function keystoreLeaksIntoImage(dir: string): string | null;
32
+ /** Clé telle que persistée : JWK privé (avec `d`) + métadonnées. */
33
+ export interface IStoredKey extends JWK {
34
+ kid: string;
35
+ alg: string;
36
+ use?: string;
37
+ createdAt?: number;
38
+ }
39
+ /** Forme du fichier `keyset.json` / de la variable d'env `keySetJson`. */
40
+ export interface IStoredKeySet {
41
+ /** `kid` de la clé qui signe (les autres ne servent qu'à vérifier). */
42
+ active: string;
43
+ keys: IStoredKey[];
44
+ }
45
+ /**
46
+ * Lit un jeu de clés sérialisé et en vérifie la FORME — la seule lecture du
47
+ * dépôt, appelée par le keystore ET par la validation de configuration.
48
+ *
49
+ * Ne vérifie que la structure (`keys` non vide, un `kid` par clé) : importer
50
+ * les clés exige `jose`, chargé paresseusement au premier jeton. Les messages
51
+ * ne recopient JAMAIS la valeur reçue — elle porte une clé privée.
52
+ *
53
+ * @param json - le jeu de clés, tel que `NF_JWT_KEYSET` ou `keyset.json` le porte.
54
+ * @returns le jeu de clés lu.
55
+ * @throws Error si le JSON est illisible ou la structure incomplète.
56
+ */
57
+ export declare function parseKeySet(json: string): IStoredKeySet;
58
+ /**
59
+ * Génère un jeu de clés de signature à partager entre tous les process d'une
60
+ * application — la valeur de `NF_JWT_KEYSET`.
61
+ *
62
+ * Même générateur que celui du keystore (une seule implémentation), sérialisé
63
+ * en JSON COMPACT : une ligne, sans quote simple, qu'un `.env` porte entre
64
+ * quotes simples et qu'un gestionnaire de secrets stocke tel quel.
65
+ *
66
+ * @returns le jeu de clés sérialisé — il contient la clé PRIVÉE.
67
+ */
68
+ export declare function generateKeySet(): Promise<string>;
32
69
  /**
33
70
  * Keystore Ed25519 — implémentation de référence d'{@link IJwtKeystore}.
34
71
  *
@@ -49,10 +86,15 @@ export declare function keystoreLeaksIntoImage(dir: string): string | null;
49
86
  * usage ; le boot ne paie rien si le JWT n'est jamais sollicité. Le chargement
50
87
  * est mémoïsé (une seule résolution concurrente).
51
88
  *
52
- * @remarks Race au 1ᵉʳ boot d'un **cluster** sans clé pré-provisionnée : deux
53
- * workers peuvent générer puis écrire des clés différentes (le dernier `rename`
54
- * gagne). En prod, provisionner la clé hors-bande (`keySetJson`/SecretProvider
55
- * P16) élimine ce cas — c'est précisément la source recommandée.
89
+ * @remarks Plusieurs process qui servent la MÊME application doivent signer
90
+ * avec la même clé, sinon un jeton signé par l'un est refusé par l'autre :
91
+ * - **pods** (aucun disque partagé) → source `env` obligatoire, la même valeur
92
+ * injectée partout (`NF_JWT_KEYSET`, générée par `security:secrets`) ;
93
+ * - **workers de `nodefony cluster`** → la source `env` est héritée au fork ; à
94
+ * défaut, la source `fichier` est sûre : la création de `keyset.json` est
95
+ * EXCLUSIVE ({@link createSecretExclusive}), un seul worker la génère et les
96
+ * autres relisent la sienne ;
97
+ * - source `mémoire` → une clé par process, jamais cohérente.
56
98
  */
57
99
  export declare class JwtKeystore implements IJwtKeystore {
58
100
  #private;
@@ -21,6 +21,13 @@ export interface ITrustedIssuer {
21
21
  * découvert.
22
22
  */
23
23
  jwksUri?: string | undefined;
24
+ /**
25
+ * Adresse où lire les métadonnées quand elle diffère de l'émetteur (service
26
+ * interne d'un cluster). Le document doit toujours se déclarer de `issuer` ;
27
+ * le `jwks_uri` peut alors être en `http` sur l'origine exacte de cette
28
+ * adresse (`isBackChannelTransportAllowed`, cœur).
29
+ */
30
+ discoveryUrl?: string | undefined;
24
31
  /**
25
32
  * Jeu de clés fourni LOCALEMENT — aucune requête, aucune découverte.
26
33
  *
@@ -123,8 +130,8 @@ export declare class RemoteJwtVerifier {
123
130
  #private;
124
131
  /**
125
132
  * @param options - émetteurs de confiance et réglages réseau
126
- * @throws Error si un émetteur est invalide, dupliqué, ou déclare un
127
- * algorithme à secret partagé
133
+ * @throws Error si un émetteur est invalide, dupliqué, déclare un
134
+ * algorithme à secret partagé ou une `discoveryUrl` invalide
128
135
  */
129
136
  constructor(options: IRemoteJwtVerifierOptions);
130
137
  /** Nombre d'émetteurs de confiance — pour l'introspection et les journaux. */
@@ -145,5 +152,25 @@ export declare class RemoteJwtVerifier {
145
152
  * « jeton invalide »
146
153
  */
147
154
  verify(token: string, audience: string): Promise<IAccessPrincipal | null>;
155
+ /**
156
+ * Vérifie un jeton et rend ses claims BRUTS — pour un jeton qui n'est pas un
157
+ * jeton d'accès (jeton de déconnexion OpenID Connect, par exemple), dont le
158
+ * sens se lit dans des claims que {@link verify} ne remonte pas.
159
+ *
160
+ * Mêmes garanties et même contrat d'erreur que {@link verify} : signature,
161
+ * émetteur de la liste fermée, algorithme imposé, audience obligatoire,
162
+ * claims exigés par l'émetteur ; un refus est un `null`, une panne lève.
163
+ *
164
+ * @param token - le jeton brut, tel que présenté
165
+ * @param audience - audience que le jeton DOIT porter dans `aud`
166
+ * @returns l'émetteur canonique et les claims vérifiés, ou `null` si le jeton
167
+ * est refusé
168
+ * @throws Error si l'émetteur ne peut pas être joint ou publie un jeu de clés
169
+ * inutilisable
170
+ */
171
+ verifyClaims(token: string, audience: string): Promise<{
172
+ issuer: string;
173
+ payload: Jose.JWTPayload;
174
+ } | null>;
148
175
  }
149
176
  export default RemoteJwtVerifier;
@@ -66,5 +66,27 @@ export declare function messageNonRestreint(file: string, mode: number): string;
66
66
  * un fichier EXISTANT conserve, sur certains systèmes, le mode de la cible.
67
67
  */
68
68
  export declare function writeSecret(file: string, content: string): Promise<void>;
69
+ /**
70
+ * Crée un secret SEULEMENT s'il n'existe pas encore : 0600, atomique, et un
71
+ * seul gagnant quand plusieurs process le tentent en même temps.
72
+ *
73
+ * ⭐ **Pourquoi pas {@link writeSecret}.** `rename` REMPLACE la cible : deux
74
+ * workers de `nodefony cluster` qui génèrent chacun une clé au premier boot
75
+ * l'écrivent tous les deux, le dernier gagne sur le disque — et l'autre garde
76
+ * en mémoire une clé que plus personne ne connaît. Ici, le temporaire est
77
+ * publié par un LIEN DUR, que le système refuse (`EEXIST`) si le nom existe :
78
+ * le perdant le sait, et relit la clé du gagnant. Le lecteur ne voit jamais un
79
+ * fichier à demi écrit, puisque le lien n'apparaît qu'une fois le contenu complet.
80
+ *
81
+ * ⚠️ Un système de fichiers sans liens durs (FAT, exFAT) retombe sur une
82
+ * création exclusive directe (`wx`) : toujours un seul gagnant, mais un lecteur
83
+ * concurrent peut lire le fichier pendant son écriture — il lève alors, au lieu
84
+ * de signer avec une clé divergente.
85
+ *
86
+ * @param file - chemin du secret.
87
+ * @param content - contenu à écrire.
88
+ * @returns `true` si ce process l'a créé, `false` s'il existait déjà.
89
+ */
90
+ export declare function createSecretExclusive(file: string, content: string): Promise<boolean>;
69
91
  /** Forme synchrone de {@link writeSecret}. */
70
92
  export declare function writeSecretSync(file: string, content: string): void;
@@ -0,0 +1,47 @@
1
+ /** Vue minimale du contexte : seul le scheme EFFECTIF compte. */
2
+ export interface ICredentialTransportContext {
3
+ /**
4
+ * Scheme vu par le client — `https` derrière un proxy de confiance qui
5
+ * termine TLS (`trustProxy` + `X-Forwarded-Proto`), jamais le transport brut.
6
+ */
7
+ readonly scheme?: string;
8
+ }
9
+ /** Réglages de la politique, résolus UNE fois au boot du firewall. */
10
+ export interface ICredentialTransportPolicyOptions {
11
+ /** `true` = un secret reçu hors TLS est refusé (production, sans échappement). */
12
+ readonly enforce: boolean;
13
+ /** Journal du premier refus (WARNING) — absent = silencieux. */
14
+ readonly log?: (message: string) => void;
15
+ }
16
+ /**
17
+ * Dit si un secret de connexion se refuse : en production, sans échappement
18
+ * explicite. Seule source de la décision — le firewall la compose au boot.
19
+ *
20
+ * @param environment - environnement normalisé du kernel.
21
+ * @param allowInsecureCredentials - échappement `security.allowInsecureCredentials`.
22
+ */
23
+ export declare function shouldEnforceCredentialTransport(environment: string | undefined, allowInsecureCredentials: boolean): boolean;
24
+ /**
25
+ * Refuse, avant toute vérification, un mot de passe ou un code de second
26
+ * facteur reçu en clair. Une instance partagée au conteneur (`credentialTransport`)
27
+ * sert TOUTES les portes qui reçoivent un secret : formulaire de session, second
28
+ * facteur, Basic, émission de jeton — une porte oubliée serait le canal clair.
29
+ *
30
+ * La décision se prend sur le scheme EFFECTIF (`context.scheme`) : derrière un
31
+ * proxy qui termine TLS et déclaré de confiance, il vaut `https` alors que le
32
+ * pod écoute en HTTP. C'est le cas de la plupart des déploiements ; c'est donc
33
+ * `trustProxy` que nomme le journal, pas « passez en HTTPS ».
34
+ */
35
+ export declare class CredentialTransportPolicy {
36
+ #private;
37
+ readonly enforce: boolean;
38
+ constructor(options: ICredentialTransportPolicyOptions);
39
+ /**
40
+ * Laisse passer un secret reçu sur TLS, ou n'importe lequel hors production.
41
+ *
42
+ * @param context - contexte de la requête qui porte le secret.
43
+ * @param door - porte qui le reçoit, nommée dans le journal.
44
+ * @throws InsecureTransportError (403) — secret reçu en clair en production.
45
+ */
46
+ assert(context: ICredentialTransportContext, door: string): void;
47
+ }