@nodefony/security 10.0.0-beta.2 → 10.0.0-beta.4
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.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/index.js +48 -16
- package/dist/nodefony/command/{security-token.js → security-mcp-token.js} +62 -24
- package/dist/nodefony/command/security-oauth-doctor.js +68 -0
- package/dist/nodefony/command/security-secrets.js +52 -29
- package/dist/nodefony/command/security-user-add.js +9 -3
- package/dist/nodefony/command/security-user-delete.js +10 -7
- package/dist/nodefony/command/security-user-list.js +5 -3
- package/dist/nodefony/command/security-user-password.js +9 -14
- package/dist/nodefony/config/config.js +99 -8
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/contracts/IAccessVoter.js +1 -2
- package/dist/nodefony/contracts/index.js +1 -2
- package/dist/nodefony/errors/AccessDeniedError.js +1 -2
- package/dist/nodefony/errors/ApiKeyError.js +3 -2
- package/dist/nodefony/errors/AuthenticationError.js +1 -2
- package/dist/nodefony/errors/CsrfError.js +1 -2
- package/dist/nodefony/errors/InsecureTransportError.js +23 -0
- package/dist/nodefony/errors/InvalidTargetError.js +1 -2
- package/dist/nodefony/errors/SsrfError.js +1 -2
- package/dist/nodefony/errors/ThrottledError.js +1 -2
- package/dist/nodefony/errors/UnverifiableTokenError.js +1 -2
- package/dist/nodefony/errors/WebAuthnError.js +1 -2
- package/dist/nodefony/errors/index.js +2 -1
- package/dist/nodefony/keycloak/keycloakAdmin.js +254 -0
- package/dist/nodefony/keycloak/keycloakRealm.js +259 -0
- package/dist/nodefony/keycloak/keycloakRealmInput.js +123 -0
- package/dist/nodefony/keycloak/scaffold.js +231 -0
- package/dist/nodefony/keycloak/security-keycloak-realm.js +130 -0
- package/dist/nodefony/scaffold/contribute.js +19 -0
- package/dist/nodefony/service/accessTokenVerifier.js +101 -34
- package/dist/nodefony/service/apiKeys.js +45 -5
- package/dist/nodefony/service/auditService.js +4 -5
- package/dist/nodefony/service/authFlow.js +118 -16
- package/dist/nodefony/service/authorization.js +1 -2
- package/dist/nodefony/service/cors.js +1 -2
- package/dist/nodefony/service/csrf.js +1 -2
- package/dist/nodefony/service/firewall.js +116 -25
- package/dist/nodefony/service/keycloak.js +208 -0
- package/dist/nodefony/service/oauth2.js +403 -28
- package/dist/nodefony/service/securityHeaders.js +1 -2
- package/dist/nodefony/service/tokenService.js +94 -27
- package/dist/nodefony/service/totp.js +55 -11
- package/dist/nodefony/service/webAuthn.js +24 -7
- package/dist/nodefony/service/webhooks.js +4 -5
- package/dist/nodefony/src/RoleHierarchyWalker.js +1 -2
- package/dist/nodefony/src/SecuredArea.js +15 -5
- package/dist/nodefony/src/accountBinding.js +96 -0
- package/dist/nodefony/src/admin/SecurityAdminApi.js +71 -42
- package/dist/nodefony/src/admin/WebhookAdminApi.js +23 -7
- package/dist/nodefony/src/admin/adminAudit.js +1 -3
- package/dist/nodefony/src/admin/userRevocationCascade.js +148 -20
- package/dist/nodefony/src/apikey/apiKeyFormat.js +1 -2
- package/dist/nodefony/src/audit/MemoryAuditStore.js +1 -2
- package/dist/nodefony/src/audit/auditBridge.js +1 -2
- package/dist/nodefony/src/audit/auditFilters.js +1 -2
- package/dist/nodefony/src/audit/auditStoreRegistry.js +1 -2
- package/dist/nodefony/src/audit/readAuditContext.js +1 -2
- package/dist/nodefony/src/audit/recordAudit.js +1 -2
- package/dist/nodefony/src/authenticator/AnonymousAuthenticator.js +1 -2
- package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +3 -2
- package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +14 -59
- package/dist/nodefony/src/authenticator/ExternalSubjectResolver.js +180 -0
- package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +80 -19
- package/dist/nodefony/src/authenticator/JwtAuthenticator.js +92 -16
- package/dist/nodefony/src/authenticator/SessionAuthenticator.js +9 -5
- package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +14 -5
- package/dist/nodefony/src/authenticator/authenticatorRegistry.js +5 -12
- package/dist/nodefony/src/authenticator/bearer.js +1 -2
- package/dist/nodefony/src/authenticator/externalSubject.js +1 -2
- package/dist/nodefony/src/authenticator/peekIssuer.js +1 -2
- package/dist/nodefony/src/boot/publicRoutesNotice.js +26 -0
- package/dist/nodefony/src/crypto/secretCipher.js +1 -2
- package/dist/nodefony/src/csp.js +1 -2
- package/dist/nodefony/src/csrfToken.js +1 -2
- package/dist/nodefony/src/entryPoint.js +38 -0
- package/dist/nodefony/src/net/ssrfGuard.js +1 -2
- package/dist/nodefony/src/net/transportFailure.js +57 -0
- package/dist/nodefony/src/oauth/clientCredentials.js +39 -0
- package/dist/nodefony/src/oauth/httpJson.js +3 -3
- package/dist/nodefony/src/oauth/metadata.js +28 -12
- package/dist/nodefony/src/oauth/oauth2Client.js +61 -35
- package/dist/nodefony/src/oauth/oauthProviderRegistry.js +40 -8
- package/dist/nodefony/src/oauth/providerDiagnosis.js +282 -0
- package/dist/nodefony/src/oauth/providerRoles.js +78 -0
- package/dist/nodefony/src/oauth/providers/github.js +48 -12
- package/dist/nodefony/src/oauth/providers/oidc.js +90 -8
- package/dist/nodefony/src/openToSignup.js +46 -0
- package/dist/nodefony/src/realtime/UserRealtimeToken.js +17 -3
- package/dist/nodefony/src/realtime/frameAuthorizer.js +1 -2
- package/dist/nodefony/src/rolesWithin.js +57 -0
- package/dist/nodefony/src/sessionAuthentication.js +25 -0
- package/dist/nodefony/src/sessionIdentity.js +29 -3
- package/dist/nodefony/src/throttle/LoginThrottler.js +1 -2
- package/dist/nodefony/src/token/AnonymousToken.js +1 -2
- package/dist/nodefony/src/token/JwtKeystore.js +89 -48
- package/dist/nodefony/src/token/MemoryTokenStore.js +1 -2
- package/dist/nodefony/src/token/RemoteJwtVerifier.js +46 -16
- package/dist/nodefony/src/token/UserToken.js +1 -2
- package/dist/nodefony/src/token/jwtRuntime.js +1 -2
- package/dist/nodefony/src/token/scopeGrants.js +109 -0
- package/dist/nodefony/src/token/secretFile.js +60 -4
- package/dist/nodefony/src/token/tokenCriteria.js +1 -2
- package/dist/nodefony/src/token/tokenFilters.js +1 -2
- package/dist/nodefony/src/token/tokenSort.js +1 -2
- package/dist/nodefony/src/token/tokenStatus.js +1 -2
- package/dist/nodefony/src/token/tokenStoreRegistry.js +1 -2
- package/dist/nodefony/src/totp/MemoryTotpSecretStore.js +1 -2
- package/dist/nodefony/src/totp/totpCipher.js +1 -2
- package/dist/nodefony/src/totp/totpCrypto.js +1 -2
- package/dist/nodefony/src/totp/totpOperations.js +1 -2
- package/dist/nodefony/src/totp/totpSecretStoreRegistry.js +1 -2
- package/dist/nodefony/src/transport/CredentialTransportPolicy.js +50 -0
- package/dist/nodefony/src/voter/RoleVoter.js +1 -2
- package/dist/nodefony/src/voter/ScopeVoter.js +1 -2
- package/dist/nodefony/src/voter/voterRegistry.js +1 -2
- package/dist/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js +1 -2
- package/dist/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js +1 -2
- package/dist/nodefony/src/webhook/MemoryWebhookStore.js +1 -2
- package/dist/nodefony/src/webhook/WebhookDispatcher.js +1 -2
- package/dist/nodefony/src/webhook/webhookCipher.js +1 -2
- package/dist/nodefony/src/webhook/webhookDelivery.js +1 -2
- package/dist/nodefony/src/webhook/webhookFilters.js +1 -2
- package/dist/nodefony/src/webhook/webhookSignature.js +1 -2
- package/dist/nodefony/src/webhook/webhookSort.js +1 -2
- package/dist/nodefony/src/webhook/webhookStoreRegistry.js +1 -2
- package/dist/types/index.d.ts +26 -6
- package/dist/types/nodefony/command/{security-token.d.ts → security-mcp-token.d.ts} +16 -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-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 +64 -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/ApiKeyError.d.ts +3 -1
- 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/accessTokenVerifier.d.ts +35 -14
- package/dist/types/nodefony/service/apiKeys.d.ts +13 -2
- 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 +145 -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/accountBinding.d.ts +82 -0
- package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +72 -12
- package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +2 -26
- package/dist/types/nodefony/src/authenticator/ExternalSubjectResolver.d.ts +99 -0
- package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +17 -1
- package/dist/types/nodefony/src/authenticator/JwtAuthenticator.d.ts +37 -1
- 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/net/transportFailure.d.ts +22 -0
- package/dist/types/nodefony/src/oauth/clientCredentials.d.ts +54 -0
- package/dist/types/nodefony/src/oauth/httpJson.d.ts +0 -8
- 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/realtime/UserRealtimeToken.d.ts +7 -1
- package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +6 -0
- package/dist/types/nodefony/src/rolesWithin.d.ts +27 -0
- package/dist/types/nodefony/src/sessionAuthentication.d.ts +14 -0
- package/dist/types/nodefony/src/sessionIdentity.d.ts +22 -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/scopeGrants.d.ts +77 -0
- 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 +182 -175
- package/docs/audit.md +141 -141
- package/docs/authenticators.md +129 -96
- package/docs/authorization.md +116 -69
- package/docs/cors.md +64 -64
- package/docs/csrf.md +77 -72
- package/docs/external-jwt.md +83 -11
- package/docs/firewall.md +119 -83
- package/docs/headers.md +82 -82
- package/docs/index.md +16 -8
- package/docs/keycloak.md +835 -0
- package/docs/login-page.md +361 -0
- package/docs/oauth2.md +291 -166
- package/docs/obtenir-un-jeton.md +131 -34
- package/docs/tokens.md +197 -140
- package/docs/totp.md +177 -136
- package/docs/webauthn.md +157 -154
- package/docs/webhooks.md +128 -125
- 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 +19 -17
- package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Service, Module } from "nodefony";
|
|
2
|
+
import type { LoginPageSkin } from "nodefony";
|
|
3
|
+
import type { ISecurityConfig } from "../config/config.js";
|
|
4
|
+
import { KeycloakAdminClient, type IKeycloakLoginThemeResult } from "../keycloak/keycloakAdmin.js";
|
|
5
|
+
/** Ce qu'on demande au realm dérivé. */
|
|
6
|
+
export interface IKeycloakRealmRequest {
|
|
7
|
+
/** Nom dans `oauth2.providers` (défaut `keycloak`). */
|
|
8
|
+
readonly provider?: string | undefined;
|
|
9
|
+
/** Fichier d'import, relatif à la racine de l'application ou absolu. */
|
|
10
|
+
readonly file?: string | undefined;
|
|
11
|
+
/** Client machine dont l'audience suit la config. */
|
|
12
|
+
readonly machine?: string | undefined;
|
|
13
|
+
/** Origine par laquelle Keycloak joint l'application (canal arrière). */
|
|
14
|
+
readonly backchannelOrigin?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
/** Le realm que la configuration implique, confronté au fichier d'import. */
|
|
17
|
+
export interface IKeycloakRealmPlan {
|
|
18
|
+
/** Fichier d'import visé (chemin natif absolu). */
|
|
19
|
+
readonly file: string;
|
|
20
|
+
/** Contenu actuel du fichier, `null` s'il n'existe pas. */
|
|
21
|
+
readonly current: string | null;
|
|
22
|
+
/** Le realm fusionné, tel qu'il serait écrit. */
|
|
23
|
+
readonly text: string;
|
|
24
|
+
/** Constats à DIRE (le realm reste écrivable). */
|
|
25
|
+
readonly warnings: readonly string[];
|
|
26
|
+
/** `true` si le fichier porte déjà exactement {@link text}. */
|
|
27
|
+
readonly upToDate: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Service `keycloak` — toutes les actions de Nodefony sur un serveur Keycloak,
|
|
31
|
+
* en un point du conteneur.
|
|
32
|
+
*
|
|
33
|
+
* - **le realm** que la configuration implique : dérivé, fusionné avec le
|
|
34
|
+
* fichier d'import existant, comparé, écrit ({@link realmPlan},
|
|
35
|
+
* {@link writeRealm}) ;
|
|
36
|
+
* - **l'API d'administration** d'un realm vivant ({@link admin}), et ce qui s'y
|
|
37
|
+
* fait : le thème de connexion qui suit `loginPage.skin`
|
|
38
|
+
* ({@link applyLoginTheme}).
|
|
39
|
+
*
|
|
40
|
+
* Les commandes `security:keycloak:*` et les routes de développement
|
|
41
|
+
* l'appellent ; elles ne gardent que l'affichage. Seule la contribution de
|
|
42
|
+
* scaffold (`create app`, `scaffold:sync`) appelle les fonctions pures en
|
|
43
|
+
* direct : elle tourne SANS démarrer l'application, donc sans conteneur.
|
|
44
|
+
*
|
|
45
|
+
* Rien n'est construit au démarrage : la configuration est lue à l'appel, et
|
|
46
|
+
* les clients d'administration (un par fournisseur, jeton gardé) au premier
|
|
47
|
+
* usage.
|
|
48
|
+
*/
|
|
49
|
+
declare class KeycloakService extends Service {
|
|
50
|
+
#private;
|
|
51
|
+
module: Module;
|
|
52
|
+
constructor(module: Module);
|
|
53
|
+
/**
|
|
54
|
+
* La configuration de sécurité VALIDÉE, relue à chaque appel (chemin froid).
|
|
55
|
+
*
|
|
56
|
+
* @returns la configuration
|
|
57
|
+
*/
|
|
58
|
+
config(): ISecurityConfig;
|
|
59
|
+
/**
|
|
60
|
+
* Le realm que la configuration implique, fusionné avec le fichier d'import.
|
|
61
|
+
*
|
|
62
|
+
* Le realm d'import répète ce que la config dit déjà (client, URL de retour,
|
|
63
|
+
* audiences, rôles de `roleMapping`) : écrit à la main, il diverge. Ce plan
|
|
64
|
+
* le DÉRIVE, puis le fusionne pour que ce qu'un humain a ajouté survive
|
|
65
|
+
* (comptes, titres, secret machine, mappers posés dans la console).
|
|
66
|
+
*
|
|
67
|
+
* @param request - fournisseur, fichier, client machine, canal arrière
|
|
68
|
+
* @returns le fichier, son contenu, le realm à écrire et les constats
|
|
69
|
+
* @throws {KeycloakRealmError} Fournisseur absent ou mal décrit, fichier qui
|
|
70
|
+
* n'est pas un realm
|
|
71
|
+
*/
|
|
72
|
+
realmPlan(request?: IKeycloakRealmRequest): IKeycloakRealmPlan;
|
|
73
|
+
/**
|
|
74
|
+
* Écrit le realm d'un plan — rien si le fichier est déjà à jour.
|
|
75
|
+
*
|
|
76
|
+
* ⚠️ Keycloak n'importe un realm qu'à sa CRÉATION : un realm déjà présent ne
|
|
77
|
+
* relit pas ce fichier ({@link applyLoginTheme} pour le thème, import
|
|
78
|
+
* partiel dans la console pour le reste).
|
|
79
|
+
*
|
|
80
|
+
* @param plan - le rendu de {@link realmPlan}
|
|
81
|
+
* @returns `true` si le fichier a été écrit
|
|
82
|
+
*/
|
|
83
|
+
writeRealm(plan: IKeycloakRealmPlan): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Le client de l'API d'administration d'un fournisseur Keycloak.
|
|
86
|
+
*
|
|
87
|
+
* @param providerName - nom dans `oauth2.providers` (défaut `keycloak`)
|
|
88
|
+
* @returns le client, construit au premier appel puis gardé (jeton réutilisé)
|
|
89
|
+
* @throws {KeycloakAdminError} Hors développement, fournisseur absent, sans
|
|
90
|
+
* émetteur ou sans compte d'administration
|
|
91
|
+
*/
|
|
92
|
+
admin(providerName?: string): KeycloakAdminClient;
|
|
93
|
+
/**
|
|
94
|
+
* Le thème de connexion qu'un habillage demande à Keycloak.
|
|
95
|
+
*
|
|
96
|
+
* @param skin - habillage ; défaut : `loginPage.skin` de la configuration
|
|
97
|
+
* @returns `nodefony` ou `nodefony-<habillage>`
|
|
98
|
+
*/
|
|
99
|
+
expectedLoginTheme(skin?: LoginPageSkin): string;
|
|
100
|
+
/**
|
|
101
|
+
* Pose sur le realm VIVANT le thème de connexion d'un habillage.
|
|
102
|
+
*
|
|
103
|
+
* @param options - fournisseur (défaut `keycloak`) et habillage (défaut :
|
|
104
|
+
* `loginPage.skin` ; un aperçu de `/login` passe le sien)
|
|
105
|
+
* @returns le thème d'avant, celui demandé, et ce qui a été fait
|
|
106
|
+
* @throws {KeycloakAdminError} Hors développement, compte absent ou refusé,
|
|
107
|
+
* Keycloak injoignable
|
|
108
|
+
*/
|
|
109
|
+
applyLoginTheme(options?: {
|
|
110
|
+
provider?: string;
|
|
111
|
+
skin?: LoginPageSkin;
|
|
112
|
+
}): Promise<IKeycloakLoginThemeResult>;
|
|
113
|
+
}
|
|
114
|
+
export { KeycloakService };
|
|
115
|
+
export default KeycloakService;
|
|
@@ -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,60 @@ 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;
|
|
74
|
+
/**
|
|
75
|
+
* Annonce qu'un annuaire accorde des rôles de PLATEFORME, à la sévérité de
|
|
76
|
+
* l'environnement — ou `null` quand il n'y a rien à dire.
|
|
77
|
+
*
|
|
78
|
+
* L'ouverture est ÉCRITE (`allowPlatformRoles`), donc voulue : l'annonce ne
|
|
79
|
+
* la conteste pas, elle la rappelle. Le risque — un annuaire compromis
|
|
80
|
+
* administre l'instance — n'est réel qu'en production : là, `WARNING` ;
|
|
81
|
+
* ailleurs le même fait est dit en `INFO`, sans sonner une alarme à chaque
|
|
82
|
+
* démarrage d'un décor de développement.
|
|
83
|
+
*
|
|
84
|
+
* @param name - nom du fournisseur (`oauth2.providers.<name>`).
|
|
85
|
+
* @param allowed - `allowPlatformRoles` tel que déclaré.
|
|
86
|
+
* @param granted - rôles de l'application que la table accorde.
|
|
87
|
+
* @param production - l'application tourne en production.
|
|
88
|
+
* @returns le message et sa sévérité, ou `null`.
|
|
89
|
+
*/
|
|
90
|
+
export declare function platformRolesNotice(name: string, allowed: boolean | undefined, granted: readonly string[], production: boolean): {
|
|
91
|
+
message: string;
|
|
92
|
+
severity: "WARNING" | "INFO";
|
|
93
|
+
} | null;
|
|
33
94
|
/**
|
|
34
95
|
* **Social login OAuth 2.0** (P6 J9) — orchestrateur du flux *Authorization Code*.
|
|
35
96
|
*
|
|
36
97
|
* Posture OAuth 2.1 (RFC 9700) : Authorization Code uniquement (jamais implicit /
|
|
37
98
|
* 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`)
|
|
99
|
+
* anti-CSRF, **iss** anti-mix-up (RFC 9207) ; aucun jeton d'accès n'atteint le
|
|
100
|
+
* navigateur (le login produit une **session BFF**, gérée hors de ce service par
|
|
101
|
+
* le controller + `AuthFlow`) — seul l'ID token y passe, une fois, à la
|
|
102
|
+
* déconnexion ({@link IFederatedLogoutHint}).
|
|
103
|
+
*
|
|
104
|
+
* Au boot (si `oauth2.enabled`), la configuration de chaque fournisseur est
|
|
105
|
+
* confrontée à ce que sa fabrique exige : un émetteur requis absent, ou qui n'est
|
|
106
|
+
* pas une URL d'émetteur valide, INTERROMPT le démarrage en nommant la clé — une
|
|
107
|
+
* faute d'écriture ne doit pas attendre le premier clic pour se montrer. Un nom
|
|
108
|
+
* inconnu du registre reste un WARNING.
|
|
41
109
|
*
|
|
42
|
-
* Les fournisseurs sont construits
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
110
|
+
* Les fournisseurs sont construits une fois puis mémoïsés : c'est là que les
|
|
111
|
+
* points d'entrée d'un émetteur OIDC sont découverts. Un run qui sert (ports
|
|
112
|
+
* ouverts) les construit DÈS le boot, sans l'attendre : un émetteur injoignable
|
|
113
|
+
* n'est pas une faute de configuration, il se journalise et son bouton quitte
|
|
114
|
+
* l'écran de connexion jusqu'à ce qu'une nouvelle tentative réussisse. Un run
|
|
115
|
+
* console ne touche jamais le réseau.
|
|
47
116
|
*
|
|
48
117
|
* Le service ne touche **ni HTTP ni session** : il rend à l'appelant les éléments
|
|
49
118
|
* (URL, state, verifier) que le controller persiste en session — testable sans
|
|
@@ -78,6 +147,9 @@ declare class OAuth2Service extends Service {
|
|
|
78
147
|
/**
|
|
79
148
|
* Redirections post-login (succès / échec) — lues par le controller.
|
|
80
149
|
* Surcharge PAR FOURNISSEUR si fournie, sinon valeur globale, sinon défaut.
|
|
150
|
+
*
|
|
151
|
+
* @remarks L'échec mène par défaut à la page de connexion (`loginPage.path`) :
|
|
152
|
+
* déplacer la page déplace la redirection, les deux ne peuvent pas diverger.
|
|
81
153
|
*/
|
|
82
154
|
getRedirects(provider?: string): {
|
|
83
155
|
success: string;
|
|
@@ -87,9 +159,13 @@ declare class OAuth2Service extends Service {
|
|
|
87
159
|
* Étape 1 — prépare l'URL d'autorisation + les éléments anti-replay à stocker
|
|
88
160
|
* en session (`state`, et `code_verifier` si PKCE).
|
|
89
161
|
*
|
|
162
|
+
* @param options.theme - thème rendu par l'application, transmis au seul
|
|
163
|
+
* fournisseur qui sait l'afficher (Keycloak) ; les autres l'ignorent
|
|
90
164
|
* @throws AuthenticationError — fournisseur non configuré / inconnu du registre.
|
|
91
165
|
*/
|
|
92
|
-
createAuthorization(provider: string
|
|
166
|
+
createAuthorization(provider: string, options?: {
|
|
167
|
+
theme?: "light" | "dark";
|
|
168
|
+
}): Promise<IOAuthAuthorization>;
|
|
93
169
|
/**
|
|
94
170
|
* Étape 2 — valide la réponse, échange le `code`, lit le profil et provisionne
|
|
95
171
|
* l'utilisateur local (Shadow User). Retourne l'identifiant à ouvrir en session.
|
|
@@ -100,7 +176,67 @@ declare class OAuth2Service extends Service {
|
|
|
100
176
|
*/
|
|
101
177
|
exchangeAndProvision(provider: string, code: string, codeVerifier: string | null, returnedIss: string | null): Promise<{
|
|
102
178
|
identifier: string;
|
|
179
|
+
logoutHint: IFederatedLogoutHint | null;
|
|
103
180
|
}>;
|
|
181
|
+
/**
|
|
182
|
+
* Retient en session l'indice de déconnexion d'un login fédéré — à appeler
|
|
183
|
+
* APRÈS l'ouverture de la session authentifiée (l'ID de session est
|
|
184
|
+
* régénéré à l'ouverture).
|
|
185
|
+
*
|
|
186
|
+
* @param session - session authentifiée de la requête.
|
|
187
|
+
* @param hint - indice rendu par {@link exchangeAndProvision}, ou `null`.
|
|
188
|
+
*/
|
|
189
|
+
rememberLogout(session: IHintSession, hint: IFederatedLogoutHint | null): void;
|
|
190
|
+
/**
|
|
191
|
+
* Adresse de déconnexion chez le fournisseur qui a ouvert la session, ou
|
|
192
|
+
* `null` : session locale, fournisseur sans point de déconnexion, ou
|
|
193
|
+
* fournisseur devenu indisponible. À lire AVANT de détruire la session.
|
|
194
|
+
*
|
|
195
|
+
* @param session - session courante, ou `null`.
|
|
196
|
+
* @returns l'URL vers laquelle envoyer le navigateur, ou `null`.
|
|
197
|
+
*/
|
|
198
|
+
logoutUrlFor(session: IHintSession | null): Promise<string | null>;
|
|
199
|
+
/**
|
|
200
|
+
* Déconnexion demandée PAR le fournisseur sur le canal arrière (OpenID
|
|
201
|
+
* Connect Back-Channel Logout 1.0) : vérifie le jeton, refuse un rejeu, puis
|
|
202
|
+
* détruit les sessions locales qu'il désigne.
|
|
203
|
+
*
|
|
204
|
+
* Une session est désignée si elle a été ouverte par CE fournisseur (index
|
|
205
|
+
* fédéré posé au login par {@link rememberLogout}) et, selon le jeton : porte son `sid` ; ou,
|
|
206
|
+
* sans `sid`, appartient à l'utilisateur lié à son `sub`. Une session ouverte
|
|
207
|
+
* par mot de passe ou par un autre fournisseur n'est jamais touchée — le
|
|
208
|
+
* fournisseur ne ferme que ce qu'il a ouvert.
|
|
209
|
+
*
|
|
210
|
+
* @remarks Les connexions WebSocket portées par une session détruite se
|
|
211
|
+
* ferment au tick de revalidation du hub temps réel (code `4001`).
|
|
212
|
+
*
|
|
213
|
+
* @param provider - fournisseur désigné par l'adresse appelée.
|
|
214
|
+
* @param logoutToken - paramètre `logout_token` reçu, brut.
|
|
215
|
+
* @returns `unsupported` si le fournisseur ne sait pas émettre de jeton de
|
|
216
|
+
* déconnexion ; `refused` si le jeton est invalide ou rejoué ; sinon le
|
|
217
|
+
* nombre de sessions détruites (0 : déjà déconnecté, c'est un succès §2.7).
|
|
218
|
+
* @throws Error - vérification impossible (jeu de clés injoignable) ou
|
|
219
|
+
* sessions impossibles à parcourir : la déconnexion a ÉCHOUÉ.
|
|
220
|
+
*/
|
|
221
|
+
backchannelLogout(provider: string, logoutToken: string): Promise<{
|
|
222
|
+
readonly outcome: "unsupported" | "refused";
|
|
223
|
+
} | {
|
|
224
|
+
readonly outcome: "done";
|
|
225
|
+
readonly destroyed: number;
|
|
226
|
+
}>;
|
|
227
|
+
/**
|
|
228
|
+
* Diagnostique les fournisseurs CONFIGURÉS sans connexion humaine : découverte
|
|
229
|
+
* et émetteur, URL de retour acceptée, secret accepté (cf
|
|
230
|
+
* `diagnoseOAuthProvider`).
|
|
231
|
+
*
|
|
232
|
+
* Le fournisseur est construit à NEUF, hors de la mémoïsation du login : un
|
|
233
|
+
* diagnostic doit voir le serveur tel qu'il est maintenant, et son échec ne
|
|
234
|
+
* doit pas retirer un bouton de l'écran de connexion.
|
|
235
|
+
*
|
|
236
|
+
* @param name - un seul fournisseur ; à défaut, tous ceux de la configuration.
|
|
237
|
+
* @returns un diagnostic par fournisseur ; vide si le social login est éteint.
|
|
238
|
+
*/
|
|
239
|
+
diagnose(name?: string): Promise<IOAuthDiagnosis[]>;
|
|
104
240
|
}
|
|
105
241
|
export default OAuth2Service;
|
|
106
242
|
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,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { IUser, IUserProvider } from "@nodefony/user";
|
|
2
|
+
/**
|
|
3
|
+
* Claim d'un jeton d'accès émis par l'application qui porte l'identifiant
|
|
4
|
+
* IMMUABLE du compte (`IUser.id`) — `sub` porte l'identifiant fonctionnel.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ACCOUNT_ID_CLAIM = "uid";
|
|
7
|
+
/**
|
|
8
|
+
* Clé, dans `metadata` d'un enregistrement de jeton (refresh, clé d'API) comme
|
|
9
|
+
* dans le `metaBag` d'une session, qui porte l'identifiant immuable du compte.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ACCOUNT_ID_KEY = "accountId";
|
|
12
|
+
/**
|
|
13
|
+
* Claim d'un jeton d'accès émis par l'application qui porte la version des
|
|
14
|
+
* identifiants du compte à l'émission (`IUser.credentialsVersion`).
|
|
15
|
+
*/
|
|
16
|
+
export declare const CREDENTIALS_VERSION_CLAIM = "cv";
|
|
17
|
+
/**
|
|
18
|
+
* Clé, dans `metadata` d'un enregistrement de jeton comme dans le `metaBag`
|
|
19
|
+
* d'une session, qui porte la version des identifiants lue à l'émission.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CREDENTIALS_VERSION_KEY = "credentialsVersion";
|
|
22
|
+
/**
|
|
23
|
+
* La version des identifiants à inscrire dans un artefact qu'on émet pour ce
|
|
24
|
+
* compte — 0 quand le compte n'en porte pas encore.
|
|
25
|
+
*
|
|
26
|
+
* @param user - compte pour lequel l'artefact est émis.
|
|
27
|
+
* @returns la version courante, jamais `null`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function credentialsVersionOf(user: IUser): number;
|
|
30
|
+
/**
|
|
31
|
+
* Dit si un artefact d'accès (jeton, session, clé) appartient bien au compte
|
|
32
|
+
* qu'on vient de recharger par son identifiant fonctionnel, ET s'il a été émis
|
|
33
|
+
* depuis le dernier changement de mot de passe.
|
|
34
|
+
*
|
|
35
|
+
* Les artefacts sont rangés sous l'`identifier`, qu'un compte RECRÉÉ peut
|
|
36
|
+
* reprendre : supprimé hors du framework (`DELETE` SQL, restauration, import)
|
|
37
|
+
* puis revenu sous le même nom, l'identité se rechargerait et l'artefact de
|
|
38
|
+
* l'ancien titulaire ouvrirait le compte du nouveau. L'identifiant immuable
|
|
39
|
+
* lève l'ambiguïté sans dépendre d'aucun événement ni d'aucun stockage : deux
|
|
40
|
+
* comptes successifs portent deux `id` différents. SEULE règle de
|
|
41
|
+
* comparaison — chaque authentificateur l'appelle, aucun ne la recopie.
|
|
42
|
+
*
|
|
43
|
+
* Un artefact SANS liaison est refusé : il a été émis avant que la règle
|
|
44
|
+
* existe, et rien ne dit à qui il appartient.
|
|
45
|
+
*
|
|
46
|
+
* La version des identifiants fait de la coupure au changement de mot de passe
|
|
47
|
+
* un ÉTAT du compte, et non une suppression : si la cascade de révocation
|
|
48
|
+
* échoue, ou si une requête en vol sur un autre processus réécrit la session
|
|
49
|
+
* qu'on vient d'effacer, l'artefact porte toujours l'ancienne version, et il
|
|
50
|
+
* tombe ici. Un artefact émis avant cette règle n'en porte pas : il vaut 0,
|
|
51
|
+
* comme un compte qui n'a jamais changé de mot de passe — le déploiement ne
|
|
52
|
+
* déconnecte personne. Une valeur d'un autre type est refusée.
|
|
53
|
+
*
|
|
54
|
+
* ⚠️ Chemin chaud — appelée sur CHAQUE requête authentifiée : deux comparaisons
|
|
55
|
+
* de chaîne et d'entier, aucune allocation.
|
|
56
|
+
*
|
|
57
|
+
* @param user - compte rechargé par l'identifiant que porte l'artefact.
|
|
58
|
+
* @param boundAccountId - identifiant immuable inscrit dans l'artefact.
|
|
59
|
+
* @param boundCredentialsVersion - version des identifiants inscrite dans
|
|
60
|
+
* l'artefact ({@link CREDENTIALS_VERSION_KEY}), `undefined` si absente.
|
|
61
|
+
* Obligatoire dans la signature pour qu'aucune porte ne l'oublie.
|
|
62
|
+
* @returns `true` si l'artefact a été émis pour CE compte, à sa version courante.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isBoundToAccount(user: IUser, boundAccountId: unknown, boundCredentialsVersion: unknown): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Recharge le compte qu'un jeton d'accès de CETTE application désigne, ou
|
|
67
|
+
* `null` : sujet inconnu, compte inactif ou verrouillé, jeton émis pour un
|
|
68
|
+
* autre compte du même nom ou avant un changement de mot de passe.
|
|
69
|
+
*
|
|
70
|
+
* SEULE implémentation de la règle « ce jeton ouvre-t-il encore ce compte ? »
|
|
71
|
+
* pour un jeton maison : la zone `jwt` (`JwtAuthenticator`) et le résolveur de
|
|
72
|
+
* compte d'une ressource protégée l'appellent, aucun ne la recopie. Un refus
|
|
73
|
+
* est `null`, jamais une exception : chaque appelant le traduit dans son
|
|
74
|
+
* propre contrat (401 uniforme, outil en `isError`).
|
|
75
|
+
*
|
|
76
|
+
* @param provider - annuaire des comptes de l'application.
|
|
77
|
+
* @param identifier - identifiant fonctionnel porté par le jeton (`sub`).
|
|
78
|
+
* @param boundAccountId - identifiant immuable inscrit dans le jeton ({@link ACCOUNT_ID_CLAIM}).
|
|
79
|
+
* @param boundCredentialsVersion - version des identifiants inscrite ({@link CREDENTIALS_VERSION_CLAIM}).
|
|
80
|
+
* @returns le compte, ou `null` s'il n'est pas (ou plus) celui du jeton.
|
|
81
|
+
*/
|
|
82
|
+
export declare function loadBoundAccount(provider: IUserProvider, identifier: string, boundAccountId: unknown, boundCredentialsVersion: unknown): Promise<IUser | null>;
|
|
@@ -1,31 +1,91 @@
|
|
|
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
|
+
* Purge, à la NAISSANCE d'un compte, tout ce qu'un ancien titulaire du même
|
|
34
|
+
* identifiant a laissé derrière lui : sessions, jetons et facteurs. Un compte
|
|
35
|
+
* qui vient de naître n'a encore rien émis — tout artefact déjà rangé sous son
|
|
36
|
+
* identifiant appartient donc à quelqu'un d'autre. C'est ce qui ferme le cas
|
|
37
|
+
* d'un compte supprimé HORS du framework (`DELETE` SQL, aucun événement), dont
|
|
38
|
+
* l'accès redeviendrait valide dès qu'un compte renaît sous son nom.
|
|
39
|
+
*
|
|
40
|
+
* Le seuil est tronqué à la SECONDE : l'`iat` d'un JWT l'est aussi (RFC 7519
|
|
41
|
+
* §2, NumericDate), et un seuil à la milliseconde rejetterait le jeton du
|
|
42
|
+
* nouveau titulaire émis dans la même seconde que sa création.
|
|
43
|
+
*
|
|
44
|
+
* @param container - container du kernel.
|
|
45
|
+
* @param identifier - `IUser.identifier` du compte créé.
|
|
46
|
+
* @param now - horloge (epoch ms) injectable pour les tests.
|
|
47
|
+
*/
|
|
48
|
+
export declare function purgeInheritedAccess(container: ServiceLookupLike, identifier: string, now?: number): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Retire les facteurs rangés sous un identifiant — secret TOTP (codes de
|
|
51
|
+
* récupération compris) et passkeys. SEULE implémentation du geste, appelée à
|
|
52
|
+
* la suppression d'un compte ET à la création d'un compte sous cet identifiant.
|
|
53
|
+
* Best-effort par brique : un TOTP indisponible n'empêche pas le retrait des
|
|
54
|
+
* passkeys.
|
|
55
|
+
*
|
|
56
|
+
* @param container - container du kernel (services `totp`, `webauthn`).
|
|
57
|
+
* @param identifier - `IUser.identifier` du compte.
|
|
58
|
+
*/
|
|
59
|
+
export declare function removeAccountFactors(container: ServiceLookupLike, identifier: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Abonne la cascade au bus kernel, UNE fois, au `onKernelBoot` de
|
|
62
|
+
* `@nodefony/security` — avant qu'aucun compte ne puisse être créé ou révoqué
|
|
63
|
+
* (les comptes semés au démarrage compris). Deux abonnements :
|
|
64
|
+
* - {@link USER_REVOKED_EVENT} → {@link cascadeUserRevocation} ;
|
|
65
|
+
* - {@link USER_CREATED_EVENT} → {@link purgeInheritedAccess} : sessions,
|
|
66
|
+
* jetons et facteurs HÉRITÉS. Un compte qui vient de naître n'a encore rien
|
|
67
|
+
* émis ni enrôlé, donc tout artefact déjà rangé sous son identifiant
|
|
68
|
+
* appartient à un ancien titulaire. Règle exacte, aucune date à comparer.
|
|
69
|
+
* ⚠️ Un import qui chargerait les facteurs AVANT les comptes les verrait
|
|
70
|
+
* retirés : importer les comptes d'abord.
|
|
71
|
+
*
|
|
72
|
+
* Les deux sont émis par `UserService` seul, et attendus : chaque handler rend
|
|
73
|
+
* sa promesse. **Extensible** : tout autre module (webhooks…) s'abonne aux
|
|
74
|
+
* MÊMES événements pour ses propres artefacts, sans toucher à ce fichier.
|
|
26
75
|
*
|
|
27
76
|
* @param kernel - bus d'événements (kernel) exposant `on`.
|
|
28
|
-
* @param container - container capturé par
|
|
77
|
+
* @param container - container capturé par les handlers.
|
|
29
78
|
*/
|
|
30
79
|
export declare function registerUserRevocationCascade(kernel: KernelListenerLike, container: Container): void;
|
|
80
|
+
/**
|
|
81
|
+
* Avis de démarrage quand le service `users` n'émet pas sur le bus du kernel —
|
|
82
|
+
* construit sans container (`new UserService(repo, encoder)`). Ses événements
|
|
83
|
+
* partent alors sur son propre bus, où personne n'écoute : supprimer un compte
|
|
84
|
+
* laisserait ses sessions, ses jetons et ses facteurs en place, SANS UN MOT.
|
|
85
|
+
*
|
|
86
|
+
* @param container - container du kernel.
|
|
87
|
+
* @param kernel - le kernel attendu comme bus des événements.
|
|
88
|
+
* @returns l'avis à déclarer, ou `null` si `users` est absent ou bien câblé.
|
|
89
|
+
*/
|
|
90
|
+
export declare function usersEventBusNotice(container: ServiceLookupLike, kernel: object): IBootNotice | null;
|
|
31
91
|
export {};
|