@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
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
// \0@oxc-project+runtime@0.155.0/helpers/esm/decorate.js
|
|
2
2
|
function __decorate(decorators, target, key, desc) {
|
|
3
3
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
4
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
5
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
7
|
}
|
|
8
|
-
//#endregion
|
|
9
8
|
export { __decorate as default };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { JwtKeystore } from "./nodefony/src/token/JwtKeystore.js";
|
|
1
2
|
import config from "./nodefony/config/config.js";
|
|
2
3
|
import { SecuredArea } from "./nodefony/src/SecuredArea.js";
|
|
3
4
|
import { LoginThrottler } from "./nodefony/src/throttle/LoginThrottler.js";
|
|
5
|
+
import { InsecureTransportError } from "./nodefony/errors/InsecureTransportError.js";
|
|
6
|
+
import { CredentialTransportPolicy, shouldEnforceCredentialTransport } from "./nodefony/src/transport/CredentialTransportPolicy.js";
|
|
4
7
|
import { RoleHierarchyWalker } from "./nodefony/src/RoleHierarchyWalker.js";
|
|
5
8
|
import { CsrfTokenManager } from "./nodefony/src/csrfToken.js";
|
|
6
9
|
import { CsrfError } from "./nodefony/errors/CsrfError.js";
|
|
@@ -26,13 +29,13 @@ import { getAuthenticatorFactory, listAuthenticatorFactories, registerAuthentica
|
|
|
26
29
|
import { recordAudit } from "./nodefony/src/audit/recordAudit.js";
|
|
27
30
|
import { readAuditContext } from "./nodefony/src/audit/readAuditContext.js";
|
|
28
31
|
import { Firewall } from "./nodefony/service/firewall.js";
|
|
32
|
+
import { amrForFactor } from "./nodefony/src/sessionAuthentication.js";
|
|
29
33
|
import { AuthFlow } from "./nodefony/service/authFlow.js";
|
|
30
34
|
import { InvalidTargetError } from "./nodefony/errors/InvalidTargetError.js";
|
|
31
35
|
import { TOKEN_DEFAULT_ORDER, TOKEN_SORTABLE_FIELDS } from "./nodefony/src/token/tokenSort.js";
|
|
32
36
|
import { matchesTokenStatus, tokenStatusOf } from "./nodefony/src/token/tokenStatus.js";
|
|
33
37
|
import { MemoryTokenStore } from "./nodefony/src/token/MemoryTokenStore.js";
|
|
34
38
|
import { getTokenStoreFactory, listTokenStores, registerTokenStore } from "./nodefony/src/token/tokenStoreRegistry.js";
|
|
35
|
-
import { JwtKeystore } from "./nodefony/src/token/JwtKeystore.js";
|
|
36
39
|
import { TokenService } from "./nodefony/service/tokenService.js";
|
|
37
40
|
import { RemoteJwtVerifier } from "./nodefony/src/token/RemoteJwtVerifier.js";
|
|
38
41
|
import { AccessTokenVerifierService } from "./nodefony/service/accessTokenVerifier.js";
|
|
@@ -44,11 +47,13 @@ import { Authorization } from "./nodefony/service/authorization.js";
|
|
|
44
47
|
import { MemoryWebAuthnCredentialStore } from "./nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js";
|
|
45
48
|
import { getWebAuthnStoreFactory, listWebAuthnStores, registerWebAuthnStore } from "./nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js";
|
|
46
49
|
import { WebAuthnService } from "./nodefony/service/webAuthn.js";
|
|
50
|
+
import { KeycloakAdminClient, KeycloakAdminError, keycloakAdminTarget, keycloakLoginThemeCheck, readKeycloakLoginTheme } from "./nodefony/keycloak/keycloakAdmin.js";
|
|
47
51
|
import { OAuth2Client, OAuth2RequestError, OAuth2Tokens, createCodeChallenge, generateCodeVerifier, generateState } from "./nodefony/src/oauth/oauth2Client.js";
|
|
48
52
|
import { discoverAuthorizationServer } from "./nodefony/src/oauth/metadata.js";
|
|
49
|
-
import { createDiscoveredOidcProvider, createOidcProvider } from "./nodefony/src/oauth/providers/oidc.js";
|
|
53
|
+
import { createDiscoveredOidcProvider, createOidcProvider, readLogoutTokenClaims } from "./nodefony/src/oauth/providers/oidc.js";
|
|
50
54
|
import { createGithubProvider } from "./nodefony/src/oauth/providers/github.js";
|
|
51
|
-
import { getOAuthProviderFactory, listOAuthProviders, registerOAuthProvider } from "./nodefony/src/oauth/oauthProviderRegistry.js";
|
|
55
|
+
import { getOAuthProviderFactory, listOAuthProviders, oauthProviderRequiresIssuer, registerOAuthProvider } from "./nodefony/src/oauth/oauthProviderRegistry.js";
|
|
56
|
+
import { diagnoseOAuthProvider } from "./nodefony/src/oauth/providerDiagnosis.js";
|
|
52
57
|
import { OAuth2Service } from "./nodefony/service/oauth2.js";
|
|
53
58
|
import { TOKEN_FACETS } from "./nodefony/src/token/tokenFilters.js";
|
|
54
59
|
import { ApiKeyError } from "./nodefony/errors/ApiKeyError.js";
|
|
@@ -69,24 +74,28 @@ import { getWebhookStoreFactory, listWebhookStores, registerWebhookStore } from
|
|
|
69
74
|
import { SsrfError } from "./nodefony/errors/SsrfError.js";
|
|
70
75
|
import { assertPublicUrl, isBlockedAddress } from "./nodefony/src/net/ssrfGuard.js";
|
|
71
76
|
import { WebhookService } from "./nodefony/service/webhooks.js";
|
|
77
|
+
import { KeycloakService } from "./nodefony/service/keycloak.js";
|
|
72
78
|
import SecuritySecrets from "./nodefony/command/security-secrets.js";
|
|
73
79
|
import SecurityUserAdd from "./nodefony/command/security-user-add.js";
|
|
74
80
|
import SecurityUserList from "./nodefony/command/security-user-list.js";
|
|
75
81
|
import SecurityUserDelete from "./nodefony/command/security-user-delete.js";
|
|
76
82
|
import SecurityUserPassword from "./nodefony/command/security-user-password.js";
|
|
77
|
-
import
|
|
83
|
+
import { publicRoutesNotice } from "./nodefony/src/boot/publicRoutesNotice.js";
|
|
84
|
+
import SecurityMcpToken from "./nodefony/command/security-mcp-token.js";
|
|
85
|
+
import SecurityKeycloakRealm from "./nodefony/keycloak/security-keycloak-realm.js";
|
|
86
|
+
import SecurityOAuthDoctor from "./nodefony/command/security-oauth-doctor.js";
|
|
78
87
|
import { createSecurityAdminApi, parseAuditQuery, registerSecurityAdminApi } from "./nodefony/src/admin/SecurityAdminApi.js";
|
|
79
|
-
import { registerUserRevocationCascade } from "./nodefony/src/admin/userRevocationCascade.js";
|
|
80
|
-
import
|
|
81
|
-
import __decorate from "./_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js";
|
|
88
|
+
import { registerUserRevocationCascade, usersEventBusNotice } from "./nodefony/src/admin/userRevocationCascade.js";
|
|
89
|
+
import __decorate from "./_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
82
90
|
import { tokenStatusCriteria } from "./nodefony/src/token/tokenCriteria.js";
|
|
91
|
+
import { requestClientCredentialsToken } from "./nodefony/src/oauth/clientCredentials.js";
|
|
83
92
|
import { AccessDeniedError } from "./nodefony/errors/AccessDeniedError.js";
|
|
84
93
|
import "./nodefony/errors/index.js";
|
|
85
94
|
import "./nodefony/contracts/index.js";
|
|
86
|
-
import {
|
|
95
|
+
import { Module, services } from "nodefony";
|
|
87
96
|
import { fileURLToPath } from "node:url";
|
|
88
97
|
import { registerUserAdminApi } from "@nodefony/user";
|
|
89
|
-
|
|
98
|
+
// index.ts
|
|
90
99
|
/**
|
|
91
100
|
* `@nodefony/security` — couche de sécurité de Nodefony (refonte 2026, P6).
|
|
92
101
|
*
|
|
@@ -96,7 +105,7 @@ import { registerUserAdminApi } from "@nodefony/user";
|
|
|
96
105
|
* + Zod validée au boot (fail-closed si invalide). Consomme `@nodefony/user`
|
|
97
106
|
* (IUser/IUserProvider/IPasswordVerifier) — jamais l'inverse.
|
|
98
107
|
*
|
|
99
|
-
* Livré : firewall (zones, mode first|all, challenge RFC
|
|
108
|
+
* Livré : firewall (zones, mode first|all, challenge RFC 9110 §11), authenticators
|
|
100
109
|
* `anonymous`/`userpassword` (Basic RFC 7617)/`session` (BFF, J3), flux
|
|
101
110
|
* login/logout/me (`AuthFlow`, anti-fixation + throttling partagé).
|
|
102
111
|
* Jwt/oauth2/mtls/apikey, CORS, CSRF, autorisation par décorateurs et data
|
|
@@ -110,7 +119,9 @@ let Security = class Security extends Module {
|
|
|
110
119
|
this.addCommand(SecurityUserList);
|
|
111
120
|
this.addCommand(SecurityUserDelete);
|
|
112
121
|
this.addCommand(SecurityUserPassword);
|
|
113
|
-
this.addCommand(
|
|
122
|
+
this.addCommand(SecurityMcpToken);
|
|
123
|
+
this.addCommand(SecurityKeycloakRealm);
|
|
124
|
+
this.addCommand(SecurityOAuthDoctor);
|
|
114
125
|
}
|
|
115
126
|
/**
|
|
116
127
|
* JSON Schema de la config security → data plane admin (config riche Studio).
|
|
@@ -135,7 +146,28 @@ let Security = class Security extends Module {
|
|
|
135
146
|
registerSecurityAdminApi(registry, container);
|
|
136
147
|
registerUserAdminApi(registry, container);
|
|
137
148
|
}
|
|
138
|
-
|
|
149
|
+
const kernel = this.kernel;
|
|
150
|
+
if (kernel && container) {
|
|
151
|
+
registerUserRevocationCascade(kernel, container);
|
|
152
|
+
kernel.once("onPostReady", () => {
|
|
153
|
+
const notice = usersEventBusNotice(container, kernel);
|
|
154
|
+
if (notice) kernel.reportBootNotice(notice);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Déclare au bilan de démarrage les routes métier laissées PUBLIQUES.
|
|
161
|
+
*
|
|
162
|
+
* `onReady` et pas `onBoot` : les zones sont compilées par le pare-feu à son
|
|
163
|
+
* propre `onBoot`, posé APRÈS celui du module — à `onBoot` la liste est encore
|
|
164
|
+
* vide. `onReady` précède l'écriture du bilan (`var/last-boot.json`).
|
|
165
|
+
*/
|
|
166
|
+
async onKernelReady() {
|
|
167
|
+
const firewall = this.kernel?.container?.get("firewall");
|
|
168
|
+
if (!firewall) return this;
|
|
169
|
+
const notice = publicRoutesNotice(firewall.describe().zones);
|
|
170
|
+
if (notice) this.kernel?.reportBootNotice(notice);
|
|
139
171
|
return this;
|
|
140
172
|
}
|
|
141
173
|
};
|
|
@@ -150,8 +182,8 @@ Security = __decorate([services([
|
|
|
150
182
|
OAuth2Service,
|
|
151
183
|
AuditService,
|
|
152
184
|
TotpService,
|
|
153
|
-
WebhookService
|
|
154
|
-
|
|
185
|
+
WebhookService,
|
|
186
|
+
KeycloakService
|
|
187
|
+
])], Security);
|
|
155
188
|
var security_default = Security;
|
|
156
|
-
|
|
157
|
-
export { AccessDeniedError, AccessTokenVerifierService, AnonymousAuthenticator, AnonymousToken, ApiKeyAuthenticator, ApiKeyError, ApiKeyService, AuditService, AuthFlow, AuthenticationError, Authorization, Cors, Csrf, CsrfError, CsrfTokenManager, ExternalJwtAuthenticator, Firewall, InvalidTargetError, JwtAuthenticator, JwtKeystore, LoginThrottler, MemoryAuditStore, MemoryTokenStore, MemoryTotpSecretStore, MemoryWebAuthnCredentialStore, MemoryWebhookStore, OAuth2Client, OAuth2RequestError, OAuth2Service, OAuth2Tokens, RemoteJwtVerifier, RoleHierarchyWalker, RoleVoter, ScopeVoter, SecuredArea, SecurityHeaders, SessionAuthenticator, SsrfError, TOKEN_DEFAULT_ORDER, TOKEN_FACETS, TOKEN_SORTABLE_FIELDS, ThrottledError, TokenService, TotpService, UnverifiableTokenError, UserPasswordAuthenticator, UserToken, VoterVote, WEBHOOK_DEFAULT_ORDER, WEBHOOK_SORTABLE_FIELDS, WebAuthnService, WebhookService, assertPublicUrl, base32Decode, beginTotpEnrollment, confirmTotpEnrollment, createCodeChallenge, createDiscoveredOidcProvider, createGithubProvider, createOidcProvider, createSecurityAdminApi, decryptSecret, security_default as default, defineSecurityConfig, deriveKey, deriveTotpKey, disableTotp, discoverAuthorizationServer, encryptSecret, generateApiKey, generateCodeVerifier, generateEphemeralKey, generateState, getAuditStoreFactory, getAuthenticatorFactory, getOAuthProviderFactory, getTokenStoreFactory, getTotpStoreFactory, getWebAuthnStoreFactory, getWebhookStoreFactory, hashApiKey, isBlockedAddress, listAuditStores, listAuthenticatorFactories, listOAuthProviders, listTokenStores, listTotpStores, listVoterFactories, listWebAuthnStores, listWebhookStores, looksLikeApiKey, matchesTokenStatus, parseApiKey, parseAuditQuery, peekIssuer, readAuditContext, recordAudit, registerAuditStore, registerAuthenticatorFactory, registerOAuthProvider, registerSecurityAdminApi, registerTokenStore, registerTotpStore, registerVoterFactory, registerWebAuthnStore, registerWebhookStore, resolveJwtRuntime, securityConfigJsonSchema, tokenStatusCriteria, tokenStatusOf, totpCode, totpStatus, verifyTotpLogin };
|
|
189
|
+
export { AccessDeniedError, AccessTokenVerifierService, AnonymousAuthenticator, AnonymousToken, ApiKeyAuthenticator, ApiKeyError, ApiKeyService, AuditService, AuthFlow, AuthenticationError, Authorization, Cors, CredentialTransportPolicy, Csrf, CsrfError, CsrfTokenManager, ExternalJwtAuthenticator, Firewall, InsecureTransportError, InvalidTargetError, JwtAuthenticator, JwtKeystore, KeycloakAdminClient, KeycloakAdminError, KeycloakService, LoginThrottler, MemoryAuditStore, MemoryTokenStore, MemoryTotpSecretStore, MemoryWebAuthnCredentialStore, MemoryWebhookStore, OAuth2Client, OAuth2RequestError, OAuth2Service, OAuth2Tokens, RemoteJwtVerifier, RoleHierarchyWalker, RoleVoter, ScopeVoter, SecuredArea, SecurityHeaders, SessionAuthenticator, SsrfError, TOKEN_DEFAULT_ORDER, TOKEN_FACETS, TOKEN_SORTABLE_FIELDS, ThrottledError, TokenService, TotpService, UnverifiableTokenError, UserPasswordAuthenticator, UserToken, VoterVote, WEBHOOK_DEFAULT_ORDER, WEBHOOK_SORTABLE_FIELDS, WebAuthnService, WebhookService, amrForFactor, assertPublicUrl, base32Decode, beginTotpEnrollment, confirmTotpEnrollment, createCodeChallenge, createDiscoveredOidcProvider, createGithubProvider, createOidcProvider, createSecurityAdminApi, decryptSecret, security_default as default, defineSecurityConfig, deriveKey, deriveTotpKey, diagnoseOAuthProvider, disableTotp, discoverAuthorizationServer, encryptSecret, generateApiKey, generateCodeVerifier, generateEphemeralKey, generateState, getAuditStoreFactory, getAuthenticatorFactory, getOAuthProviderFactory, getTokenStoreFactory, getTotpStoreFactory, getWebAuthnStoreFactory, getWebhookStoreFactory, hashApiKey, isBlockedAddress, keycloakAdminTarget, keycloakLoginThemeCheck, listAuditStores, listAuthenticatorFactories, listOAuthProviders, listTokenStores, listTotpStores, listVoterFactories, listWebAuthnStores, listWebhookStores, looksLikeApiKey, matchesTokenStatus, oauthProviderRequiresIssuer, parseApiKey, parseAuditQuery, peekIssuer, readAuditContext, readKeycloakLoginTheme, readLogoutTokenClaims, recordAudit, registerAuditStore, registerAuthenticatorFactory, registerOAuthProvider, registerSecurityAdminApi, registerTokenStore, registerTotpStore, registerVoterFactory, registerWebAuthnStore, registerWebhookStore, requestClientCredentialsToken, resolveJwtRuntime, securityConfigJsonSchema, shouldEnforceCredentialTransport, tokenStatusCriteria, tokenStatusOf, totpCode, totpStatus, verifyTotpLogin };
|
|
@@ -3,7 +3,7 @@ import { ADMIN_SCOPE_READ, ADMIN_SCOPE_WRITE, AGENT_TARGETS, CONSOLE_DATA_RUN_PR
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
|
-
|
|
6
|
+
// nodefony/command/security-mcp-token.ts
|
|
7
7
|
const options = {
|
|
8
8
|
runProfile: CONSOLE_DATA_RUN_PROFILE,
|
|
9
9
|
helpGroup: "COMPTES ET SECRETS",
|
|
@@ -38,13 +38,43 @@ function ttlSeconds(raw) {
|
|
|
38
38
|
if (minutes > TTL_MAX_MINUTES) return /* @__PURE__ */ new Error(`--ttl est borné à ${TTL_MAX_MINUTES} minutes (30 jours) — un jeton posé dans un fichier est une clé, et une clé se remplace`);
|
|
39
39
|
return minutes * 60;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Durée d'un jeton POSÉ dans un agent : 8 h, une journée de travail.
|
|
43
|
+
*
|
|
44
|
+
* L'en-tête d'un agent est figé, rien ne le rafraîchit : le défaut de la
|
|
45
|
+
* configuration (15 min, taillé pour un client d'API qui renouvelle son jeton)
|
|
46
|
+
* y produisait un 401 au bout d'un quart d'heure, qui n'accusait pas
|
|
47
|
+
* l'expiration. Huit heures évitent la relance dans la journée et bornent une
|
|
48
|
+
* fuite à quelques heures. Le défaut global (`jwt.accessTtlS`) ne bouge pas :
|
|
49
|
+
* le relever allongerait TOUS les jetons de l'application.
|
|
50
|
+
*/
|
|
51
|
+
const AGENT_TTL_MINUTES = 480;
|
|
52
|
+
/**
|
|
53
|
+
* Durée demandée à l'émetteur : `--ttl` explicite, sinon 8 h quand le jeton
|
|
54
|
+
* part chez les agents, sinon rien — la configuration décide.
|
|
55
|
+
*
|
|
56
|
+
* @param raw - la valeur de `--ttl` telle que tapée, ou rien
|
|
57
|
+
* @param write - le jeton sera posé dans la configuration des agents
|
|
58
|
+
* @returns les secondes, `undefined` pour le défaut de configuration, une `Error` sinon
|
|
59
|
+
*/
|
|
60
|
+
function tokenTtlSeconds(raw, write) {
|
|
61
|
+
if (raw !== void 0) return ttlSeconds(raw);
|
|
62
|
+
return write ? AGENT_TTL_MINUTES * 60 : void 0;
|
|
63
|
+
}
|
|
64
|
+
/** « 8 h », « 1 h 30 », « 15 min » — la durée telle qu'on la dit. */
|
|
65
|
+
function humanDuration(seconds) {
|
|
66
|
+
const minutes = Math.round(seconds / 60);
|
|
67
|
+
if (minutes < 60) return `${minutes} min`;
|
|
68
|
+
const rest = minutes % 60;
|
|
69
|
+
return rest === 0 ? `${minutes / 60} h` : `${Math.floor(minutes / 60)} h ${rest}`;
|
|
70
|
+
}
|
|
41
71
|
const GREEN = "\x1B[32m";
|
|
42
72
|
const YELLOW = "\x1B[33m";
|
|
43
73
|
const DIM = "\x1B[2m";
|
|
44
74
|
const BOLD = "\x1B[1m";
|
|
45
75
|
const RESET = "\x1B[0m";
|
|
46
76
|
/**
|
|
47
|
-
* `nodefony security:token` — émet un jeton d'accès pour une porte de cette
|
|
77
|
+
* `nodefony security:mcp:token` — émet un jeton d'accès pour une porte de cette
|
|
48
78
|
* application (la porte MCP par défaut).
|
|
49
79
|
*
|
|
50
80
|
* **Pourquoi une commande, et pas un `curl`.** Le jeton s'obtenait par un appel
|
|
@@ -58,18 +88,26 @@ const RESET = "\x1B[0m";
|
|
|
58
88
|
* raison d'être de la liaison d'audience. La commande la vise d'elle-même,
|
|
59
89
|
* `--resource` ne sert qu'à en viser une autre.
|
|
60
90
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
91
|
+
* `--write` pose la valeur là où l'AGENT la lit (gitignoré), jamais dans le
|
|
92
|
+
* `.env` de l'application. Relancée, elle REMPLACE le jeton chez les agents qui
|
|
93
|
+
* le portent déjà — la rotation est le geste le plus fréquent — et n'en câble
|
|
94
|
+
* aucun nouveau sans le proposer.
|
|
64
95
|
*/
|
|
65
|
-
var
|
|
96
|
+
var SecurityMcpToken = class extends Command {
|
|
66
97
|
constructor(cli) {
|
|
67
|
-
super("security:token", `émet un jeton d'accès pour la porte MCP`, cli,
|
|
98
|
+
super("security:mcp:token", `émet un jeton d'accès pour la porte MCP`, cli, {
|
|
99
|
+
...options,
|
|
100
|
+
examples: [
|
|
101
|
+
"nodefony security:mcp:token",
|
|
102
|
+
"nodefony security:mcp:token --write",
|
|
103
|
+
"nodefony security:mcp:token alice --scope admin:read --ttl 60"
|
|
104
|
+
]
|
|
105
|
+
});
|
|
68
106
|
this.addArgument("[identifier]", "compte porteur du jeton (défaut : admin)");
|
|
69
107
|
this.addOption("-s, --scope <scopes>", `scopes demandés, séparés par des espaces (défaut : « ${ADMIN_SCOPE_READ} » ; ajouter « ${ADMIN_SCOPE_WRITE} » pour les mutations)`);
|
|
70
108
|
this.addOption("-r, --resource <uri>", "audience visée (défaut : la porte MCP de cette application)");
|
|
71
109
|
this.addOption("-a, --agent <noms>", "agents à servir, séparés par des virgules (défaut : ceux détectés ; « none » pour aucun)");
|
|
72
|
-
this.addOption("-t, --ttl <
|
|
110
|
+
this.addOption("-t, --ttl <minutes>", "durée de validité, en minutes (défaut : 8 h avec --write, sinon celle de la config, 15 min)");
|
|
73
111
|
this.addOption("-w, --write", `pose ${MCP_TOKEN_ENV} dans la configuration des agents présents`);
|
|
74
112
|
this.addOption("-j, --json", "sortie JSON (scripts/CI)");
|
|
75
113
|
}
|
|
@@ -239,7 +277,15 @@ var SecurityToken = class extends Command {
|
|
|
239
277
|
const resource = opts.resource ?? this.#defaultResource();
|
|
240
278
|
const requestedScopes = (opts.scope ?? "").split(/\s+/u).filter(Boolean);
|
|
241
279
|
const scopes = requestedScopes.length > 0 ? requestedScopes : [ADMIN_SCOPE_READ];
|
|
242
|
-
|
|
280
|
+
let write = opts.write === true;
|
|
281
|
+
if (!write && !opts.json && process.stdin.isTTY) {
|
|
282
|
+
await this.loadPrompts();
|
|
283
|
+
write = await this.prompts.confirm({
|
|
284
|
+
message: `Poser ${MCP_TOKEN_ENV} dans la configuration des agents présents ?`,
|
|
285
|
+
default: true
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const ttlS = tokenTtlSeconds(opts.ttl, write);
|
|
243
289
|
if (ttlS instanceof Error) {
|
|
244
290
|
this.log(ttlS.message, "ERROR");
|
|
245
291
|
process.exitCode = 1;
|
|
@@ -262,7 +308,7 @@ var SecurityToken = class extends Command {
|
|
|
262
308
|
porteur obtiendrait un jeton pour la ressource de son choix :
|
|
263
309
|
|
|
264
310
|
use("@nodefony/security", {
|
|
265
|
-
jwt: { audiences: ["${resource}"] },\n })\n\n → puis npm run build (le runtime lit le dist)\n` : ` La porte est servie par un module de DÉVELOPPEMENT, absent en\n « ${env} » : un jeton pour une porte absente n'aurait personne\n pour l'accepter.\n\n → NODE_ENV=development nodefony security:token${opts.write ? " --write" : ""}\n`) + ` → ou vise une autre porte : --resource <uri>`, "ERROR");
|
|
311
|
+
jwt: { audiences: ["${resource}"] },\n })\n\n → puis npm run build (le runtime lit le dist)\n` : ` La porte est servie par un module de DÉVELOPPEMENT, absent en\n « ${env} » : un jeton pour une porte absente n'aurait personne\n pour l'accepter.\n\n → NODE_ENV=development nodefony security:mcp:token${opts.write ? " --write" : ""}\n`) + ` → ou vise une autre porte : --resource <uri>`, "ERROR");
|
|
266
312
|
process.exitCode = 1;
|
|
267
313
|
return this;
|
|
268
314
|
}
|
|
@@ -271,30 +317,23 @@ var SecurityToken = class extends Command {
|
|
|
271
317
|
const token = issued.access_token;
|
|
272
318
|
const accordes = issued.scope.split(/\s+/u).filter(Boolean);
|
|
273
319
|
const nonAccordes = scopes.filter((s) => !accordes.includes(s));
|
|
320
|
+
const expiresAt = new Date(Date.now() + issued.expires_in * 1e3);
|
|
274
321
|
if (opts.json) {
|
|
275
322
|
process.stdout.write(`${JSON.stringify({
|
|
276
323
|
access_token: token,
|
|
277
324
|
resource,
|
|
278
325
|
scopes: accordes,
|
|
279
326
|
requested: scopes,
|
|
280
|
-
expires_in: issued.expires_in
|
|
327
|
+
expires_in: issued.expires_in,
|
|
328
|
+
expires_at: expiresAt.toISOString()
|
|
281
329
|
}, null, 2)}\n`);
|
|
282
330
|
return this;
|
|
283
331
|
}
|
|
284
332
|
const w = (s) => {
|
|
285
333
|
process.stdout.write(s);
|
|
286
334
|
};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
await this.loadPrompts();
|
|
290
|
-
write = await this.prompts.confirm({
|
|
291
|
-
message: `Poser ${MCP_TOKEN_ENV} dans la configuration des agents présents ?`,
|
|
292
|
-
default: true
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
if (this.#ephemeralKey()) w(`\n${YELLOW}⚠ Clé de signature ÉPHÉMÈRE — ce jeton sera REFUSÉ.${RESET}\n${DIM} Cette application n'a pas de clé persistante : chaque process en génère\n une au démarrage. Le jeton ci-dessous n'est vérifiable que par le process\n qui vient de le signer — pas par le serveur en marche, qui a la sienne.\n → déclare une source de clés dans nodefony.config.ts :\n use("@nodefony/security", { jwt: { keystore: { dir: "var/keys" } } })\n ou, en production, keySetJson depuis l'environnement.${RESET}\n`);
|
|
296
|
-
const minutes = Math.round(issued.expires_in / 60);
|
|
297
|
-
w(`\n${BOLD}🔑 Jeton d'accès${RESET} ${DIM}— compte ${identifier}, audience ${resource}${RESET}\n${DIM} valable ${minutes} min${accordes.length ? `, scopes : ${accordes.join(" ")}` : ", aucun scope"}${RESET}\n\n`);
|
|
335
|
+
if (this.#ephemeralKey()) w(`\n${YELLOW}⚠ Clé de signature ÉPHÉMÈRE — ce jeton sera REFUSÉ.${RESET}\n${DIM} Cette application n'a pas de clé persistante : chaque process en génère\n une au démarrage. Le jeton ci-dessous n'est vérifiable que par le process\n qui vient de le signer — pas par le serveur en marche, qui a la sienne.\n → déclare une source de clés dans nodefony.config.ts :\n use("@nodefony/security", { jwt: { keystore: { dir: "var/keys" } } })\n ou, en production, keySetJson depuis l'environnement (NF_JWT_KEYSET —\n npx nodefony security:secrets --jwt-keyset).${RESET}\n`);
|
|
336
|
+
w(`\n${BOLD}🔑 Jeton d'accès${RESET} ${DIM}— compte ${identifier}, audience ${resource}${RESET}\n${DIM} valable ${humanDuration(issued.expires_in)}, expire le ${expiresAt.toLocaleString()}${accordes.length ? `, scopes : ${accordes.join(" ")}` : ", aucun scope"}${RESET}\n\n`);
|
|
298
337
|
if (nonAccordes.length > 0) w(`${DIM} ⚠️ non accordé(s) : ${nonAccordes.join(" ")} — réservé(s) au rôle d'administration, que le compte « ${identifier} » ne porte pas.${RESET}\n\n`);
|
|
299
338
|
if (write) {
|
|
300
339
|
const requested = requestedAgents(opts.agent);
|
|
@@ -332,5 +371,4 @@ var SecurityToken = class extends Command {
|
|
|
332
371
|
return this;
|
|
333
372
|
}
|
|
334
373
|
};
|
|
335
|
-
|
|
336
|
-
export { SecurityToken as default, ttlSeconds };
|
|
374
|
+
export { SecurityMcpToken as default, tokenTtlSeconds, ttlSeconds };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Command } from "nodefony";
|
|
2
|
+
// nodefony/command/security-oauth-doctor.ts
|
|
3
|
+
const options = {
|
|
4
|
+
helpGroup: "COMPTES ET SECRETS",
|
|
5
|
+
showBanner: false,
|
|
6
|
+
kernelEvent: "onReady",
|
|
7
|
+
quietBoot: true
|
|
8
|
+
};
|
|
9
|
+
const GREEN = "\x1B[32m";
|
|
10
|
+
const RED = "\x1B[31m";
|
|
11
|
+
const YELLOW = "\x1B[33m";
|
|
12
|
+
const DIM = "\x1B[2m";
|
|
13
|
+
const RESET = "\x1B[0m";
|
|
14
|
+
/** Codes de sortie (sysexits). */
|
|
15
|
+
const EXIT_FAILED = 1;
|
|
16
|
+
const EXIT_CONFIG = 78;
|
|
17
|
+
const LABELS = {
|
|
18
|
+
discovery: "découverte",
|
|
19
|
+
authorization: "URL de retour",
|
|
20
|
+
token: "secret",
|
|
21
|
+
theme: "thème"
|
|
22
|
+
};
|
|
23
|
+
function mark(check) {
|
|
24
|
+
switch (check.status) {
|
|
25
|
+
case "ok": return `${GREEN}✓${RESET}`;
|
|
26
|
+
case "failed": return `${RED}✗${RESET}`;
|
|
27
|
+
case "inconclusive": return `${YELLOW}?${RESET}`;
|
|
28
|
+
case "skipped": return `${DIM}–${RESET}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `nodefony security:oauth:doctor` — dit POURQUOI un fournisseur OAuth refuse
|
|
33
|
+
* l'application, sans qu'un humain ait à se connecter.
|
|
34
|
+
*
|
|
35
|
+
* Trois verdicts distincts là où le journal ne montre qu'un 401 : l'émetteur
|
|
36
|
+
* se déclare autrement que la configuration ne l'écrit, l'URL de retour n'est
|
|
37
|
+
* pas enregistrée, le secret est faux. La même analyse alimente
|
|
38
|
+
* `nodefony doctor --live` (famille « Fournisseurs OAuth ») et l'endpoint
|
|
39
|
+
* d'administration `security/oauth/diagnosis` : une seule implémentation, celle
|
|
40
|
+
* du service `oauth2`.
|
|
41
|
+
*
|
|
42
|
+
* Valable pour tout fournisseur : les sondes passent par le fournisseur que le
|
|
43
|
+
* login construit, Keycloak n'y a rien de particulier.
|
|
44
|
+
*/
|
|
45
|
+
var SecurityOAuthDoctor = class extends Command {
|
|
46
|
+
constructor(cli) {
|
|
47
|
+
super("security:oauth:doctor", "diagnostique le branchement des fournisseurs OAuth", cli, {
|
|
48
|
+
...options,
|
|
49
|
+
examples: ["nodefony security:oauth:doctor", "nodefony security:oauth:doctor --provider github --json"]
|
|
50
|
+
});
|
|
51
|
+
this.addOption("-p, --provider <nom>", "un seul fournisseur de oauth2.providers");
|
|
52
|
+
this.addOption("-j, --json", "sortie JSON (scripts/CI)");
|
|
53
|
+
}
|
|
54
|
+
async generate(opts) {
|
|
55
|
+
const oauth = this.kernel?.container?.get("oauth2");
|
|
56
|
+
const diagnoses = oauth?.isEnabled() === true ? await oauth.diagnose(opts.provider) : [];
|
|
57
|
+
if (opts.json) process.stdout.write(`${JSON.stringify(diagnoses, null, 2)}\n`);
|
|
58
|
+
else if (diagnoses.length === 0) process.stderr.write("aucun fournisseur OAuth à diagnostiquer — `security.oauth2` est désactivé ou ne déclare aucun fournisseur\n");
|
|
59
|
+
else for (const diagnosis of diagnoses) {
|
|
60
|
+
process.stdout.write(`${diagnosis.ok ? GREEN : RED}${diagnosis.provider}${RESET}\n`);
|
|
61
|
+
for (const check of diagnosis.checks) process.stdout.write(` ${mark(check)} ${LABELS[check.name].padEnd(14)}${check.message}\n`);
|
|
62
|
+
}
|
|
63
|
+
if (diagnoses.length === 0) process.exitCode = EXIT_CONFIG;
|
|
64
|
+
else if (diagnoses.some((d) => !d.ok)) process.exitCode = EXIT_FAILED;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
export { SecurityOAuthDoctor as default };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { readIfPresentSync } from "../src/token/secretFile.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { generateKeySet } from "../src/token/JwtKeystore.js";
|
|
3
|
+
import { Command, ENV_FILE, diskManifestReader, manifestFileWith, readManifestCode } from "nodefony";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { appendFileSync } from "node:fs";
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
6
7
|
import { spawnSync } from "node:child_process";
|
|
7
|
-
|
|
8
|
+
// nodefony/command/security-secrets.ts
|
|
8
9
|
const options = {
|
|
9
10
|
helpGroup: "COMPTES ET SECRETS",
|
|
10
11
|
showBanner: false,
|
|
@@ -45,11 +46,11 @@ const ROLES = {
|
|
|
45
46
|
},
|
|
46
47
|
NF_CSRF_SECRET: {
|
|
47
48
|
protected: "signe les jetons anti-rejeu des mutations (`@CsrfProtect`)",
|
|
48
|
-
without: "en
|
|
49
|
+
without: "démarrage REFUSÉ en production (code 78) — une valeur tirée au hasard différerait d'un exemplaire à l'autre"
|
|
49
50
|
},
|
|
50
|
-
|
|
51
|
-
protected: "signe les JWT (
|
|
52
|
-
without: "
|
|
51
|
+
NF_JWT_KEYSET: {
|
|
52
|
+
protected: "signe les jetons JWT (paire Ed25519) — la MÊME pour tous les pods et workers",
|
|
53
|
+
without: "démarrage REFUSÉ en production dès que l'application émet des jetons — sinon 401 au hasard derrière plusieurs exemplaires, et tous les jetons perdus au redémarrage"
|
|
53
54
|
}
|
|
54
55
|
};
|
|
55
56
|
/**
|
|
@@ -68,25 +69,34 @@ const ROLES = {
|
|
|
68
69
|
*/
|
|
69
70
|
var SecuritySecrets = class extends Command {
|
|
70
71
|
constructor(cli) {
|
|
71
|
-
super("security:secrets", "engendre les clés de chiffrement du module security", cli,
|
|
72
|
+
super("security:secrets", "engendre les clés de chiffrement du module security", cli, {
|
|
73
|
+
...options,
|
|
74
|
+
examples: [
|
|
75
|
+
"nodefony security:secrets",
|
|
76
|
+
"nodefony security:secrets --write",
|
|
77
|
+
"nodefony security:secrets --jwt-keyset"
|
|
78
|
+
]
|
|
79
|
+
});
|
|
72
80
|
this.addOption("-j, --json", "sortie JSON (scripts/CI)");
|
|
73
81
|
this.addOption("-w, --write", "écrit les clés manquantes dans le .env du projet (jamais de remplacement)");
|
|
82
|
+
this.addOption("-e, --env", "les 4 secrets en lignes CLÉ=valeur (docker run --env-file, gestionnaire de secrets)");
|
|
83
|
+
this.addOption("-k, --jwt-keyset", "génère la valeur de NF_JWT_KEYSET (une ligne JSON, à ranger dans le gestionnaire de secrets)");
|
|
74
84
|
}
|
|
75
85
|
/** Racine du projet (kernel booté) — repli cwd. */
|
|
76
86
|
#root() {
|
|
77
87
|
return this.kernel?.path ?? process.cwd();
|
|
78
88
|
}
|
|
79
89
|
/**
|
|
80
|
-
* `true` si `.env
|
|
81
|
-
*
|
|
82
|
-
*
|
|
90
|
+
* `true` si `.env` est SUIVI par git — y écrire des secrets les mènerait au
|
|
91
|
+
* commit (`.env` doit être gitignoré). Best-effort : git absent / hors repo →
|
|
92
|
+
* `false` (on écrit).
|
|
83
93
|
*/
|
|
84
94
|
#dotenvTracked() {
|
|
85
95
|
try {
|
|
86
96
|
return spawnSync("git", [
|
|
87
97
|
"ls-files",
|
|
88
98
|
"--error-unmatch",
|
|
89
|
-
|
|
99
|
+
ENV_FILE
|
|
90
100
|
], {
|
|
91
101
|
cwd: this.#root(),
|
|
92
102
|
stdio: "ignore"
|
|
@@ -104,15 +114,24 @@ var SecuritySecrets = class extends Command {
|
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
116
|
async generate(opts) {
|
|
117
|
+
if (opts.jwtKeyset) {
|
|
118
|
+
process.stdout.write(await generateKeySet() + "\n");
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
107
121
|
const gen = () => randomBytes(32).toString("base64");
|
|
108
122
|
const secrets = {};
|
|
109
123
|
for (const k of KEYS) secrets[k] = gen();
|
|
110
124
|
if (opts.json) {
|
|
125
|
+
secrets["NF_JWT_KEYSET"] = await generateKeySet();
|
|
111
126
|
process.stdout.write(JSON.stringify(secrets, null, 2) + "\n");
|
|
112
127
|
return this;
|
|
113
128
|
}
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
if (opts.env) {
|
|
130
|
+
secrets["NF_JWT_KEYSET"] = await generateKeySet();
|
|
131
|
+
process.stdout.write(Object.entries(secrets).map(([k, v]) => `${k}=${v}`).join("\n") + "\n");
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
const dotenv = this.#read(ENV_FILE);
|
|
116
135
|
const envTs = this.#read("env.ts");
|
|
117
136
|
const cfgTs = readManifestCode(this.#root(), diskManifestReader);
|
|
118
137
|
const cfgFile = manifestFileWith(this.#root(), diskManifestReader, /satisfies\s+ISecurityConfigInput/);
|
|
@@ -130,18 +149,18 @@ var SecuritySecrets = class extends Command {
|
|
|
130
149
|
process.stdout.write(s);
|
|
131
150
|
};
|
|
132
151
|
w(`\n${BOLD}🔐 Secrets du module security${RESET} ${DIM}— 4 secrets, 3 fichiers${RESET}\n\n`);
|
|
133
|
-
for (const [name, role] of Object.entries(ROLES)) w(` ${(name === "
|
|
134
|
-
const tokenStillThere = /^\s*NF_MCP_TOKEN\s*=/m.test(
|
|
135
|
-
w(`\n${DIM} · NF_MCP_TOKEN n'est PAS un secret de cette application, et n'a rien à\n faire dans cette liste : c'est un jeton qu'elle ÉMET, présenté par un\n agent pour entrer. Aucun code d'ici ne le lit. Il se pose chez l'agent\n qui le porte — nodefony security:token --write.${RESET}\n`);
|
|
136
|
-
if (tokenStillThere) w(`${YELLOW} ⚠ une ligne NF_MCP_TOKEN traîne encore dans .env
|
|
152
|
+
for (const [name, role] of Object.entries(ROLES)) w(` ${(name === "NF_JWT_KEYSET" ? cfgTs.includes(name) : new RegExp(`^\\s*${name}\\s*=`, "m").test(dotenv)) ? "\x1B[32m✓" : "\x1B[33m○"}${RESET} ${BOLD}${name.padEnd(16)}${RESET}${DIM}${role.protected}${RESET}\n ${DIM}sans → ${role.without}${RESET}\n`);
|
|
153
|
+
const tokenStillThere = /^\s*NF_MCP_TOKEN\s*=/m.test(dotenv);
|
|
154
|
+
w(`\n${DIM} · NF_MCP_TOKEN n'est PAS un secret de cette application, et n'a rien à\n faire dans cette liste : c'est un jeton qu'elle ÉMET, présenté par un\n agent pour entrer. Aucun code d'ici ne le lit. Il se pose chez l'agent\n qui le porte — nodefony security:mcp:token --write.${RESET}\n`);
|
|
155
|
+
if (tokenStillThere) w(`${YELLOW} ⚠ une ligne NF_MCP_TOKEN traîne encore dans .env — rien ne la lit,\n tu peux la retirer.${RESET}\n`);
|
|
137
156
|
w(`\n${YELLOW}⚠ rien ne se tape dans le terminal : chaque bloc se colle dans le fichier indiqué.${RESET}\n\n`);
|
|
138
|
-
w(`${BOLD}1. Fichier ${CYAN}.env
|
|
157
|
+
w(`${BOLD}1. Fichier ${CYAN}.env${RESET}${BOLD} — les valeurs${RESET} ${DIM}(valeurs du poste, gitignoré ; la notice est .env.example)${RESET}\n`);
|
|
139
158
|
if (missingInDotenv.length === 0) w(` ${GREEN}✓ les 3 clés y sont déjà${RESET} ${DIM}(rien à faire — rotation = remplacer la valeur à la main)${RESET}\n\n`);
|
|
140
|
-
else if (opts.write && this.#dotenvTracked()) w(` ${YELLOW}⚠ .env
|
|
159
|
+
else if (opts.write && this.#dotenvTracked()) w(` ${YELLOW}⚠ .env est suivi par git — je n'y écris PAS de secrets.${RESET}\n ${DIM}Ajoute \`.env\` au .gitignore (et \`git rm --cached .env\`), puis relance --write ;\n ou colle les lignes ci-dessous à la main :${RESET}\n\n` + missingInDotenv.map((k) => ` ${k}=${secrets[k]}`).join("\n") + `\n\n`);
|
|
141
160
|
else if (opts.write) {
|
|
142
|
-
const block = (
|
|
143
|
-
appendFileSync(path.resolve(this.#root(),
|
|
144
|
-
w(` ${GREEN}✓ écrit dans .env
|
|
161
|
+
const block = (dotenv && !dotenv.endsWith("\n") ? "\n" : "") + `# clés security — générées par \`nodefony security:secrets\`\n` + missingInDotenv.map((k) => `${k}=${secrets[k]}`).join("\n") + "\n";
|
|
162
|
+
appendFileSync(path.resolve(this.#root(), ENV_FILE), block);
|
|
163
|
+
w(` ${GREEN}✓ écrit dans .env${RESET} ${DIM}(${missingInDotenv.join(", ")} — les clés déjà présentes n'ont pas été touchées)${RESET}\n\n`);
|
|
145
164
|
} else w(` colle ces lignes ${DIM}(ou relance avec ${RESET}${CYAN}--write${RESET}${DIM} pour que je les écrive)${RESET} :\n\n` + missingInDotenv.map((k) => ` ${k}=${secrets[k]}`).join("\n") + `\n ${DIM}(en prod : Secret k8s / vault — jamais en git)${RESET}\n\n`);
|
|
146
165
|
w(`${BOLD}2. Fichier ${CYAN}env.ts${RESET}${BOLD} — la déclaration typée${RESET} ${DIM}(env.ts est le seul lecteur de process.env)${RESET}\n`);
|
|
147
166
|
if (missingInEnvTs.length === 0) w(` ${GREEN}✓ déjà déclarées${RESET}\n\n`);
|
|
@@ -150,13 +169,17 @@ var SecuritySecrets = class extends Command {
|
|
|
150
169
|
if (missingInCfg.length === 0) w(` ${GREEN}✓ déjà câblées${RESET}\n\n`);
|
|
151
170
|
else if (cfgIsFragment) w(` complète le descripteur ${CYAN}securityConfig${RESET} :\n\n` + missingInCfg.map((k) => WIRING[k]).join("\n") + `\n\n`);
|
|
152
171
|
else w(" complète l'entrée security du manifeste modules :\n\n use(\"@nodefony/security\", {\n" + missingInCfg.map((k) => WIRING[k]).join("\n") + `\n }),\n\n`);
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
172
|
+
const jwtDeclared = envTs.includes("NF_JWT_KEYSET");
|
|
173
|
+
const jwtWired = cfgTs.includes("NF_JWT_KEYSET");
|
|
174
|
+
w(`${BOLD}4. La clé de SIGNATURE des jetons${RESET} ${DIM}(NF_JWT_KEYSET — production seulement)${RESET}\n`);
|
|
175
|
+
if (jwtDeclared && jwtWired) w(` ${GREEN}✓ déclarée dans env.ts et câblée dans ${cfgRel}${RESET}\n`);
|
|
176
|
+
else {
|
|
177
|
+
if (!jwtDeclared) w(` ${CYAN}env.ts${RESET}, dans le defineEnv({ … }) :\n\n NF_JWT_KEYSET: envString({ optional: true }),\n\n`);
|
|
178
|
+
if (!jwtWired) w(` ${CYAN}${cfgRel}${RESET}, entrée security :\n\n jwt: {\n keystore: {\n keySetJson: ctx.env.NF_JWT_KEYSET,\n dir: ctx.isProd ? undefined : "var/keys",\n },\n },\n\n`);
|
|
179
|
+
}
|
|
180
|
+
w(` ${DIM}Développement : rien à poser, la clé vit dans var/keys/.\n Production : UNE valeur pour tous les pods et workers, générée une fois —\n ${RESET}${CYAN}npx nodefony security:secrets --jwt-keyset${RESET}${DIM} — puis rangée dans le\n gestionnaire de secrets et injectée en NF_JWT_KEYSET. Jamais dans .env,\n jamais dans git. STABLE : la changer refuse les jetons en vol ; pour une\n rotation, ajouter la nouvelle clé au jeu, la rendre « active », garder l'ancienne.${RESET}\n\n`);
|
|
181
|
+
w(`${DIM}Pourquoi 3 fichiers ? .env porte la VALEUR (secret du poste, gitignoré ;\nen production, le gestionnaire de secrets) ; env.ts la DÉCLARE\n(catalogue typé, validé au boot) ; ${cfgRel} la CÂBLE au module.\nLes étapes 2 et 3 ne se font qu'une fois — ensuite seule l'étape 1 vit.${RESET}\n\nRelance le serveur : plus aucun warning « clé ÉPHÉMÈRE » au boot.\n\n`);
|
|
158
182
|
return this;
|
|
159
183
|
}
|
|
160
184
|
};
|
|
161
|
-
//#endregion
|
|
162
185
|
export { SecuritySecrets as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AUTH_LOGIN_PATH, CONSOLE_DATA_RUN_PROFILE, Command, askPasswordMasked } from "nodefony";
|
|
2
2
|
import { WeakPasswordError } from "@nodefony/user";
|
|
3
|
-
|
|
3
|
+
// nodefony/command/security-user-add.ts
|
|
4
4
|
const options = {
|
|
5
5
|
runProfile: CONSOLE_DATA_RUN_PROFILE,
|
|
6
6
|
helpGroup: "COMPTES ET SECRETS",
|
|
@@ -42,7 +42,14 @@ const ROLE_BASE = "ROLE_USER";
|
|
|
42
42
|
*/
|
|
43
43
|
var SecurityUserAdd = class extends Command {
|
|
44
44
|
constructor(cli) {
|
|
45
|
-
super("security:user:add", "crée un compte (mot de passe demandé masqué)", cli,
|
|
45
|
+
super("security:user:add", "crée un compte (mot de passe demandé masqué)", cli, {
|
|
46
|
+
...options,
|
|
47
|
+
examples: [
|
|
48
|
+
"nodefony security:user:add alice",
|
|
49
|
+
"nodefony security:user:add admin --admin",
|
|
50
|
+
"nodefony security:user:add bob --roles ROLE_USER,ROLE_DEV"
|
|
51
|
+
]
|
|
52
|
+
});
|
|
46
53
|
this.addArgument("[identifier]", "identifiant (login) du compte");
|
|
47
54
|
this.addOption("-p, --password <password>", "mot de passe (sinon : prompt masqué en TTY)");
|
|
48
55
|
this.addOption("-r, --roles <roles>", "rôles CSV (ex: ROLE_USER,ROLE_DEV) — défaut ROLE_USER");
|
|
@@ -146,5 +153,4 @@ var SecurityUserAdd = class extends Command {
|
|
|
146
153
|
return this;
|
|
147
154
|
}
|
|
148
155
|
};
|
|
149
|
-
//#endregion
|
|
150
156
|
export { SecurityUserAdd as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CONSOLE_DATA_RUN_PROFILE, Command } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/command/security-user-delete.ts
|
|
3
3
|
const options = {
|
|
4
4
|
runProfile: CONSOLE_DATA_RUN_PROFILE,
|
|
5
5
|
helpGroup: "COMPTES ET SECRETS",
|
|
@@ -30,13 +30,17 @@ const ADMIN_ROLE = "ROLE_NODEFONY_ADMIN";
|
|
|
30
30
|
* seul recours est une écriture directe en base. Le même garde-fou existe côté
|
|
31
31
|
* data plane (`UserAdminApi`) — ici il est constaté, pas supposé.
|
|
32
32
|
*
|
|
33
|
-
* La
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* La suppression passe par `users.delete`, qui émet `onUserRevoked`
|
|
34
|
+
* (`reason: "deleted"`) et ATTEND la cascade de `@nodefony/security` : sessions
|
|
35
|
+
* et jetons éjectés, secret TOTP et passkeys retirés. La commande n'émet rien
|
|
36
|
+
* elle-même — le service est le seul émetteur du cycle de vie d'un compte.
|
|
36
37
|
*/
|
|
37
38
|
var SecurityUserDelete = class extends Command {
|
|
38
39
|
constructor(cli) {
|
|
39
|
-
super("security:user:delete", "supprime un compte, après confirmation", cli,
|
|
40
|
+
super("security:user:delete", "supprime un compte, après confirmation", cli, {
|
|
41
|
+
...options,
|
|
42
|
+
examples: ["nodefony security:user:delete alice", "nodefony security:user:delete alice --yes"]
|
|
43
|
+
});
|
|
40
44
|
this.addArgument("[identifier]", "identifiant (login) du compte à retirer");
|
|
41
45
|
this.addOption("-y, --yes", "ne pas demander confirmation (obligatoire hors terminal)");
|
|
42
46
|
}
|
|
@@ -95,9 +99,8 @@ var SecurityUserDelete = class extends Command {
|
|
|
95
99
|
process.exitCode = 1;
|
|
96
100
|
return this;
|
|
97
101
|
}
|
|
98
|
-
w(`${GREEN}✓ compte supprimé${RESET} — ${BOLD}${identifier}${RESET}\n${DIM} ses sessions
|
|
102
|
+
w(`${GREEN}✓ compte supprimé${RESET} — ${BOLD}${identifier}${RESET}\n${DIM} ses sessions, ses jetons et ses facteurs (2FA, passkeys) sont retirés.${RESET}\n\n`);
|
|
99
103
|
return this;
|
|
100
104
|
}
|
|
101
105
|
};
|
|
102
|
-
//#endregion
|
|
103
106
|
export { SecurityUserDelete as default };
|