@nodefony/security 10.0.0-alpha.1
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/LICENSE +544 -0
- package/README.md +182 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/index.js +151 -0
- package/dist/nodefony/command/security-secrets.js +158 -0
- package/dist/nodefony/command/security-token.js +335 -0
- package/dist/nodefony/command/security-user-add.js +131 -0
- package/dist/nodefony/command/security-user-delete.js +102 -0
- package/dist/nodefony/command/security-user-list.js +77 -0
- package/dist/nodefony/config/config.js +366 -0
- package/dist/nodefony/config/defineModuleConfig.js +35 -0
- package/dist/nodefony/contracts/IAccessVoter.js +13 -0
- package/dist/nodefony/contracts/IApiKey.js +1 -0
- package/dist/nodefony/contracts/IAuditEvent.js +1 -0
- package/dist/nodefony/contracts/IAuditStore.js +1 -0
- package/dist/nodefony/contracts/IAuthenticator.js +1 -0
- package/dist/nodefony/contracts/IAuthorizationService.js +1 -0
- package/dist/nodefony/contracts/IFirewall.js +1 -0
- package/dist/nodefony/contracts/IFirewallDescription.js +1 -0
- package/dist/nodefony/contracts/IJwtKeystore.js +1 -0
- package/dist/nodefony/contracts/IOAuthProvider.js +1 -0
- package/dist/nodefony/contracts/ISecuredArea.js +1 -0
- package/dist/nodefony/contracts/IToken.js +1 -0
- package/dist/nodefony/contracts/ITokenStore.js +1 -0
- package/dist/nodefony/contracts/ITotpSecret.js +1 -0
- package/dist/nodefony/contracts/ITotpSecretStore.js +1 -0
- package/dist/nodefony/contracts/IWebAuthnCredential.js +1 -0
- package/dist/nodefony/contracts/IWebAuthnCredentialStore.js +1 -0
- package/dist/nodefony/contracts/IWebhookEndpoint.js +1 -0
- package/dist/nodefony/contracts/IWebhookStore.js +1 -0
- package/dist/nodefony/contracts/index.js +2 -0
- package/dist/nodefony/errors/AccessDeniedError.js +14 -0
- package/dist/nodefony/errors/ApiKeyError.js +21 -0
- package/dist/nodefony/errors/AuthenticationError.js +14 -0
- package/dist/nodefony/errors/CsrfError.js +23 -0
- package/dist/nodefony/errors/InvalidTargetError.js +39 -0
- package/dist/nodefony/errors/SsrfError.js +17 -0
- package/dist/nodefony/errors/ThrottledError.js +21 -0
- package/dist/nodefony/errors/UnverifiableTokenError.js +42 -0
- package/dist/nodefony/errors/WebAuthnError.js +21 -0
- package/dist/nodefony/errors/index.js +9 -0
- package/dist/nodefony/service/accessTokenVerifier.js +77 -0
- package/dist/nodefony/service/apiKeys.js +310 -0
- package/dist/nodefony/service/auditService.js +145 -0
- package/dist/nodefony/service/authFlow.js +332 -0
- package/dist/nodefony/service/authorization.js +95 -0
- package/dist/nodefony/service/cors.js +81 -0
- package/dist/nodefony/service/csrf.js +97 -0
- package/dist/nodefony/service/firewall.js +699 -0
- package/dist/nodefony/service/oauth2.js +153 -0
- package/dist/nodefony/service/securityHeaders.js +80 -0
- package/dist/nodefony/service/tokenService.js +486 -0
- package/dist/nodefony/service/totp.js +209 -0
- package/dist/nodefony/service/webAuthn.js +343 -0
- package/dist/nodefony/service/webhooks.js +539 -0
- package/dist/nodefony/src/RoleHierarchyWalker.js +77 -0
- package/dist/nodefony/src/SecuredArea.js +51 -0
- package/dist/nodefony/src/admin/SecurityAdminApi.js +495 -0
- package/dist/nodefony/src/admin/WebhookAdminApi.js +378 -0
- package/dist/nodefony/src/admin/adminAudit.js +37 -0
- package/dist/nodefony/src/admin/userRevocationCascade.js +40 -0
- package/dist/nodefony/src/apikey/apiKeyFormat.js +107 -0
- package/dist/nodefony/src/audit/MemoryAuditStore.js +121 -0
- package/dist/nodefony/src/audit/auditBridge.js +82 -0
- package/dist/nodefony/src/audit/auditFilters.js +60 -0
- package/dist/nodefony/src/audit/auditStoreRegistry.js +25 -0
- package/dist/nodefony/src/audit/readAuditContext.js +24 -0
- package/dist/nodefony/src/audit/recordAudit.js +16 -0
- package/dist/nodefony/src/authenticator/AnonymousAuthenticator.js +36 -0
- package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +164 -0
- package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +224 -0
- package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +174 -0
- package/dist/nodefony/src/authenticator/JwtAuthenticator.js +176 -0
- package/dist/nodefony/src/authenticator/SessionAuthenticator.js +92 -0
- package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +95 -0
- package/dist/nodefony/src/authenticator/authenticatorRegistry.js +63 -0
- package/dist/nodefony/src/authenticator/bearer.js +2 -0
- package/dist/nodefony/src/authenticator/externalSubject.js +36 -0
- package/dist/nodefony/src/authenticator/peekIssuer.js +56 -0
- package/dist/nodefony/src/crypto/secretCipher.js +79 -0
- package/dist/nodefony/src/csp.js +54 -0
- package/dist/nodefony/src/csrfToken.js +65 -0
- package/dist/nodefony/src/net/ssrfGuard.js +130 -0
- package/dist/nodefony/src/oauth/oauthProviderRegistry.js +37 -0
- package/dist/nodefony/src/oauth/providers/github.js +65 -0
- package/dist/nodefony/src/oauth/providers/oidc.js +48 -0
- package/dist/nodefony/src/realtime/UserRealtimeToken.js +94 -0
- package/dist/nodefony/src/realtime/frameAuthorizer.js +279 -0
- package/dist/nodefony/src/realtime/realtimeContracts.js +1 -0
- package/dist/nodefony/src/sessionIdentity.js +35 -0
- package/dist/nodefony/src/throttle/LoginThrottler.js +97 -0
- package/dist/nodefony/src/token/AnonymousToken.js +40 -0
- package/dist/nodefony/src/token/JwtKeystore.js +160 -0
- package/dist/nodefony/src/token/MemoryTokenStore.js +236 -0
- package/dist/nodefony/src/token/RemoteJwtVerifier.js +231 -0
- package/dist/nodefony/src/token/UserToken.js +67 -0
- package/dist/nodefony/src/token/jwtRuntime.js +19 -0
- package/dist/nodefony/src/token/secretFile.js +134 -0
- package/dist/nodefony/src/token/tokenCriteria.js +35 -0
- package/dist/nodefony/src/token/tokenFilters.js +72 -0
- package/dist/nodefony/src/token/tokenSort.js +40 -0
- package/dist/nodefony/src/token/tokenStatus.js +35 -0
- package/dist/nodefony/src/token/tokenStoreRegistry.js +25 -0
- package/dist/nodefony/src/totp/MemoryTotpSecretStore.js +97 -0
- package/dist/nodefony/src/totp/totpCipher.js +30 -0
- package/dist/nodefony/src/totp/totpCrypto.js +226 -0
- package/dist/nodefony/src/totp/totpOperations.js +129 -0
- package/dist/nodefony/src/totp/totpSecretStoreRegistry.js +18 -0
- package/dist/nodefony/src/voter/RoleVoter.js +32 -0
- package/dist/nodefony/src/voter/ScopeVoter.js +52 -0
- package/dist/nodefony/src/voter/voterRegistry.js +20 -0
- package/dist/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js +121 -0
- package/dist/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js +18 -0
- package/dist/nodefony/src/webhook/MemoryWebhookStore.js +87 -0
- package/dist/nodefony/src/webhook/WebhookDispatcher.js +208 -0
- package/dist/nodefony/src/webhook/webhookCipher.js +27 -0
- package/dist/nodefony/src/webhook/webhookDelivery.js +102 -0
- package/dist/nodefony/src/webhook/webhookFilters.js +56 -0
- package/dist/nodefony/src/webhook/webhookSignature.js +51 -0
- package/dist/nodefony/src/webhook/webhookSort.js +48 -0
- package/dist/nodefony/src/webhook/webhookStoreRegistry.js +18 -0
- package/dist/types/index.d.ts +157 -0
- package/dist/types/nodefony/command/security-secrets.d.ts +24 -0
- package/dist/types/nodefony/command/security-token.d.ts +44 -0
- package/dist/types/nodefony/command/security-user-add.d.ts +28 -0
- package/dist/types/nodefony/command/security-user-delete.d.ts +25 -0
- package/dist/types/nodefony/command/security-user-list.d.ts +28 -0
- package/dist/types/nodefony/config/config.d.ts +295 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +27 -0
- package/dist/types/nodefony/contracts/IAccessVoter.d.ts +23 -0
- package/dist/types/nodefony/contracts/IApiKey.d.ts +75 -0
- package/dist/types/nodefony/contracts/IAuditEvent.d.ts +94 -0
- package/dist/types/nodefony/contracts/IAuditStore.d.ts +80 -0
- package/dist/types/nodefony/contracts/IAuthenticator.d.ts +66 -0
- package/dist/types/nodefony/contracts/IAuthorizationService.d.ts +28 -0
- package/dist/types/nodefony/contracts/IFirewall.d.ts +64 -0
- package/dist/types/nodefony/contracts/IFirewallDescription.d.ts +120 -0
- package/dist/types/nodefony/contracts/IJwtKeystore.d.ts +40 -0
- package/dist/types/nodefony/contracts/IOAuthProvider.d.ts +51 -0
- package/dist/types/nodefony/contracts/ISecuredArea.d.ts +57 -0
- package/dist/types/nodefony/contracts/IToken.d.ts +41 -0
- package/dist/types/nodefony/contracts/ITokenStore.d.ts +240 -0
- package/dist/types/nodefony/contracts/ITotpSecret.d.ts +41 -0
- package/dist/types/nodefony/contracts/ITotpSecretStore.d.ts +88 -0
- package/dist/types/nodefony/contracts/IWebAuthnCredential.d.ts +56 -0
- package/dist/types/nodefony/contracts/IWebAuthnCredentialStore.d.ts +118 -0
- package/dist/types/nodefony/contracts/IWebhookEndpoint.d.ts +82 -0
- package/dist/types/nodefony/contracts/IWebhookStore.d.ts +85 -0
- package/dist/types/nodefony/contracts/index.d.ts +9 -0
- package/dist/types/nodefony/errors/AccessDeniedError.d.ts +10 -0
- package/dist/types/nodefony/errors/ApiKeyError.d.ts +17 -0
- package/dist/types/nodefony/errors/AuthenticationError.d.ts +10 -0
- package/dist/types/nodefony/errors/CsrfError.d.ts +19 -0
- package/dist/types/nodefony/errors/InvalidTargetError.d.ts +34 -0
- package/dist/types/nodefony/errors/SsrfError.d.ts +13 -0
- package/dist/types/nodefony/errors/ThrottledError.d.ts +16 -0
- package/dist/types/nodefony/errors/UnverifiableTokenError.d.ts +37 -0
- package/dist/types/nodefony/errors/WebAuthnError.d.ts +17 -0
- package/dist/types/nodefony/errors/index.d.ts +8 -0
- package/dist/types/nodefony/service/accessTokenVerifier.d.ts +29 -0
- package/dist/types/nodefony/service/apiKeys.d.ts +103 -0
- package/dist/types/nodefony/service/auditService.d.ts +30 -0
- package/dist/types/nodefony/service/authFlow.d.ts +123 -0
- package/dist/types/nodefony/service/authorization.d.ts +33 -0
- package/dist/types/nodefony/service/cors.d.ts +48 -0
- package/dist/types/nodefony/service/csrf.d.ts +57 -0
- package/dist/types/nodefony/service/firewall.d.ts +148 -0
- package/dist/types/nodefony/service/oauth2.d.ts +66 -0
- package/dist/types/nodefony/service/securityHeaders.d.ts +66 -0
- package/dist/types/nodefony/service/tokenService.d.ts +103 -0
- package/dist/types/nodefony/service/totp.d.ts +58 -0
- package/dist/types/nodefony/service/webAuthn.d.ts +123 -0
- package/dist/types/nodefony/service/webhooks.d.ts +160 -0
- package/dist/types/nodefony/src/RoleHierarchyWalker.d.ts +21 -0
- package/dist/types/nodefony/src/SecuredArea.d.ts +31 -0
- package/dist/types/nodefony/src/admin/SecurityAdminApi.d.ts +82 -0
- package/dist/types/nodefony/src/admin/WebhookAdminApi.d.ts +30 -0
- package/dist/types/nodefony/src/admin/adminAudit.d.ts +27 -0
- package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +31 -0
- package/dist/types/nodefony/src/apikey/apiKeyFormat.d.ts +43 -0
- package/dist/types/nodefony/src/audit/MemoryAuditStore.d.ts +33 -0
- package/dist/types/nodefony/src/audit/auditBridge.d.ts +49 -0
- package/dist/types/nodefony/src/audit/auditFilters.d.ts +56 -0
- package/dist/types/nodefony/src/audit/auditStoreRegistry.d.ts +37 -0
- package/dist/types/nodefony/src/audit/readAuditContext.d.ts +17 -0
- package/dist/types/nodefony/src/audit/recordAudit.d.ts +13 -0
- package/dist/types/nodefony/src/authenticator/AnonymousAuthenticator.d.ts +26 -0
- package/dist/types/nodefony/src/authenticator/ApiKeyAuthenticator.d.ts +74 -0
- package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +132 -0
- package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +78 -0
- package/dist/types/nodefony/src/authenticator/JwtAuthenticator.d.ts +69 -0
- package/dist/types/nodefony/src/authenticator/SessionAuthenticator.d.ts +70 -0
- package/dist/types/nodefony/src/authenticator/UserPasswordAuthenticator.d.ts +53 -0
- package/dist/types/nodefony/src/authenticator/authenticatorRegistry.d.ts +39 -0
- package/dist/types/nodefony/src/authenticator/bearer.d.ts +22 -0
- package/dist/types/nodefony/src/authenticator/externalSubject.d.ts +27 -0
- package/dist/types/nodefony/src/authenticator/peekIssuer.d.ts +31 -0
- package/dist/types/nodefony/src/crypto/secretCipher.d.ts +31 -0
- package/dist/types/nodefony/src/csp.d.ts +39 -0
- package/dist/types/nodefony/src/csrfToken.d.ts +36 -0
- package/dist/types/nodefony/src/net/ssrfGuard.d.ts +43 -0
- package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +45 -0
- package/dist/types/nodefony/src/oauth/providers/github.d.ts +9 -0
- package/dist/types/nodefony/src/oauth/providers/oidc.d.ts +35 -0
- package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +62 -0
- package/dist/types/nodefony/src/realtime/frameAuthorizer.d.ts +171 -0
- package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +139 -0
- package/dist/types/nodefony/src/sessionIdentity.d.ts +20 -0
- package/dist/types/nodefony/src/throttle/LoginThrottler.d.ts +68 -0
- package/dist/types/nodefony/src/token/AnonymousToken.d.ts +23 -0
- package/dist/types/nodefony/src/token/JwtKeystore.d.ts +43 -0
- package/dist/types/nodefony/src/token/MemoryTokenStore.d.ts +66 -0
- package/dist/types/nodefony/src/token/RemoteJwtVerifier.d.ts +149 -0
- package/dist/types/nodefony/src/token/UserToken.d.ts +41 -0
- package/dist/types/nodefony/src/token/jwtRuntime.d.ts +28 -0
- package/dist/types/nodefony/src/token/secretFile.d.ts +70 -0
- package/dist/types/nodefony/src/token/tokenCriteria.d.ts +20 -0
- package/dist/types/nodefony/src/token/tokenFilters.d.ts +76 -0
- package/dist/types/nodefony/src/token/tokenSort.d.ts +33 -0
- package/dist/types/nodefony/src/token/tokenStatus.d.ts +38 -0
- package/dist/types/nodefony/src/token/tokenStoreRegistry.d.ts +38 -0
- package/dist/types/nodefony/src/totp/MemoryTotpSecretStore.d.ts +43 -0
- package/dist/types/nodefony/src/totp/totpCipher.d.ts +9 -0
- package/dist/types/nodefony/src/totp/totpCrypto.d.ts +164 -0
- package/dist/types/nodefony/src/totp/totpOperations.d.ts +73 -0
- package/dist/types/nodefony/src/totp/totpSecretStoreRegistry.d.ts +27 -0
- package/dist/types/nodefony/src/voter/RoleVoter.d.ts +25 -0
- package/dist/types/nodefony/src/voter/ScopeVoter.d.ts +30 -0
- package/dist/types/nodefony/src/voter/voterRegistry.d.ts +33 -0
- package/dist/types/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.d.ts +39 -0
- package/dist/types/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.d.ts +26 -0
- package/dist/types/nodefony/src/webhook/MemoryWebhookStore.d.ts +37 -0
- package/dist/types/nodefony/src/webhook/WebhookDispatcher.d.ts +69 -0
- package/dist/types/nodefony/src/webhook/webhookCipher.d.ts +8 -0
- package/dist/types/nodefony/src/webhook/webhookDelivery.d.ts +28 -0
- package/dist/types/nodefony/src/webhook/webhookFilters.d.ts +64 -0
- package/dist/types/nodefony/src/webhook/webhookSignature.d.ts +20 -0
- package/dist/types/nodefony/src/webhook/webhookSort.d.ts +39 -0
- package/dist/types/nodefony/src/webhook/webhookStoreRegistry.d.ts +31 -0
- package/docs/api-keys.md +691 -0
- package/docs/audit.md +751 -0
- package/docs/authenticators.md +487 -0
- package/docs/authorization.md +497 -0
- package/docs/cors.md +497 -0
- package/docs/csrf.md +392 -0
- package/docs/external-jwt.md +181 -0
- package/docs/firewall.md +546 -0
- package/docs/headers.md +616 -0
- package/docs/index.md +207 -0
- package/docs/lexique.md +190 -0
- package/docs/oauth2.md +575 -0
- package/docs/obtenir-un-jeton.md +225 -0
- package/docs/tokens.md +520 -0
- package/docs/totp.md +804 -0
- package/docs/webauthn.md +733 -0
- package/docs/webhooks.md +1016 -0
- package/package.json +83 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import { TOKEN_FACETS, TOKEN_FILTERS, TOKEN_STATS_FILTERS } from "../token/tokenFilters.js";
|
|
2
|
+
import { AUDIT_FILTERS } from "../audit/auditFilters.js";
|
|
3
|
+
import { adminActor, auditAdmin } from "./adminAudit.js";
|
|
4
|
+
import { webhookAdminEndpoints } from "./WebhookAdminApi.js";
|
|
5
|
+
import { parseFilters, parsePageQuery } from "nodefony";
|
|
6
|
+
//#region nodefony/src/admin/SecurityAdminApi.ts
|
|
7
|
+
/**
|
|
8
|
+
* Déduit le **driver** logique d'un store à partir de son nom de classe — le
|
|
9
|
+
* `tokenService` connaît le driver config (`tokenStore.store`) mais ne l'expose
|
|
10
|
+
* pas au container ; on le re-dérive du store réel posé (miroir du pattern
|
|
11
|
+
* `HttpAdminApi.sessions` qui lit `storage.constructor.name`). Mapping fermé sur
|
|
12
|
+
* les implémentations connues ({@link ITokenStore}) ; `null` pour un store tiers
|
|
13
|
+
* inconnu (honnête — on n'invente pas un driver).
|
|
14
|
+
*
|
|
15
|
+
* @param className - `store.constructor.name` (ex. `DrizzleTokenStore`).
|
|
16
|
+
* @returns driver logique, ou `null` si la classe n'est pas reconnue.
|
|
17
|
+
*/
|
|
18
|
+
function tokenStoreDriver(className) {
|
|
19
|
+
switch (className) {
|
|
20
|
+
case "MemoryTokenStore": return "memory";
|
|
21
|
+
case "DrizzleTokenStore":
|
|
22
|
+
case "MongooseTokenStore": return "orm";
|
|
23
|
+
case "RedisTokenStore": return "redis";
|
|
24
|
+
default: return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Défaut/cap de taille de page du listing admin des clés API. */
|
|
28
|
+
const KEYS_DEFAULT_LIMIT = 50;
|
|
29
|
+
const KEYS_MAX_LIMIT = 200;
|
|
30
|
+
/**
|
|
31
|
+
* Traduit la query string admin en {@link ITokenListQuery} bornée (`limit` par
|
|
32
|
+
* défaut 50, cap 200 ; pagination, tri et filtres de {@link TOKEN_FILTERS}).
|
|
33
|
+
*
|
|
34
|
+
* **UN SEUL traducteur par dimension, jamais deux.** Pour la page et le tri,
|
|
35
|
+
* `parsePageQuery` ; pour les filtres, `parseFilters`. En appeler un second sans
|
|
36
|
+
* son allowlist ferait refuser en 400 ce que le premier venait d'accepter, et
|
|
37
|
+
* aucun test unitaire ne le verrait (chaque appel est correct isolément).
|
|
38
|
+
*
|
|
39
|
+
* Un filtre inconnu ou mal formé est désormais **refusé** (400) au lieu d'être
|
|
40
|
+
* ignoré : `?status=revoqué` rendait la liste ENTIÈRE, que la console affichait
|
|
41
|
+
* comme le résultat du filtre demandé.
|
|
42
|
+
*
|
|
43
|
+
* @param query - `request.query` du broker admin.
|
|
44
|
+
* @param sortable - champs que le backend branché sait trier ; un `?order=`
|
|
45
|
+
* portant autre chose est refusé en 400 par le traducteur. Liste vide (store à
|
|
46
|
+
* curseur, store absent) ⇒ tout tri est refusé, ce qui est la vérité du backend.
|
|
47
|
+
*/
|
|
48
|
+
function parseTokenListQuery(query, sortable) {
|
|
49
|
+
const page = parsePageQuery(query, {
|
|
50
|
+
defaultLimit: KEYS_DEFAULT_LIMIT,
|
|
51
|
+
maxLimit: KEYS_MAX_LIMIT,
|
|
52
|
+
sortable
|
|
53
|
+
});
|
|
54
|
+
const filters = parseFilters(query, TOKEN_FILTERS);
|
|
55
|
+
const out = {
|
|
56
|
+
limit: page.limit,
|
|
57
|
+
...filters
|
|
58
|
+
};
|
|
59
|
+
if (page.offset !== void 0) out.offset = page.offset;
|
|
60
|
+
if (page.cursor !== void 0) out.cursor = page.cursor;
|
|
61
|
+
if (page.order !== void 0) out.order = page.order;
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
/** Premier param d'une clé (la query admin peut être `string | string[]`). */
|
|
65
|
+
function one(query, key) {
|
|
66
|
+
const value = query[key];
|
|
67
|
+
if (value === void 0) return void 0;
|
|
68
|
+
return Array.isArray(value) ? value[0] : value;
|
|
69
|
+
}
|
|
70
|
+
/** Taille de page du journal d'audit quand l'appelant n'en demande pas. */
|
|
71
|
+
const AUDIT_DEFAULT_LIMIT = 100;
|
|
72
|
+
/**
|
|
73
|
+
* Traduit la query string admin en {@link IAuditListQuery} typée — pagination
|
|
74
|
+
* par curseur, plus les filtres de {@link AUDIT_FILTERS}.
|
|
75
|
+
*
|
|
76
|
+
* Un filtre inconnu ou mal formé est **refusé** (400). Il était auparavant
|
|
77
|
+
* ignoré, au nom de la robustesse de la console : mais un journal d'audit rendu
|
|
78
|
+
* ENTIER à qui demandait `?outcome=deneid` n'est pas robuste — c'est la pire
|
|
79
|
+
* réponse possible à un auditeur, qui lit l'absence de refus comme l'absence
|
|
80
|
+
* d'incident. Le typage suit la même source : les valeurs viennent de la spec,
|
|
81
|
+
* il n'y a plus de `as AuditCategory` à écrire ici.
|
|
82
|
+
*
|
|
83
|
+
* Le `limit` est **toujours posé** (défaut {@link AUDIT_DEFAULT_LIMIT}, cap du
|
|
84
|
+
* traducteur) : le contrat de page n'admet pas « tout » ; le store applique en
|
|
85
|
+
* plus son propre plafond, l'appelant ne peut donc pas s'en servir pour tirer un
|
|
86
|
+
* journal entier.
|
|
87
|
+
*
|
|
88
|
+
* @param query - `request.query` du broker admin.
|
|
89
|
+
* @returns filtre prêt pour `auditService.listPage`.
|
|
90
|
+
* @throws `PageQueryError` (400) sur un filtre inconnu ou mal formé.
|
|
91
|
+
*/
|
|
92
|
+
function parseAuditQuery(query) {
|
|
93
|
+
const page = parsePageQuery(query, { defaultLimit: AUDIT_DEFAULT_LIMIT });
|
|
94
|
+
const filter = {
|
|
95
|
+
limit: page.limit,
|
|
96
|
+
...parseFilters(query, AUDIT_FILTERS)
|
|
97
|
+
};
|
|
98
|
+
if (page.cursor !== void 0) filter.cursor = page.cursor;
|
|
99
|
+
return filter;
|
|
100
|
+
}
|
|
101
|
+
/** Projette un credential serveur vers sa vue admin (sans `publicKey`/`userId`). */
|
|
102
|
+
function toCredentialView(c) {
|
|
103
|
+
return {
|
|
104
|
+
id: c.id,
|
|
105
|
+
nickname: c.nickname ?? null,
|
|
106
|
+
transports: c.transports,
|
|
107
|
+
backupEligible: c.backupEligible,
|
|
108
|
+
backupState: c.backupState,
|
|
109
|
+
uvInitialized: c.uvInitialized,
|
|
110
|
+
createdAt: c.createdAt,
|
|
111
|
+
lastUsedAt: c.lastUsedAt
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Producteur admin (`IAdminApi`) du module sécurité — data plane consommé par
|
|
116
|
+
* Studio (section Sécurité, P6.15) :
|
|
117
|
+
*
|
|
118
|
+
* - `GET /nodefony/security/api/audit/events` — page filtrée du journal d'audit
|
|
119
|
+
* (P6.14 Lot 3 ; `?category&outcome&actor&action&requestId&since&until&limit&cursor`),
|
|
120
|
+
* du plus récent au plus ancien, pagination par curseur (`cursor` / `nextCursor`).
|
|
121
|
+
* - `GET /nodefony/security/api/firewall` — introspection du firewall (zones,
|
|
122
|
+
* authenticators montés, défenses) — état RUNTIME, secrets exclus.
|
|
123
|
+
* - `GET /nodefony/security/api/roleHierarchy` — hiérarchie de rôles + résolution.
|
|
124
|
+
* - `GET /nodefony/security/api/apikeys` — toutes les clés API (gouvernance),
|
|
125
|
+
* `GET …/apikeys/status` — backend du token store (« où on écrit »),
|
|
126
|
+
* `POST …/apikeys/{id}/revoke` — révocation par id (réponse à incident).
|
|
127
|
+
* - `GET …/users/{id}/passkeys` + `DELETE …/users/{id}/passkeys/{credentialId}`
|
|
128
|
+
* — passkeys d'un utilisateur (vue admin sans clé publique) + révocation
|
|
129
|
+
* owner-scopée (reset facteur fort, audité).
|
|
130
|
+
* - `GET …/users/{id}/totp` + `POST …/users/{id}/totp/disable` — état du 2FA
|
|
131
|
+
* TOTP + désactivation (reset facteur fort, audité). Pas d'enrôlement
|
|
132
|
+
* cross-user (le secret se scanne sur l'appareil de l'utilisateur).
|
|
133
|
+
*
|
|
134
|
+
* **RBAC `ROLE_NODEFONY_ADMIN`** (appliqué par le broker, 403 sinon) — la console
|
|
135
|
+
* sécurité ne se consulte qu'en administrateur. Handlers **lazy** : ils résolvent
|
|
136
|
+
* `auditService`/`firewall` à la requête (service désactivable → 503), jamais au
|
|
137
|
+
* montage. Le namespace `"security"` est distinct des routes classiques
|
|
138
|
+
* `/nodefony/security/api/keys` (P6.12) — paths disjoints, zéro collision.
|
|
139
|
+
*
|
|
140
|
+
* @param container - container du kernel (résolution lazy des services).
|
|
141
|
+
*/
|
|
142
|
+
function createSecurityAdminApi(container) {
|
|
143
|
+
const endpoints = [
|
|
144
|
+
{
|
|
145
|
+
path: "audit/events",
|
|
146
|
+
method: "GET",
|
|
147
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
148
|
+
summary: "Journal d'audit de sécurité (login/refus/jetons). Filtres : ?category&outcome&actor&action&since&until&limit&cursor (curseur).",
|
|
149
|
+
handler: async (request) => {
|
|
150
|
+
const audit = container.get("auditService");
|
|
151
|
+
if (!audit || !audit.isEnabled()) return {
|
|
152
|
+
status: 503,
|
|
153
|
+
body: { error: "audit journal unavailable" }
|
|
154
|
+
};
|
|
155
|
+
return audit.listPage(parseAuditQuery(request.query));
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
path: "firewall",
|
|
160
|
+
method: "GET",
|
|
161
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
162
|
+
summary: "Introspection du firewall : zones (URL/host/authenticators), authenticators montés, défenses (CSRF/CORS/en-têtes/throttle). Secrets exclus (présence, jamais valeur).",
|
|
163
|
+
handler: () => {
|
|
164
|
+
const firewall = container.get("firewall");
|
|
165
|
+
if (!firewall) return {
|
|
166
|
+
status: 503,
|
|
167
|
+
body: { error: "firewall unavailable" }
|
|
168
|
+
};
|
|
169
|
+
return firewall.describe();
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
path: "roleHierarchy",
|
|
174
|
+
method: "GET",
|
|
175
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
176
|
+
summary: "Hiérarchie de rôles déclarée + résolution transitive (RoleHierarchyWalker, niveau A de l'autorisation).",
|
|
177
|
+
handler: () => {
|
|
178
|
+
const firewall = container.get("firewall");
|
|
179
|
+
if (!firewall) return {
|
|
180
|
+
status: 503,
|
|
181
|
+
body: { error: "firewall unavailable" }
|
|
182
|
+
};
|
|
183
|
+
return firewall.describeRoleHierarchy();
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
path: "apikeys",
|
|
188
|
+
method: "GET",
|
|
189
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
190
|
+
summary: "Clés API (PAT) du système — gouvernance cross-porteur, pagination NATIVE au store (?subjectId&revoked&limit&offset|cursor&order=champ:ASC). Tri limité aux champs que le backend branché déclare savoir trier. Vue publique, sans secret.",
|
|
191
|
+
page: {
|
|
192
|
+
sortable: () => {
|
|
193
|
+
const svc = container.get("apiKeys");
|
|
194
|
+
return svc?.isEnabled() ? svc.sortableFields() : [];
|
|
195
|
+
},
|
|
196
|
+
filters: TOKEN_FILTERS
|
|
197
|
+
},
|
|
198
|
+
handler: async (request) => {
|
|
199
|
+
const svc = container.get("apiKeys");
|
|
200
|
+
if (!svc || !svc.isEnabled()) return {
|
|
201
|
+
status: 503,
|
|
202
|
+
body: { error: "api keys unavailable" }
|
|
203
|
+
};
|
|
204
|
+
const page = await svc.listPagePat(parseTokenListQuery(request.query, svc.sortableFields()));
|
|
205
|
+
return {
|
|
206
|
+
keys: page.items,
|
|
207
|
+
total: page.total,
|
|
208
|
+
limit: page.limit,
|
|
209
|
+
offset: page.offset,
|
|
210
|
+
nextCursor: page.nextCursor
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
path: "apikeys/stats",
|
|
216
|
+
method: "GET",
|
|
217
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
218
|
+
summary: "Compteurs des clés d'API sur la collection ENTIÈRE (total, actives, expirées, révoquées) — mêmes filtres que la liste. `null` = le backend ne sait pas compter (store Redis en curseur).",
|
|
219
|
+
page: {
|
|
220
|
+
filters: TOKEN_STATS_FILTERS,
|
|
221
|
+
facets: TOKEN_FACETS
|
|
222
|
+
},
|
|
223
|
+
handler: async (request) => {
|
|
224
|
+
const svc = container.get("apiKeys");
|
|
225
|
+
if (!svc || !svc.isEnabled()) return {
|
|
226
|
+
status: 503,
|
|
227
|
+
body: { error: "api keys unavailable" }
|
|
228
|
+
};
|
|
229
|
+
parsePageQuery(request.query, {});
|
|
230
|
+
return svc.countKeyFacets(parseFilters(request.query, TOKEN_STATS_FILTERS));
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
path: "apikeys/status",
|
|
235
|
+
method: "GET",
|
|
236
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
237
|
+
summary: "Statut du sous-système clés API : « où on écrit » = backend du token store (classe réelle + driver memory/orm/redis). Sans secret.",
|
|
238
|
+
handler: () => {
|
|
239
|
+
const enabled = container.get("apiKeys")?.isEnabled() ?? false;
|
|
240
|
+
const className = container.get("tokenStore")?.constructor?.name;
|
|
241
|
+
return {
|
|
242
|
+
enabled,
|
|
243
|
+
store: className ?? "none",
|
|
244
|
+
driver: className ? tokenStoreDriver(className) : null
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
path: "apikeys/{id}/revoke",
|
|
250
|
+
method: "POST",
|
|
251
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
252
|
+
summary: "Révoque une clé API par id (réponse à incident : clé compromise) — audité (acteur admin + porteur cible). 404 si introuvable.",
|
|
253
|
+
handler: async (request) => {
|
|
254
|
+
const svc = container.get("apiKeys");
|
|
255
|
+
if (!svc || !svc.isEnabled()) return {
|
|
256
|
+
status: 503,
|
|
257
|
+
body: { error: "api keys unavailable" }
|
|
258
|
+
};
|
|
259
|
+
const id = request.params.id;
|
|
260
|
+
if (typeof id !== "string" || id.length === 0) return {
|
|
261
|
+
status: 404,
|
|
262
|
+
body: { error: "not found" }
|
|
263
|
+
};
|
|
264
|
+
const key = await svc.revokeAnyPat(id, adminActor(request.user));
|
|
265
|
+
if (!key) return {
|
|
266
|
+
status: 404,
|
|
267
|
+
body: { error: "not found" }
|
|
268
|
+
};
|
|
269
|
+
return {
|
|
270
|
+
ok: true,
|
|
271
|
+
key
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
path: "webauthn/list",
|
|
277
|
+
method: "GET",
|
|
278
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
279
|
+
summary: "Passkeys de la plateforme (id, porteur, transports, sauvegarde, compteur anti-clone, dates — jamais la clé publique). Paginé serveur : ?userId&backedUp&q&limit&offset. `enabled:false` = passkeys désactivés en config. `total:-1` = backend sans comptage (Redis).",
|
|
280
|
+
handler: async (request) => {
|
|
281
|
+
const svc = container.get("webauthn");
|
|
282
|
+
const pageQuery = parsePageQuery(request.query, {
|
|
283
|
+
defaultLimit: KEYS_DEFAULT_LIMIT,
|
|
284
|
+
maxLimit: KEYS_MAX_LIMIT,
|
|
285
|
+
searchable: true
|
|
286
|
+
});
|
|
287
|
+
const limit = pageQuery.limit;
|
|
288
|
+
const offset = pageQuery.offset ?? 0;
|
|
289
|
+
if (!svc) return {
|
|
290
|
+
enabled: false,
|
|
291
|
+
items: [],
|
|
292
|
+
total: 0,
|
|
293
|
+
limit,
|
|
294
|
+
offset
|
|
295
|
+
};
|
|
296
|
+
const userId = one(request.query, "userId");
|
|
297
|
+
const backedUp = one(request.query, "backedUp");
|
|
298
|
+
const q = pageQuery.q;
|
|
299
|
+
const page = await svc.listCredentialsPage({
|
|
300
|
+
limit,
|
|
301
|
+
offset,
|
|
302
|
+
...userId !== void 0 ? { userId } : {},
|
|
303
|
+
...backedUp === "true" ? { backedUp: true } : backedUp === "false" ? { backedUp: false } : {},
|
|
304
|
+
...q !== void 0 ? { q } : {}
|
|
305
|
+
});
|
|
306
|
+
return {
|
|
307
|
+
enabled: true,
|
|
308
|
+
items: page.items,
|
|
309
|
+
total: page.total,
|
|
310
|
+
limit,
|
|
311
|
+
offset
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
path: "users/{id}/passkeys",
|
|
317
|
+
method: "GET",
|
|
318
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
319
|
+
summary: "Passkeys (WebAuthn) d'un utilisateur — vue admin SANS clé publique (id/surnom/transports/sauvegarde/UV/dates). 404 si utilisateur inconnu.",
|
|
320
|
+
handler: async (request) => {
|
|
321
|
+
const svc = container.get("webauthn");
|
|
322
|
+
if (!svc) return {
|
|
323
|
+
status: 503,
|
|
324
|
+
body: { error: "webauthn unavailable" }
|
|
325
|
+
};
|
|
326
|
+
const id = request.params.id;
|
|
327
|
+
if (typeof id !== "string" || id.length === 0) return {
|
|
328
|
+
status: 404,
|
|
329
|
+
body: { error: "not found" }
|
|
330
|
+
};
|
|
331
|
+
const users = container.get("users");
|
|
332
|
+
if (users && !await users.findById(id)) return {
|
|
333
|
+
status: 404,
|
|
334
|
+
body: { error: "not found" }
|
|
335
|
+
};
|
|
336
|
+
return { credentials: (await svc.listUserCredentials(id)).map(toCredentialView) };
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
path: "users/{id}/passkeys/{credentialId}",
|
|
341
|
+
method: "DELETE",
|
|
342
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
343
|
+
summary: "Révoque une passkey d'un utilisateur (reset admin) — audité. 404 si la passkey n'existe pas ou n'appartient pas à l'utilisateur (anti-IDOR).",
|
|
344
|
+
handler: async (request) => {
|
|
345
|
+
const svc = container.get("webauthn");
|
|
346
|
+
if (!svc) return {
|
|
347
|
+
status: 503,
|
|
348
|
+
body: { error: "webauthn unavailable" }
|
|
349
|
+
};
|
|
350
|
+
const id = request.params.id;
|
|
351
|
+
const credentialId = request.params.credentialId;
|
|
352
|
+
if (typeof id !== "string" || id.length === 0 || typeof credentialId !== "string" || credentialId.length === 0) return {
|
|
353
|
+
status: 404,
|
|
354
|
+
body: { error: "not found" }
|
|
355
|
+
};
|
|
356
|
+
if (!await svc.removeUserCredential(id, credentialId)) return {
|
|
357
|
+
status: 404,
|
|
358
|
+
body: { error: "not found" }
|
|
359
|
+
};
|
|
360
|
+
auditAdmin(container, {
|
|
361
|
+
category: "webauthn",
|
|
362
|
+
action: "user.passkey_revoked",
|
|
363
|
+
outcome: "success",
|
|
364
|
+
actor: adminActor(request.user),
|
|
365
|
+
resource: id,
|
|
366
|
+
metadata: {
|
|
367
|
+
credentialId,
|
|
368
|
+
viaAdmin: true
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
return { ok: true };
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
path: "totp/list",
|
|
376
|
+
method: "GET",
|
|
377
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
378
|
+
summary: "Enrôlements 2FA TOTP (userId, paramètres, confirmation, codes de récupération RESTANTS — jamais le secret). Paginé serveur : ?confirmed&q&limit&offset. `enabled:false` = 2FA désactivé en config.",
|
|
379
|
+
handler: async (request) => {
|
|
380
|
+
const svc = container.get("totp");
|
|
381
|
+
const pageQuery = parsePageQuery(request.query, {
|
|
382
|
+
defaultLimit: KEYS_DEFAULT_LIMIT,
|
|
383
|
+
maxLimit: KEYS_MAX_LIMIT
|
|
384
|
+
});
|
|
385
|
+
const limit = pageQuery.limit;
|
|
386
|
+
const offset = pageQuery.offset ?? 0;
|
|
387
|
+
if (!svc) return {
|
|
388
|
+
enabled: false,
|
|
389
|
+
items: [],
|
|
390
|
+
total: 0,
|
|
391
|
+
limit,
|
|
392
|
+
offset
|
|
393
|
+
};
|
|
394
|
+
const confirmed = one(request.query, "confirmed");
|
|
395
|
+
const q = one(request.query, "q");
|
|
396
|
+
const page = await svc.listPage({
|
|
397
|
+
limit,
|
|
398
|
+
offset,
|
|
399
|
+
...confirmed === "true" ? { confirmed: true } : confirmed === "false" ? { confirmed: false } : {},
|
|
400
|
+
...q !== void 0 ? { q } : {}
|
|
401
|
+
});
|
|
402
|
+
return {
|
|
403
|
+
enabled: true,
|
|
404
|
+
items: page.items,
|
|
405
|
+
total: page.total,
|
|
406
|
+
limit,
|
|
407
|
+
offset
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
path: "users/{id}/totp",
|
|
413
|
+
method: "GET",
|
|
414
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
415
|
+
summary: "État du 2FA TOTP d'un utilisateur (activé / en attente / codes de récupération restants). 404 si utilisateur inconnu.",
|
|
416
|
+
handler: async (request) => {
|
|
417
|
+
const svc = container.get("totp");
|
|
418
|
+
if (!svc) return {
|
|
419
|
+
status: 503,
|
|
420
|
+
body: { error: "totp unavailable" }
|
|
421
|
+
};
|
|
422
|
+
const id = request.params.id;
|
|
423
|
+
if (typeof id !== "string" || id.length === 0) return {
|
|
424
|
+
status: 404,
|
|
425
|
+
body: { error: "not found" }
|
|
426
|
+
};
|
|
427
|
+
const users = container.get("users");
|
|
428
|
+
if (users && !await users.findById(id)) return {
|
|
429
|
+
status: 404,
|
|
430
|
+
body: { error: "not found" }
|
|
431
|
+
};
|
|
432
|
+
return svc.status(id);
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
path: "users/{id}/totp/disable",
|
|
437
|
+
method: "POST",
|
|
438
|
+
role: "ROLE_NODEFONY_ADMIN",
|
|
439
|
+
summary: "Désactive le 2FA TOTP d'un utilisateur (reset admin : appareil perdu) — audité. Idempotent (no-op si déjà désactivé).",
|
|
440
|
+
handler: async (request) => {
|
|
441
|
+
const svc = container.get("totp");
|
|
442
|
+
if (!svc) return {
|
|
443
|
+
status: 503,
|
|
444
|
+
body: { error: "totp unavailable" }
|
|
445
|
+
};
|
|
446
|
+
const id = request.params.id;
|
|
447
|
+
if (typeof id !== "string" || id.length === 0) return {
|
|
448
|
+
status: 404,
|
|
449
|
+
body: { error: "not found" }
|
|
450
|
+
};
|
|
451
|
+
const users = container.get("users");
|
|
452
|
+
if (users && !await users.findById(id)) return {
|
|
453
|
+
status: 404,
|
|
454
|
+
body: { error: "not found" }
|
|
455
|
+
};
|
|
456
|
+
await svc.disable(id);
|
|
457
|
+
auditAdmin(container, {
|
|
458
|
+
category: "auth",
|
|
459
|
+
action: "user.totp_disabled",
|
|
460
|
+
outcome: "success",
|
|
461
|
+
actor: adminActor(request.user),
|
|
462
|
+
resource: id,
|
|
463
|
+
metadata: { viaAdmin: true }
|
|
464
|
+
});
|
|
465
|
+
return { ok: true };
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
...webhookAdminEndpoints(container)
|
|
469
|
+
];
|
|
470
|
+
const descriptor = {
|
|
471
|
+
label: "Sécurité",
|
|
472
|
+
icon: "shield-lock",
|
|
473
|
+
order: 15,
|
|
474
|
+
role: "ROLE_NODEFONY_ADMIN"
|
|
475
|
+
};
|
|
476
|
+
return {
|
|
477
|
+
adminNamespace: "security",
|
|
478
|
+
adminDescriptor: () => descriptor,
|
|
479
|
+
adminEndpoints: () => endpoints
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Enregistre le producteur admin sécurité sur le broker — **idempotent** (no-op
|
|
484
|
+
* si déjà monté). À appeler au `onKernelBoot` du module (avant le montage des
|
|
485
|
+
* routes par framework à `onKernelReady`). Calque `registerOrmAdminApi`.
|
|
486
|
+
*
|
|
487
|
+
* @param registry - broker admin (`container.get("adminBroker")`).
|
|
488
|
+
* @param container - container du kernel (capturé par le handler lazy).
|
|
489
|
+
*/
|
|
490
|
+
function registerSecurityAdminApi(registry, container) {
|
|
491
|
+
if (registry.has("security")) return;
|
|
492
|
+
registry.register(createSecurityAdminApi(container));
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
495
|
+
export { createSecurityAdminApi, parseAuditQuery, parseTokenListQuery, registerSecurityAdminApi };
|