@nodefony/security 10.0.0-beta.2 → 10.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/index.js +48 -16
- package/dist/nodefony/command/{security-token.js → security-mcp-token.js} +62 -24
- package/dist/nodefony/command/security-oauth-doctor.js +68 -0
- package/dist/nodefony/command/security-secrets.js +52 -29
- package/dist/nodefony/command/security-user-add.js +9 -3
- package/dist/nodefony/command/security-user-delete.js +10 -7
- package/dist/nodefony/command/security-user-list.js +5 -3
- package/dist/nodefony/command/security-user-password.js +9 -14
- package/dist/nodefony/config/config.js +99 -8
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/contracts/IAccessVoter.js +1 -2
- package/dist/nodefony/contracts/index.js +1 -2
- package/dist/nodefony/errors/AccessDeniedError.js +1 -2
- package/dist/nodefony/errors/ApiKeyError.js +3 -2
- package/dist/nodefony/errors/AuthenticationError.js +1 -2
- package/dist/nodefony/errors/CsrfError.js +1 -2
- package/dist/nodefony/errors/InsecureTransportError.js +23 -0
- package/dist/nodefony/errors/InvalidTargetError.js +1 -2
- package/dist/nodefony/errors/SsrfError.js +1 -2
- package/dist/nodefony/errors/ThrottledError.js +1 -2
- package/dist/nodefony/errors/UnverifiableTokenError.js +1 -2
- package/dist/nodefony/errors/WebAuthnError.js +1 -2
- package/dist/nodefony/errors/index.js +2 -1
- package/dist/nodefony/keycloak/keycloakAdmin.js +254 -0
- package/dist/nodefony/keycloak/keycloakRealm.js +259 -0
- package/dist/nodefony/keycloak/keycloakRealmInput.js +123 -0
- package/dist/nodefony/keycloak/scaffold.js +231 -0
- package/dist/nodefony/keycloak/security-keycloak-realm.js +130 -0
- package/dist/nodefony/scaffold/contribute.js +19 -0
- package/dist/nodefony/service/accessTokenVerifier.js +101 -34
- package/dist/nodefony/service/apiKeys.js +45 -5
- package/dist/nodefony/service/auditService.js +4 -5
- package/dist/nodefony/service/authFlow.js +118 -16
- package/dist/nodefony/service/authorization.js +1 -2
- package/dist/nodefony/service/cors.js +1 -2
- package/dist/nodefony/service/csrf.js +1 -2
- package/dist/nodefony/service/firewall.js +116 -25
- package/dist/nodefony/service/keycloak.js +208 -0
- package/dist/nodefony/service/oauth2.js +403 -28
- package/dist/nodefony/service/securityHeaders.js +1 -2
- package/dist/nodefony/service/tokenService.js +94 -27
- package/dist/nodefony/service/totp.js +55 -11
- package/dist/nodefony/service/webAuthn.js +24 -7
- package/dist/nodefony/service/webhooks.js +4 -5
- package/dist/nodefony/src/RoleHierarchyWalker.js +1 -2
- package/dist/nodefony/src/SecuredArea.js +15 -5
- package/dist/nodefony/src/accountBinding.js +96 -0
- package/dist/nodefony/src/admin/SecurityAdminApi.js +71 -42
- package/dist/nodefony/src/admin/WebhookAdminApi.js +23 -7
- package/dist/nodefony/src/admin/adminAudit.js +1 -3
- package/dist/nodefony/src/admin/userRevocationCascade.js +148 -20
- package/dist/nodefony/src/apikey/apiKeyFormat.js +1 -2
- package/dist/nodefony/src/audit/MemoryAuditStore.js +1 -2
- package/dist/nodefony/src/audit/auditBridge.js +1 -2
- package/dist/nodefony/src/audit/auditFilters.js +1 -2
- package/dist/nodefony/src/audit/auditStoreRegistry.js +1 -2
- package/dist/nodefony/src/audit/readAuditContext.js +1 -2
- package/dist/nodefony/src/audit/recordAudit.js +1 -2
- package/dist/nodefony/src/authenticator/AnonymousAuthenticator.js +1 -2
- package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +3 -2
- package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +14 -59
- package/dist/nodefony/src/authenticator/ExternalSubjectResolver.js +180 -0
- package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +80 -19
- package/dist/nodefony/src/authenticator/JwtAuthenticator.js +92 -16
- package/dist/nodefony/src/authenticator/SessionAuthenticator.js +9 -5
- package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +14 -5
- package/dist/nodefony/src/authenticator/authenticatorRegistry.js +5 -12
- package/dist/nodefony/src/authenticator/bearer.js +1 -2
- package/dist/nodefony/src/authenticator/externalSubject.js +1 -2
- package/dist/nodefony/src/authenticator/peekIssuer.js +1 -2
- package/dist/nodefony/src/boot/publicRoutesNotice.js +26 -0
- package/dist/nodefony/src/crypto/secretCipher.js +1 -2
- package/dist/nodefony/src/csp.js +1 -2
- package/dist/nodefony/src/csrfToken.js +1 -2
- package/dist/nodefony/src/entryPoint.js +38 -0
- package/dist/nodefony/src/net/ssrfGuard.js +1 -2
- package/dist/nodefony/src/net/transportFailure.js +57 -0
- package/dist/nodefony/src/oauth/clientCredentials.js +39 -0
- package/dist/nodefony/src/oauth/httpJson.js +3 -3
- package/dist/nodefony/src/oauth/metadata.js +28 -12
- package/dist/nodefony/src/oauth/oauth2Client.js +61 -35
- package/dist/nodefony/src/oauth/oauthProviderRegistry.js +40 -8
- package/dist/nodefony/src/oauth/providerDiagnosis.js +282 -0
- package/dist/nodefony/src/oauth/providerRoles.js +78 -0
- package/dist/nodefony/src/oauth/providers/github.js +48 -12
- package/dist/nodefony/src/oauth/providers/oidc.js +90 -8
- package/dist/nodefony/src/openToSignup.js +46 -0
- package/dist/nodefony/src/realtime/UserRealtimeToken.js +17 -3
- package/dist/nodefony/src/realtime/frameAuthorizer.js +1 -2
- package/dist/nodefony/src/rolesWithin.js +57 -0
- package/dist/nodefony/src/sessionAuthentication.js +25 -0
- package/dist/nodefony/src/sessionIdentity.js +29 -3
- package/dist/nodefony/src/throttle/LoginThrottler.js +1 -2
- package/dist/nodefony/src/token/AnonymousToken.js +1 -2
- package/dist/nodefony/src/token/JwtKeystore.js +89 -48
- package/dist/nodefony/src/token/MemoryTokenStore.js +1 -2
- package/dist/nodefony/src/token/RemoteJwtVerifier.js +46 -16
- package/dist/nodefony/src/token/UserToken.js +1 -2
- package/dist/nodefony/src/token/jwtRuntime.js +1 -2
- package/dist/nodefony/src/token/scopeGrants.js +109 -0
- package/dist/nodefony/src/token/secretFile.js +60 -4
- package/dist/nodefony/src/token/tokenCriteria.js +1 -2
- package/dist/nodefony/src/token/tokenFilters.js +1 -2
- package/dist/nodefony/src/token/tokenSort.js +1 -2
- package/dist/nodefony/src/token/tokenStatus.js +1 -2
- package/dist/nodefony/src/token/tokenStoreRegistry.js +1 -2
- package/dist/nodefony/src/totp/MemoryTotpSecretStore.js +1 -2
- package/dist/nodefony/src/totp/totpCipher.js +1 -2
- package/dist/nodefony/src/totp/totpCrypto.js +1 -2
- package/dist/nodefony/src/totp/totpOperations.js +1 -2
- package/dist/nodefony/src/totp/totpSecretStoreRegistry.js +1 -2
- package/dist/nodefony/src/transport/CredentialTransportPolicy.js +50 -0
- package/dist/nodefony/src/voter/RoleVoter.js +1 -2
- package/dist/nodefony/src/voter/ScopeVoter.js +1 -2
- package/dist/nodefony/src/voter/voterRegistry.js +1 -2
- package/dist/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js +1 -2
- package/dist/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js +1 -2
- package/dist/nodefony/src/webhook/MemoryWebhookStore.js +1 -2
- package/dist/nodefony/src/webhook/WebhookDispatcher.js +1 -2
- package/dist/nodefony/src/webhook/webhookCipher.js +1 -2
- package/dist/nodefony/src/webhook/webhookDelivery.js +1 -2
- package/dist/nodefony/src/webhook/webhookFilters.js +1 -2
- package/dist/nodefony/src/webhook/webhookSignature.js +1 -2
- package/dist/nodefony/src/webhook/webhookSort.js +1 -2
- package/dist/nodefony/src/webhook/webhookStoreRegistry.js +1 -2
- package/dist/types/index.d.ts +26 -6
- package/dist/types/nodefony/command/{security-token.d.ts → security-mcp-token.d.ts} +16 -6
- package/dist/types/nodefony/command/security-oauth-doctor.d.ts +23 -0
- package/dist/types/nodefony/command/security-secrets.d.ts +2 -0
- package/dist/types/nodefony/command/security-user-delete.d.ts +4 -3
- package/dist/types/nodefony/command/security-user-password.d.ts +4 -4
- package/dist/types/nodefony/config/config.d.ts +64 -1
- package/dist/types/nodefony/contracts/IAuthenticator.d.ts +1 -1
- package/dist/types/nodefony/contracts/IFirewallDescription.d.ts +42 -1
- package/dist/types/nodefony/contracts/IOAuthProvider.d.ts +48 -0
- package/dist/types/nodefony/contracts/ISecuredArea.d.ts +22 -0
- package/dist/types/nodefony/contracts/ITotpSecret.d.ts +4 -1
- package/dist/types/nodefony/contracts/ITotpSecretStore.d.ts +7 -1
- package/dist/types/nodefony/contracts/IWebAuthnCredential.d.ts +4 -1
- package/dist/types/nodefony/contracts/IWebAuthnCredentialStore.d.ts +6 -0
- package/dist/types/nodefony/errors/ApiKeyError.d.ts +3 -1
- package/dist/types/nodefony/errors/InsecureTransportError.d.ts +20 -0
- package/dist/types/nodefony/errors/index.d.ts +1 -0
- package/dist/types/nodefony/keycloak/keycloakAdmin.d.ts +154 -0
- package/dist/types/nodefony/keycloak/keycloakRealm.d.ts +138 -0
- package/dist/types/nodefony/keycloak/keycloakRealmInput.d.ts +92 -0
- package/dist/types/nodefony/keycloak/scaffold.d.ts +98 -0
- package/dist/types/nodefony/keycloak/security-keycloak-realm.d.ts +43 -0
- package/dist/types/nodefony/scaffold/contribute.d.ts +15 -0
- package/dist/types/nodefony/service/accessTokenVerifier.d.ts +35 -14
- package/dist/types/nodefony/service/apiKeys.d.ts +13 -2
- package/dist/types/nodefony/service/authFlow.d.ts +35 -3
- package/dist/types/nodefony/service/firewall.d.ts +17 -3
- package/dist/types/nodefony/service/keycloak.d.ts +115 -0
- package/dist/types/nodefony/service/oauth2.d.ts +145 -9
- package/dist/types/nodefony/service/tokenService.d.ts +7 -1
- package/dist/types/nodefony/service/totp.d.ts +28 -4
- package/dist/types/nodefony/service/webAuthn.d.ts +23 -1
- package/dist/types/nodefony/src/SecuredArea.d.ts +4 -0
- package/dist/types/nodefony/src/accountBinding.d.ts +82 -0
- package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +72 -12
- package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +2 -26
- package/dist/types/nodefony/src/authenticator/ExternalSubjectResolver.d.ts +99 -0
- package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +17 -1
- package/dist/types/nodefony/src/authenticator/JwtAuthenticator.d.ts +37 -1
- package/dist/types/nodefony/src/authenticator/UserPasswordAuthenticator.d.ts +11 -3
- package/dist/types/nodefony/src/boot/publicRoutesNotice.d.ts +16 -0
- package/dist/types/nodefony/src/entryPoint.d.ts +37 -0
- package/dist/types/nodefony/src/net/transportFailure.d.ts +22 -0
- package/dist/types/nodefony/src/oauth/clientCredentials.d.ts +54 -0
- package/dist/types/nodefony/src/oauth/httpJson.d.ts +0 -8
- package/dist/types/nodefony/src/oauth/metadata.d.ts +24 -1
- package/dist/types/nodefony/src/oauth/oauth2Client.d.ts +50 -1
- package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +46 -2
- package/dist/types/nodefony/src/oauth/providerDiagnosis.d.ts +117 -0
- package/dist/types/nodefony/src/oauth/providerRoles.d.ts +58 -0
- package/dist/types/nodefony/src/oauth/providers/oidc.d.ts +34 -1
- package/dist/types/nodefony/src/openToSignup.d.ts +36 -0
- package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +7 -1
- package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +6 -0
- package/dist/types/nodefony/src/rolesWithin.d.ts +27 -0
- package/dist/types/nodefony/src/sessionAuthentication.d.ts +14 -0
- package/dist/types/nodefony/src/sessionIdentity.d.ts +22 -0
- package/dist/types/nodefony/src/token/JwtKeystore.d.ts +47 -5
- package/dist/types/nodefony/src/token/RemoteJwtVerifier.d.ts +29 -2
- package/dist/types/nodefony/src/token/scopeGrants.d.ts +77 -0
- package/dist/types/nodefony/src/token/secretFile.d.ts +22 -0
- package/dist/types/nodefony/src/transport/CredentialTransportPolicy.d.ts +47 -0
- package/docs/api-keys.md +182 -175
- package/docs/audit.md +141 -141
- package/docs/authenticators.md +129 -96
- package/docs/authorization.md +116 -69
- package/docs/cors.md +64 -64
- package/docs/csrf.md +77 -72
- package/docs/external-jwt.md +83 -11
- package/docs/firewall.md +119 -83
- package/docs/headers.md +82 -82
- package/docs/index.md +16 -8
- package/docs/keycloak.md +835 -0
- package/docs/login-page.md +361 -0
- package/docs/oauth2.md +291 -166
- package/docs/obtenir-un-jeton.md +131 -34
- package/docs/tokens.md +197 -140
- package/docs/totp.md +177 -136
- package/docs/webauthn.md +157 -154
- package/docs/webhooks.md +128 -125
- package/keycloak/themes/nodefony/account/resources/css/nodefony.css +66 -0
- package/keycloak/themes/nodefony/account/resources/img/favicon.ico +0 -0
- package/keycloak/themes/nodefony/account/resources/img/logo.png +0 -0
- package/keycloak/themes/nodefony/account/theme.properties +14 -0
- package/keycloak/themes/nodefony/admin/resources/css/nodefony.css +66 -0
- package/keycloak/themes/nodefony/admin/resources/img/favicon.ico +0 -0
- package/keycloak/themes/nodefony/admin/resources/img/logo.png +0 -0
- package/keycloak/themes/nodefony/admin/theme.properties +12 -0
- package/keycloak/themes/nodefony/email/html/template.ftl +73 -0
- package/keycloak/themes/nodefony/email/messages/messages_en.properties +2 -0
- package/keycloak/themes/nodefony/email/messages/messages_fr.properties +3 -0
- package/keycloak/themes/nodefony/email/resources/img/logo.png +0 -0
- package/keycloak/themes/nodefony/email/theme.properties +8 -0
- package/keycloak/themes/nodefony/login/messages/messages_en.properties +22 -0
- package/keycloak/themes/nodefony/login/messages/messages_fr.properties +25 -0
- package/keycloak/themes/nodefony/login/resources/css/login.css +1141 -0
- package/keycloak/themes/nodefony/login/resources/css/nodefony.css +688 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/blueprint.css +70 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/console.css +57 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/dots.css +67 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/enterprise.css +84 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/horizon.css +158 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/ledger.css +224 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/minimal.css +63 -0
- package/keycloak/themes/nodefony/login/resources/css/skins/photo-card.css +72 -0
- package/keycloak/themes/nodefony/login/resources/img/favicon.ico +0 -0
- package/keycloak/themes/nodefony/login/resources/img/logo.png +0 -0
- package/keycloak/themes/nodefony/login/template.ftl +423 -0
- package/keycloak/themes/nodefony/login/theme.properties +17 -0
- package/keycloak/themes/nodefony/welcome/resources/css/nodefony.css +32 -0
- package/keycloak/themes/nodefony/welcome/resources/img/logo.png +0 -0
- package/keycloak/themes/nodefony/welcome/theme.properties +11 -0
- package/keycloak/themes/nodefony-blueprint/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-console/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-dots/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-enterprise/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-horizon/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-ledger/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-minimal/login/theme.properties +12 -0
- package/keycloak/themes/nodefony-photo-card/login/theme.properties +12 -0
- package/package.json +19 -17
- package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ADMIN_SCOPE_READ, ADMIN_SCOPE_WRITE, refusedAdminScopes } from "nodefony";
|
|
2
|
+
// nodefony/src/token/scopeGrants.ts
|
|
3
|
+
const SCOPE_TOKEN = /^[\x21\x23-\x5B\x5D-\x7E]+$/u;
|
|
4
|
+
const WILDCARD_SUFFIX = ":*";
|
|
5
|
+
/**
|
|
6
|
+
* Clés que `grantableScopes` refuse : le plan d'administration est jugé par le
|
|
7
|
+
* framework, et une déclaration ici serait soit sans effet, soit un
|
|
8
|
+
* assouplissement — deux pièges que rien ne signalerait.
|
|
9
|
+
*/
|
|
10
|
+
const RESERVED_SCOPE_GRANT_KEYS = [
|
|
11
|
+
ADMIN_SCOPE_READ,
|
|
12
|
+
ADMIN_SCOPE_WRITE,
|
|
13
|
+
"admin:*"
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Dit pourquoi une clé de `grantableScopes` est invalide, ou `null` si elle
|
|
17
|
+
* est acceptable.
|
|
18
|
+
*
|
|
19
|
+
* Une clé est un `scope-token` (RFC 6749 §3.3) ; un `*` n'y est admis qu'en
|
|
20
|
+
* dernière position, juste après `:` (une famille `préfixe:*`).
|
|
21
|
+
*
|
|
22
|
+
* @param key - la clé déclarée.
|
|
23
|
+
* @returns le motif du refus, ou `null`.
|
|
24
|
+
*/
|
|
25
|
+
function scopeGrantKeyProblem(key) {
|
|
26
|
+
if (!SCOPE_TOKEN.test(key)) return "n'est pas un scope valide (RFC 6749 §3.3 : ni espace, ni guillemet, ni barre oblique inverse)";
|
|
27
|
+
const star = key.indexOf("*");
|
|
28
|
+
if (star !== -1 && (star !== key.length - 1 || !key.endsWith(WILDCARD_SUFFIX) || key.length === 2)) return "porte un `*` ailleurs qu'en fin de famille (`préfixe:*`)";
|
|
29
|
+
if (RESERVED_SCOPE_GRANT_KEYS.includes(key)) return "est réservé au plan d'administration du framework (les scopes `admin:read`/`admin:write` sont accordés au seul rôle ROLE_NODEFONY_ADMIN)";
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Politique d'attribution des scopes d'application, compilée une fois au boot.
|
|
34
|
+
*
|
|
35
|
+
* Résolution : une clé EXACTE l'emporte sur une famille, et la famille la plus
|
|
36
|
+
* longue sur une plus courte (`shop:admin:*` > `shop:*`). Le résultat ne dépend
|
|
37
|
+
* donc pas de l'ordre des clés. Un scope qu'aucune clé ne couvre reste accordé
|
|
38
|
+
* à tout porteur (comportement historique).
|
|
39
|
+
*/
|
|
40
|
+
var ScopeGrantPolicy = class {
|
|
41
|
+
#exact = /* @__PURE__ */ new Map();
|
|
42
|
+
#families = [];
|
|
43
|
+
/**
|
|
44
|
+
* @param grants - la section `grantableScopes`, déjà validée par le schéma.
|
|
45
|
+
*/
|
|
46
|
+
constructor(grants = {}) {
|
|
47
|
+
for (const [key, roles] of Object.entries(grants)) if (key.endsWith(WILDCARD_SUFFIX)) this.#families.push([key.slice(0, -1), roles]);
|
|
48
|
+
else this.#exact.set(key, roles);
|
|
49
|
+
this.#families.sort((a, b) => b[0].length - a[0].length);
|
|
50
|
+
}
|
|
51
|
+
/** `true` si aucune clé n'est déclarée — tout scope d'application est accordé. */
|
|
52
|
+
get isEmpty() {
|
|
53
|
+
return this.#exact.size === 0 && this.#families.length === 0;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Les rôles dont UN suffit pour obtenir ce scope.
|
|
57
|
+
*
|
|
58
|
+
* @param scope - un scope demandé.
|
|
59
|
+
* @returns les rôles de la clé la plus spécifique, ou `null` si aucune clé ne
|
|
60
|
+
* le couvre (scope libre).
|
|
61
|
+
*/
|
|
62
|
+
rolesFor(scope) {
|
|
63
|
+
const exact = this.#exact.get(scope);
|
|
64
|
+
if (exact !== void 0) return exact;
|
|
65
|
+
for (const [prefix, roles] of this.#families) if (scope.startsWith(prefix)) return roles;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Les scopes demandés que ce porteur ne peut pas obtenir, avec la règle qui
|
|
71
|
+
* refuse chacun.
|
|
72
|
+
*
|
|
73
|
+
* Seul juge des DEUX familles, appelé par chaque émetteur de l'application
|
|
74
|
+
* (jeton d'accès, clé API) : la règle d'administration du framework
|
|
75
|
+
* ({@link refusedAdminScopes}, jugée sur les rôles PLATS du compte, comme le
|
|
76
|
+
* plan d'administration) et la politique de l'application
|
|
77
|
+
* ({@link ScopeGrantPolicy}, jugée hiérarchie de rôles comprise, comme un
|
|
78
|
+
* `@IsGranted`).
|
|
79
|
+
*
|
|
80
|
+
* @param requested - scopes demandés (RFC 6749 §3.3).
|
|
81
|
+
* @param roles - rôles du porteur, tels que son compte les porte.
|
|
82
|
+
* @param policy - politique compilée de `grantableScopes`.
|
|
83
|
+
* @param hierarchy - hiérarchie de rôles ; absente, seuls les rôles plats comptent.
|
|
84
|
+
* @returns les refus, dans l'ordre de la demande — vide si tout est accordé.
|
|
85
|
+
*/
|
|
86
|
+
function refusedScopes(requested, roles, policy, hierarchy) {
|
|
87
|
+
const admin = refusedAdminScopes(requested, roles);
|
|
88
|
+
if (admin.length === 0 && policy.isEmpty) return [];
|
|
89
|
+
let reachable = null;
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const scope of requested) {
|
|
92
|
+
if (admin.includes(scope)) {
|
|
93
|
+
out.push({
|
|
94
|
+
scope,
|
|
95
|
+
rule: "admin"
|
|
96
|
+
});
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const allowed = policy.rolesFor(scope);
|
|
100
|
+
if (allowed === null) continue;
|
|
101
|
+
reachable ??= hierarchy ? hierarchy.reachableRoles(roles) : new Set(roles);
|
|
102
|
+
if (!allowed.some((role) => reachable?.has(role))) out.push({
|
|
103
|
+
scope,
|
|
104
|
+
rule: "application"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
export { RESERVED_SCOPE_GRANT_KEYS, ScopeGrantPolicy, refusedScopes, scopeGrantKeyProblem };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { link, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
5
|
import process from "node:process";
|
|
5
|
-
|
|
6
|
+
// nodefony/src/token/secretFile.ts
|
|
6
7
|
/**
|
|
7
8
|
* Écrire un SECRET sur disque — la seule implémentation du dépôt.
|
|
8
9
|
*
|
|
@@ -113,6 +114,62 @@ async function writeSecret(file, content) {
|
|
|
113
114
|
throw e;
|
|
114
115
|
}
|
|
115
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Codes d'un système de fichiers qui ne sait pas poser de lien dur (FAT, exFAT,
|
|
119
|
+
* certains montages réseau) — et non d'une cible déjà présente.
|
|
120
|
+
*/
|
|
121
|
+
const NO_HARD_LINK = /* @__PURE__ */ new Set([
|
|
122
|
+
"EPERM",
|
|
123
|
+
"ENOTSUP",
|
|
124
|
+
"ENOSYS",
|
|
125
|
+
"EOPNOTSUPP"
|
|
126
|
+
]);
|
|
127
|
+
/**
|
|
128
|
+
* Crée un secret SEULEMENT s'il n'existe pas encore : 0600, atomique, et un
|
|
129
|
+
* seul gagnant quand plusieurs process le tentent en même temps.
|
|
130
|
+
*
|
|
131
|
+
* ⭐ **Pourquoi pas {@link writeSecret}.** `rename` REMPLACE la cible : deux
|
|
132
|
+
* workers de `nodefony cluster` qui génèrent chacun une clé au premier boot
|
|
133
|
+
* l'écrivent tous les deux, le dernier gagne sur le disque — et l'autre garde
|
|
134
|
+
* en mémoire une clé que plus personne ne connaît. Ici, le temporaire est
|
|
135
|
+
* publié par un LIEN DUR, que le système refuse (`EEXIST`) si le nom existe :
|
|
136
|
+
* le perdant le sait, et relit la clé du gagnant. Le lecteur ne voit jamais un
|
|
137
|
+
* fichier à demi écrit, puisque le lien n'apparaît qu'une fois le contenu complet.
|
|
138
|
+
*
|
|
139
|
+
* ⚠️ Un système de fichiers sans liens durs (FAT, exFAT) retombe sur une
|
|
140
|
+
* création exclusive directe (`wx`) : toujours un seul gagnant, mais un lecteur
|
|
141
|
+
* concurrent peut lire le fichier pendant son écriture — il lève alors, au lieu
|
|
142
|
+
* de signer avec une clé divergente.
|
|
143
|
+
*
|
|
144
|
+
* @param file - chemin du secret.
|
|
145
|
+
* @param content - contenu à écrire.
|
|
146
|
+
* @returns `true` si ce process l'a créé, `false` s'il existait déjà.
|
|
147
|
+
*/
|
|
148
|
+
async function createSecretExclusive(file, content) {
|
|
149
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
150
|
+
const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
151
|
+
await writeFile(tmp, content, { mode: 384 });
|
|
152
|
+
try {
|
|
153
|
+
await link(tmp, file);
|
|
154
|
+
return true;
|
|
155
|
+
} catch (e) {
|
|
156
|
+
const code = e.code ?? "";
|
|
157
|
+
if (code === "EEXIST") return false;
|
|
158
|
+
if (!NO_HARD_LINK.has(code)) throw e;
|
|
159
|
+
try {
|
|
160
|
+
await writeFile(file, content, {
|
|
161
|
+
mode: 384,
|
|
162
|
+
flag: "wx"
|
|
163
|
+
});
|
|
164
|
+
return true;
|
|
165
|
+
} catch (fallback) {
|
|
166
|
+
if (fallback.code === "EEXIST") return false;
|
|
167
|
+
throw fallback;
|
|
168
|
+
}
|
|
169
|
+
} finally {
|
|
170
|
+
await rm(tmp, { force: true }).catch(() => {});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
116
173
|
/** Forme synchrone de {@link writeSecret}. */
|
|
117
174
|
function writeSecretSync(file, content) {
|
|
118
175
|
mkdirSync(path.dirname(file), { recursive: true });
|
|
@@ -130,5 +187,4 @@ function writeSecretSync(file, content) {
|
|
|
130
187
|
chmodSync(file, 384);
|
|
131
188
|
} catch {}
|
|
132
189
|
}
|
|
133
|
-
|
|
134
|
-
export { MODE_SECRET, messageNonRestreint, modeNonRestreint, modeNonRestreintAsync, readIfPresent, readIfPresentSync, writeSecret, writeSecretSync };
|
|
190
|
+
export { MODE_SECRET, createSecretExclusive, messageNonRestreint, modeNonRestreint, modeNonRestreintAsync, readIfPresent, readIfPresentSync, writeSecret, writeSecretSync };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/token/tokenCriteria.ts
|
|
2
2
|
/**
|
|
3
3
|
* Fragment de critère **portable** exprimant l'état de vie d'un jeton — la
|
|
4
4
|
* traduction que les adapters SQL et Mongo partagent au lieu de la réécrire.
|
|
@@ -32,5 +32,4 @@ function tokenStatusCriteria(status, now) {
|
|
|
32
32
|
default: return {};
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
//#endregion
|
|
36
35
|
export { tokenStatusCriteria };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/token/tokenFilters.ts
|
|
2
2
|
/**
|
|
3
3
|
* **Le vocabulaire de filtre des jetons**, en noms PUBLICS — ceux qu'un client
|
|
4
4
|
* écrit dans l'URL (`?status=revoked`), jamais des noms de colonne.
|
|
@@ -68,5 +68,4 @@ const TOKEN_FACETS = {
|
|
|
68
68
|
* Un test verrouille l'accord entre cette liste et {@link TOKEN_FACETS}.
|
|
69
69
|
*/
|
|
70
70
|
const TOKEN_STATS_FILTERS = { subjectId: "string" };
|
|
71
|
-
//#endregion
|
|
72
71
|
export { TOKEN_FACETS, TOKEN_FILTERS, TOKEN_STATS_FILTERS };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/token/tokenSort.ts
|
|
2
2
|
/**
|
|
3
3
|
* **Le vocabulaire de tri des jetons**, en noms PUBLICS — ceux qu'un client écrit
|
|
4
4
|
* dans l'URL (`?order=createdAt:DESC`), jamais des noms de colonne.
|
|
@@ -36,5 +36,4 @@ const TOKEN_SORTABLE_FIELDS = [
|
|
|
36
36
|
* ligne entre deux pages).
|
|
37
37
|
*/
|
|
38
38
|
const TOKEN_DEFAULT_ORDER = [["createdAt", "DESC"], ["id", "DESC"]];
|
|
39
|
-
//#endregion
|
|
40
39
|
export { TOKEN_DEFAULT_ORDER, TOKEN_SORTABLE_FIELDS };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/token/tokenStatus.ts
|
|
2
2
|
/**
|
|
3
3
|
* **La** définition de l'état d'un jeton — un seul exemplaire, pour les backends
|
|
4
4
|
* qui évaluent en mémoire (store mémoire, filtrage inline d'un batch `SCAN`).
|
|
@@ -31,5 +31,4 @@ function tokenStatusOf(token, now) {
|
|
|
31
31
|
function matchesTokenStatus(token, status, now) {
|
|
32
32
|
return status === void 0 || tokenStatusOf(token, now) === status;
|
|
33
33
|
}
|
|
34
|
-
//#endregion
|
|
35
34
|
export { matchesTokenStatus, tokenStatusOf };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MemoryTokenStore } from "./MemoryTokenStore.js";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/token/tokenStoreRegistry.ts
|
|
3
3
|
const factories = /* @__PURE__ */ new Map();
|
|
4
4
|
/**
|
|
5
5
|
* Enregistre (ou remplace) la fabrique d'un store de jetons. Appelée par le
|
|
@@ -21,5 +21,4 @@ registerTokenStore("memory", (ctx) => {
|
|
|
21
21
|
const days = ctx?.config?.tokenStore?.retentionRevokedDays;
|
|
22
22
|
return typeof days === "number" ? new MemoryTokenStore(Date.now, days * 864e5) : new MemoryTokenStore();
|
|
23
23
|
});
|
|
24
|
-
//#endregion
|
|
25
24
|
export { getTokenStoreFactory, listTokenStores, registerTokenStore };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assertPageQuery } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/totp/MemoryTotpSecretStore.ts
|
|
3
3
|
/**
|
|
4
4
|
* Projette un secret en vue d'introspection — **le seul endroit** où l'on
|
|
5
5
|
* décide ce qui sort d'un store TOTP en mémoire. `secretEnc` et les condensats
|
|
@@ -105,5 +105,4 @@ var MemoryTotpSecretStore = class {
|
|
|
105
105
|
for (const secret of snapshot.secrets) this.#byUser.set(secret.userId, secret);
|
|
106
106
|
}
|
|
107
107
|
};
|
|
108
|
-
//#endregion
|
|
109
108
|
export { MemoryTotpSecretStore, MemoryTotpSecretStore as default, matchesTotpQuery, toTotpEnrollment };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { decryptSecret, deriveKey, encryptSecret, generateEphemeralKey } from "../crypto/secretCipher.js";
|
|
2
2
|
import { Buffer } from "node:buffer";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/totp/totpCipher.ts
|
|
4
4
|
/**
|
|
5
5
|
* Chiffrement réversible du secret TOTP au repos — façade de domaine au-dessus
|
|
6
6
|
* de la brique générique {@link ../crypto/secretCipher}.
|
|
@@ -26,5 +26,4 @@ const TOTP_DERIVATION = {
|
|
|
26
26
|
function deriveTotpKey(material) {
|
|
27
27
|
return deriveKey(material, TOTP_DERIVATION);
|
|
28
28
|
}
|
|
29
|
-
//#endregion
|
|
30
29
|
export { decryptSecret, deriveTotpKey, encryptSecret, generateEphemeralKey };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/totp/totpCrypto.ts
|
|
3
3
|
/** Paramètres TOTP — défauts interopérables (Google Authenticator). */
|
|
4
4
|
const TOTP_DEFAULTS = {
|
|
5
5
|
/** Taille du secret en octets (RFC 4226 R6 : 160 bits recommandés). */
|
|
@@ -222,5 +222,4 @@ function matchRecoveryCode(presented, hashes) {
|
|
|
222
222
|
}
|
|
223
223
|
return found;
|
|
224
224
|
}
|
|
225
|
-
//#endregion
|
|
226
225
|
export { TOTP_DEFAULTS, base32Decode, base32Encode, buildOtpauthUri, generateRecoveryCodes, generateTotpSecret, hashRecoveryCode, hotp, matchRecoveryCode, totpCode, totpCounter, unbiasedIndices, verifyTotp };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { decryptSecret, encryptSecret } from "../crypto/secretCipher.js";
|
|
2
2
|
import "./totpCipher.js";
|
|
3
3
|
import { base32Encode, buildOtpauthUri, generateRecoveryCodes, generateTotpSecret, hashRecoveryCode, matchRecoveryCode, verifyTotp } from "./totpCrypto.js";
|
|
4
|
-
|
|
4
|
+
// nodefony/src/totp/totpOperations.ts
|
|
5
5
|
/**
|
|
6
6
|
* Démarre l'enrôlement : génère un secret aléatoire, le **chiffre** au repos et
|
|
7
7
|
* l'enregistre en attente de confirmation (`confirmedAt: null`). Retourne le
|
|
@@ -125,5 +125,4 @@ async function totpStatus(deps, userId) {
|
|
|
125
125
|
recoveryCodesRemaining: record.recoveryCodes.length
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
-
//#endregion
|
|
129
128
|
export { beginTotpEnrollment, confirmTotpEnrollment, disableTotp, totpStatus, verifyTotpLogin };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MemoryTotpSecretStore } from "./MemoryTotpSecretStore.js";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/totp/totpSecretStoreRegistry.ts
|
|
3
3
|
const factories = /* @__PURE__ */ new Map();
|
|
4
4
|
/** Enregistre (ou remplace) la fabrique d'un store de secrets TOTP. */
|
|
5
5
|
function registerTotpStore(name, factory) {
|
|
@@ -14,5 +14,4 @@ function listTotpStores() {
|
|
|
14
14
|
return [...factories.keys()];
|
|
15
15
|
}
|
|
16
16
|
registerTotpStore("memory", () => new MemoryTotpSecretStore());
|
|
17
|
-
//#endregion
|
|
18
17
|
export { getTotpStoreFactory, listTotpStores, registerTotpStore };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { InsecureTransportError } from "../../errors/InsecureTransportError.js";
|
|
2
|
+
// nodefony/src/transport/CredentialTransportPolicy.ts
|
|
3
|
+
/**
|
|
4
|
+
* Dit si un secret de connexion se refuse : en production, sans échappement
|
|
5
|
+
* explicite. Seule source de la décision — le firewall la compose au boot.
|
|
6
|
+
*
|
|
7
|
+
* @param environment - environnement normalisé du kernel.
|
|
8
|
+
* @param allowInsecureCredentials - échappement `security.allowInsecureCredentials`.
|
|
9
|
+
*/
|
|
10
|
+
function shouldEnforceCredentialTransport(environment, allowInsecureCredentials) {
|
|
11
|
+
return environment === "production" && !allowInsecureCredentials;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Refuse, avant toute vérification, un mot de passe ou un code de second
|
|
15
|
+
* facteur reçu en clair. Une instance partagée au conteneur (`credentialTransport`)
|
|
16
|
+
* sert TOUTES les portes qui reçoivent un secret : formulaire de session, second
|
|
17
|
+
* facteur, Basic, émission de jeton — une porte oubliée serait le canal clair.
|
|
18
|
+
*
|
|
19
|
+
* La décision se prend sur le scheme EFFECTIF (`context.scheme`) : derrière un
|
|
20
|
+
* proxy qui termine TLS et déclaré de confiance, il vaut `https` alors que le
|
|
21
|
+
* pod écoute en HTTP. C'est le cas de la plupart des déploiements ; c'est donc
|
|
22
|
+
* `trustProxy` que nomme le journal, pas « passez en HTTPS ».
|
|
23
|
+
*/
|
|
24
|
+
var CredentialTransportPolicy = class {
|
|
25
|
+
enforce;
|
|
26
|
+
#log;
|
|
27
|
+
#warned = false;
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.enforce = options.enforce;
|
|
30
|
+
this.#log = options.log ?? null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Laisse passer un secret reçu sur TLS, ou n'importe lequel hors production.
|
|
34
|
+
*
|
|
35
|
+
* @param context - contexte de la requête qui porte le secret.
|
|
36
|
+
* @param door - porte qui le reçoit, nommée dans le journal.
|
|
37
|
+
* @throws InsecureTransportError (403) — secret reçu en clair en production.
|
|
38
|
+
*/
|
|
39
|
+
assert(context, door) {
|
|
40
|
+
if (!this.enforce) return;
|
|
41
|
+
const scheme = context.scheme;
|
|
42
|
+
if (scheme === "https" || scheme === "wss") return;
|
|
43
|
+
if (!this.#warned && this.#log !== null) {
|
|
44
|
+
this.#warned = true;
|
|
45
|
+
this.#log(`Secret de connexion REFUSÉ (${door}) : la requête est arrivée en ${scheme ?? "scheme inconnu"} en production. Derrière un proxy qui termine TLS, déclarez-le de confiance (\`trustProxy\`, config @nodefony/http) pour que \`X-Forwarded-Proto\` soit lu ; sinon servez l'application en HTTPS. Échappement explicite : \`security.allowInsecureCredentials\`. Les refus suivants ne sont plus journalisés ici (voir l'audit).`);
|
|
46
|
+
}
|
|
47
|
+
throw new InsecureTransportError();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
export { CredentialTransportPolicy, shouldEnforceCredentialTransport };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RoleHierarchyWalker } from "../RoleHierarchyWalker.js";
|
|
2
2
|
import "../../contracts/IAccessVoter.js";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/voter/RoleVoter.ts
|
|
4
4
|
/**
|
|
5
5
|
* Voter built-in **niveau A** — résout les attributs `ROLE_*` via la hiérarchie
|
|
6
6
|
* de rôles ({@link RoleHierarchyWalker}).
|
|
@@ -28,5 +28,4 @@ var RoleVoter = class {
|
|
|
28
28
|
return Promise.resolve(vote);
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
-
//#endregion
|
|
32
31
|
export { RoleVoter, RoleVoter as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../../contracts/IAccessVoter.js";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/voter/ScopeVoter.ts
|
|
3
3
|
/**
|
|
4
4
|
* Types de jetons **non scopables** — identité humaine interactive (session BFF
|
|
5
5
|
* web/Studio/WS, login mot de passe) ou visiteur anonyme. Un scope `api:action`
|
|
@@ -48,5 +48,4 @@ var ScopeVoter = class {
|
|
|
48
48
|
return Promise.resolve(token.getScopes().includes(attribute) ? "GRANT" : "ABSTAIN");
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
|
-
//#endregion
|
|
52
51
|
export { ScopeVoter, ScopeVoter as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RoleVoter } from "./RoleVoter.js";
|
|
2
2
|
import { ScopeVoter } from "./ScopeVoter.js";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/voter/voterRegistry.ts
|
|
4
4
|
const factories = /* @__PURE__ */ new Map();
|
|
5
5
|
/**
|
|
6
6
|
* Enregistre (ou remplace) la fabrique d'un voter. Appelé par les builtins au
|
|
@@ -16,5 +16,4 @@ function listVoterFactories() {
|
|
|
16
16
|
}
|
|
17
17
|
registerVoterFactory("role", ({ container }) => new RoleVoter(container));
|
|
18
18
|
registerVoterFactory("scope", () => new ScopeVoter());
|
|
19
|
-
//#endregion
|
|
20
19
|
export { listVoterFactories, registerVoterFactory };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assertPageQuery } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/webauthn/MemoryWebAuthnCredentialStore.ts
|
|
3
3
|
/**
|
|
4
4
|
* Projection contractuelle : credential complet → vue admin **sans `publicKey`**.
|
|
5
5
|
* Partagée par les backends qui matérialisent des credentials en mémoire.
|
|
@@ -135,5 +135,4 @@ var MemoryWebAuthnCredentialStore = class {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
};
|
|
138
|
-
//#endregion
|
|
139
138
|
export { MemoryWebAuthnCredentialStore, MemoryWebAuthnCredentialStore as default, toWebAuthnSummary };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MemoryWebAuthnCredentialStore } from "./MemoryWebAuthnCredentialStore.js";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/webauthn/webAuthnCredentialStoreRegistry.ts
|
|
3
3
|
const factories = /* @__PURE__ */ new Map();
|
|
4
4
|
/** Enregistre (ou remplace) la fabrique d'un store de credentials WebAuthn. */
|
|
5
5
|
function registerWebAuthnStore(name, factory) {
|
|
@@ -14,5 +14,4 @@ function listWebAuthnStores() {
|
|
|
14
14
|
return [...factories.keys()];
|
|
15
15
|
}
|
|
16
16
|
registerWebAuthnStore("memory", () => new MemoryWebAuthnCredentialStore());
|
|
17
|
-
//#endregion
|
|
18
17
|
export { getWebAuthnStoreFactory, listWebAuthnStores, registerWebAuthnStore };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { WEBHOOK_DEFAULT_ORDER, WEBHOOK_SORTABLE_FIELDS } from "./webhookSort.js";
|
|
2
2
|
import { assertPageQuery, compareByOrder, pickOrder } from "nodefony";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/webhook/MemoryWebhookStore.ts
|
|
4
4
|
/**
|
|
5
5
|
* Applique les filtres d'{@link IWebhookListQuery} à un endpoint — sémantique de
|
|
6
6
|
* RÉFÉRENCE du contrat, partagée par les backends qui filtrent en mémoire.
|
|
@@ -91,5 +91,4 @@ var MemoryWebhookStore = class {
|
|
|
91
91
|
return n;
|
|
92
92
|
}
|
|
93
93
|
};
|
|
94
|
-
//#endregion
|
|
95
94
|
export { MemoryWebhookStore, matchesWebhookQuery };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { webhookSignatureHeaders } from "./webhookSignature.js";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/webhook/WebhookDispatcher.ts
|
|
3
3
|
/**
|
|
4
4
|
* Dispatcher de webhooks sortants — abonné au journal d'audit, il filtre les
|
|
5
5
|
* événements par souscription, signe (Standard Webhooks v1) et livre, **sans
|
|
@@ -205,5 +205,4 @@ var WebhookDispatcher = class {
|
|
|
205
205
|
this.#queue = null;
|
|
206
206
|
}
|
|
207
207
|
};
|
|
208
|
-
//#endregion
|
|
209
208
|
export { WebhookDispatcher, backoffMs, classifyDelivery, matchesSubscription };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { decryptSecret, deriveKey, encryptSecret, generateEphemeralKey } from "../crypto/secretCipher.js";
|
|
2
2
|
import { Buffer } from "node:buffer";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/webhook/webhookCipher.ts
|
|
4
4
|
/**
|
|
5
5
|
* Chiffrement réversible du secret de signature webhook au repos — façade de
|
|
6
6
|
* domaine au-dessus de {@link ../crypto/secretCipher} (calque {@link ../totp/totpCipher}).
|
|
@@ -23,5 +23,4 @@ const WEBHOOK_DERIVATION = {
|
|
|
23
23
|
function deriveWebhookKey(material) {
|
|
24
24
|
return deriveKey(material, WEBHOOK_DERIVATION);
|
|
25
25
|
}
|
|
26
|
-
//#endregion
|
|
27
26
|
export { decryptSecret, deriveWebhookKey, encryptSecret, generateEphemeralKey };
|
|
@@ -2,7 +2,7 @@ import { Buffer } from "node:buffer";
|
|
|
2
2
|
import { isIP } from "node:net";
|
|
3
3
|
import { request } from "node:https";
|
|
4
4
|
import { request as request$1 } from "node:http";
|
|
5
|
-
|
|
5
|
+
// nodefony/src/webhook/webhookDelivery.ts
|
|
6
6
|
/**
|
|
7
7
|
* Émetteur HTTP d'une livraison webhook — `node:http(s)` natif (0 dép). Trois
|
|
8
8
|
* propriétés de sécurité par rapport à `fetch` :
|
|
@@ -98,5 +98,4 @@ function deliverWebhook(url, body, headers, opts) {
|
|
|
98
98
|
req.end();
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
-
//#endregion
|
|
102
101
|
export { deliverWebhook };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/webhook/webhookFilters.ts
|
|
2
2
|
/**
|
|
3
3
|
* **Le vocabulaire de filtre des endpoints webhook**, en noms PUBLICS — ceux
|
|
4
4
|
* qu'un client écrit dans l'URL (`?enabled=false&event=user.created`).
|
|
@@ -52,5 +52,4 @@ const WEBHOOK_FACETS = {
|
|
|
52
52
|
* Un test verrouille l'accord entre cette liste et {@link WEBHOOK_FACETS}.
|
|
53
53
|
*/
|
|
54
54
|
const WEBHOOK_STATS_FILTERS = { event: "string" };
|
|
55
|
-
//#endregion
|
|
56
55
|
export { WEBHOOK_FACETS, WEBHOOK_FILTERS, WEBHOOK_STATS_FILTERS };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHmac } from "node:crypto";
|
|
2
2
|
import { Buffer } from "node:buffer";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/webhook/webhookSignature.ts
|
|
4
4
|
/**
|
|
5
5
|
* Signature des webhooks sortants — schéma **Standard Webhooks v1**
|
|
6
6
|
* (standardwebhooks.com), choisi plutôt que RFC 9421 (trop lourd pour un webhook,
|
|
@@ -47,5 +47,4 @@ function webhookSignatureHeaders(secret, id, timestampS, body) {
|
|
|
47
47
|
"webhook-signature": signStandardWebhook(secret, id, timestampS, body)
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
//#endregion
|
|
51
50
|
export { buildSignatureBase, parseWebhookSecret, signStandardWebhook, webhookSignatureHeaders };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/webhook/webhookSort.ts
|
|
2
2
|
/**
|
|
3
3
|
* **Le vocabulaire de tri des endpoints webhook**, en noms PUBLICS — ceux qu'un
|
|
4
4
|
* client écrit dans l'URL (`?order=failureCount:DESC`), jamais des noms de
|
|
@@ -44,5 +44,4 @@ const WEBHOOK_SORTABLE_FIELDS = [
|
|
|
44
44
|
* des deux ne jamais apparaître.
|
|
45
45
|
*/
|
|
46
46
|
const WEBHOOK_DEFAULT_ORDER = [["createdAt", "DESC"], ["id", "ASC"]];
|
|
47
|
-
//#endregion
|
|
48
47
|
export { WEBHOOK_DEFAULT_ORDER, WEBHOOK_SORTABLE_FIELDS };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MemoryWebhookStore } from "./MemoryWebhookStore.js";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/webhook/webhookStoreRegistry.ts
|
|
3
3
|
const factories = /* @__PURE__ */ new Map();
|
|
4
4
|
/** Enregistre (ou remplace) la fabrique d'un store d'endpoints webhook. */
|
|
5
5
|
function registerWebhookStore(name, factory) {
|
|
@@ -14,5 +14,4 @@ function listWebhookStores() {
|
|
|
14
14
|
return [...factories.keys()];
|
|
15
15
|
}
|
|
16
16
|
registerWebhookStore("memory", () => new MemoryWebhookStore());
|
|
17
|
-
//#endregion
|
|
18
17
|
export { getWebhookStoreFactory, listWebhookStores, registerWebhookStore };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import ApiKeyService from "./nodefony/service/apiKeys.js";
|
|
|
10
10
|
import AuditService from "./nodefony/service/auditService.js";
|
|
11
11
|
import TotpService from "./nodefony/service/totp.js";
|
|
12
12
|
import WebhookService from "./nodefony/service/webhooks.js";
|
|
13
|
+
import KeycloakService from "./nodefony/service/keycloak.js";
|
|
13
14
|
import type { ISecurityConfigInput } from "./nodefony/config/defineModuleConfig.js";
|
|
14
15
|
declare module "nodefony" {
|
|
15
16
|
interface NodefonyModuleConfig {
|
|
@@ -25,7 +26,7 @@ declare module "nodefony" {
|
|
|
25
26
|
* + Zod validée au boot (fail-closed si invalide). Consomme `@nodefony/user`
|
|
26
27
|
* (IUser/IUserProvider/IPasswordVerifier) — jamais l'inverse.
|
|
27
28
|
*
|
|
28
|
-
* Livré : firewall (zones, mode first|all, challenge RFC
|
|
29
|
+
* Livré : firewall (zones, mode first|all, challenge RFC 9110 §11), authenticators
|
|
29
30
|
* `anonymous`/`userpassword` (Basic RFC 7617)/`session` (BFF, J3), flux
|
|
30
31
|
* login/logout/me (`AuthFlow`, anti-fixation + throttling partagé).
|
|
31
32
|
* Jwt/oauth2/mtls/apikey, CORS, CSRF, autorisation par décorateurs et data
|
|
@@ -48,6 +49,14 @@ declare class Security extends Module {
|
|
|
48
49
|
* No-op si le broker est absent (Studio non chargé) ou déjà enregistré.
|
|
49
50
|
*/
|
|
50
51
|
onKernelBoot(): Promise<this>;
|
|
52
|
+
/**
|
|
53
|
+
* Déclare au bilan de démarrage les routes métier laissées PUBLIQUES.
|
|
54
|
+
*
|
|
55
|
+
* `onReady` et pas `onBoot` : les zones sont compilées par le pare-feu à son
|
|
56
|
+
* propre `onBoot`, posé APRÈS celui du module — à `onBoot` la liste est encore
|
|
57
|
+
* vide. `onReady` précède l'écriture du bilan (`var/last-boot.json`).
|
|
58
|
+
*/
|
|
59
|
+
onKernelReady(): Promise<this>;
|
|
51
60
|
}
|
|
52
61
|
export default Security;
|
|
53
62
|
export { Firewall };
|
|
@@ -63,8 +72,11 @@ export { OAuth2Service };
|
|
|
63
72
|
export { AuditService };
|
|
64
73
|
export { TotpService };
|
|
65
74
|
export { WebhookService };
|
|
75
|
+
export { KeycloakService };
|
|
76
|
+
export type { IKeycloakRealmPlan, IKeycloakRealmRequest, } from "./nodefony/service/keycloak.js";
|
|
66
77
|
export type { IWebhookRegisterInput, IWebhookSecretReveal, IWebhookDeliveryPolicy, } from "./nodefony/service/webhooks.js";
|
|
67
78
|
export type { ISafeUser, ILoginOutcome } from "./nodefony/service/authFlow.js";
|
|
79
|
+
export { amrForFactor } from "./nodefony/src/sessionAuthentication.js";
|
|
68
80
|
export type { ITokenResponse } from "./nodefony/service/tokenService.js";
|
|
69
81
|
export type { IApiKeyView, IApiKeyCreated, IApiKeyCapabilities, ICreateApiKeyOptions, } from "./nodefony/contracts/IApiKey.js";
|
|
70
82
|
export { ApiKeyError } from "./nodefony/errors/ApiKeyError.js";
|
|
@@ -114,14 +126,20 @@ export type { IWebAuthnUser, IWebAuthnAssertionResult, } from "./nodefony/servic
|
|
|
114
126
|
export type { IWebAuthnCredential } from "./nodefony/contracts/IWebAuthnCredential.js";
|
|
115
127
|
export type { IWebAuthnCredentialStore, IWebAuthnCredentialSummary, IWebAuthnListQuery, WebAuthnAuthUpdate, } from "./nodefony/contracts/IWebAuthnCredentialStore.js";
|
|
116
128
|
export type { IOAuthAuthorization } from "./nodefony/service/oauth2.js";
|
|
117
|
-
export type { IOAuthProvider, IIssuerPolicy, } from "./nodefony/contracts/IOAuthProvider.js";
|
|
118
|
-
export { registerOAuthProvider, getOAuthProviderFactory, listOAuthProviders, } from "./nodefony/src/oauth/oauthProviderRegistry.js";
|
|
119
|
-
export type { OAuthProviderFactory, IOAuthProviderContext, } from "./nodefony/src/oauth/oauthProviderRegistry.js";
|
|
129
|
+
export type { IOAuthProvider, IIssuerPolicy, ILogoutRequest, ILogoutTokenClaims, } from "./nodefony/contracts/IOAuthProvider.js";
|
|
130
|
+
export { registerOAuthProvider, getOAuthProviderFactory, listOAuthProviders, oauthProviderRequiresIssuer, } from "./nodefony/src/oauth/oauthProviderRegistry.js";
|
|
131
|
+
export type { OAuthProviderFactory, IOAuthProviderContext, IOAuthProviderRegistration, } from "./nodefony/src/oauth/oauthProviderRegistry.js";
|
|
120
132
|
export { OAuth2Client, OAuth2Tokens, OAuth2RequestError, generateState, generateCodeVerifier, createCodeChallenge, } from "./nodefony/src/oauth/oauth2Client.js";
|
|
121
133
|
export type { IOAuth2ClientOptions } from "./nodefony/src/oauth/oauth2Client.js";
|
|
122
134
|
export { discoverAuthorizationServer } from "./nodefony/src/oauth/metadata.js";
|
|
135
|
+
export { requestClientCredentialsToken } from "./nodefony/src/oauth/clientCredentials.js";
|
|
136
|
+
export type { IClientCredentialsRequest } from "./nodefony/src/oauth/clientCredentials.js";
|
|
137
|
+
export { diagnoseOAuthProvider } from "./nodefony/src/oauth/providerDiagnosis.js";
|
|
138
|
+
export type { IOAuthDiagnosis, IOAuthDiagnosisInput, IOAuthCheck, OAuthCheckName, OAuthCheckStatus, OAuthFailureKind, } from "./nodefony/src/oauth/providerDiagnosis.js";
|
|
139
|
+
export { KeycloakAdminClient, KeycloakAdminError, keycloakAdminTarget, keycloakLoginThemeCheck, readKeycloakLoginTheme, } from "./nodefony/keycloak/keycloakAdmin.js";
|
|
140
|
+
export type { IKeycloakAdminClientOptions, IKeycloakAdminCredentials, IKeycloakAdminTarget, IKeycloakLoginThemeResult, KeycloakLoginThemeOutcome, } from "./nodefony/keycloak/keycloakAdmin.js";
|
|
123
141
|
export type { IDiscoveredAuthorizationServer, IDiscoveryOptions, } from "./nodefony/src/oauth/metadata.js";
|
|
124
|
-
export { createOidcProvider, createDiscoveredOidcProvider, } from "./nodefony/src/oauth/providers/oidc.js";
|
|
142
|
+
export { createOidcProvider, createDiscoveredOidcProvider, readLogoutTokenClaims, } from "./nodefony/src/oauth/providers/oidc.js";
|
|
125
143
|
export type { IOidcPkceClient, IOidcProviderOptions, } from "./nodefony/src/oauth/providers/oidc.js";
|
|
126
144
|
export { createGithubProvider } from "./nodefony/src/oauth/providers/github.js";
|
|
127
145
|
export { MemoryAuditStore } from "./nodefony/src/audit/MemoryAuditStore.js";
|
|
@@ -159,6 +177,8 @@ export { defineSecurityConfig, securityConfigJsonSchema, } from "./nodefony/conf
|
|
|
159
177
|
export type { ISecurityConfig, ISecurityConfigInput, ISecurityAreaConfig, } from "./nodefony/config/defineModuleConfig.js";
|
|
160
178
|
export { LoginThrottler } from "./nodefony/src/throttle/LoginThrottler.js";
|
|
161
179
|
export type { ILoginThrottleOptions } from "./nodefony/src/throttle/LoginThrottler.js";
|
|
162
|
-
export {
|
|
180
|
+
export { CredentialTransportPolicy, shouldEnforceCredentialTransport, } from "./nodefony/src/transport/CredentialTransportPolicy.js";
|
|
181
|
+
export type { ICredentialTransportContext, ICredentialTransportPolicyOptions, } from "./nodefony/src/transport/CredentialTransportPolicy.js";
|
|
182
|
+
export { AuthenticationError, AccessDeniedError, ThrottledError, UnverifiableTokenError, InvalidTargetError, CsrfError, SsrfError, InsecureTransportError, } from "./nodefony/errors/index.js";
|
|
163
183
|
export { VoterVote } from "./nodefony/contracts/index.js";
|
|
164
184
|
export type { IToken, IAuthenticator, ISecuredArea, IFirewall, IAccessVoter, IAuthorizationService, IAccessTokenRecord, ITokenStore, ITokenListQuery, ITokenUsage, IResourcePermission, TokenRevokeReason, TokenStatus, IJwtKeystore, IJwtSigningKey, } from "./nodefony/contracts/index.js";
|