@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,423 @@
|
|
|
1
|
+
<#import "field.ftl" as field>
|
|
2
|
+
<#import "footer.ftl" as loginFooter>
|
|
3
|
+
<#import "theme-resources.ftl" as themeResourceTags>
|
|
4
|
+
<#macro username>
|
|
5
|
+
<#assign label>
|
|
6
|
+
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
|
|
7
|
+
</#assign>
|
|
8
|
+
<@field.group name="username" label=label>
|
|
9
|
+
<div class="${properties.kcInputGroup}">
|
|
10
|
+
<div class="${properties.kcInputGroupItemClass} ${properties.kcFill}">
|
|
11
|
+
<span class="${properties.kcInputClass} ${properties.kcFormReadOnlyClass}">
|
|
12
|
+
<input id="kc-attempted-username" value="${auth.attemptedUsername}" readonly>
|
|
13
|
+
</span>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="${properties.kcInputGroupItemClass}">
|
|
16
|
+
<button id="reset-login" class="${properties.kcFormPasswordVisibilityButtonClass} kc-login-tooltip" type="button"
|
|
17
|
+
aria-label="${msg('restartLoginTooltip')}" onclick="location.href='${url.loginRestartFlowUrl}'">
|
|
18
|
+
<i class="fa-sync-alt fas" aria-hidden="true"></i>
|
|
19
|
+
<span class="kc-tooltip-text">${msg("restartLoginTooltip")}</span>
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</@field.group>
|
|
24
|
+
</#macro>
|
|
25
|
+
|
|
26
|
+
<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false>
|
|
27
|
+
<!DOCTYPE html>
|
|
28
|
+
<#-- NODEFONY : `data-theme` dit à login.css le thème que KEYCLOAK a choisi — le
|
|
29
|
+
domaine peut refuser le sombre (`darkMode`), et la feuille partagée ne doit
|
|
30
|
+
jamais suivre le système contre lui. -->
|
|
31
|
+
<html class="${properties.kcHtmlClass!}"<#if !darkMode> data-theme="light"</#if> lang="${lang}"<#if realm.internationalizationEnabled> dir="${(locale.rtl)?then('rtl','ltr')}"</#if>>
|
|
32
|
+
|
|
33
|
+
<head>
|
|
34
|
+
<meta charset="utf-8">
|
|
35
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
36
|
+
<meta name="color-scheme" content="light${darkMode?then(' dark', '')}">
|
|
37
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
38
|
+
|
|
39
|
+
<#if properties.meta?has_content>
|
|
40
|
+
<#list properties.meta?split(' ') as meta>
|
|
41
|
+
<meta name="${meta?split('==')[0]}" content="${meta?split('==')[1]}"/>
|
|
42
|
+
</#list>
|
|
43
|
+
</#if>
|
|
44
|
+
<title>${title!}</title>
|
|
45
|
+
<#-- NODEFONY : la page est `noindex` (Keycloak), mais un agent ou un lecteur
|
|
46
|
+
d'onglets lit encore sa description. -->
|
|
47
|
+
<meta name="description" content="${msg("nfMetaDescription", (realm.displayName!realm.name))}">
|
|
48
|
+
<#if themeResources?? && themeResources.favicons?has_content>
|
|
49
|
+
<@themeResourceTags.renderFavicons themeResources.favicons url.resourcesPath />
|
|
50
|
+
<#else>
|
|
51
|
+
<link rel="icon" href="${url.resourcesPath}/img/favicon.ico" />
|
|
52
|
+
</#if>
|
|
53
|
+
<#if themeResources?? && themeResources.stylesCommon?has_content>
|
|
54
|
+
<@themeResourceTags.renderStyles themeResources.stylesCommon url.resourcesCommonPath />
|
|
55
|
+
<#elseif properties.stylesCommon?has_content>
|
|
56
|
+
<#list properties.stylesCommon?split(' ') as style>
|
|
57
|
+
<link href="${url.resourcesCommonPath}/${style}" rel="stylesheet" />
|
|
58
|
+
</#list>
|
|
59
|
+
</#if>
|
|
60
|
+
<#if themeResources?? && themeResources.styles?has_content>
|
|
61
|
+
<@themeResourceTags.renderStyles themeResources.styles url.resourcesPath />
|
|
62
|
+
<#elseif properties.styles?has_content>
|
|
63
|
+
<#list properties.styles?split(' ') as style>
|
|
64
|
+
<link href="${url.resourcesPath}/${style}" rel="stylesheet" />
|
|
65
|
+
</#list>
|
|
66
|
+
</#if>
|
|
67
|
+
<script type="importmap">
|
|
68
|
+
{
|
|
69
|
+
"imports": {
|
|
70
|
+
"rfc4648": "${url.resourcesCommonPath}/vendor/rfc4648/rfc4648.js"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
<#if darkMode>
|
|
75
|
+
<script type="module" async blocking="render">
|
|
76
|
+
<#outputformat "JavaScript">
|
|
77
|
+
const DARK_MODE_CLASS = ${properties.kcDarkModeClass?c};
|
|
78
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
79
|
+
|
|
80
|
+
// NODEFONY : l'application transmet le thème qu'elle a RENDU (`nf_theme`,
|
|
81
|
+
// posé par @nodefony/security sur la demande d'autorisation). Il vaut pour
|
|
82
|
+
// tout le flux : retenu en session d'onglet, puisque les pages suivantes
|
|
83
|
+
// (mot de passe erroné, second facteur) n'ont plus ce paramètre. Liste
|
|
84
|
+
// fermée ; sans lui, le système décide, comme avant.
|
|
85
|
+
const NF_THEME_KEY = "nf.theme";
|
|
86
|
+
let nfForced = null;
|
|
87
|
+
try {
|
|
88
|
+
const asked = new URLSearchParams(window.location.search).get("nf_theme");
|
|
89
|
+
if (asked === "light" || asked === "dark") sessionStorage.setItem(NF_THEME_KEY, asked);
|
|
90
|
+
const kept = sessionStorage.getItem(NF_THEME_KEY);
|
|
91
|
+
if (kept === "light" || kept === "dark") nfForced = kept;
|
|
92
|
+
} catch {
|
|
93
|
+
// Stockage refusé (navigation privée stricte) : le système décide.
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
updateDarkMode(nfForced ? nfForced === "dark" : mediaQuery.matches);
|
|
97
|
+
mediaQuery.addEventListener("change", (event) => {
|
|
98
|
+
if (!nfForced) updateDarkMode(event.matches);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
function updateDarkMode(isEnabled) {
|
|
102
|
+
const { classList, dataset } = document.documentElement;
|
|
103
|
+
// NODEFONY : login.css suit la même décision que PatternFly.
|
|
104
|
+
dataset.theme = isEnabled ? "dark" : "light";
|
|
105
|
+
|
|
106
|
+
if (isEnabled) {
|
|
107
|
+
classList.add(DARK_MODE_CLASS);
|
|
108
|
+
} else {
|
|
109
|
+
classList.remove(DARK_MODE_CLASS);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
</#outputformat>
|
|
113
|
+
</script>
|
|
114
|
+
</#if>
|
|
115
|
+
<#if themeResources?? && themeResources.scripts?has_content>
|
|
116
|
+
<@themeResourceTags.renderScripts themeResources.scripts url.resourcesPath "text/javascript" />
|
|
117
|
+
<#elseif properties.scripts?has_content>
|
|
118
|
+
<#list properties.scripts?split(' ') as script>
|
|
119
|
+
<script src="${url.resourcesPath}/${script}" type="text/javascript"></script>
|
|
120
|
+
</#list>
|
|
121
|
+
</#if>
|
|
122
|
+
<#if scripts??>
|
|
123
|
+
<#list scripts as script>
|
|
124
|
+
<script src="${script}" type="text/javascript"></script>
|
|
125
|
+
</#list>
|
|
126
|
+
</#if>
|
|
127
|
+
<script type="module" src="${url.resourcesPath}/js/passwordVisibility.js"></script>
|
|
128
|
+
<script type="module">
|
|
129
|
+
<#outputformat "JavaScript">
|
|
130
|
+
import { startSessionPolling } from ${(url.resourcesPath + "/js/authChecker.js")?c};
|
|
131
|
+
|
|
132
|
+
startSessionPolling(
|
|
133
|
+
${url.ssoLoginInOtherTabsUrl?c}
|
|
134
|
+
);
|
|
135
|
+
</#outputformat>
|
|
136
|
+
</script>
|
|
137
|
+
<script type="module">
|
|
138
|
+
document.addEventListener("click", (event) => {
|
|
139
|
+
const link = event.target.closest("a[data-once-link]");
|
|
140
|
+
|
|
141
|
+
if (!link) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (link.getAttribute("aria-disabled") === "true") {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const { disabledClass } = link.dataset;
|
|
151
|
+
|
|
152
|
+
if (disabledClass) {
|
|
153
|
+
link.classList.add(...disabledClass.trim().split(/\s+/));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
link.setAttribute("role", "link");
|
|
157
|
+
link.setAttribute("aria-disabled", "true");
|
|
158
|
+
});
|
|
159
|
+
</script>
|
|
160
|
+
<#if authenticationSession??>
|
|
161
|
+
<script type="module">
|
|
162
|
+
<#outputformat "JavaScript">
|
|
163
|
+
import { checkAuthSession } from ${(url.resourcesPath + "/js/authChecker.js")?c};
|
|
164
|
+
|
|
165
|
+
checkAuthSession(
|
|
166
|
+
${authenticationSession.authSessionIdHash?c}
|
|
167
|
+
);
|
|
168
|
+
</#outputformat>
|
|
169
|
+
</script>
|
|
170
|
+
</#if>
|
|
171
|
+
<script>
|
|
172
|
+
// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1404468
|
|
173
|
+
const isFirefox = true;
|
|
174
|
+
</script>
|
|
175
|
+
</head>
|
|
176
|
+
|
|
177
|
+
<#-- ── NODEFONY : le balisage de la page /login du framework ─────────────────
|
|
178
|
+
`nf-layout` · `nf-hero` · `nf-stage` · `nf-card` : les classes de
|
|
179
|
+
`nodefony/login.css`, dont `css/login.css` est la copie conforme. La mise en
|
|
180
|
+
page suit `nfLayout` (theme.properties : split | card | bare) et l'habillage
|
|
181
|
+
`nfSkin` : les thèmes enfants `nodefony-<habillage>` posent les deux, comme
|
|
182
|
+
`loginPage.skin` sur la page /login — un thème de l'application peut aussi
|
|
183
|
+
les surcharger.
|
|
184
|
+
Seuls ce bloc d'ouverture et sa fermeture diffèrent du cadre `keycloak.v2` :
|
|
185
|
+
messages, choix de langue, scripts de session et formulaires imbriqués
|
|
186
|
+
restent ceux de Keycloak. -->
|
|
187
|
+
<body id="keycloak-bg" class="${properties.kcBodyClass!} nf-kc" data-skin="${properties.nfSkin!'frontispiece'}" data-layout="${properties.nfLayout!'split'}" data-page-id="login-${pageId}">
|
|
188
|
+
<div class="nf-layout">
|
|
189
|
+
<#-- NODEFONY : le panneau de gauche montre le REALM et l'APPLICATION qui
|
|
190
|
+
demande la connexion — uniquement avec ce que Keycloak expose aux
|
|
191
|
+
gabarits, donc réglable depuis SA console, sans toucher au thème :
|
|
192
|
+
· realm.displayName / name (Realm settings › General) — jamais
|
|
193
|
+
`displayNameHtml` : il porte le logo d'un thème qui n'en a pas (le
|
|
194
|
+
realm `master` y met celui de Keycloak, qui chevauchait le nôtre)
|
|
195
|
+
· client.name / client.description (Clients › Settings)
|
|
196
|
+
· capacités du realm, lues et non décrites (Realm settings › Login)
|
|
197
|
+
· textes `nf*` surchargeables par realm et par langue
|
|
198
|
+
(Realm settings › Localization › Realm overrides). -->
|
|
199
|
+
<#assign nfRealmLabel = (realm.displayName?has_content)?then(realm.displayName, realm.name)>
|
|
200
|
+
<aside class="nf-hero">
|
|
201
|
+
<div class="nf-hero-brand">
|
|
202
|
+
<img src="${url.resourcesPath}/img/logo.png" alt="" class="nf-logo" width="26" height="42"/>
|
|
203
|
+
<span class="nf-hero-name">${nfRealmLabel}</span>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="nf-hero-body">
|
|
206
|
+
<span class="nf-pill">${msg("nfRealmPill", realm.name)}</span>
|
|
207
|
+
<#if client?? && (client.name?has_content || client.clientId?has_content)>
|
|
208
|
+
<p class="nf-hero-title">${msg("nfLoginTo", (client.name?has_content)?then(advancedMsg(client.name), client.clientId))}</p>
|
|
209
|
+
<#if client.description?has_content>
|
|
210
|
+
<p class="nf-hero-lead">${advancedMsg(client.description)}</p>
|
|
211
|
+
<#elseif msg("nfRealmDescription")?has_content && msg("nfRealmDescription") != "nfRealmDescription">
|
|
212
|
+
<p class="nf-hero-lead">${msg("nfRealmDescription")}</p>
|
|
213
|
+
</#if>
|
|
214
|
+
<#else>
|
|
215
|
+
<p class="nf-hero-title">${nfRealmLabel}</p>
|
|
216
|
+
</#if>
|
|
217
|
+
<p class="nf-caps-title">${msg("nfCapsTitle", nfRealmLabel)}</p>
|
|
218
|
+
<ul class="nf-caps">
|
|
219
|
+
<li><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="nf-icon" aria-hidden="true" > <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /> <path d="M9 12l2 2l4 -4" /> </svg><span>${msg("nfCapSso")}</span></li>
|
|
220
|
+
<#if realm.identityFederationEnabled><li><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="nf-icon" aria-hidden="true" > <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /> <path d="M9 12l2 2l4 -4" /> </svg><span>${msg("nfCapFederation")}</span></li></#if>
|
|
221
|
+
<#if realm.resetPasswordAllowed><li><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="nf-icon" aria-hidden="true" > <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /> <path d="M9 12l2 2l4 -4" /> </svg><span>${msg("nfCapReset")}</span></li></#if>
|
|
222
|
+
<#if realm.registrationAllowed><li><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="nf-icon" aria-hidden="true" > <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /> <path d="M9 12l2 2l4 -4" /> </svg><span>${msg("nfCapRegister")}</span></li><#else><li><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="nf-icon" aria-hidden="true" > <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /> <path d="M9 12l2 2l4 -4" /> </svg><span>${msg("nfCapManaged")}</span></li></#if>
|
|
223
|
+
<#if realm.rememberMe><li><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="nf-icon" aria-hidden="true" > <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /> <path d="M9 12l2 2l4 -4" /> </svg><span>${msg("nfCapRemember")}</span></li></#if>
|
|
224
|
+
</ul>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="nf-hero-footer">
|
|
227
|
+
<span>${msg("nfRealmFooter", realm.name)}</span>
|
|
228
|
+
<span>${msg("nfThemeCredit")}</span>
|
|
229
|
+
</div>
|
|
230
|
+
</aside>
|
|
231
|
+
<div class="nf-stage">
|
|
232
|
+
<#-- NODEFONY : revenir à l'application qui a demandé la connexion — pour
|
|
233
|
+
choisir une autre méthode (mot de passe local, passkey, autre
|
|
234
|
+
fournisseur). GÉNÉRIQUE : aucune adresse n'est écrite ni configurée.
|
|
235
|
+
Le bouton ANNULE la connexion (champ `cancel`, que traite le
|
|
236
|
+
formulaire identifiant/mot de passe de Keycloak) : Keycloak renvoie
|
|
237
|
+
alors vers le `redirect_uri` DE LA REQUÊTE EN COURS avec
|
|
238
|
+
`error=access_denied` + `state` (OAuth 2.0, RFC 6749 §4.1.2.1) — c'est
|
|
239
|
+
l'application appelante, quelle qu'elle soit, qui décide où atterrir.
|
|
240
|
+
Seule la page identifiant/mot de passe connaît `cancel` : ailleurs,
|
|
241
|
+
pas de bouton plutôt qu'un bouton sans effet.
|
|
242
|
+
Jamais sur les consoles de Keycloak lui-même (administration,
|
|
243
|
+
compte) : elles n'ont pas d'autre méthode vers laquelle revenir, et
|
|
244
|
+
reçoivent `access_denied` en affichant « Something went wrong ». -->
|
|
245
|
+
<#assign nfKeycloakConsole = client?? && ["security-admin-console", "account-console"]?seq_contains(client.clientId!"")>
|
|
246
|
+
<#if pageId == "login" && url.loginAction?has_content && !nfKeycloakConsole>
|
|
247
|
+
<form class="nf-back-form" action="${url.loginAction}" method="post" novalidate="novalidate">
|
|
248
|
+
<input type="hidden" name="cancel" value="on"/>
|
|
249
|
+
<button type="submit" class="nf-back">
|
|
250
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="nf-icon" aria-hidden="true" > <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M5 12l14 0" /> <path d="M5 12l6 6" /> <path d="M5 12l6 -6" /> </svg>
|
|
251
|
+
<span>
|
|
252
|
+
<strong>${msg("nfBackTo", (client?? && client.name?has_content)?then(advancedMsg(client.name), (client.clientId)!""))}</strong>
|
|
253
|
+
<small>${msg("nfBackHint")}</small>
|
|
254
|
+
</span>
|
|
255
|
+
</button>
|
|
256
|
+
</form>
|
|
257
|
+
</#if>
|
|
258
|
+
<div class="${properties.kcLogin!}">
|
|
259
|
+
<div class="${properties.kcLoginContainer!} nf-card">
|
|
260
|
+
<header id="kc-header" class="pf-v5-c-login__header">
|
|
261
|
+
<div id="kc-header-wrapper" class="nf-compact-brand">
|
|
262
|
+
<img src="${url.resourcesPath}/img/logo.png" alt="" class="nf-logo" width="19" height="30"/>
|
|
263
|
+
<span>${msg("nfBrandName")}</span>
|
|
264
|
+
</div>
|
|
265
|
+
</header>
|
|
266
|
+
<main class="${properties.kcLoginMain!}">
|
|
267
|
+
<div class="${properties.kcLoginMainHeader!}">
|
|
268
|
+
<#-- NODEFONY : le badge dit d'emblée qu'on entre par le SSO, pas dans une seule application. -->
|
|
269
|
+
<div class="nf-heading">
|
|
270
|
+
<span class="nf-eyebrow"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="nf-icon" aria-hidden="true" > <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M11.46 20.846a12 12 0 0 1 -7.96 -14.846a12 12 0 0 0 8.5 -3a12 12 0 0 0 8.5 3a12 12 0 0 1 -.09 7.06" /> <path d="M15 19l2 2l4 -4" /> </svg>${msg("nfSsoBadge")}</span>
|
|
271
|
+
<h1 class="${properties.kcLoginMainTitle!}" id="kc-page-title"><#nested "header"></h1>
|
|
272
|
+
<#if pageId == "login"><p class="nf-subtitle">${msg("nfSubtitle", (realm.displayName!''))}</p></#if>
|
|
273
|
+
</div>
|
|
274
|
+
<#if realm.internationalizationEnabled && locale.supported?size gt 1>
|
|
275
|
+
<div class="${properties.kcLoginMainHeaderUtilities!}">
|
|
276
|
+
<div class="${properties.kcInputClass!}">
|
|
277
|
+
<select
|
|
278
|
+
aria-label="${msg("languages")}"
|
|
279
|
+
id="login-select-toggle"
|
|
280
|
+
onchange="if (this.value) window.location.href=this.value"
|
|
281
|
+
>
|
|
282
|
+
<#list locale.supported?sort_by("label") as l>
|
|
283
|
+
<option
|
|
284
|
+
value="${l.url}"
|
|
285
|
+
${(l.languageTag == locale.currentLanguageTag)?then('selected','')}
|
|
286
|
+
>
|
|
287
|
+
${l.label}
|
|
288
|
+
</option>
|
|
289
|
+
</#list>
|
|
290
|
+
</select>
|
|
291
|
+
<span class="${properties.kcFormControlUtilClass}">
|
|
292
|
+
<span class="${properties.kcFormControlToggleIcon!}">
|
|
293
|
+
<svg
|
|
294
|
+
class="pf-v5-svg"
|
|
295
|
+
viewBox="0 0 320 512"
|
|
296
|
+
fill="currentColor"
|
|
297
|
+
aria-hidden="true"
|
|
298
|
+
role="img"
|
|
299
|
+
width="1em"
|
|
300
|
+
height="1em"
|
|
301
|
+
>
|
|
302
|
+
<path
|
|
303
|
+
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
|
|
304
|
+
>
|
|
305
|
+
</path>
|
|
306
|
+
</svg>
|
|
307
|
+
</span>
|
|
308
|
+
</span>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
</#if>
|
|
312
|
+
</div>
|
|
313
|
+
<div class="${properties.kcLoginMainBody!}">
|
|
314
|
+
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
|
315
|
+
<#if displayRequiredFields>
|
|
316
|
+
<div class="${properties.kcContentWrapperClass!}">
|
|
317
|
+
<div class="${properties.kcLabelWrapperClass!} subtitle">
|
|
318
|
+
<span class="${properties.kcInputHelperTextItemTextClass!}">
|
|
319
|
+
<span class="${properties.kcInputRequiredClass!}">*</span> ${msg("requiredFields")}
|
|
320
|
+
</span>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</#if>
|
|
324
|
+
<#else>
|
|
325
|
+
<#if displayRequiredFields>
|
|
326
|
+
<div class="${properties.kcContentWrapperClass!}">
|
|
327
|
+
<div class="${properties.kcLabelWrapperClass!} subtitle">
|
|
328
|
+
<span class="${properties.kcInputHelperTextItemTextClass!}">
|
|
329
|
+
<span class="${properties.kcInputRequiredClass!}">*</span> ${msg("requiredFields")}
|
|
330
|
+
</span>
|
|
331
|
+
</div>
|
|
332
|
+
<div class="${properties.kcFormClass} ${properties.kcContentWrapperClass}">
|
|
333
|
+
<#nested "show-username">
|
|
334
|
+
<@username />
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
<#else>
|
|
338
|
+
<div class="${properties.kcFormClass} ${properties.kcContentWrapperClass}">
|
|
339
|
+
<#nested "show-username">
|
|
340
|
+
<@username />
|
|
341
|
+
</div>
|
|
342
|
+
</#if>
|
|
343
|
+
</#if>
|
|
344
|
+
|
|
345
|
+
<#-- App-initiated actions should not see warning messages about the need to complete the action -->
|
|
346
|
+
<#-- during login. -->
|
|
347
|
+
<#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)>
|
|
348
|
+
<div class="${properties.kcAlertClass!} pf-m-${(message.type = 'error')?then('danger', message.type)}">
|
|
349
|
+
<div class="${properties.kcAlertIconClass!}">
|
|
350
|
+
<#if message.type = 'success'><span class="${properties.kcFeedbackSuccessIcon!}"></span></#if>
|
|
351
|
+
<#if message.type = 'warning'><span class="${properties.kcFeedbackWarningIcon!}"></span></#if>
|
|
352
|
+
<#if message.type = 'error'><span class="${properties.kcFeedbackErrorIcon!}"></span></#if>
|
|
353
|
+
<#if message.type = 'info'><span class="${properties.kcFeedbackInfoIcon!}"></span></#if>
|
|
354
|
+
</div>
|
|
355
|
+
<span class="${properties.kcAlertTitleClass!} kc-feedback-text">${message.summary}</span>
|
|
356
|
+
</div>
|
|
357
|
+
</#if>
|
|
358
|
+
|
|
359
|
+
<#nested "form">
|
|
360
|
+
|
|
361
|
+
<#if auth?has_content && auth.showTryAnotherWayLink()>
|
|
362
|
+
<form id="kc-select-try-another-way-form" action="${url.loginAction}" method="post" novalidate="novalidate">
|
|
363
|
+
<input type="hidden" name="tryAnotherWay" value="on"/>
|
|
364
|
+
<a id="try-another-way" href="javascript:document.forms['kc-select-try-another-way-form'].requestSubmit()"
|
|
365
|
+
class="${properties.kcButtonSecondaryClass} ${properties.kcButtonBlockClass} ${properties.kcMarginTopClass}">
|
|
366
|
+
${msg("doTryAnotherWay")}
|
|
367
|
+
</a>
|
|
368
|
+
</form>
|
|
369
|
+
</#if>
|
|
370
|
+
|
|
371
|
+
<#if switchOrganizationEnabled?? && switchOrganizationEnabled>
|
|
372
|
+
<form id="kc-switch-organization-form" action="${url.loginAction}" method="post" novalidate="novalidate">
|
|
373
|
+
<input type="hidden" name="switchOrganization" value="true"/>
|
|
374
|
+
<a id="switch-organization" href="javascript:document.forms['kc-switch-organization-form'].requestSubmit()"
|
|
375
|
+
class="${properties.kcButtonSecondaryClass} ${properties.kcButtonBlockClass} ${properties.kcMarginTopClass}">
|
|
376
|
+
${msg("doSwitchOrganization")}
|
|
377
|
+
</a>
|
|
378
|
+
</form>
|
|
379
|
+
</#if>
|
|
380
|
+
|
|
381
|
+
<div class="${properties.kcLoginMainFooter!}">
|
|
382
|
+
<#nested "socialProviders">
|
|
383
|
+
|
|
384
|
+
<#if displayInfo>
|
|
385
|
+
<div id="kc-info" class="${properties.kcLoginMainFooterBand!} ${properties.kcFormClass}">
|
|
386
|
+
<div id="kc-info-wrapper" class="${properties.kcLoginMainFooterBandItem!}">
|
|
387
|
+
<#nested "info">
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
</#if>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<div class="${properties.kcLoginMainFooter!}">
|
|
395
|
+
<@loginFooter.content/>
|
|
396
|
+
</div>
|
|
397
|
+
</main>
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
<#-- NODEFONY : rappel du périmètre SSO, comme la ligne d'état de Studio. -->
|
|
401
|
+
<footer class="nf-panel-footer">
|
|
402
|
+
<span class="nf-sso-scope"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="nf-icon" aria-hidden="true" > <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /> <path d="M3.6 9h16.8" /> <path d="M3.6 15h16.8" /> <path d="M11.5 3a17 17 0 0 0 0 18" /> <path d="M12.5 3a17 17 0 0 1 0 18" /> </svg>${msg("nfSsoFooter", (realm.displayName!realm.name))}</span>
|
|
403
|
+
<span class="nf-provider"><span class="nf-dot" aria-hidden="true"></span>${msg("nfProvidedBy")}</span>
|
|
404
|
+
</footer>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
<#-- NODEFONY : un libellé dont la cible `for` n'existe pas est rattaché au champ
|
|
408
|
+
de son groupe. Keycloak 26.8 écrit `for="form-vertical-name"` sur les deux
|
|
409
|
+
libellés de `login-config-totp.ftl` : axe relève deux champs sans nom
|
|
410
|
+
accessible (critique). Réparer ici plutôt que recopier ce gabarit : la copie
|
|
411
|
+
masquerait ses évolutions, et ce geste est inerte quand la cible existe. -->
|
|
412
|
+
<script>
|
|
413
|
+
for (const label of document.querySelectorAll("label[for]")) {
|
|
414
|
+
if (document.getElementById(label.htmlFor)) continue;
|
|
415
|
+
const field = label
|
|
416
|
+
.closest(".pf-v5-c-form__group")
|
|
417
|
+
?.querySelector("input:not([type=hidden]), select, textarea");
|
|
418
|
+
if (field?.id) label.htmlFor = field.id;
|
|
419
|
+
}
|
|
420
|
+
</script>
|
|
421
|
+
</body>
|
|
422
|
+
</html>
|
|
423
|
+
</#macro>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Thème de connexion Nodefony — habille le parcours Keycloak aux couleurs de
|
|
2
|
+
# la console d'administration (Nodefony Studio).
|
|
3
|
+
#
|
|
4
|
+
# HÉRITE de `keycloak.v2` : seules l'enveloppe de la page (template.ftl), une
|
|
5
|
+
# feuille de style et les textes de marque sont posés ici. Les ~40 formulaires
|
|
6
|
+
# du parcours (mot de passe, mot de passe oublié, double facteur, profil,
|
|
7
|
+
# erreurs…) restent ceux de Keycloak, donc justes à chaque mise à jour.
|
|
8
|
+
parent=keycloak.v2
|
|
9
|
+
import=common/keycloak
|
|
10
|
+
styles=css/styles.css css/login.css css/nodefony.css
|
|
11
|
+
# Langues dont le thème porte les textes de marque (messages/messages_<l>).
|
|
12
|
+
locales=fr,en
|
|
13
|
+
|
|
14
|
+
# Mise en page de la page de connexion — la même que la page /login du
|
|
15
|
+
# framework (`loginPage.layout`) : split | card | bare. Un thème enfant de
|
|
16
|
+
# l'application (`parent=nodefony`) la surcharge, avec ses couleurs.
|
|
17
|
+
nfLayout=split
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Page d'accueil du serveur Keycloak aux couleurs de Nodefony Studio.
|
|
3
|
+
*
|
|
4
|
+
* Le gabarit du parent pose `logo.svg` et `background.svg` en dur : l'image
|
|
5
|
+
* est remplacée par `content` (pas de copie du gabarit à resynchroniser), le
|
|
6
|
+
* fond par le dégradé du panneau de marque de Studio (`AuthLayout.tsx`).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
--pf-v5-global--primary-color--100: #0067ba;
|
|
11
|
+
--pf-v5-global--primary-color--200: #00579c;
|
|
12
|
+
--pf-v5-global--link--Color: #0067ba;
|
|
13
|
+
--pf-v5-global--link--Color--hover: #00579c;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.pf-v5-c-background-image {
|
|
17
|
+
--pf-v5-c-background-image--BackgroundImage: none !important;
|
|
18
|
+
background: linear-gradient(135deg, #00467e 0%, #0067ba 55%, #4792cd 100%);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Pastille claire : l'arc extérieur du logo (#0067ba) se fondait dans le
|
|
22
|
+
dégradé, qui en part. */
|
|
23
|
+
.kc-brand {
|
|
24
|
+
content: url("../img/logo.png");
|
|
25
|
+
box-sizing: content-box;
|
|
26
|
+
width: auto;
|
|
27
|
+
height: 72px;
|
|
28
|
+
padding: 16px 22px;
|
|
29
|
+
background: #ffffff;
|
|
30
|
+
border-radius: 16px;
|
|
31
|
+
box-shadow: 0 8px 24px rgb(0 0 0 / 0.18);
|
|
32
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Page d'accueil du serveur Keycloak aux couleurs de Nodefony.
|
|
2
|
+
#
|
|
3
|
+
# Le seul type qui ne se choisit PAS par realm : le serveur le désigne par
|
|
4
|
+
# l'option `spi-theme--welcome-theme` (variable `KC_SPI_THEME__WELCOME_THEME`
|
|
5
|
+
# dans le compose). Elle ne s'affiche qu'avant le premier administrateur —
|
|
6
|
+
# ensuite, le parent redirige vers la console d'administration.
|
|
7
|
+
# HÉRITE de `keycloak` : logo et fond remplacés par la feuille, rien d'autre.
|
|
8
|
+
parent=keycloak
|
|
9
|
+
import=common/keycloak
|
|
10
|
+
stylesCommon=vendor/patternfly-v5/patternfly.min.css vendor/patternfly-v5/patternfly-addons.css
|
|
11
|
+
styles=css/welcome.css css/nodefony.css
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Thème de connexion Nodefony, habillage « blueprint » — le même que la page
|
|
2
|
+
# /login du framework avec `loginPage.skin: "blueprint"`.
|
|
3
|
+
#
|
|
4
|
+
# HÉRITE du thème `nodefony` : gabarit, textes, images et propriétés viennent
|
|
5
|
+
# de lui ; seuls la feuille d'habillage (copie conforme de
|
|
6
|
+
# `nodefony/login/skins/blueprint.css`, éprouvée par le test des copies) et la
|
|
7
|
+
# mise en page de l'habillage sont posées ici. Le realm le choisit par
|
|
8
|
+
# `loginTheme` (`security:keycloak:realm --write` le dérive de la config).
|
|
9
|
+
parent=nodefony
|
|
10
|
+
styles=css/styles.css css/login.css css/nodefony.css css/skins/blueprint.css
|
|
11
|
+
nfSkin=blueprint
|
|
12
|
+
nfLayout=card
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Thème de connexion Nodefony, habillage « console » — le même que la page
|
|
2
|
+
# /login du framework avec `loginPage.skin: "console"`.
|
|
3
|
+
#
|
|
4
|
+
# HÉRITE du thème `nodefony` : gabarit, textes, images et propriétés viennent
|
|
5
|
+
# de lui ; seuls la feuille d'habillage (copie conforme de
|
|
6
|
+
# `nodefony/login/skins/console.css`, éprouvée par le test des copies) et la
|
|
7
|
+
# mise en page de l'habillage sont posées ici. Le realm le choisit par
|
|
8
|
+
# `loginTheme` (`security:keycloak:realm --write` le dérive de la config).
|
|
9
|
+
parent=nodefony
|
|
10
|
+
styles=css/styles.css css/login.css css/nodefony.css css/skins/console.css
|
|
11
|
+
nfSkin=console
|
|
12
|
+
nfLayout=card
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Thème de connexion Nodefony, habillage « dots » — le même que la page
|
|
2
|
+
# /login du framework avec `loginPage.skin: "dots"`.
|
|
3
|
+
#
|
|
4
|
+
# HÉRITE du thème `nodefony` : gabarit, textes, images et propriétés viennent
|
|
5
|
+
# de lui ; seuls la feuille d'habillage (copie conforme de
|
|
6
|
+
# `nodefony/login/skins/dots.css`, éprouvée par le test des copies) et la
|
|
7
|
+
# mise en page de l'habillage sont posées ici. Le realm le choisit par
|
|
8
|
+
# `loginTheme` (`security:keycloak:realm --write` le dérive de la config).
|
|
9
|
+
parent=nodefony
|
|
10
|
+
styles=css/styles.css css/login.css css/nodefony.css css/skins/dots.css
|
|
11
|
+
nfSkin=dots
|
|
12
|
+
nfLayout=card
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Thème de connexion Nodefony, habillage « enterprise » — le même que la page
|
|
2
|
+
# /login du framework avec `loginPage.skin: "enterprise"`.
|
|
3
|
+
#
|
|
4
|
+
# HÉRITE du thème `nodefony` : gabarit, textes, images et propriétés viennent
|
|
5
|
+
# de lui ; seuls la feuille d'habillage (copie conforme de
|
|
6
|
+
# `nodefony/login/skins/enterprise.css`, éprouvée par le test des copies) et la
|
|
7
|
+
# mise en page de l'habillage sont posées ici. Le realm le choisit par
|
|
8
|
+
# `loginTheme` (`security:keycloak:realm --write` le dérive de la config).
|
|
9
|
+
parent=nodefony
|
|
10
|
+
styles=css/styles.css css/login.css css/nodefony.css css/skins/enterprise.css
|
|
11
|
+
nfSkin=enterprise
|
|
12
|
+
nfLayout=card
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Thème de connexion Nodefony, habillage « horizon » — le même que la page
|
|
2
|
+
# /login du framework avec `loginPage.skin: "horizon"`.
|
|
3
|
+
#
|
|
4
|
+
# HÉRITE du thème `nodefony` : gabarit, textes, images et propriétés viennent
|
|
5
|
+
# de lui ; seuls la feuille d'habillage (copie conforme de
|
|
6
|
+
# `nodefony/login/skins/horizon.css`, éprouvée par le test des copies) et la
|
|
7
|
+
# mise en page de l'habillage sont posées ici. Le realm le choisit par
|
|
8
|
+
# `loginTheme` (`security:keycloak:realm --write` le dérive de la config).
|
|
9
|
+
parent=nodefony
|
|
10
|
+
styles=css/styles.css css/login.css css/nodefony.css css/skins/horizon.css
|
|
11
|
+
nfSkin=horizon
|
|
12
|
+
nfLayout=card
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Thème de connexion Nodefony, habillage « ledger » — le même que la page
|
|
2
|
+
# /login du framework avec `loginPage.skin: "ledger"`.
|
|
3
|
+
#
|
|
4
|
+
# HÉRITE du thème `nodefony` : gabarit, textes, images et propriétés viennent
|
|
5
|
+
# de lui ; seuls la feuille d'habillage (copie conforme de
|
|
6
|
+
# `nodefony/login/skins/ledger.css`, éprouvée par le test des copies) et la
|
|
7
|
+
# mise en page de l'habillage sont posées ici. Le realm le choisit par
|
|
8
|
+
# `loginTheme` (`security:keycloak:realm --write` le dérive de la config).
|
|
9
|
+
parent=nodefony
|
|
10
|
+
styles=css/styles.css css/login.css css/nodefony.css css/skins/ledger.css
|
|
11
|
+
nfSkin=ledger
|
|
12
|
+
nfLayout=bare
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Thème de connexion Nodefony, habillage « minimal » — le même que la page
|
|
2
|
+
# /login du framework avec `loginPage.skin: "minimal"`.
|
|
3
|
+
#
|
|
4
|
+
# HÉRITE du thème `nodefony` : gabarit, textes, images et propriétés viennent
|
|
5
|
+
# de lui ; seuls la feuille d'habillage (copie conforme de
|
|
6
|
+
# `nodefony/login/skins/minimal.css`, éprouvée par le test des copies) et la
|
|
7
|
+
# mise en page de l'habillage sont posées ici. Le realm le choisit par
|
|
8
|
+
# `loginTheme` (`security:keycloak:realm --write` le dérive de la config).
|
|
9
|
+
parent=nodefony
|
|
10
|
+
styles=css/styles.css css/login.css css/nodefony.css css/skins/minimal.css
|
|
11
|
+
nfSkin=minimal
|
|
12
|
+
nfLayout=bare
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Thème de connexion Nodefony, habillage « photo-card » — le même que la page
|
|
2
|
+
# /login du framework avec `loginPage.skin: "photo-card"`.
|
|
3
|
+
#
|
|
4
|
+
# HÉRITE du thème `nodefony` : gabarit, textes, images et propriétés viennent
|
|
5
|
+
# de lui ; seuls la feuille d'habillage (copie conforme de
|
|
6
|
+
# `nodefony/login/skins/photo-card.css`, éprouvée par le test des copies) et la
|
|
7
|
+
# mise en page de l'habillage sont posées ici. Le realm le choisit par
|
|
8
|
+
# `loginTheme` (`security:keycloak:realm --write` le dérive de la config).
|
|
9
|
+
parent=nodefony
|
|
10
|
+
styles=css/styles.css css/login.css css/nodefony.css css/skins/photo-card.css
|
|
11
|
+
nfSkin=photo-card
|
|
12
|
+
nfLayout=card
|