@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.
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/index.js +44 -10
- package/dist/nodefony/command/security-oauth-doctor.js +66 -0
- package/dist/nodefony/command/security-secrets.js +42 -25
- package/dist/nodefony/command/security-token.js +46 -15
- package/dist/nodefony/command/security-user-delete.js +5 -4
- package/dist/nodefony/command/security-user-password.js +4 -11
- package/dist/nodefony/config/config.js +80 -5
- package/dist/nodefony/contracts/index.js +1 -2
- package/dist/nodefony/errors/InsecureTransportError.js +24 -0
- package/dist/nodefony/errors/index.js +2 -1
- package/dist/nodefony/keycloak/keycloakAdmin.js +255 -0
- package/dist/nodefony/keycloak/keycloakRealm.js +260 -0
- package/dist/nodefony/keycloak/keycloakRealmInput.js +124 -0
- package/dist/nodefony/keycloak/scaffold.js +231 -0
- package/dist/nodefony/keycloak/security-keycloak-realm.js +128 -0
- package/dist/nodefony/scaffold/contribute.js +20 -0
- package/dist/nodefony/service/authFlow.js +111 -11
- package/dist/nodefony/service/firewall.js +111 -21
- package/dist/nodefony/service/keycloak.js +209 -0
- package/dist/nodefony/service/oauth2.js +379 -26
- package/dist/nodefony/service/tokenService.js +57 -6
- package/dist/nodefony/service/totp.js +51 -6
- package/dist/nodefony/service/webAuthn.js +20 -2
- package/dist/nodefony/src/SecuredArea.js +14 -3
- package/dist/nodefony/src/admin/SecurityAdminApi.js +70 -40
- package/dist/nodefony/src/admin/adminAudit.js +0 -1
- package/dist/nodefony/src/admin/userRevocationCascade.js +79 -15
- package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +66 -6
- package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +38 -15
- package/dist/nodefony/src/authenticator/JwtAuthenticator.js +1 -0
- package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +13 -3
- package/dist/nodefony/src/authenticator/authenticatorRegistry.js +9 -2
- package/dist/nodefony/src/authenticator/bearer.js +1 -2
- package/dist/nodefony/src/boot/publicRoutesNotice.js +27 -0
- package/dist/nodefony/src/entryPoint.js +39 -0
- package/dist/nodefony/src/oauth/clientCredentials.js +40 -0
- package/dist/nodefony/src/oauth/metadata.js +36 -9
- package/dist/nodefony/src/oauth/oauth2Client.js +60 -33
- package/dist/nodefony/src/oauth/oauthProviderRegistry.js +39 -6
- package/dist/nodefony/src/oauth/providerDiagnosis.js +283 -0
- package/dist/nodefony/src/oauth/providerRoles.js +79 -0
- package/dist/nodefony/src/oauth/providers/github.js +32 -6
- package/dist/nodefony/src/oauth/providers/oidc.js +82 -3
- package/dist/nodefony/src/openToSignup.js +47 -0
- package/dist/nodefony/src/sessionAuthentication.js +26 -0
- package/dist/nodefony/src/token/JwtKeystore.js +78 -46
- package/dist/nodefony/src/token/RemoteJwtVerifier.js +40 -13
- package/dist/nodefony/src/token/secretFile.js +59 -2
- package/dist/nodefony/src/transport/CredentialTransportPolicy.js +51 -0
- package/dist/types/index.d.ts +26 -6
- package/dist/types/nodefony/command/security-oauth-doctor.d.ts +23 -0
- package/dist/types/nodefony/command/security-secrets.d.ts +2 -0
- package/dist/types/nodefony/command/security-token.d.ts +9 -0
- package/dist/types/nodefony/command/security-user-delete.d.ts +4 -3
- package/dist/types/nodefony/command/security-user-password.d.ts +4 -4
- package/dist/types/nodefony/config/config.d.ts +63 -1
- package/dist/types/nodefony/contracts/IAuthenticator.d.ts +1 -1
- package/dist/types/nodefony/contracts/IFirewallDescription.d.ts +42 -1
- package/dist/types/nodefony/contracts/IOAuthProvider.d.ts +48 -0
- package/dist/types/nodefony/contracts/ISecuredArea.d.ts +22 -0
- package/dist/types/nodefony/contracts/ITotpSecret.d.ts +4 -1
- package/dist/types/nodefony/contracts/ITotpSecretStore.d.ts +7 -1
- package/dist/types/nodefony/contracts/IWebAuthnCredential.d.ts +4 -1
- package/dist/types/nodefony/contracts/IWebAuthnCredentialStore.d.ts +6 -0
- package/dist/types/nodefony/errors/InsecureTransportError.d.ts +20 -0
- package/dist/types/nodefony/errors/index.d.ts +1 -0
- package/dist/types/nodefony/keycloak/keycloakAdmin.d.ts +154 -0
- package/dist/types/nodefony/keycloak/keycloakRealm.d.ts +138 -0
- package/dist/types/nodefony/keycloak/keycloakRealmInput.d.ts +92 -0
- package/dist/types/nodefony/keycloak/scaffold.d.ts +98 -0
- package/dist/types/nodefony/keycloak/security-keycloak-realm.d.ts +43 -0
- package/dist/types/nodefony/scaffold/contribute.d.ts +15 -0
- package/dist/types/nodefony/service/authFlow.d.ts +35 -3
- package/dist/types/nodefony/service/firewall.d.ts +17 -3
- package/dist/types/nodefony/service/keycloak.d.ts +115 -0
- package/dist/types/nodefony/service/oauth2.d.ts +125 -9
- package/dist/types/nodefony/service/tokenService.d.ts +7 -1
- package/dist/types/nodefony/service/totp.d.ts +28 -4
- package/dist/types/nodefony/service/webAuthn.d.ts +23 -1
- package/dist/types/nodefony/src/SecuredArea.d.ts +4 -0
- package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +54 -12
- package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +18 -0
- package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +13 -0
- package/dist/types/nodefony/src/authenticator/UserPasswordAuthenticator.d.ts +11 -3
- package/dist/types/nodefony/src/boot/publicRoutesNotice.d.ts +16 -0
- package/dist/types/nodefony/src/entryPoint.d.ts +37 -0
- package/dist/types/nodefony/src/oauth/clientCredentials.d.ts +54 -0
- package/dist/types/nodefony/src/oauth/metadata.d.ts +24 -1
- package/dist/types/nodefony/src/oauth/oauth2Client.d.ts +50 -1
- package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +46 -2
- package/dist/types/nodefony/src/oauth/providerDiagnosis.d.ts +117 -0
- package/dist/types/nodefony/src/oauth/providerRoles.d.ts +58 -0
- package/dist/types/nodefony/src/oauth/providers/oidc.d.ts +34 -1
- package/dist/types/nodefony/src/openToSignup.d.ts +36 -0
- package/dist/types/nodefony/src/sessionAuthentication.d.ts +14 -0
- package/dist/types/nodefony/src/token/JwtKeystore.d.ts +47 -5
- package/dist/types/nodefony/src/token/RemoteJwtVerifier.d.ts +29 -2
- package/dist/types/nodefony/src/token/secretFile.d.ts +22 -0
- package/dist/types/nodefony/src/transport/CredentialTransportPolicy.d.ts +47 -0
- package/docs/api-keys.md +17 -17
- package/docs/audit.md +16 -16
- package/docs/authenticators.md +60 -27
- package/docs/authorization.md +16 -16
- package/docs/cors.md +29 -29
- package/docs/csrf.md +35 -33
- package/docs/external-jwt.md +66 -4
- package/docs/firewall.md +57 -24
- package/docs/headers.md +30 -30
- package/docs/index.md +12 -4
- package/docs/keycloak.md +818 -0
- package/docs/login-page.md +361 -0
- package/docs/oauth2.md +236 -122
- package/docs/obtenir-un-jeton.md +18 -11
- package/docs/tokens.md +136 -85
- package/docs/totp.md +105 -64
- package/docs/webauthn.md +90 -87
- package/docs/webhooks.md +3 -3
- package/keycloak/themes/nodefony/account/resources/css/nodefony.css +66 -0
- package/keycloak/themes/nodefony/account/resources/img/favicon.ico +0 -0
- package/keycloak/themes/nodefony/account/resources/img/logo.png +0 -0
- package/keycloak/themes/nodefony/account/theme.properties +14 -0
- package/keycloak/themes/nodefony/admin/resources/css/nodefony.css +66 -0
- package/keycloak/themes/nodefony/admin/resources/img/favicon.ico +0 -0
- package/keycloak/themes/nodefony/admin/resources/img/logo.png +0 -0
- package/keycloak/themes/nodefony/admin/theme.properties +12 -0
- package/keycloak/themes/nodefony/email/html/template.ftl +73 -0
- package/keycloak/themes/nodefony/email/messages/messages_en.properties +2 -0
- package/keycloak/themes/nodefony/email/messages/messages_fr.properties +3 -0
- package/keycloak/themes/nodefony/email/resources/img/logo.png +0 -0
- package/keycloak/themes/nodefony/email/theme.properties +8 -0
- package/keycloak/themes/nodefony/login/messages/messages_en.properties +22 -0
- package/keycloak/themes/nodefony/login/messages/messages_fr.properties +25 -0
- package/keycloak/themes/nodefony/login/resources/css/login.css +1141 -0
- package/keycloak/themes/nodefony/login/resources/css/nodefony.css +688 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/blueprint.css +70 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/console.css +57 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/dots.css +67 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/enterprise.css +84 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/horizon.css +158 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/ledger.css +224 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/minimal.css +63 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/photo-card.css +72 -0
- package/keycloak/themes/nodefony/login/resources/img/favicon.ico +0 -0
- package/keycloak/themes/nodefony/login/resources/img/logo.png +0 -0
- package/keycloak/themes/nodefony/login/template.ftl +423 -0
- package/keycloak/themes/nodefony/login/theme.properties +17 -0
- package/keycloak/themes/nodefony/welcome/resources/css/nodefony.css +32 -0
- package/keycloak/themes/nodefony/welcome/resources/img/logo.png +0 -0
- package/keycloak/themes/nodefony/welcome/theme.properties +11 -0
- package/keycloak/themes/nodefony-blueprint/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-console/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-dots/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-enterprise/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-horizon/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-ledger/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-minimal/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-photo-card/login/theme.properties +12 -0
- package/package.json +17 -15
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import { Service, Module } from "nodefony";
|
|
2
|
+
import { type IOAuthDiagnosis } from "../src/oauth/providerDiagnosis.js";
|
|
3
|
+
/**
|
|
4
|
+
* Ce que la session retient d'un login fédéré pour pouvoir fermer AUSSI la
|
|
5
|
+
* session du fournisseur (RP-Initiated Logout). Stocké côté serveur ; l'ID
|
|
6
|
+
* token ne traverse le navigateur qu'une fois, dans l'adresse de déconnexion
|
|
7
|
+
* (`id_token_hint`), la session locale déjà détruite.
|
|
8
|
+
*
|
|
9
|
+
* @remarks L'ID token est une exception assumée à « les jetons du fournisseur
|
|
10
|
+
* ne sont pas conservés » : il n'ouvre aucun accès (ni API, ni rafraîchissement),
|
|
11
|
+
* il désigne la session à fermer. Jetons d'accès et de rafraîchissement restent
|
|
12
|
+
* jetés.
|
|
13
|
+
*/
|
|
14
|
+
export interface IFederatedLogoutHint {
|
|
15
|
+
/** Fournisseur qui a ouvert la session. */
|
|
16
|
+
readonly provider: string;
|
|
17
|
+
/** ID token reçu au login, rejoué en `id_token_hint`. */
|
|
18
|
+
readonly idToken: string;
|
|
19
|
+
/** Identifiant de session du fournisseur (claim `sid`), ou `null`. */
|
|
20
|
+
readonly sid: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** Vue minimale d'une session — celle que porte le contexte. */
|
|
23
|
+
interface IHintSession {
|
|
24
|
+
get(key: string): unknown;
|
|
25
|
+
set(key: string, value: unknown): unknown;
|
|
26
|
+
/** Métadonnée énumérable — absente d'une session réduite (tests). */
|
|
27
|
+
setMetaBag?(key: string, value: unknown): unknown;
|
|
28
|
+
}
|
|
2
29
|
/**
|
|
3
30
|
* Libellé affichable d'un fournisseur, quand sa configuration n'en donne pas.
|
|
4
31
|
*
|
|
@@ -20,6 +47,8 @@ export interface IOAuthDisplayProvider {
|
|
|
20
47
|
readonly name: string;
|
|
21
48
|
/** Libellé du bouton : celui de la config, sinon dérivé du nom. */
|
|
22
49
|
readonly label: string;
|
|
50
|
+
/** Image du bouton (`icon` de la config) — absente = icône du framework. */
|
|
51
|
+
readonly icon?: string;
|
|
23
52
|
}
|
|
24
53
|
/** Données à porter en session entre `authorize` et `callback` (anti-replay). */
|
|
25
54
|
export interface IOAuthAuthorization {
|
|
@@ -30,20 +59,40 @@ export interface IOAuthAuthorization {
|
|
|
30
59
|
/** `code_verifier` PKCE à stocker en session, ou `null` (fournisseur sans PKCE). */
|
|
31
60
|
readonly codeVerifier: string | null;
|
|
32
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Confronte l'émetteur configuré d'un fournisseur à ce que sa fabrique exige.
|
|
64
|
+
*
|
|
65
|
+
* Une URL qui ne peut pas être un émetteur est refusée QUEL QUE SOIT le
|
|
66
|
+
* fournisseur : aucune valeur mal formée n'est une configuration voulue. Une
|
|
67
|
+
* absence ne l'est que si la fabrique a déclaré l'émetteur requis.
|
|
68
|
+
*
|
|
69
|
+
* @param name - nom du fournisseur (`oauth2.providers.<name>`).
|
|
70
|
+
* @param issuer - émetteur lu dans sa configuration.
|
|
71
|
+
* @throws BootConfigurationError - émetteur requis absent, ou mal formé.
|
|
72
|
+
*/
|
|
73
|
+
export declare function checkProviderIssuer(name: string, issuer: string | undefined): void;
|
|
33
74
|
/**
|
|
34
75
|
* **Social login OAuth 2.0** (P6 J9) — orchestrateur du flux *Authorization Code*.
|
|
35
76
|
*
|
|
36
77
|
* Posture OAuth 2.1 (RFC 9700) : Authorization Code uniquement (jamais implicit /
|
|
37
78
|
* ROPC), **PKCE S256** quand le fournisseur le supporte (RFC 7636), **state**
|
|
38
|
-
* anti-CSRF, **iss** anti-mix-up (RFC 9207) ; aucun jeton n'atteint le
|
|
39
|
-
* (le login produit une **session BFF**, gérée hors de ce service par
|
|
40
|
-
* controller + `AuthFlow`)
|
|
79
|
+
* anti-CSRF, **iss** anti-mix-up (RFC 9207) ; aucun jeton d'accès n'atteint le
|
|
80
|
+
* navigateur (le login produit une **session BFF**, gérée hors de ce service par
|
|
81
|
+
* le controller + `AuthFlow`) — seul l'ID token y passe, une fois, à la
|
|
82
|
+
* déconnexion ({@link IFederatedLogoutHint}).
|
|
41
83
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
84
|
+
* Au boot (si `oauth2.enabled`), la configuration de chaque fournisseur est
|
|
85
|
+
* confrontée à ce que sa fabrique exige : un émetteur requis absent, ou qui n'est
|
|
86
|
+
* pas une URL d'émetteur valide, INTERROMPT le démarrage en nommant la clé — une
|
|
87
|
+
* faute d'écriture ne doit pas attendre le premier clic pour se montrer. Un nom
|
|
88
|
+
* inconnu du registre reste un WARNING.
|
|
89
|
+
*
|
|
90
|
+
* Les fournisseurs sont construits une fois puis mémoïsés : c'est là que les
|
|
91
|
+
* points d'entrée d'un émetteur OIDC sont découverts. Un run qui sert (ports
|
|
92
|
+
* ouverts) les construit DÈS le boot, sans l'attendre : un émetteur injoignable
|
|
93
|
+
* n'est pas une faute de configuration, il se journalise et son bouton quitte
|
|
94
|
+
* l'écran de connexion jusqu'à ce qu'une nouvelle tentative réussisse. Un run
|
|
95
|
+
* console ne touche jamais le réseau.
|
|
47
96
|
*
|
|
48
97
|
* Le service ne touche **ni HTTP ni session** : il rend à l'appelant les éléments
|
|
49
98
|
* (URL, state, verifier) que le controller persiste en session — testable sans
|
|
@@ -78,6 +127,9 @@ declare class OAuth2Service extends Service {
|
|
|
78
127
|
/**
|
|
79
128
|
* Redirections post-login (succès / échec) — lues par le controller.
|
|
80
129
|
* Surcharge PAR FOURNISSEUR si fournie, sinon valeur globale, sinon défaut.
|
|
130
|
+
*
|
|
131
|
+
* @remarks L'échec mène par défaut à la page de connexion (`loginPage.path`) :
|
|
132
|
+
* déplacer la page déplace la redirection, les deux ne peuvent pas diverger.
|
|
81
133
|
*/
|
|
82
134
|
getRedirects(provider?: string): {
|
|
83
135
|
success: string;
|
|
@@ -87,9 +139,13 @@ declare class OAuth2Service extends Service {
|
|
|
87
139
|
* Étape 1 — prépare l'URL d'autorisation + les éléments anti-replay à stocker
|
|
88
140
|
* en session (`state`, et `code_verifier` si PKCE).
|
|
89
141
|
*
|
|
142
|
+
* @param options.theme - thème rendu par l'application, transmis au seul
|
|
143
|
+
* fournisseur qui sait l'afficher (Keycloak) ; les autres l'ignorent
|
|
90
144
|
* @throws AuthenticationError — fournisseur non configuré / inconnu du registre.
|
|
91
145
|
*/
|
|
92
|
-
createAuthorization(provider: string
|
|
146
|
+
createAuthorization(provider: string, options?: {
|
|
147
|
+
theme?: "light" | "dark";
|
|
148
|
+
}): Promise<IOAuthAuthorization>;
|
|
93
149
|
/**
|
|
94
150
|
* Étape 2 — valide la réponse, échange le `code`, lit le profil et provisionne
|
|
95
151
|
* l'utilisateur local (Shadow User). Retourne l'identifiant à ouvrir en session.
|
|
@@ -100,7 +156,67 @@ declare class OAuth2Service extends Service {
|
|
|
100
156
|
*/
|
|
101
157
|
exchangeAndProvision(provider: string, code: string, codeVerifier: string | null, returnedIss: string | null): Promise<{
|
|
102
158
|
identifier: string;
|
|
159
|
+
logoutHint: IFederatedLogoutHint | null;
|
|
103
160
|
}>;
|
|
161
|
+
/**
|
|
162
|
+
* Retient en session l'indice de déconnexion d'un login fédéré — à appeler
|
|
163
|
+
* APRÈS l'ouverture de la session authentifiée (l'ID de session est
|
|
164
|
+
* régénéré à l'ouverture).
|
|
165
|
+
*
|
|
166
|
+
* @param session - session authentifiée de la requête.
|
|
167
|
+
* @param hint - indice rendu par {@link exchangeAndProvision}, ou `null`.
|
|
168
|
+
*/
|
|
169
|
+
rememberLogout(session: IHintSession, hint: IFederatedLogoutHint | null): void;
|
|
170
|
+
/**
|
|
171
|
+
* Adresse de déconnexion chez le fournisseur qui a ouvert la session, ou
|
|
172
|
+
* `null` : session locale, fournisseur sans point de déconnexion, ou
|
|
173
|
+
* fournisseur devenu indisponible. À lire AVANT de détruire la session.
|
|
174
|
+
*
|
|
175
|
+
* @param session - session courante, ou `null`.
|
|
176
|
+
* @returns l'URL vers laquelle envoyer le navigateur, ou `null`.
|
|
177
|
+
*/
|
|
178
|
+
logoutUrlFor(session: IHintSession | null): Promise<string | null>;
|
|
179
|
+
/**
|
|
180
|
+
* Déconnexion demandée PAR le fournisseur sur le canal arrière (OpenID
|
|
181
|
+
* Connect Back-Channel Logout 1.0) : vérifie le jeton, refuse un rejeu, puis
|
|
182
|
+
* détruit les sessions locales qu'il désigne.
|
|
183
|
+
*
|
|
184
|
+
* Une session est désignée si elle a été ouverte par CE fournisseur (index
|
|
185
|
+
* fédéré posé au login par {@link rememberLogout}) et, selon le jeton : porte son `sid` ; ou,
|
|
186
|
+
* sans `sid`, appartient à l'utilisateur lié à son `sub`. Une session ouverte
|
|
187
|
+
* par mot de passe ou par un autre fournisseur n'est jamais touchée — le
|
|
188
|
+
* fournisseur ne ferme que ce qu'il a ouvert.
|
|
189
|
+
*
|
|
190
|
+
* @remarks Les connexions WebSocket portées par une session détruite se
|
|
191
|
+
* ferment au tick de revalidation du hub temps réel (code `4001`).
|
|
192
|
+
*
|
|
193
|
+
* @param provider - fournisseur désigné par l'adresse appelée.
|
|
194
|
+
* @param logoutToken - paramètre `logout_token` reçu, brut.
|
|
195
|
+
* @returns `unsupported` si le fournisseur ne sait pas émettre de jeton de
|
|
196
|
+
* déconnexion ; `refused` si le jeton est invalide ou rejoué ; sinon le
|
|
197
|
+
* nombre de sessions détruites (0 : déjà déconnecté, c'est un succès §2.7).
|
|
198
|
+
* @throws Error - vérification impossible (jeu de clés injoignable) ou
|
|
199
|
+
* sessions impossibles à parcourir : la déconnexion a ÉCHOUÉ.
|
|
200
|
+
*/
|
|
201
|
+
backchannelLogout(provider: string, logoutToken: string): Promise<{
|
|
202
|
+
readonly outcome: "unsupported" | "refused";
|
|
203
|
+
} | {
|
|
204
|
+
readonly outcome: "done";
|
|
205
|
+
readonly destroyed: number;
|
|
206
|
+
}>;
|
|
207
|
+
/**
|
|
208
|
+
* Diagnostique les fournisseurs CONFIGURÉS sans connexion humaine : découverte
|
|
209
|
+
* et émetteur, URL de retour acceptée, secret accepté (cf
|
|
210
|
+
* `diagnoseOAuthProvider`).
|
|
211
|
+
*
|
|
212
|
+
* Le fournisseur est construit à NEUF, hors de la mémoïsation du login : un
|
|
213
|
+
* diagnostic doit voir le serveur tel qu'il est maintenant, et son échec ne
|
|
214
|
+
* doit pas retirer un bouton de l'écran de connexion.
|
|
215
|
+
*
|
|
216
|
+
* @param name - un seul fournisseur ; à défaut, tous ceux de la configuration.
|
|
217
|
+
* @returns un diagnostic par fournisseur ; vide si le social login est éteint.
|
|
218
|
+
*/
|
|
219
|
+
diagnose(name?: string): Promise<IOAuthDiagnosis[]>;
|
|
104
220
|
}
|
|
105
221
|
export default OAuth2Service;
|
|
106
222
|
export { OAuth2Service };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Service, Module } from "nodefony";
|
|
2
2
|
import type * as Jose from "jose";
|
|
3
3
|
import type { IUser } from "@nodefony/user";
|
|
4
|
+
import type { ICredentialTransportContext } from "../src/transport/CredentialTransportPolicy.js";
|
|
4
5
|
/**
|
|
5
6
|
* Réponse d'émission de jetons — nommage RFC 6749 §5.1 (snake_case, JSON).
|
|
6
7
|
* Le JWT part en `Authorization: Bearer`, JAMAIS en cookie ni en URL.
|
|
@@ -76,10 +77,15 @@ declare class TokenService extends Service {
|
|
|
76
77
|
* Émet un couple access/refresh après vérification d'un credential
|
|
77
78
|
* identifiant/mot de passe (grant M2M/CLI). Throttling NIST partagé si activé.
|
|
78
79
|
*
|
|
80
|
+
* @param context - requête HTTP qui porte le credential. Présente, elle
|
|
81
|
+
* soumet l'appel au refus du clair en production ; absente (CLI, appel
|
|
82
|
+
* programmatique), il n'y a pas de transport à juger. Un endpoint HTTP DOIT
|
|
83
|
+
* la passer.
|
|
84
|
+
* @throws InsecureTransportError (403) — credential reçu en clair en production.
|
|
79
85
|
* @throws ThrottledError (429) — backoff actif.
|
|
80
86
|
* @throws AuthenticationError (401, message uniforme) — credential invalide.
|
|
81
87
|
*/
|
|
82
|
-
issueForCredentials(identifier: unknown, password: unknown, requestedScopes?: string[], resource?: unknown): Promise<ITokenResponse>;
|
|
88
|
+
issueForCredentials(identifier: unknown, password: unknown, requestedScopes?: string[], resource?: unknown, context?: ICredentialTransportContext): Promise<ITokenResponse>;
|
|
83
89
|
issueTokens(user: IUser, requestedScopes?: string[], resource?: unknown, accessTtlS?: number): Promise<ITokenResponse>;
|
|
84
90
|
/**
|
|
85
91
|
* Rotation d'un refresh token (RFC 9700 §4.14) : valide le refresh présenté,
|
|
@@ -27,12 +27,36 @@ declare class TotpService extends Service {
|
|
|
27
27
|
isEnabled(): boolean;
|
|
28
28
|
/** Démarre l'enrôlement (secret + QR affichés 1×). */
|
|
29
29
|
beginEnrollment(userId: string, account: string): Promise<ITotpEnrollment>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Confirme l'enrôlement par un 1ᵉʳ code → active + codes de récupération clairs.
|
|
32
|
+
*
|
|
33
|
+
* Activer un second facteur est un geste de SÉCURITÉ : il est audité ici
|
|
34
|
+
* (`user.totp_enabled`), avec la requête qui l'a porté — sans trace, un facteur
|
|
35
|
+
* posé à l'insu du titulaire ne se retrouve pas.
|
|
36
|
+
*
|
|
37
|
+
* @param userId - compte dont on active le 2FA (le porteur de la session).
|
|
38
|
+
* @param code - premier code de l'application d'authentification.
|
|
39
|
+
* @param context - contexte de la requête (IP, navigateur, identifiant de
|
|
40
|
+
* requête) recopié dans l'événement d'audit ; absent, l'événement part sans.
|
|
41
|
+
* @returns les codes de récupération, affichés une seule fois.
|
|
42
|
+
* @throws quand le code est faux, l'enrôlement absent ou déjà confirmé.
|
|
43
|
+
*/
|
|
44
|
+
confirmEnrollment(userId: string, code: string, context?: unknown): Promise<ITotpActivation>;
|
|
32
45
|
/** Vérifie un second facteur au login (code TOTP ou code de récupération). */
|
|
33
46
|
verifyLogin(userId: string, code: string): Promise<ITotpLoginResult>;
|
|
34
|
-
/**
|
|
35
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Désactive le 2FA d'un utilisateur.
|
|
49
|
+
*
|
|
50
|
+
* Avec un `context`, c'est le TITULAIRE qui retire son facteur (porte
|
|
51
|
+
* libre-service) : le geste est audité ici (`user.totp_disabled`). Sans
|
|
52
|
+
* contexte, l'appelant porte lui-même l'audit — le plan d'administration le
|
|
53
|
+
* fait avec l'identité de l'administrateur (`viaAdmin`), qu'ici on ignore.
|
|
54
|
+
* Un seul événement par geste, jamais deux.
|
|
55
|
+
*
|
|
56
|
+
* @param userId - compte dont on retire le 2FA.
|
|
57
|
+
* @param context - contexte de la requête du titulaire (audit libre-service).
|
|
58
|
+
*/
|
|
59
|
+
disable(userId: string, context?: unknown): Promise<void>;
|
|
36
60
|
/** État 2FA d'un utilisateur (absent / pending / activé + codes restants). */
|
|
37
61
|
status(userId: string): Promise<ITotpStatus>;
|
|
38
62
|
/**
|
|
@@ -5,13 +5,35 @@ import type { IWebAuthnCredential } from "../contracts/IWebAuthnCredential.js";
|
|
|
5
5
|
import type { IWebAuthnCredentialSummary, IWebAuthnListQuery } from "../contracts/IWebAuthnCredentialStore.js";
|
|
6
6
|
/** Sujet d'une cérémonie d'enregistrement — l'utilisateur qui crée un passkey. */
|
|
7
7
|
export interface IWebAuthnUser {
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Clé de propriétaire = `IUser.identifier` — range les credentials
|
|
10
|
+
* (`findByUser`), cf `IWebAuthnCredentialStore`. N'est PAS le `userHandle`.
|
|
11
|
+
*/
|
|
9
12
|
readonly id: string;
|
|
13
|
+
/**
|
|
14
|
+
* Source du `userHandle` WebAuthn : l'`IUser.id` (UUID) du compte. WebAuthn
|
|
15
|
+
* L3 (§4, « User Handle ») l'exige opaque, d'au plus 64 octets, et « MUST NOT
|
|
16
|
+
* contain personally identifying information » — l'identifiant de connexion,
|
|
17
|
+
* souvent un email, est donc exclu. Omis (ou trop long), le handle est le
|
|
18
|
+
* condensat SHA-256 de {@link IWebAuthnUser.id} : 32 octets, jamais l'identifiant
|
|
19
|
+
* en clair. Le handle n'est jamais relu par le serveur — l'authentification
|
|
20
|
+
* retrouve le compte par l'id du credential stocké.
|
|
21
|
+
*/
|
|
22
|
+
readonly handle?: string;
|
|
10
23
|
/** Nom de compte affiché par l'OS (email, identifiant…). */
|
|
11
24
|
readonly name: string;
|
|
12
25
|
/** Nom complet optionnel (UX de l'invite système). */
|
|
13
26
|
readonly displayName?: string;
|
|
14
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Octets du `userHandle` d'une cérémonie d'enregistrement — opaques, bornés, sans
|
|
30
|
+
* donnée personnelle (cf {@link IWebAuthnUser.handle}).
|
|
31
|
+
*
|
|
32
|
+
* @param user - sujet de la cérémonie.
|
|
33
|
+
* @returns `handle` encodé s'il est fourni et tient en 64 octets, sinon le
|
|
34
|
+
* condensat SHA-256 de l'`id`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function userHandleBytes(user: IWebAuthnUser): Uint8Array<ArrayBuffer>;
|
|
15
37
|
/** Résultat d'une authentification WebAuthn vérifiée. */
|
|
16
38
|
export interface IWebAuthnAssertionResult {
|
|
17
39
|
/** Le credential résolu (état déjà mis à jour : compteur, sauvegarde, usage). */
|
|
@@ -9,6 +9,7 @@ import type { ISecurityAreaConfig } from "../config/defineModuleConfig.js";
|
|
|
9
9
|
* spécificité au boot.
|
|
10
10
|
*/
|
|
11
11
|
export declare class SecuredArea implements ISecuredArea {
|
|
12
|
+
#private;
|
|
12
13
|
readonly name: string;
|
|
13
14
|
readonly pattern: RegExp;
|
|
14
15
|
readonly security: boolean;
|
|
@@ -18,6 +19,9 @@ export declare class SecuredArea implements ISecuredArea {
|
|
|
18
19
|
readonly host?: string | undefined;
|
|
19
20
|
readonly realtime: boolean;
|
|
20
21
|
readonly resource?: string | undefined;
|
|
22
|
+
readonly entryPoint?: string | undefined;
|
|
23
|
+
readonly subjectPolicy?: "require" | "ephemeral" | undefined;
|
|
24
|
+
readonly ephemeralRoles?: readonly string[] | undefined;
|
|
21
25
|
/**
|
|
22
26
|
* Rôles exigés par défaut dans la zone, ou `null` quand elle n'en exige
|
|
23
27
|
* aucun. `null` plutôt qu'un tableau vide : le hot path teste une référence,
|
|
@@ -1,31 +1,73 @@
|
|
|
1
|
-
import type { Container } from "nodefony";
|
|
1
|
+
import type { Container, IBootNotice } from "nodefony";
|
|
2
2
|
import { type IUserRevokedEvent } from "@nodefony/user";
|
|
3
|
+
/** Résolution d'un service par nom — tout ce que la purge demande au container. */
|
|
4
|
+
interface ServiceLookupLike {
|
|
5
|
+
get(name: string): unknown;
|
|
6
|
+
}
|
|
3
7
|
/** Bus d'événements minimal (kernel) capable d'abonner un handler. */
|
|
4
8
|
interface KernelListenerLike {
|
|
5
|
-
on(event: string, handler: (...args: unknown[]) =>
|
|
9
|
+
on(event: string, handler: (...args: unknown[]) => unknown): unknown;
|
|
6
10
|
}
|
|
7
11
|
/**
|
|
8
12
|
* Cascade de révocation déclenchée par {@link USER_REVOKED_EVENT} : éjecte
|
|
9
13
|
* **immédiatement** les artefacts d'accès du porteur — ses **sessions** (http,
|
|
10
14
|
* `destroyByUser`) et ses **jetons/PAT** (`tokenStore.revokeAllForSubject`,
|
|
11
|
-
* seuil `invalidBefore
|
|
12
|
-
* l'autre) — l'accès était DÉJÀ neutralisé par
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
+
* seuil `invalidBefore`, qui couvre aussi les clés d'API). Best-effort par
|
|
16
|
+
* brique (une indispo n'empêche pas l'autre) — l'accès était DÉJÀ neutralisé par
|
|
17
|
+
* le re-fetch des authenticators. `tenantId` du payload est réservé (scoping non
|
|
18
|
+
* câblé en mono-tenant).
|
|
19
|
+
*
|
|
20
|
+
* **Suppression du compte** (`reason: "deleted"`) : retire AUSSI ses facteurs —
|
|
21
|
+
* secret TOTP (codes de récupération compris) et passkeys. Ils sont rangés sous
|
|
22
|
+
* l'`identifier`, qu'un compte recréé peut reprendre : laissés en place, le
|
|
23
|
+
* nouveau titulaire hériterait du second facteur de l'ancien. Un compte
|
|
24
|
+
* seulement désactivé ou verrouillé les GARDE — il les retrouve à sa
|
|
25
|
+
* réactivation.
|
|
15
26
|
*
|
|
16
|
-
* @param container - container du kernel (résolution lazy de `sessions
|
|
27
|
+
* @param container - container du kernel (résolution lazy de `sessions`, `tokenStore`, `totp`, `webauthn`).
|
|
17
28
|
* @param event - charge utile de l'événement (porteur + raison).
|
|
18
29
|
* @param now - horloge (epoch ms) injectable pour les tests.
|
|
19
30
|
*/
|
|
20
31
|
export declare function cascadeUserRevocation(container: Container, event: IUserRevokedEvent, now?: number): Promise<void>;
|
|
21
32
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
33
|
+
* Retire les facteurs rangés sous un identifiant — secret TOTP (codes de
|
|
34
|
+
* récupération compris) et passkeys. SEULE implémentation du geste, appelée à
|
|
35
|
+
* la suppression d'un compte ET à la création d'un compte sous cet identifiant.
|
|
36
|
+
* Best-effort par brique : un TOTP indisponible n'empêche pas le retrait des
|
|
37
|
+
* passkeys.
|
|
38
|
+
*
|
|
39
|
+
* @param container - container du kernel (services `totp`, `webauthn`).
|
|
40
|
+
* @param identifier - `IUser.identifier` du compte.
|
|
41
|
+
*/
|
|
42
|
+
export declare function removeAccountFactors(container: ServiceLookupLike, identifier: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Abonne la cascade au bus kernel, UNE fois, au `onKernelBoot` de
|
|
45
|
+
* `@nodefony/security` — avant qu'aucun compte ne puisse être créé ou révoqué
|
|
46
|
+
* (les comptes semés au démarrage compris). Deux abonnements :
|
|
47
|
+
* - {@link USER_REVOKED_EVENT} → {@link cascadeUserRevocation} ;
|
|
48
|
+
* - {@link USER_CREATED_EVENT} → purge des facteurs HÉRITÉS : un compte qui
|
|
49
|
+
* vient de naître n'a encore rien enrôlé, donc tout facteur déjà rangé sous
|
|
50
|
+
* son identifiant appartient à un ancien titulaire. Règle exacte, aucune
|
|
51
|
+
* date à comparer. ⚠️ Un import qui chargerait les facteurs AVANT les
|
|
52
|
+
* comptes les verrait retirés : importer les comptes d'abord.
|
|
53
|
+
*
|
|
54
|
+
* Les deux sont émis par `UserService` seul, et attendus : chaque handler rend
|
|
55
|
+
* sa promesse. **Extensible** : tout autre module (webhooks…) s'abonne aux
|
|
56
|
+
* MÊMES événements pour ses propres artefacts, sans toucher à ce fichier.
|
|
26
57
|
*
|
|
27
58
|
* @param kernel - bus d'événements (kernel) exposant `on`.
|
|
28
|
-
* @param container - container capturé par
|
|
59
|
+
* @param container - container capturé par les handlers.
|
|
29
60
|
*/
|
|
30
61
|
export declare function registerUserRevocationCascade(kernel: KernelListenerLike, container: Container): void;
|
|
62
|
+
/**
|
|
63
|
+
* Avis de démarrage quand le service `users` n'émet pas sur le bus du kernel —
|
|
64
|
+
* construit sans container (`new UserService(repo, encoder)`). Ses événements
|
|
65
|
+
* partent alors sur son propre bus, où personne n'écoute : supprimer un compte
|
|
66
|
+
* laisserait ses sessions, ses jetons et ses facteurs en place, SANS UN MOT.
|
|
67
|
+
*
|
|
68
|
+
* @param container - container du kernel.
|
|
69
|
+
* @param kernel - le kernel attendu comme bus des événements.
|
|
70
|
+
* @returns l'avis à déclarer, ou `null` si `users` est absent ou bien câblé.
|
|
71
|
+
*/
|
|
72
|
+
export declare function usersEventBusNotice(container: ServiceLookupLike, kernel: object): IBootNotice | null;
|
|
31
73
|
export {};
|
|
@@ -4,6 +4,7 @@ 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
6
|
import { type ExternalSubjectMapping } from "./externalSubject.js";
|
|
7
|
+
import { type IProviderRoleMapping } from "../oauth/providerRoles.js";
|
|
7
8
|
/** Comment le sujet d'un jeton devient un utilisateur de cette application. */
|
|
8
9
|
export type ExternalSubjectPolicy = "require" | "ephemeral";
|
|
9
10
|
/** Un émetteur reconnu, et la façon dont ses sujets entrent chez nous. */
|
|
@@ -28,6 +29,23 @@ export interface IExternalJwtAuthenticatorOptions {
|
|
|
28
29
|
subjectPolicy: ExternalSubjectPolicy;
|
|
29
30
|
/** Rôles accordés en mode `ephemeral`. */
|
|
30
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
|
+
}[];
|
|
31
49
|
}
|
|
32
50
|
/**
|
|
33
51
|
* Authentification par **jeton d'accès émis par un serveur d'autorisation
|
|
@@ -62,6 +62,19 @@ export declare class FirewallRealtimeAuthenticator implements IRealtimeAuthentic
|
|
|
62
62
|
* filet défensif fail-closed → le hub ferme la socket en 4001).
|
|
63
63
|
*/
|
|
64
64
|
authenticate(_handshake: IRealtimeHandshake): Promise<IRealtimeToken>;
|
|
65
|
+
/**
|
|
66
|
+
* Le revalidateur de l'identité que le firewall a posée sur la requête
|
|
67
|
+
* COURANTE (ALS), pour toute connexion longue — socket WebSocket ou flux SSE.
|
|
68
|
+
*
|
|
69
|
+
* Une seule règle décide qu'une identité est morte, quel que soit le
|
|
70
|
+
* transport qui la porte : c'est celle-ci, et le hub comme le noyau HTTP
|
|
71
|
+
* l'appellent au lieu de la recopier.
|
|
72
|
+
*
|
|
73
|
+
* @returns `null` pour un anonyme (rien à révoquer), sinon la fonction qui
|
|
74
|
+
* dit si l'identité est TOUJOURS valable — fail-closed : `false` quand elle
|
|
75
|
+
* ne peut pas le prouver.
|
|
76
|
+
*/
|
|
77
|
+
currentRevalidator(): ((nowMs?: number) => Promise<boolean>) | null;
|
|
65
78
|
}
|
|
66
79
|
/**
|
|
67
80
|
* Surface MINIMALE du store de jetons consommée ici : les deux lectures qui
|
|
@@ -3,6 +3,7 @@ import type { IPasswordVerifier } from "@nodefony/user";
|
|
|
3
3
|
import type { IAuthenticator } from "../../contracts/IAuthenticator.js";
|
|
4
4
|
import type { IToken } from "../../contracts/IToken.js";
|
|
5
5
|
import type { LoginThrottler } from "../throttle/LoginThrottler.js";
|
|
6
|
+
import type { CredentialTransportPolicy } from "../transport/CredentialTransportPolicy.js";
|
|
6
7
|
/**
|
|
7
8
|
* Authentification par identifiant + mot de passe — schéma **HTTP Basic**
|
|
8
9
|
* (RFC 7617) : `Authorization: Basic base64(identifiant:motdepasse)`, charset
|
|
@@ -25,11 +26,18 @@ export declare class UserPasswordAuthenticator implements IAuthenticator {
|
|
|
25
26
|
* @param resolveVerifier - résolution lazy de la source de vérification
|
|
26
27
|
* (typiquement `container.get("users")`) — appelée au premier login.
|
|
27
28
|
* @param throttler - limiteur de tentatives (backoff NIST), `null` = désactivé.
|
|
29
|
+
* @param transport - politique du transport des secrets, `null` = aucune.
|
|
28
30
|
*/
|
|
29
|
-
constructor(resolveVerifier: () => IPasswordVerifier, throttler?: LoginThrottler | null);
|
|
31
|
+
constructor(resolveVerifier: () => IPasswordVerifier, throttler?: LoginThrottler | null, transport?: CredentialTransportPolicy | null);
|
|
30
32
|
/** La requête porte-t-elle un en-tête `Authorization: Basic ...` ? */
|
|
31
33
|
supports(context: ContextType): boolean;
|
|
32
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Décode l'enveloppe Basic — un contenu malformé donne un credential vide
|
|
36
|
+
* (échec uniforme).
|
|
37
|
+
*
|
|
38
|
+
* @throws InsecureTransportError (403) — en production, en-tête reçu hors
|
|
39
|
+
* TLS : refusé avant même d'être décodé.
|
|
40
|
+
*/
|
|
33
41
|
createToken(context: ContextType): Promise<IToken>;
|
|
34
42
|
/**
|
|
35
43
|
* Vérifie le credential via le verifier ou lève un 401 au message uniforme.
|
|
@@ -47,7 +55,7 @@ export declare class UserPasswordAuthenticator implements IAuthenticator {
|
|
|
47
55
|
onSuccess(_context: ContextType, _token: IToken): Promise<void>;
|
|
48
56
|
/** Slot J3+ (audit events). Le throttling vit dans `authenticate` (clé = identifiant) ; le 401 + challenge sont posés par le firewall. */
|
|
49
57
|
onFailure(_context: ContextType, _error: Error): Promise<void>;
|
|
50
|
-
/** Challenge RFC
|
|
58
|
+
/** Challenge RFC 9110 §11 posé par le firewall sur les 401 de la zone. */
|
|
51
59
|
challenge(): string;
|
|
52
60
|
}
|
|
53
61
|
export default UserPasswordAuthenticator;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IBootNotice, IFirewallZoneView } from "nodefony";
|
|
2
|
+
/**
|
|
3
|
+
* Le point d'attention « routes métier publiques » du bilan de démarrage.
|
|
4
|
+
*
|
|
5
|
+
* Hors d'une zone du pare-feu, une requête n'est jamais authentifiée : son
|
|
6
|
+
* identité n'est pas résolue, et une route métier qu'on croyait protégée
|
|
7
|
+
* répond à n'importe qui. Les seules zones présentes sont alors les aires du
|
|
8
|
+
* framework (`/nodefony/*`) ; on le DIT au démarrage, avec la recette.
|
|
9
|
+
*
|
|
10
|
+
* La classification « applicative ou framework » n'est pas recopiée : elle
|
|
11
|
+
* vient du cœur (`isFrameworkZone`), qui l'applique aussi au tableau des zones.
|
|
12
|
+
*
|
|
13
|
+
* @param zones - zones montées (`firewall.describe().zones`).
|
|
14
|
+
* @returns le point à déclarer, ou `null` si une zone applicative existe.
|
|
15
|
+
*/
|
|
16
|
+
export declare function publicRoutesNotice(zones: readonly IFirewallZoneView[]): IBootNotice | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Ce que la décision lit d'une requête refusée — rien de plus. */
|
|
2
|
+
export interface IEntryPointRequest {
|
|
3
|
+
/** Méthode HTTP (`GET`, `POST`…). */
|
|
4
|
+
readonly method: string | undefined;
|
|
5
|
+
/** En-tête `Accept`. */
|
|
6
|
+
readonly accept: string | undefined;
|
|
7
|
+
/** En-tête `Sec-Fetch-Mode` (Fetch Metadata), absent d'un client ancien. */
|
|
8
|
+
readonly fetchMode: string | undefined;
|
|
9
|
+
/** Chemin et requête demandés (`/compte?onglet=2`), jamais l'hôte. */
|
|
10
|
+
readonly path: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Dit si une requête refusée est la NAVIGATION d'un navigateur — le seul cas
|
|
14
|
+
* où conduire à la page de connexion aide, au lieu de casser un client.
|
|
15
|
+
*
|
|
16
|
+
* `GET` ou `HEAD`, `Accept` qui demande du HTML, et `Sec-Fetch-Mode: navigate`
|
|
17
|
+
* quand l'en-tête est présent : un `fetch()` d'une page porte `cors` ou
|
|
18
|
+
* `same-origin` même s'il accepte du HTML, et recevrait une page de connexion
|
|
19
|
+
* à la place de ses données.
|
|
20
|
+
*
|
|
21
|
+
* @param request - la requête refusée
|
|
22
|
+
* @returns `true` pour une navigation
|
|
23
|
+
*/
|
|
24
|
+
export declare function isBrowserNavigation(request: IEntryPointRequest): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* L'adresse de la page de connexion pour un visiteur refusé :
|
|
27
|
+
* `<entryPoint>?from=<chemin demandé>`, composée par `loginPagePath` (cœur).
|
|
28
|
+
* Le chemin demandé repasse par `safeRedirectPath` — la seule garde
|
|
29
|
+
* anti-redirection ouverte du framework, que la page de connexion appliquera
|
|
30
|
+
* de nouveau au retour.
|
|
31
|
+
*
|
|
32
|
+
* @param entryPoint - chemin de la page de connexion (vérifié au boot)
|
|
33
|
+
* @param request - la requête refusée
|
|
34
|
+
* @returns l'adresse du `Location`, ou `null` quand ce n'est pas une
|
|
35
|
+
* navigation, ou quand la page demandée EST la page de connexion
|
|
36
|
+
*/
|
|
37
|
+
export declare function entryPointLocation(entryPoint: string, request: IEntryPointRequest): string | null;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { OAuth2ClientAuthMethod, OAuth2Tokens } from "./oauth2Client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Ce qu'un programme présente pour obtenir un jeton EN SON PROPRE NOM
|
|
4
|
+
* (RFC 6749 §4.4) — un script, un service partenaire, un agent : aucun
|
|
5
|
+
* utilisateur, aucun navigateur.
|
|
6
|
+
*/
|
|
7
|
+
export interface IClientCredentialsRequest {
|
|
8
|
+
/**
|
|
9
|
+
* Identifiant de l'émetteur (claim `iss`), en https. Le point de jeton en est
|
|
10
|
+
* DÉCOUVERT (RFC 8414), jamais écrit à côté : un point de jeton recopié
|
|
11
|
+
* survit au déménagement de l'émetteur et envoie le secret ailleurs.
|
|
12
|
+
*/
|
|
13
|
+
readonly issuer: string;
|
|
14
|
+
/** Identifiant du client confidentiel (ex. `<app>-machine` chez Keycloak). */
|
|
15
|
+
readonly clientId: string;
|
|
16
|
+
/** Secret du client — obligatoire : ce flux n'existe que pour un client confidentiel. */
|
|
17
|
+
readonly clientSecret: string;
|
|
18
|
+
/**
|
|
19
|
+
* Comment le client s'authentifie. Défaut `client_secret_basic`, la méthode
|
|
20
|
+
* que la RFC 6749 §2.3.1 rend obligatoire côté serveur. `none` n'existe pas
|
|
21
|
+
* ici : un client public ne peut pas obtenir de jeton en son nom.
|
|
22
|
+
*/
|
|
23
|
+
readonly clientAuthMethod?: Exclude<OAuth2ClientAuthMethod, "none">;
|
|
24
|
+
/** Portées demandées ; aucune par défaut (le serveur applique les siennes). */
|
|
25
|
+
readonly scopes?: readonly string[];
|
|
26
|
+
/**
|
|
27
|
+
* Ressource visée (RFC 8707 §2) — l'audience que le jeton doit porter. Un
|
|
28
|
+
* serveur qui ne la connaît pas l'ignore ; Keycloak, lui, inscrit l'audience
|
|
29
|
+
* par un mapper déclaré sur le client.
|
|
30
|
+
*/
|
|
31
|
+
readonly resource?: string;
|
|
32
|
+
/** Adresse où lire les métadonnées, si elle diffère de l'émetteur (cf `IDiscoveryOptions`). */
|
|
33
|
+
readonly discoveryUrl?: string | undefined;
|
|
34
|
+
/** Implémentation de `fetch` (banc sans réseau). */
|
|
35
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
36
|
+
/** Délai d'attente de CHAQUE requête (découverte, puis jeton), en millisecondes. */
|
|
37
|
+
readonly timeoutMs?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Obtient un jeton d'accès pour un programme, par le flux `client_credentials`
|
|
41
|
+
* (RFC 6749 §4.4), auprès d'un émetteur découvert (RFC 8414).
|
|
42
|
+
*
|
|
43
|
+
* C'est la voie de l'appelant MACHINE : il s'authentifie avec son propre
|
|
44
|
+
* secret, et le jeton rendu le désigne lui, pas un utilisateur. La même
|
|
45
|
+
* authentification de client que l'échange de code ({@link postTokenRequest}) —
|
|
46
|
+
* une seule implémentation, pour qu'aucune règle ne diverge d'un flux à l'autre.
|
|
47
|
+
*
|
|
48
|
+
* @param request - émetteur, identité du client, portées et ressource.
|
|
49
|
+
* @returns Les jetons rendus ; `accessToken()` est celui à présenter en `Bearer`.
|
|
50
|
+
* @throws Error - secret vide, émetteur invalide ou injoignable, métadonnées discordantes.
|
|
51
|
+
* @throws OAuth2RequestError - le serveur a refusé (`invalid_client` : secret
|
|
52
|
+
* faux ; `unauthorized_client` : le client n'a pas droit à ce flux).
|
|
53
|
+
*/
|
|
54
|
+
export declare function requestClientCredentialsToken(request: IClientCredentialsRequest): Promise<OAuth2Tokens>;
|
|
@@ -23,6 +23,19 @@ export interface IDiscoveredAuthorizationServer {
|
|
|
23
23
|
* alors pas exiger ce qu'il n'a pas promis.
|
|
24
24
|
*/
|
|
25
25
|
readonly issParameterSupported: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Point de déconnexion initiée par l'application (`end_session_endpoint`,
|
|
28
|
+
* OpenID Connect RP-Initiated Logout 1.0 §2.1), ou `null` si le serveur n'en
|
|
29
|
+
* publie aucun : la déconnexion reste alors locale.
|
|
30
|
+
*/
|
|
31
|
+
readonly endSessionEndpoint: string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Algorithmes de signature des ID tokens annoncés
|
|
34
|
+
* (`id_token_signing_alg_values_supported`, OpenID Connect Discovery §3), ou
|
|
35
|
+
* `null` si le serveur n'en publie aucun. Ce sont aussi ceux des jetons de
|
|
36
|
+
* déconnexion (OpenID Connect Back-Channel Logout 1.0 §2.6).
|
|
37
|
+
*/
|
|
38
|
+
readonly idTokenSigningAlgValuesSupported: string[] | null;
|
|
26
39
|
}
|
|
27
40
|
/** Réglages de la découverte — l'injection de `fetch` est la voie pour éprouver sans TLS. */
|
|
28
41
|
export interface IDiscoveryOptions {
|
|
@@ -36,6 +49,16 @@ export interface IDiscoveryOptions {
|
|
|
36
49
|
readonly fetch?: typeof globalThis.fetch;
|
|
37
50
|
/** Délai d'attente par URL candidate, en millisecondes. */
|
|
38
51
|
readonly timeoutMs?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Adresse où LIRE le document, quand elle diffère de l'émetteur — le service
|
|
54
|
+
* interne d'un cluster (`http://keycloak.sso:8080/realms/x/.well-known/openid-configuration`).
|
|
55
|
+
*
|
|
56
|
+
* @remarks Seul le lieu de lecture change : le document doit toujours se
|
|
57
|
+
* déclarer de l'émetteur attendu (RFC 8414 §3.3). Les points de serveur à
|
|
58
|
+
* serveur (jeton, clés) peuvent alors être en `http` sur l'origine exacte de
|
|
59
|
+
* cette adresse ; ceux vers lesquels le navigateur est envoyé restent en `https`.
|
|
60
|
+
*/
|
|
61
|
+
readonly discoveryUrl?: string | undefined;
|
|
39
62
|
}
|
|
40
63
|
/**
|
|
41
64
|
* Interroge un serveur d'autorisation et rend ses points d'entrée.
|
|
@@ -47,7 +70,7 @@ export interface IDiscoveryOptions {
|
|
|
47
70
|
* d'entrée — la même attaque que le paramètre `iss` couvre au retour (RFC 9207).
|
|
48
71
|
*
|
|
49
72
|
* @param rawIssuer - identifiant d'émetteur tel qu'écrit en configuration.
|
|
50
|
-
* @param options - transport injectable
|
|
73
|
+
* @param options - transport injectable, délai d'attente, adresse de découverte.
|
|
51
74
|
* @returns Les points d'entrée, prêts pour `OAuth2Client`.
|
|
52
75
|
* @throws Error - émetteur mal formé, document introuvable, incomplet, ou `issuer` discordant.
|
|
53
76
|
*/
|