@nodefony/security 10.0.0-beta.3 → 10.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/index.js +6 -8
- package/dist/nodefony/command/{security-token.js → security-mcp-token.js} +18 -11
- package/dist/nodefony/command/security-oauth-doctor.js +5 -3
- package/dist/nodefony/command/security-secrets.js +10 -4
- package/dist/nodefony/command/security-user-add.js +9 -3
- package/dist/nodefony/command/security-user-delete.js +5 -3
- package/dist/nodefony/command/security-user-list.js +5 -3
- package/dist/nodefony/command/security-user-password.js +5 -3
- package/dist/nodefony/config/config.js +20 -4
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/contracts/IAccessVoter.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 +1 -2
- 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/keycloak/keycloakAdmin.js +1 -2
- package/dist/nodefony/keycloak/keycloakRealm.js +1 -2
- package/dist/nodefony/keycloak/keycloakRealmInput.js +1 -2
- package/dist/nodefony/keycloak/scaffold.js +3 -3
- package/dist/nodefony/keycloak/security-keycloak-realm.js +5 -3
- package/dist/nodefony/scaffold/contribute.js +1 -2
- 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 +10 -8
- 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 +5 -4
- package/dist/nodefony/service/keycloak.js +1 -2
- package/dist/nodefony/service/oauth2.js +29 -7
- package/dist/nodefony/service/securityHeaders.js +1 -2
- package/dist/nodefony/service/tokenService.js +38 -22
- package/dist/nodefony/service/totp.js +4 -5
- package/dist/nodefony/service/webAuthn.js +4 -5
- package/dist/nodefony/service/webhooks.js +4 -5
- package/dist/nodefony/src/RoleHierarchyWalker.js +1 -2
- package/dist/nodefony/src/SecuredArea.js +1 -2
- package/dist/nodefony/src/accountBinding.js +96 -0
- package/dist/nodefony/src/admin/SecurityAdminApi.js +1 -2
- package/dist/nodefony/src/admin/WebhookAdminApi.js +23 -7
- package/dist/nodefony/src/admin/adminAudit.js +1 -2
- package/dist/nodefony/src/admin/userRevocationCascade.js +79 -15
- 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 +8 -113
- package/dist/nodefony/src/authenticator/ExternalSubjectResolver.js +180 -0
- package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +42 -4
- package/dist/nodefony/src/authenticator/JwtAuthenticator.js +91 -16
- package/dist/nodefony/src/authenticator/SessionAuthenticator.js +9 -5
- package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +1 -2
- package/dist/nodefony/src/authenticator/authenticatorRegistry.js +3 -17
- 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 +1 -2
- 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 +1 -2
- 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 +1 -2
- package/dist/nodefony/src/oauth/httpJson.js +3 -3
- package/dist/nodefony/src/oauth/metadata.js +5 -16
- package/dist/nodefony/src/oauth/oauth2Client.js +1 -2
- package/dist/nodefony/src/oauth/oauthProviderRegistry.js +1 -2
- package/dist/nodefony/src/oauth/providerDiagnosis.js +5 -6
- package/dist/nodefony/src/oauth/providerRoles.js +1 -2
- package/dist/nodefony/src/oauth/providers/github.js +16 -6
- package/dist/nodefony/src/oauth/providers/oidc.js +9 -6
- package/dist/nodefony/src/openToSignup.js +1 -2
- package/dist/nodefony/src/realtime/UserRealtimeToken.js +17 -3
- package/dist/nodefony/src/realtime/frameAuthorizer.js +2 -3
- package/dist/nodefony/src/rolesWithin.js +57 -0
- package/dist/nodefony/src/sessionAuthentication.js +1 -2
- 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 +14 -5
- package/dist/nodefony/src/token/MemoryTokenStore.js +1 -2
- package/dist/nodefony/src/token/RemoteJwtVerifier.js +6 -3
- 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 +1 -2
- 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 +1 -2
- 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/nodefony/testing/index.js +108 -0
- package/dist/types/nodefony/command/{security-token.d.ts → security-mcp-token.d.ts} +7 -6
- package/dist/types/nodefony/config/config.d.ts +1 -0
- package/dist/types/nodefony/errors/ApiKeyError.d.ts +3 -1
- 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/oauth2.d.ts +20 -0
- package/dist/types/nodefony/src/accountBinding.d.ts +82 -0
- package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +23 -5
- package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +2 -44
- package/dist/types/nodefony/src/authenticator/ExternalSubjectResolver.d.ts +99 -0
- package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +4 -1
- package/dist/types/nodefony/src/authenticator/JwtAuthenticator.d.ts +37 -1
- package/dist/types/nodefony/src/net/transportFailure.d.ts +22 -0
- package/dist/types/nodefony/src/oauth/httpJson.d.ts +0 -8
- package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +7 -1
- package/dist/types/nodefony/src/realtime/frameAuthorizer.d.ts +2 -2
- 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/sessionIdentity.d.ts +22 -0
- package/dist/types/nodefony/src/token/scopeGrants.d.ts +77 -0
- package/dist/types/nodefony/testing/index.d.ts +51 -0
- package/docs/api-keys.md +181 -174
- package/docs/audit.md +140 -140
- package/docs/authenticators.md +101 -101
- package/docs/authorization.md +116 -69
- package/docs/cors.md +64 -64
- package/docs/csrf.md +74 -71
- package/docs/external-jwt.md +22 -12
- package/docs/firewall.md +85 -82
- package/docs/headers.md +81 -81
- package/docs/index.md +4 -4
- package/docs/keycloak.md +95 -78
- package/docs/login-page.md +23 -23
- package/docs/oauth2.md +157 -146
- package/docs/obtenir-un-jeton.md +116 -26
- package/docs/tokens.md +128 -122
- package/docs/totp.md +136 -136
- package/docs/webauthn.md +153 -153
- package/docs/webhooks.md +136 -124
- package/package.json +19 -13
- package/dist/_virtual/_@oxc-project_runtime@0.153.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
|
@@ -81,22 +81,21 @@ import SecurityUserList from "./nodefony/command/security-user-list.js";
|
|
|
81
81
|
import SecurityUserDelete from "./nodefony/command/security-user-delete.js";
|
|
82
82
|
import SecurityUserPassword from "./nodefony/command/security-user-password.js";
|
|
83
83
|
import { publicRoutesNotice } from "./nodefony/src/boot/publicRoutesNotice.js";
|
|
84
|
-
import
|
|
84
|
+
import SecurityMcpToken from "./nodefony/command/security-mcp-token.js";
|
|
85
85
|
import SecurityKeycloakRealm from "./nodefony/keycloak/security-keycloak-realm.js";
|
|
86
86
|
import SecurityOAuthDoctor from "./nodefony/command/security-oauth-doctor.js";
|
|
87
87
|
import { createSecurityAdminApi, parseAuditQuery, registerSecurityAdminApi } from "./nodefony/src/admin/SecurityAdminApi.js";
|
|
88
88
|
import { registerUserRevocationCascade, usersEventBusNotice } from "./nodefony/src/admin/userRevocationCascade.js";
|
|
89
|
-
import
|
|
90
|
-
import __decorate from "./_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
|
|
89
|
+
import __decorate from "./_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
91
90
|
import { tokenStatusCriteria } from "./nodefony/src/token/tokenCriteria.js";
|
|
92
91
|
import { requestClientCredentialsToken } from "./nodefony/src/oauth/clientCredentials.js";
|
|
93
92
|
import { AccessDeniedError } from "./nodefony/errors/AccessDeniedError.js";
|
|
94
93
|
import "./nodefony/errors/index.js";
|
|
95
94
|
import "./nodefony/contracts/index.js";
|
|
96
|
-
import {
|
|
95
|
+
import { Module, services } from "nodefony";
|
|
97
96
|
import { fileURLToPath } from "node:url";
|
|
98
97
|
import { registerUserAdminApi } from "@nodefony/user";
|
|
99
|
-
|
|
98
|
+
// index.ts
|
|
100
99
|
/**
|
|
101
100
|
* `@nodefony/security` — couche de sécurité de Nodefony (refonte 2026, P6).
|
|
102
101
|
*
|
|
@@ -120,7 +119,7 @@ let Security = class Security extends Module {
|
|
|
120
119
|
this.addCommand(SecurityUserList);
|
|
121
120
|
this.addCommand(SecurityUserDelete);
|
|
122
121
|
this.addCommand(SecurityUserPassword);
|
|
123
|
-
this.addCommand(
|
|
122
|
+
this.addCommand(SecurityMcpToken);
|
|
124
123
|
this.addCommand(SecurityKeycloakRealm);
|
|
125
124
|
this.addCommand(SecurityOAuthDoctor);
|
|
126
125
|
}
|
|
@@ -185,7 +184,6 @@ Security = __decorate([services([
|
|
|
185
184
|
TotpService,
|
|
186
185
|
WebhookService,
|
|
187
186
|
KeycloakService
|
|
188
|
-
])
|
|
187
|
+
])], Security);
|
|
189
188
|
var security_default = Security;
|
|
190
|
-
//#endregion
|
|
191
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",
|
|
@@ -74,7 +74,7 @@ const DIM = "\x1B[2m";
|
|
|
74
74
|
const BOLD = "\x1B[1m";
|
|
75
75
|
const RESET = "\x1B[0m";
|
|
76
76
|
/**
|
|
77
|
-
* `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
|
|
78
78
|
* application (la porte MCP par défaut).
|
|
79
79
|
*
|
|
80
80
|
* **Pourquoi une commande, et pas un `curl`.** Le jeton s'obtenait par un appel
|
|
@@ -88,18 +88,26 @@ const RESET = "\x1B[0m";
|
|
|
88
88
|
* raison d'être de la liaison d'audience. La commande la vise d'elle-même,
|
|
89
89
|
* `--resource` ne sert qu'à en viser une autre.
|
|
90
90
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
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.
|
|
94
95
|
*/
|
|
95
|
-
var
|
|
96
|
+
var SecurityMcpToken = class extends Command {
|
|
96
97
|
constructor(cli) {
|
|
97
|
-
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
|
+
});
|
|
98
106
|
this.addArgument("[identifier]", "compte porteur du jeton (défaut : admin)");
|
|
99
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)`);
|
|
100
108
|
this.addOption("-r, --resource <uri>", "audience visée (défaut : la porte MCP de cette application)");
|
|
101
109
|
this.addOption("-a, --agent <noms>", "agents à servir, séparés par des virgules (défaut : ceux détectés ; « none » pour aucun)");
|
|
102
|
-
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)");
|
|
103
111
|
this.addOption("-w, --write", `pose ${MCP_TOKEN_ENV} dans la configuration des agents présents`);
|
|
104
112
|
this.addOption("-j, --json", "sortie JSON (scripts/CI)");
|
|
105
113
|
}
|
|
@@ -300,7 +308,7 @@ var SecurityToken = class extends Command {
|
|
|
300
308
|
porteur obtiendrait un jeton pour la ressource de son choix :
|
|
301
309
|
|
|
302
310
|
use("@nodefony/security", {
|
|
303
|
-
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");
|
|
304
312
|
process.exitCode = 1;
|
|
305
313
|
return this;
|
|
306
314
|
}
|
|
@@ -363,5 +371,4 @@ var SecurityToken = class extends Command {
|
|
|
363
371
|
return this;
|
|
364
372
|
}
|
|
365
373
|
};
|
|
366
|
-
|
|
367
|
-
export { SecurityToken as default, tokenTtlSeconds, ttlSeconds };
|
|
374
|
+
export { SecurityMcpToken as default, tokenTtlSeconds, ttlSeconds };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/command/security-oauth-doctor.ts
|
|
3
3
|
const options = {
|
|
4
4
|
helpGroup: "COMPTES ET SECRETS",
|
|
5
5
|
showBanner: false,
|
|
@@ -44,7 +44,10 @@ function mark(check) {
|
|
|
44
44
|
*/
|
|
45
45
|
var SecurityOAuthDoctor = class extends Command {
|
|
46
46
|
constructor(cli) {
|
|
47
|
-
super("security:oauth:doctor", "diagnostique le branchement des fournisseurs OAuth", 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
|
+
});
|
|
48
51
|
this.addOption("-p, --provider <nom>", "un seul fournisseur de oauth2.providers");
|
|
49
52
|
this.addOption("-j, --json", "sortie JSON (scripts/CI)");
|
|
50
53
|
}
|
|
@@ -62,5 +65,4 @@ var SecurityOAuthDoctor = class extends Command {
|
|
|
62
65
|
return this;
|
|
63
66
|
}
|
|
64
67
|
};
|
|
65
|
-
//#endregion
|
|
66
68
|
export { SecurityOAuthDoctor as default };
|
|
@@ -5,7 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { appendFileSync } from "node:fs";
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
|
-
|
|
8
|
+
// nodefony/command/security-secrets.ts
|
|
9
9
|
const options = {
|
|
10
10
|
helpGroup: "COMPTES ET SECRETS",
|
|
11
11
|
showBanner: false,
|
|
@@ -69,7 +69,14 @@ const ROLES = {
|
|
|
69
69
|
*/
|
|
70
70
|
var SecuritySecrets = class extends Command {
|
|
71
71
|
constructor(cli) {
|
|
72
|
-
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
|
+
});
|
|
73
80
|
this.addOption("-j, --json", "sortie JSON (scripts/CI)");
|
|
74
81
|
this.addOption("-w, --write", "écrit les clés manquantes dans le .env du projet (jamais de remplacement)");
|
|
75
82
|
this.addOption("-e, --env", "les 4 secrets en lignes CLÉ=valeur (docker run --env-file, gestionnaire de secrets)");
|
|
@@ -144,7 +151,7 @@ var SecuritySecrets = class extends Command {
|
|
|
144
151
|
w(`\n${BOLD}🔐 Secrets du module security${RESET} ${DIM}— 4 secrets, 3 fichiers${RESET}\n\n`);
|
|
145
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`);
|
|
146
153
|
const tokenStillThere = /^\s*NF_MCP_TOKEN\s*=/m.test(dotenv);
|
|
147
|
-
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`);
|
|
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`);
|
|
148
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`);
|
|
149
156
|
w(`\n${YELLOW}⚠ rien ne se tape dans le terminal : chaque bloc se colle dans le fichier indiqué.${RESET}\n\n`);
|
|
150
157
|
w(`${BOLD}1. Fichier ${CYAN}.env${RESET}${BOLD} — les valeurs${RESET} ${DIM}(valeurs du poste, gitignoré ; la notice est .env.example)${RESET}\n`);
|
|
@@ -175,5 +182,4 @@ var SecuritySecrets = class extends Command {
|
|
|
175
182
|
return this;
|
|
176
183
|
}
|
|
177
184
|
};
|
|
178
|
-
//#endregion
|
|
179
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",
|
|
@@ -37,7 +37,10 @@ const ADMIN_ROLE = "ROLE_NODEFONY_ADMIN";
|
|
|
37
37
|
*/
|
|
38
38
|
var SecurityUserDelete = class extends Command {
|
|
39
39
|
constructor(cli) {
|
|
40
|
-
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
|
+
});
|
|
41
44
|
this.addArgument("[identifier]", "identifiant (login) du compte à retirer");
|
|
42
45
|
this.addOption("-y, --yes", "ne pas demander confirmation (obligatoire hors terminal)");
|
|
43
46
|
}
|
|
@@ -100,5 +103,4 @@ var SecurityUserDelete = class extends Command {
|
|
|
100
103
|
return this;
|
|
101
104
|
}
|
|
102
105
|
};
|
|
103
|
-
//#endregion
|
|
104
106
|
export { SecurityUserDelete as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CONSOLE_DATA_RUN_PROFILE, Command } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/command/security-user-list.ts
|
|
3
3
|
const options = {
|
|
4
4
|
runProfile: CONSOLE_DATA_RUN_PROFILE,
|
|
5
5
|
helpGroup: "COMPTES ET SECRETS",
|
|
@@ -31,7 +31,10 @@ const DEFAULT_LIMIT = 50;
|
|
|
31
31
|
*/
|
|
32
32
|
var SecurityUserList = class extends Command {
|
|
33
33
|
constructor(cli) {
|
|
34
|
-
super("security:user:list", "liste les comptes : identifiant, rôles, état", cli,
|
|
34
|
+
super("security:user:list", "liste les comptes : identifiant, rôles, état", cli, {
|
|
35
|
+
...options,
|
|
36
|
+
examples: ["nodefony security:user:list", "nodefony security:user:list --role ROLE_ADMIN --json"]
|
|
37
|
+
});
|
|
35
38
|
this.addOption("-q, --query <texte>", "filtre sur l'identifiant (sous-chaîne, insensible à la casse)");
|
|
36
39
|
this.addOption("-r, --role <role>", "n'affiche que les porteurs d'un rôle");
|
|
37
40
|
this.addOption("-l, --limit <n>", `nombre maximum de comptes (défaut ${DEFAULT_LIMIT})`);
|
|
@@ -74,5 +77,4 @@ var SecurityUserList = class extends Command {
|
|
|
74
77
|
return this;
|
|
75
78
|
}
|
|
76
79
|
};
|
|
77
|
-
//#endregion
|
|
78
80
|
export { SecurityUserList as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AUTH_LOGIN_PATH, CONSOLE_DATA_RUN_PROFILE, Command, askPasswordMasked } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/command/security-user-password.ts
|
|
3
3
|
const options = {
|
|
4
4
|
runProfile: CONSOLE_DATA_RUN_PROFILE,
|
|
5
5
|
helpGroup: "COMPTES ET SECRETS",
|
|
@@ -40,7 +40,10 @@ const RESET = "\x1B[0m";
|
|
|
40
40
|
*/
|
|
41
41
|
var SecurityUserPassword = class extends Command {
|
|
42
42
|
constructor(cli) {
|
|
43
|
-
super("security:user:password", "change le mot de passe d'un compte", cli,
|
|
43
|
+
super("security:user:password", "change le mot de passe d'un compte", cli, {
|
|
44
|
+
...options,
|
|
45
|
+
examples: ["nodefony security:user:password alice"]
|
|
46
|
+
});
|
|
44
47
|
this.addArgument("[identifier]", "identifiant (login) du compte");
|
|
45
48
|
this.addOption("-p, --password <password>", "nouveau mot de passe (sinon : prompt masqué en TTY)");
|
|
46
49
|
}
|
|
@@ -103,5 +106,4 @@ var SecurityUserPassword = class extends Command {
|
|
|
103
106
|
return this;
|
|
104
107
|
}
|
|
105
108
|
};
|
|
106
|
-
//#endregion
|
|
107
109
|
export { SecurityUserPassword as default };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { parseKeySet } from "../src/token/JwtKeystore.js";
|
|
2
|
+
import { scopeGrantKeyProblem } from "../src/token/scopeGrants.js";
|
|
2
3
|
import { PLATFORM_ROLE_PREFIX, isPlatformRole } from "../src/oauth/providerRoles.js";
|
|
3
4
|
import { DEFAULT_LOGIN_PAGE_SKIN, LOGIN_PAGE_LAYOUTS, LOGIN_PAGE_PATH, LOGIN_PAGE_SKINS, parseDiscoveryUrl, safeRedirectPath } from "nodefony";
|
|
4
5
|
import { z } from "zod";
|
|
5
|
-
|
|
6
|
+
// nodefony/config/config.ts
|
|
6
7
|
/**
|
|
7
8
|
* ════════════════════════════════════════════════════════════════════════════
|
|
8
9
|
* @nodefony/security — CONFIGURATION DU MODULE (schéma Zod = source unique)
|
|
@@ -263,7 +264,7 @@ const trustedIssuerSchema = z.strictObject({
|
|
|
263
264
|
subjectMapping: z.enum(["prefixed", "subject"]).default("prefixed").describe("Comment le sujet (`sub`) de CET émetteur devient un identifiant dans l'espace de noms local. « prefixed » (défaut) : l'identifiant cherché est « <issuer>#<sub> » — deux émetteurs ne peuvent pas se disputer un compte, et surtout aucun sujet étranger ne peut tomber par hasard sur un identifiant local existant. « subject » : le `sub` est cherché TEL QUEL, l'ancien comportement — à ne déclarer que si l'on maîtrise l'espace de noms de cet émetteur, typiquement parce qu'il EST cette application (jetons maison) ou parce que ses sujets sont déjà des identifiants locaux. 🔴 Le défaut n'est pas un réglage de confort : un annuaire où l'utilisateur choisit son identifiant permet de présenter `sub: \"admin\"`, et « subject » rattache alors le porteur au compte local du même nom. `sub` n'est unique que dans l'espace de son émetteur (OIDC Core §2) — l'identité est la paire, jamais le sujet seul.")
|
|
264
265
|
});
|
|
265
266
|
const resourceServerSchema = z.strictObject({
|
|
266
|
-
issuers: z.array(trustedIssuerSchema).default([]).describe("Émetteurs dont l'application accepte les jetons. VIDE (défaut) = aucun jeton tiers n'est vérifiable
|
|
267
|
+
issuers: z.array(trustedIssuerSchema).default([]).describe("Émetteurs TIERS dont l'application accepte les jetons. Les jetons signés par l'application elle-même n'ont pas à y figurer : dès que `jwt.enabled`, le service `accessTokenVerifier` les reconnaît, et les juge par les règles de l'authentificateur `jwt` (révocation, compte actif). VIDE (défaut) = aucun jeton tiers n'est vérifiable ; avec `jwt.enabled: false`, le service n'est pas posé et une porte protégée refusera de servir plutôt que d'accepter des porteurs qu'elle ne sait pas lire. Une liste INVALIDE n'en pose aucun, jetons locaux compris."),
|
|
267
268
|
subjectPolicy: z.enum(["require", "ephemeral"]).default("require").describe("Comment le sujet d'un jeton tiers (`sub`) devient un utilisateur de CETTE application. « require » (défaut) : le sujet doit correspondre à un compte local (`loadUserByIdentifier`) — absent, désactivé ou verrouillé, l'accès est refusé. « ephemeral » : aucun compte local n'est exigé ni créé, l'appelant vit le temps de la requête avec les rôles de `ephemeralRoles` — c'est le mode de l'appelant PUREMENT machine (agent, service). Le défaut est « require » parce qu'un jeton d'un annuaire d'entreprise vaut pour des milliers de comptes : l'accepter d'office ferait de cet annuaire l'unique autorité d'accès à l'application, et supprimerait la seconde décision — locale — qui est la raison d'être du pare-feu."),
|
|
268
269
|
ephemeralRoles: z.array(z.string().min(1)).default([]).describe("Rôles accordés à un appelant accepté en mode « ephemeral ». VIDE par défaut, et ce n'est pas une omission : sans rôle, l'appelant ne passe aucun `@IsGranted` et n'est autorisé que par ses SCOPES (`@RequireScope`), qui viennent du jeton et sont bornés par le serveur d'autorisation. Y écrire un rôle revient à accorder un pouvoir local à quiconque détient un jeton valide pour cette ressource — à faire sciemment, jamais par confort."),
|
|
269
270
|
timeoutMs: z.number().int().min(100).default(5e3).describe("Délai maximal d'une requête vers l'émetteur (découverte + clés)."),
|
|
@@ -363,7 +364,7 @@ const oauthProviderSchema = z.strictObject({
|
|
|
363
364
|
defaultRoles: z.array(z.string()).optional().describe("Rôles du Shadow User à la création — surcharge le global pour CE fournisseur."),
|
|
364
365
|
roleMapping: z.record(z.string().min(1), z.string().min(1)).optional().describe("Rôles du FOURNISSEUR traduits en rôles de l'application, recalculés à chaque connexion ET à chaque jeton présenté à l'API : `{ \"app-admin\": \"ROLE_ADMIN\" }`. Un rôle du fournisseur absent de la table est ignoré, jamais recopié. Les rôles donnés à la main sont conservés. OMIS = les rôles restent entièrement locaux (posés à la création, puis la base fait foi). Un rôle `ROLE_NODEFONY_*` refuse le démarrage, sauf `allowPlatformRoles`."),
|
|
365
366
|
audiences: z.array(z.string().min(1)).optional().describe("Ressources pour lesquelles CE fournisseur émet des jetons à l'application — la mention « valable pour » (`aud`) qu'il doit imprimer. Chaque entrée est la `resource` d'une zone `external-jwt`. Lue par `security:keycloak:realm`, qui en écrit les mappers d'audience ; sans effet sur la vérification, que la `resource` de la zone porte seule. OMIS = la `resource` de TOUTES les zones `external-jwt`. À écrire dès qu'une zone existe pour REFUSER les jetons de ce fournisseur : la déduire ferait imprimer son adresse."),
|
|
366
|
-
allowPlatformRoles: z.boolean().default(false).describe("Autorise `roleMapping` à accorder des rôles de PLATEFORME (`ROLE_NODEFONY_*` : console d'administration, génération de code, secrets). OMIS = refusé au démarrage. L'ouvrir fait de l'administrateur de l'annuaire un administrateur de l'instance, et d'un annuaire compromis une prise de contrôle complète —
|
|
367
|
+
allowPlatformRoles: z.boolean().default(false).describe("Autorise `roleMapping` à accorder des rôles de PLATEFORME (`ROLE_NODEFONY_*` : console d'administration, génération de code, secrets). OMIS = refusé au démarrage. L'ouvrir fait de l'administrateur de l'annuaire un administrateur de l'instance, et d'un annuaire compromis une prise de contrôle complète — rappelé à chaque démarrage, en avertissement en production, en information ailleurs."),
|
|
367
368
|
rolesSource: z.array(z.enum([
|
|
368
369
|
"client",
|
|
369
370
|
"realm",
|
|
@@ -396,6 +397,21 @@ const oauth2Schema = z.strictObject({
|
|
|
396
397
|
providers: z.record(z.string(), oauthProviderSchema).default({}).describe("Fournisseurs activés par nom (doit correspondre au registre : builtins google/keycloak/oidc/github ; tout autre serveur OpenID Connect en enregistrant une fabrique, décrit par son seul émetteur).")
|
|
397
398
|
}).describe("Social login OAuth 2.0 — Authorization Code + PKCE, session BFF.");
|
|
398
399
|
/**
|
|
400
|
+
* Qui peut OBTENIR un scope d'application — appliqué par CHAQUE émetteur de
|
|
401
|
+
* l'application (jeton d'accès, clé API), jamais par une zone : un jeton ne
|
|
402
|
+
* sait pas quelle zone le lira, et `security:mcp:token` signe sans requête.
|
|
403
|
+
*/
|
|
404
|
+
const grantableScopesSchema = z.record(z.string(), z.array(z.string().min(1)).min(1, "au moins un rôle — pour n'accorder ce scope à personne, ne pas l'exposer")).superRefine((grants, ctx) => {
|
|
405
|
+
for (const key of Object.keys(grants)) {
|
|
406
|
+
const problem = scopeGrantKeyProblem(key);
|
|
407
|
+
if (problem !== null) ctx.addIssue({
|
|
408
|
+
code: "custom",
|
|
409
|
+
path: [key],
|
|
410
|
+
message: `« ${key} » ${problem}`
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}).default({}).describe("Qui peut OBTENIR un scope d'application. Clé : un scope exact (`vitrine:write`) ou une famille (`shop:*`) ; valeur : les rôles dont UN suffit, hiérarchie de rôles comprise. Une clé exacte l'emporte sur une famille, la famille la plus longue sur une plus courte. Un scope qu'aucune clé ne couvre reste accordé à tout compte connecté. Appliqué par chaque émetteur de CETTE application : un jeton d'accès est émis sans les scopes refusés (RFC 6749 §3.3, nommés au journal), une clé API demandée avec l'un d'eux est refusée (403). Ne borne PAS les jetons d'un émetteur tiers (Keycloak…), qui signe selon sa propre politique. `admin:read`, `admin:write` et `admin:*` sont refusés au démarrage : réservés au framework. Un module peut compléter cette section par `module-security` ; un préfixe = un propriétaire, car la fusion de configuration fusionne les listes case par case (`[\"A\",\"B\"]` surchargé par `[\"C\"]` donne `[\"C\",\"B\"]`).");
|
|
414
|
+
/**
|
|
399
415
|
* Règle d'autorisation d'un **namespace de canaux WebSocket** (subscribe/inbound)
|
|
400
416
|
* par préfixe. Contraintes cumulatives ; un champ absent = pas de contrainte sur
|
|
401
417
|
* cet axe. Étend OU surcharge les défauts système (placée AVANT eux à l'évaluation).
|
|
@@ -409,6 +425,7 @@ const realtimeChannelRuleSchema = z.strictObject({
|
|
|
409
425
|
const securityConfigSchema = z.strictObject({
|
|
410
426
|
encoders: z.record(z.string(), encoderSchema).default(() => ({ default: encoderSchema.parse({ type: "argon2id" }) })).describe("Chaîne d'encodeurs de mot de passe. 1re entrée = principal (hash à la création), suivantes = legacy en lecture seule (migration au login). Défaut : un argon2id sûr — le firewall en dérive le service `passwordEncoder`."),
|
|
411
427
|
roleHierarchy: z.record(z.string(), z.array(z.string())).default({}).describe("Hiérarchie de rôles (ROLE_ADMIN: ['ROLE_USER'])."),
|
|
428
|
+
grantableScopes: grantableScopesSchema,
|
|
412
429
|
areas: z.record(z.string(), areaSchema).default({}).describe("Zones sécurisées (firewall) par nom."),
|
|
413
430
|
realtimeChannels: z.array(realtimeChannelRuleSchema).default([]).describe("Politiques d'autorisation par préfixe sur le temps réel. Couvre les canaux WS (subscribe/inbound) **ET les actions RPC** : le nom de méthode d'une frame entrante est soumis aux mêmes règles de préfixe — une règle `admin:` garde donc aussi l'action `admin:purge`. Étend/surcharge les défauts système (syslog:/orm:/node:/dashboard:/debugbar:/realtime:/cluster: + :health/:stats = ROLE_NODEFONY_ADMIN, l'échelle PLATEFORME : ces canaux exposent l'état du processus, pas celui d'une application). Ce qui n'est couvert par aucune règle reste libre, sauf gating `@RealtimeChannel` côté controller."),
|
|
414
431
|
cors: corsSchema.default(() => corsSchema.parse({})),
|
|
@@ -441,5 +458,4 @@ const securityConfigSchema = z.strictObject({
|
|
|
441
458
|
* construction ; passés au `super(..., config)` du Module class.
|
|
442
459
|
*/
|
|
443
460
|
const config = securityConfigSchema.parse({});
|
|
444
|
-
//#endregion
|
|
445
461
|
export { config as default, securityConfigSchema };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { securityConfigSchema } from "./config.js";
|
|
2
2
|
import { BootConfigurationError, parseModuleConfig } from "nodefony";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
|
|
4
|
+
// nodefony/config/defineModuleConfig.ts
|
|
5
5
|
/**
|
|
6
6
|
* Valide + normalise + gèle la configuration de sécurité.
|
|
7
7
|
*
|
|
@@ -31,5 +31,4 @@ function detectConflicts(areas) {
|
|
|
31
31
|
seen.set(area.pattern, name);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
//#endregion
|
|
35
34
|
export { defineSecurityConfig, securityConfigJsonSchema };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/contracts/IAccessVoter.ts
|
|
2
2
|
/**
|
|
3
3
|
* Vote d'un {@link IAccessVoter}. Stratégie firewall = affirmative + DENY veto :
|
|
4
4
|
* un seul DENY bloque ; sinon un GRANT suffit ; tous ABSTAIN → DENY (Zero Trust).
|
|
@@ -9,5 +9,4 @@ let VoterVote = /* @__PURE__ */ function(VoterVote) {
|
|
|
9
9
|
VoterVote["ABSTAIN"] = "ABSTAIN";
|
|
10
10
|
return VoterVote;
|
|
11
11
|
}({});
|
|
12
|
-
//#endregion
|
|
13
12
|
export { VoterVote };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nodefonyError } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/errors/AccessDeniedError.ts
|
|
3
3
|
/**
|
|
4
4
|
* Accès refusé — `code = 403`. Levée par l'autorisation (un `@IsGranted` non
|
|
5
5
|
* satisfait, un voter DENY) : l'utilisateur EST authentifié mais n'a pas le droit.
|
|
@@ -10,5 +10,4 @@ var AccessDeniedError = class extends nodefonyError {
|
|
|
10
10
|
super(message, 403);
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
|
-
//#endregion
|
|
14
13
|
export { AccessDeniedError, AccessDeniedError as default };
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { nodefonyError } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/errors/ApiKeyError.ts
|
|
3
3
|
/**
|
|
4
4
|
* Erreur de **gestion** d'une clé API (création/révocation) — porte un `code`
|
|
5
5
|
* HTTP que l'adaptateur framework mappe par duck-typing (il n'importe jamais les
|
|
6
6
|
* classes de `@nodefony/security`).
|
|
7
7
|
*
|
|
8
8
|
* - `400` — entrée invalide (nom vide, scope hors catalogue) ;
|
|
9
|
+
* - `403` — scope demandé que le porteur ne peut pas obtenir (`grantableScopes`,
|
|
10
|
+
* ou `admin:*` sans le rôle d'administration) ;
|
|
9
11
|
* - `409` — plafond `apiKeys.maxPerSubject` atteint ;
|
|
10
12
|
* - `503` — store de jetons indisponible (clés activées mais non provisionnées).
|
|
11
13
|
*
|
|
@@ -17,5 +19,4 @@ var ApiKeyError = class extends nodefonyError {
|
|
|
17
19
|
super(message, code);
|
|
18
20
|
}
|
|
19
21
|
};
|
|
20
|
-
//#endregion
|
|
21
22
|
export { ApiKeyError, ApiKeyError as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nodefonyError } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/errors/AuthenticationError.ts
|
|
3
3
|
/**
|
|
4
4
|
* Échec d'authentification — `code = 401`. Levée par un {@link IAuthenticator}
|
|
5
5
|
* quand le credential est absent/invalide, ou par le firewall en Zero Trust
|
|
@@ -10,5 +10,4 @@ var AuthenticationError = class extends nodefonyError {
|
|
|
10
10
|
super(message, 401);
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
|
-
//#endregion
|
|
14
13
|
export { AuthenticationError, AuthenticationError as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nodefonyError } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/errors/CsrfError.ts
|
|
3
3
|
/**
|
|
4
4
|
* Mutation cross-site bloquée — `code = 403` (RFC 9110 §15.5.4 : le serveur a
|
|
5
5
|
* compris la requête mais refuse de l'honorer).
|
|
@@ -19,5 +19,4 @@ var CsrfError = class extends nodefonyError {
|
|
|
19
19
|
super(message, 403);
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
-
//#endregion
|
|
23
22
|
export { CsrfError, CsrfError as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { INSECURE_TRANSPORT_MESSAGE, nodefonyError } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/errors/InsecureTransportError.ts
|
|
3
3
|
/**
|
|
4
4
|
* Secret refusé parce qu'il arrive en clair — `code = 403` (RFC 9110 §15.5.4 :
|
|
5
5
|
* le serveur a compris la requête mais refuse de l'honorer).
|
|
@@ -20,5 +20,4 @@ var InsecureTransportError = class extends nodefonyError {
|
|
|
20
20
|
super(INSECURE_TRANSPORT_MESSAGE, 403);
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
|
-
//#endregion
|
|
24
23
|
export { InsecureTransportError, InsecureTransportError as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nodefonyError } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/errors/InvalidTargetError.ts
|
|
3
3
|
/**
|
|
4
4
|
* La ressource demandée à l'émission ne peut pas être servie — `code = 400`,
|
|
5
5
|
* code d'erreur OAuth `invalid_target` (RFC 8707 §2).
|
|
@@ -35,5 +35,4 @@ var InvalidTargetError = class extends nodefonyError {
|
|
|
35
35
|
this.requested = requested;
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
-
//#endregion
|
|
39
38
|
export { InvalidTargetError, InvalidTargetError as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nodefonyError } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/errors/SsrfError.ts
|
|
3
3
|
/**
|
|
4
4
|
* URL rejetée par la protection SSRF — `code = 422`. Levée quand une URL sortante
|
|
5
5
|
* (endpoint webhook, fetch applicatif…) est syntaxiquement valide mais cible une
|
|
@@ -13,5 +13,4 @@ var SsrfError = class extends nodefonyError {
|
|
|
13
13
|
super(message, 422);
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
-
//#endregion
|
|
17
16
|
export { SsrfError, SsrfError as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nodefonyError } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/errors/ThrottledError.ts
|
|
3
3
|
/**
|
|
4
4
|
* Tentatives de login trop rapprochées — `code = 429` (RFC 6585 §4).
|
|
5
5
|
*
|
|
@@ -17,5 +17,4 @@ var ThrottledError = class extends nodefonyError {
|
|
|
17
17
|
this.retryAfterS = retryAfterS;
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
-
//#endregion
|
|
21
20
|
export { ThrottledError, ThrottledError as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nodefonyError } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/errors/UnverifiableTokenError.ts
|
|
3
3
|
/**
|
|
4
4
|
* Le jeton n'a pas pu être VÉRIFIÉ — `code = 503`, et surtout **pas 401**.
|
|
5
5
|
*
|
|
@@ -38,5 +38,4 @@ var UnverifiableTokenError = class extends nodefonyError {
|
|
|
38
38
|
this.detail = detail;
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
-
//#endregion
|
|
42
41
|
export { UnverifiableTokenError, UnverifiableTokenError as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nodefonyError } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/errors/WebAuthnError.ts
|
|
3
3
|
/**
|
|
4
4
|
* Erreur de **gestion** d'un credential WebAuthn (enrôlement) — porte un `code`
|
|
5
5
|
* HTTP que l'adaptateur framework mappe par duck-typing (il n'importe jamais les
|
|
@@ -17,5 +17,4 @@ var WebAuthnError = class extends nodefonyError {
|
|
|
17
17
|
super(message, code);
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
-
//#endregion
|
|
21
20
|
export { WebAuthnError, WebAuthnError as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isNodefonyKeycloakTheme, keycloakLoginTheme, realmNameFromIssuer } from "./keycloakRealmInput.js";
|
|
2
|
-
|
|
2
|
+
// nodefony/keycloak/keycloakAdmin.ts
|
|
3
3
|
/** Refus ou échec d'un appel ; le message nomme la cause et le geste suivant. */
|
|
4
4
|
var KeycloakAdminError = class extends Error {
|
|
5
5
|
status;
|
|
@@ -251,5 +251,4 @@ function keycloakLoginThemeCheck(skin) {
|
|
|
251
251
|
};
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
|
-
//#endregion
|
|
255
254
|
export { KeycloakAdminClient, KeycloakAdminError, keycloakAdminTarget, keycloakLoginThemeCheck, readKeycloakLoginTheme };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/keycloak/keycloakRealm.ts
|
|
2
2
|
/** Le type du mapper qui ajoute une audience au jeton d'accès. */
|
|
3
3
|
const AUDIENCE_MAPPER = "oidc-audience-mapper";
|
|
4
4
|
/**
|
|
@@ -256,5 +256,4 @@ function renderValue(value, indent, lead) {
|
|
|
256
256
|
function renderKeycloakRealm(realm) {
|
|
257
257
|
return `${renderValue(realm, "", 0)}\n`;
|
|
258
258
|
}
|
|
259
|
-
//#endregion
|
|
260
259
|
export { buildKeycloakRealm, keycloakDefaultRole, mergeKeycloakRealm, renderKeycloakRealm };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KEYCLOAK_THEME_NAME } from "./scaffold.js";
|
|
2
2
|
import { DEFAULT_LOGIN_PAGE_SKIN } from "nodefony";
|
|
3
|
-
|
|
3
|
+
// nodefony/keycloak/keycloakRealmInput.ts
|
|
4
4
|
/** Erreur de dérivation : la config ne permet pas d'écrire un realm. */
|
|
5
5
|
var KeycloakRealmError = class extends Error {
|
|
6
6
|
name = "KeycloakRealmError";
|
|
@@ -120,5 +120,4 @@ function deriveKeycloakRealmInput(ctx) {
|
|
|
120
120
|
warnings
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
|
-
//#endregion
|
|
124
123
|
export { KeycloakRealmError, deriveKeycloakRealmInput, isNodefonyKeycloakTheme, keycloakLoginTheme, realmNameFromIssuer };
|
|
@@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
|
-
|
|
6
|
+
// nodefony/keycloak/scaffold.ts
|
|
7
7
|
/**
|
|
8
8
|
* Le décor Keycloak de développement que `@nodefony/security` livre à une
|
|
9
9
|
* application : realm d'import et thème aux couleurs du framework.
|
|
@@ -54,7 +54,8 @@ function packageRoot() {
|
|
|
54
54
|
for (;;) {
|
|
55
55
|
const manifest = path.join(dir, "package.json");
|
|
56
56
|
if (existsSync(manifest)) {
|
|
57
|
-
|
|
57
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
58
|
+
if (typeof parsed === "object" && parsed !== null && "name" in parsed && parsed.name === PACKAGE_NAME) return dir;
|
|
58
59
|
}
|
|
59
60
|
const parent = path.dirname(dir);
|
|
60
61
|
if (parent === dir) throw new Error(`racine du paquet ${PACKAGE_NAME} introuvable`);
|
|
@@ -227,5 +228,4 @@ function contributeKeycloak(context) {
|
|
|
227
228
|
context.write(APP_KEYCLOAK_REALM_FILE, renderKeycloakRealm(realm));
|
|
228
229
|
copyKeycloakTheme(context);
|
|
229
230
|
}
|
|
230
|
-
//#endregion
|
|
231
231
|
export { APP_KEYCLOAK_REALM_FILE, APP_KEYCLOAK_THEMES_DIR, APP_KEYCLOAK_THEME_DIR, APP_PARTNER_DEV_RESOURCE, KEYCLOAK_THEME_NAME, appDevRealmInput, contributeKeycloak, copyKeycloakTheme, deliveredThemeTypes, devClientSecret, devMachineSecret, keycloakThemeSource, keycloakThemesSource };
|