Compare commits

..

65 Commits

Author SHA1 Message Date
66ead628f3 Enable CI 2024-08-13 14:30:17 +02:00
e143257b48 Release patch version 2024-08-13 14:29:00 +02:00
0b68e051e6 #607 2024-08-13 14:28:45 +02:00
9557145f72 Bump version 2024-06-28 19:01:13 +02:00
249877b9c5 Better wording for assertNoPnpmDlx 2024-06-28 19:01:00 +02:00
ff2321fde5 Bump version 2024-06-28 18:51:06 +02:00
1edd6e4193 No pnpm dlx 2024-06-28 18:50:45 +02:00
bdde9162d9 Merge pull request #572 from keycloakify/fix/readme-update-discord
Fix/readme update discord
2024-06-25 16:38:31 -05:00
99b4933536 Merge branch 'main' into fix/readme-update-discord 2024-06-25 16:32:59 -05:00
c5caf7e0da fix: fix for previous discord readme addition, forgot the <a> tag 2024-06-25 16:32:21 -05:00
bcc5308cfb Merge pull request #571 from keycloakify/fix/readme-update-discord
Modified Readme.md to have a more visible discord invitation link
2024-06-25 21:29:50 +00:00
9fb902db5c fix: modified the readme using a slightly more visible discord invitation link 2024-06-25 16:27:22 -05:00
239f98aa9c fmt 2024-06-19 01:49:13 +00:00
f5d0511662 Update README.md 2024-06-19 03:36:00 +02:00
75582d2a26 Update README.md 2024-06-19 03:33:44 +02:00
0796b3dedf Bump version 2024-05-17 17:54:23 +02:00
662a76bbb6 Merge pull request #553 from giorgoslytos/feat/federated-identity-account-page
Addition of Federated Identity Account page
2024-05-17 13:56:27 +00:00
a664195625 fix: Use of 'Meta' from storybook instead of 'ComponentMeta' 2024-05-17 12:17:17 +03:00
e533e127bf feat: Addition of stories for account pages that were missing them 2024-05-17 12:16:36 +03:00
346e3df009 feat: Addition of FederatedIdentity Account Page 2024-05-17 12:14:47 +03:00
19ba0873f5 fix: Prune 2024-05-16 17:11:45 +03:00
fb4acc62c4 fix: Addition of prop logoutUrl 2024-05-16 17:09:03 +03:00
fd538e95ca fix: Correction of import of useGetClassName 2024-05-16 14:05:58 +03:00
def2d8b75b fix: Correction of Application page 2024-05-16 14:05:28 +03:00
586b28af1c fix: Correction of types on KcContext for account pages 2024-05-16 12:53:31 +03:00
585c279d10 Bump version 2024-04-17 15:57:39 +02:00
51bc65e671 Avoid logging expected error messages to the console 2024-04-17 15:57:13 +02:00
ff1758cdce Update README.md 2024-04-13 04:50:34 +02:00
72a3c37e84 Update the downlaod-builtin-keycloak-theme for v24 2024-04-13 01:12:36 +02:00
c99cdf5566 Bump version 2024-04-10 03:33:46 +02:00
ad339710f1 Add corect document title and html lang property in account theme 2024-04-10 03:33:33 +02:00
00d2d12056 Bump version 2024-04-10 03:20:02 +02:00
6a7b472c0e #535 2024-04-10 03:19:46 +02:00
6991d868be Set html lang property and page title in usePrepareTemplate 2024-04-10 01:38:59 +02:00
85cc665d17 Bump version 2024-04-09 17:17:27 +02:00
5bb22fc345 #534 2024-04-09 17:17:12 +02:00
5417dc1bed Add notice about Keycloak 24 2024-04-09 14:42:43 +02:00
ee20d33724 Bump version 2024-03-19 14:57:47 +01:00
7887bd2b67 Filter out some Ftl context 2024-03-19 14:57:32 +01:00
168582efea Bump version 2024-03-16 05:42:50 +01:00
2c55d13f91 Add some more options for Keycloak version when downloading builtin theme (in the prompt) 2024-03-16 05:42:27 +01:00
23e5f553d4 #522 2024-03-16 05:34:55 +01:00
bc44eadcec Bump version 2024-03-05 19:43:06 +01:00
a3e3136600 #519 2024-03-05 19:42:49 +01:00
c7bfcee8d2 Update README.md 2024-03-04 21:46:00 +01:00
12ebd19716 Bump version 2024-03-02 18:45:21 +01:00
aec9ffa5db Merge pull request #515 from giorgoslytos/feat/additional-account-pages
feat: addition of extra account pages
2024-03-02 17:54:45 +01:00
2e6321342e Merge branch 'main' into feat/additional-account-pages 2024-03-02 13:41:48 +02:00
6e71da62f0 Bump version 2024-03-02 09:29:50 +01:00
5bf33aae75 Better support for environnement variables 2024-03-02 09:29:36 +01:00
06b2dc63ff Bump version 2024-03-02 09:02:17 +01:00
1bb0c9dfc2 Add generic type for kcContext.properties 2024-03-02 09:01:57 +01:00
a2b167e120 Bump version 2024-02-29 21:57:54 +01:00
0909a4b7cc Remove dead code, (I didn't fully understand how it landed here in the first place) 2024-02-29 21:57:37 +01:00
fd7d2bb9bf Bump version 2024-02-27 23:55:40 +01:00
63c40fd816 Add type definition for the user property in the kcContext of the terms.ftl page 2024-02-27 23:55:06 +01:00
79e25e69bb feat: addition of Log account page 2024-02-21 13:44:33 +02:00
b95c12772d style: Add spaces between words 2024-02-21 13:36:55 +02:00
de47525d7c feat: Addition of Application Account page 2024-02-19 17:29:46 +02:00
f49d20e47c feat: Totp account page fixed and completed 2024-02-19 11:57:03 +02:00
33b9917229 fix: locales in account totp page 2024-02-19 08:58:27 +02:00
319d7dbe94 feat: Addition of Totp account page 2024-02-16 17:40:12 +02:00
feb8eaf95a Merge branch 'keycloakify:main' into additional-account-pages 2024-02-13 22:27:25 +02:00
e88be30fc8 Merge branch 'keycloakify:main' into additional-account-pages 2024-02-10 22:50:42 +02:00
22496e36eb feat: Addition of Sessions page 2024-02-07 15:18:27 +02:00
36 changed files with 1960 additions and 137 deletions

View File

@ -3,6 +3,7 @@ on:
push:
branches:
- main
- v9
pull_request:
branches:
- main

View File

@ -17,9 +17,12 @@
<a href="https://github.com/thomasdarimont/awesome-keycloak">
<img src="https://awesome.re/mentioned-badge.svg"/>
</a>
<a href="https://discord.gg/kYFZG7fQmn">
<img src="https://img.shields.io/discord/1097708346976505977"/>
</a>
<p align="center">
Check out our discord server!<br/>
<a href="https://discord.gg/mJdYJSdcm4">
<img src="https://dcbadge.limes.pink/api/server/kYFZG7fQmn"/>
</a>
</p>
<p align="center">
<a href="https://www.keycloakify.dev">Home</a>
-
@ -38,7 +41,9 @@
<img width="400" src="https://github.com/keycloakify/keycloakify/assets/6702424/e66d105c-c06f-47d1-8a31-a6ab09da4e80">
</p>
Keycloakify is fully compatible with Keycloak 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, [~~22~~](https://github.com/keycloakify/keycloakify/issues/389#issuecomment-1822509763), **23** [and up](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)!
Keycloakify is fully compatible with Keycloak 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, [~~22~~](https://github.com/keycloakify/keycloakify/issues/389#issuecomment-1822509763), 23, 24, 25...[and beyond](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)
> NOTE: Keycloakify 10 is still in realase-candidate state. [Follow progress](https://github.com/keycloakify/keycloakify/pull/538).
## Sponsor
@ -63,12 +68,12 @@ Their dedicated support helps us continue the development and maintenance of thi
</div>
<p align="center">
<i>Checkout <a href="https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github">Cloud IAM</a> and use promo code <code>keycloakify5</code></i>
<i>Checkout <a href="https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github">Cloud-IAM</a> and use promo code <code>keycloakify5</code></i>
<br/>
<i>5% of your annual subscription will be donated to us, and you'll get 5% off too.</i>
</p>
Thank you, [Cloud IAM](https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github), for your support!
Thank you, [Cloud-IAM](https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github), for your support!
## Contributors ✨

View File

@ -1,6 +1,6 @@
{
"name": "keycloakify",
"version": "9.5.4",
"version": "9.7.3",
"description": "Create Keycloak themes using React",
"repository": {
"type": "git",
@ -113,7 +113,7 @@
"@babel/parser": "^7.22.7",
"@babel/types": "^7.22.5",
"@octokit/rest": "^18.12.0",
"cheerio": "^1.0.0-rc.5",
"cheerio": "1.0.0-rc.5",
"cli-select": "^1.1.2",
"evt": "^2.4.18",
"make-fetch-happen": "^11.0.3",

View File

@ -3,9 +3,14 @@ import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { I18n } from "keycloakify/account/i18n";
import type { KcContext } from "./kcContext";
import { assert, type Equals } from "tsafe/assert";
import FederatedIdentity from "./pages/FederatedIdentity";
const Password = lazy(() => import("keycloakify/account/pages/Password"));
const Account = lazy(() => import("keycloakify/account/pages/Account"));
const Sessions = lazy(() => import("keycloakify/account/pages/Sessions"));
const Totp = lazy(() => import("keycloakify/account/pages/Totp"));
const Applications = lazy(() => import("keycloakify/account/pages/Applications"));
const Log = lazy(() => import("keycloakify/account/pages/Log"));
export default function Fallback(props: PageProps<KcContext, I18n>) {
const { kcContext, ...rest } = props;
@ -16,8 +21,18 @@ export default function Fallback(props: PageProps<KcContext, I18n>) {
switch (kcContext.pageId) {
case "password.ftl":
return <Password kcContext={kcContext} {...rest} />;
case "sessions.ftl":
return <Sessions kcContext={kcContext} {...rest} />;
case "account.ftl":
return <Account kcContext={kcContext} {...rest} />;
case "totp.ftl":
return <Totp kcContext={kcContext} {...rest} />;
case "applications.ftl":
return <Applications kcContext={kcContext} {...rest} />;
case "log.ftl":
return <Log kcContext={kcContext} {...rest} />;
case "federatedIdentity.ftl":
return <FederatedIdentity kcContext={kcContext} {...rest} />;
}
assert<Equals<typeof kcContext, never>>(false);
})()}

View File

@ -23,7 +23,9 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
`${url.resourcesPath}/css/account.css`
],
"htmlClassName": getClassName("kcHtmlClass"),
"bodyClassName": clsx("admin-console", "user", getClassName("kcBodyClass"))
"bodyClassName": clsx("admin-console", "user", getClassName("kcBodyClass")),
"htmlLangProperty": locale?.currentLanguageTag,
"documentTitle": i18n.msgStr("accountManagementTitle")
});
if (!isReady) {

View File

@ -11,4 +11,17 @@ export type TemplateProps<KcContext extends KcContext.Common, I18nExtended exten
children: ReactNode;
};
export type ClassKey = "kcHtmlClass" | "kcBodyClass" | "kcButtonClass" | "kcButtonPrimaryClass" | "kcButtonLargeClass" | "kcButtonDefaultClass";
export type ClassKey =
| "kcHtmlClass"
| "kcBodyClass"
| "kcButtonClass"
| "kcButtonPrimaryClass"
| "kcButtonLargeClass"
| "kcButtonDefaultClass"
| "kcContentWrapperClass"
| "kcFormClass"
| "kcFormGroupClass"
| "kcInputWrapperClass"
| "kcLabelClass"
| "kcInputClass"
| "kcInputErrorMessageClass";

View File

@ -3,7 +3,14 @@ import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import { type ThemeType } from "keycloakify/bin/constants";
export type KcContext = KcContext.Password | KcContext.Account;
export type KcContext =
| KcContext.Password
| KcContext.Account
| KcContext.Sessions
| KcContext.Totp
| KcContext.Applications
| KcContext.Log
| KcContext.FederatedIdentity;
export declare namespace KcContext {
export type Common = {
@ -27,6 +34,7 @@ export declare namespace KcContext {
sessionsUrl: string;
applicationsUrl: string;
logUrl: string;
logoutUrl: string;
resourceUrl: string;
resourcesCommonPath: string;
resourcesPath: string;
@ -90,6 +98,7 @@ export declare namespace KcContext {
lastName?: string;
username?: string;
};
properties: Record<string, string | undefined>;
};
export type Password = Common & {
@ -111,6 +120,171 @@ export declare namespace KcContext {
};
stateChecker: string;
};
export type Sessions = Common & {
pageId: "sessions.ftl";
sessions: {
sessions: {
expires: string;
clients: string[];
ipAddress: string;
started: string;
lastAccess: string;
id: string;
}[];
};
stateChecker: string;
};
export type Totp = Common & {
pageId: "totp.ftl";
totp: {
enabled: boolean;
totpSecretEncoded: string;
qrUrl: string;
policy: {
algorithm: "HmacSHA1" | "HmacSHA256" | "HmacSHA512";
digits: number;
lookAheadWindow: number;
} & (
| {
type: "totp";
period: number;
}
| {
type: "hotp";
initialCounter: number;
}
);
supportedApplications: string[];
totpSecretQrCode: string;
manualUrl: string;
totpSecret: string;
otpCredentials: { id: string; userLabel: string }[];
};
mode?: "qr" | "manual" | undefined | null;
isAppInitiatedAction: boolean;
url: {
accountUrl: string;
passwordUrl: string;
totpUrl: string;
socialUrl: string;
sessionsUrl: string;
applicationsUrl: string;
logUrl: string;
resourceUrl: string;
resourcesCommonPath: string;
resourcesPath: string;
/** @deprecated, not present in recent keycloak version apparently, use kcContext.referrer instead */
referrerURI?: string;
getLogoutUrl: () => string;
};
stateChecker: string;
};
export type Applications = Common & {
pageId: "applications.ftl";
features: {
log: boolean;
identityFederation: boolean;
authorization: boolean;
passwordUpdateSupported: boolean;
};
stateChecker: string;
applications: {
applications: {
realmRolesAvailable: {
name: string;
description: string;
compositesStream?: Record<string, unknown>;
clientRole?: boolean;
composite?: boolean;
id?: string;
containerId?: string;
attributes?: Record<string, unknown>;
}[];
resourceRolesAvailable: Record<
string,
{
roleName: string;
roleDescription?: string;
clientName: string;
clientId: string;
}[]
>;
additionalGrants: string[];
clientScopesGranted: string[];
effectiveUrl?: string;
client: {
alwaysDisplayInConsole: boolean;
attributes: Record<string, unknown>;
authenticationFlowBindingOverrides: Record<string, unknown>;
baseUrl?: string;
bearerOnly: boolean;
clientAuthenticatorType: string;
clientId: string;
consentRequired: boolean;
consentScreenText: string;
description: string;
directAccessGrantsEnabled: boolean;
displayOnConsentScreen: boolean;
dynamicScope: boolean;
enabled: boolean;
frontchannelLogout: boolean;
fullScopeAllowed: boolean;
id: string;
implicitFlowEnabled: boolean;
includeInTokenScope: boolean;
managementUrl: string;
name?: string;
nodeReRegistrationTimeout: string;
notBefore: string;
protocol: string;
protocolMappersStream: Record<string, unknown>;
publicClient: boolean;
realm: Record<string, unknown>;
realmScopeMappingsStream: Record<string, unknown>;
redirectUris: string[];
registeredNodes: Record<string, unknown>;
rolesStream: Record<string, unknown>;
rootUrl?: string;
scopeMappingsStream: Record<string, unknown>;
secret: string;
serviceAccountsEnabled: boolean;
standardFlowEnabled: boolean;
surrogateAuthRequired: boolean;
webOrigins: string[];
};
}[];
};
};
export type Log = Common & {
pageId: "log.ftl";
log: {
events: {
date: string | number | Date;
event: string;
ipAddress: string;
client: string;
details: { value: string; key: string }[];
}[];
};
};
export type FederatedIdentity = Common & {
pageId: "federatedIdentity.ftl";
stateChecker: string;
federatedIdentity: {
identities: {
providerId: string;
displayName: string;
userName: string;
connected: boolean;
}[];
removeLinkPossible: boolean;
};
};
}
{

View File

@ -8,8 +8,9 @@ import { kcContextMocks, kcContextCommonMock } from "keycloakify/account/kcConte
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
mockProperties?: Record<string, string>;
}) {
const { mockData } = params ?? {};
const { mockData, mockProperties } = params ?? {};
function getKcContext<PageId extends ExtendKcContext<KcContextExtension>["pageId"] | undefined = undefined>(params?: {
mockPageId?: PageId;
@ -82,6 +83,13 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
});
}
if (mockProperties !== undefined) {
deepAssign({
"target": kcContext.properties,
"source": mockProperties
});
}
return { kcContext };
}

View File

@ -17,6 +17,7 @@ export const kcContextCommonMock: KcContext.Common = {
"resourceUrl": "#",
"accountUrl": "#",
"applicationsUrl": "#",
"logoutUrl": "#",
"getLogoutUrl": () => "#",
"logUrl": "#",
"passwordUrl": "#",
@ -145,6 +146,17 @@ export const kcContextCommonMock: KcContext.Common = {
"lastName": "doe",
"email": "john.doe@code.gouv.fr",
"username": "doe_j"
},
"properties": {
"parent": "account-v1",
"kcButtonLargeClass": "btn-lg",
"locales": "ar,ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN",
"kcButtonPrimaryClass": "btn-primary",
"accountResourceProvider": "account-v1",
"styles":
"css/account.css img/icon-sidebar-active.png img/logo.png resources-common/node_modules/patternfly/dist/css/patternfly.min.css resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css",
"kcButtonClass": "btn",
"kcButtonDefaultClass": "btn-default"
}
};
@ -171,5 +183,78 @@ export const kcContextMocks: KcContext[] = [
"editUsernameAllowed": true
},
"stateChecker": ""
}),
id<KcContext.Sessions>({
...kcContextCommonMock,
"pageId": "sessions.ftl",
"sessions": {
"sessions": [
{
"ipAddress": "127.0.0.1",
"started": new Date().toString(),
"lastAccess": new Date().toString(),
"expires": new Date().toString(),
"clients": ["Chrome", "Firefox"],
"id": "f8951177-817d-4a70-9c02-86d3c170fe51"
}
]
},
"stateChecker": "g6WB1FaYnKotTkiy7ZrlxvFztSqS0U8jvHsOOOb2z4g"
}),
id<KcContext.Totp>({
...kcContextCommonMock,
"pageId": "totp.ftl",
"totp": {
"enabled": true,
"totpSecretEncoded": "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
"qrUrl": "#",
"totpSecretQrCode":
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACM0lEQVR4Xu3OIZJgOQwDUDFd2UxiurLAVnnbHw4YGDKtSiWOn4Gxf81//7r/+q8b4HfLGBZDK9d85NmNR+sB42sXvOYrN5P1DcgYYFTGfOlbzE8gzwy3euweGizw7cfdl34/GRhlkxjKNV+5AebPXPORX1JuB9x8ZfbyyD2y1krWAKsbMq1HnqQDaLfa77p4+MqvzEGSqvSAD/2IHW2yHaigR9tX3m8dDIYGcNf3f+gDpVBZbZU77zyJ6Rlcy+qoTMG887KAPD9hsh6a1Sv3gJUHGHUAxSMzj7zqDDe7Phmt2eG+8UsMxjRGm816MAO+8VMl1R1jGHOrZB/5Zo/WXAPgxixm9Mo96vDGrM1eOto8c4Ax4wF437mifOXlpiPzCnN7Y9l95NnEMxgMY9AAGA8fucH14Y1aVb6N/cqrmyh0BVht7k1e+bU8LK0Cg5vmVq9c5vHIjOfqxDIfeTraNVTwewa4wVe+SW5N+uP1qACeudUZbqGOfA6VZV750Noq2Xx3kpveV44ZelSV1V7KFHzkWyVrrlUwG0Pl9pWnoy3vsQoME6vKI69i5osVgwWzHT7zjmJtMcNUSVn1oYMd7ZodbgowZl45VG0uVuLPUr1yc79uaQBag/mqR34xhlWyHm1prplHboCWdZ4TeZjsK8+dI+jbz1C5hl65mcpgB5dhcj8+dGO+0Ko68+lD37JDD83dpDLzzK+TrQyaVwGj6pUboGV+7+AyN8An/pf84/7rv/4/1l4OCc/1BYMAAAAASUVORK5CYII=",
"manualUrl": "#",
"totpSecret": "G4nsI8lQagRMUchH8jEG",
"otpCredentials": [],
"supportedApplications": ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
"policy": {
"algorithm": "HmacSHA1",
"digits": 6,
"lookAheadWindow": 1,
"type": "totp",
"period": 30
}
},
"mode": "qr",
"isAppInitiatedAction": false,
"stateChecker": ""
}),
id<KcContext.Log>({
...kcContextCommonMock,
"pageId": "log.ftl",
"log": {
"events": [
{
"date": "2/21/2024, 1:28:39 PM",
"event": "login",
"ipAddress": "172.17.0.1",
"client": "security-admin-console",
"details": [{ key: "openid-connect", value: "admin" }]
}
]
}
}),
id<KcContext.FederatedIdentity>({
...kcContextCommonMock,
"stateChecker": "",
"pageId": "federatedIdentity.ftl",
"federatedIdentity": {
"identities": [
{
"providerId": "keycloak-oidc",
"displayName": "keycloak-oidc",
"userName": "John",
"connected": true
}
],
"removeLinkPossible": true
}
})
];

View File

@ -6,8 +6,15 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
"kcHtmlClass": undefined,
"kcBodyClass": undefined,
"kcButtonClass": "btn",
"kcContentWrapperClass": "row",
"kcButtonPrimaryClass": "btn-primary",
"kcButtonLargeClass": "btn-lg",
"kcButtonDefaultClass": "btn-default"
"kcButtonDefaultClass": "btn-default",
"kcFormClass": "form-horizontal",
"kcFormGroupClass": "form-group",
"kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcLabelClass": "control-label",
"kcInputClass": "form-control",
"kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text"
}
});

View File

@ -0,0 +1,138 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
function isArrayWithEmptyObject(variable: any): boolean {
return Array.isArray(variable) && variable.length === 1 && typeof variable[0] === "object" && Object.keys(variable[0]).length === 0;
}
export default function Applications(props: PageProps<Extract<KcContext, { pageId: "applications.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
classes
});
const {
url,
applications: { applications },
stateChecker
} = kcContext;
const { msg, advancedMsg } = i18n;
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="applications">
<div className="row">
<div className="col-md-10">
<h2>{msg("applicationsHtmlTitle")}</h2>
</div>
<form action={url.applicationsUrl} method="post">
<input type="hidden" id="stateChecker" name="stateChecker" value={stateChecker} />
<input type="hidden" id="referrer" name="referrer" value={stateChecker} />
<table className="table table-striped table-bordered">
<thead>
<tr>
<td>{msg("application")}</td>
<td>{msg("availableRoles")}</td>
<td>{msg("grantedPermissions")}</td>
<td>{msg("additionalGrants")}</td>
<td>{msg("action")}</td>
</tr>
</thead>
<tbody>
{applications.map(application => (
<tr key={application.client.clientId}>
<td>
{application.effectiveUrl && (
<a href={application.effectiveUrl}>
{(application.client.name && advancedMsg(application.client.name)) || application.client.clientId}
</a>
)}
{!application.effectiveUrl &&
((application.client.name && advancedMsg(application.client.name)) || application.client.clientId)}
</td>
<td>
{!isArrayWithEmptyObject(application.realmRolesAvailable) &&
application.realmRolesAvailable.map((role, index) => (
<span key={role.name}>
{role.description ? advancedMsg(role.description) : advancedMsg(role.name)}
{index < application.realmRolesAvailable.length - 1 && ", "}
</span>
))}
{!isArrayWithEmptyObject(application.realmRolesAvailable) && application.resourceRolesAvailable && ", "}
{application.resourceRolesAvailable &&
Object.keys(application.resourceRolesAvailable).map(resource => (
<span key={resource}>
{!isArrayWithEmptyObject(application.realmRolesAvailable) && ", "}
{application.resourceRolesAvailable[resource].map(clientRole => (
<span key={clientRole.roleName}>
{clientRole.roleDescription
? advancedMsg(clientRole.roleDescription)
: advancedMsg(clientRole.roleName)}{" "}
{msg("inResource")}{" "}
<strong>
{clientRole.clientName ? advancedMsg(clientRole.clientName) : clientRole.clientId}
</strong>
{clientRole !==
application.resourceRolesAvailable[resource][
application.resourceRolesAvailable[resource].length - 1
] && ", "}
</span>
))}
</span>
))}
</td>
<td>
{application.client.consentRequired ? (
application.clientScopesGranted.map(claim => (
<span key={claim}>
{advancedMsg(claim)}
{claim !== application.clientScopesGranted[application.clientScopesGranted.length - 1] && ", "}
</span>
))
) : (
<strong>{msg("fullAccess")}</strong>
)}
</td>
<td>
{application.additionalGrants.map(grant => (
<span key={grant}>
{advancedMsg(grant)}
{grant !== application.additionalGrants[application.additionalGrants.length - 1] && ", "}
</span>
))}
</td>
<td>
{(application.client.consentRequired && application.clientScopesGranted.length > 0) ||
application.additionalGrants.length > 0 ? (
<button
type="submit"
className={clsx(getClassName("kcButtonPrimaryClass"), getClassName("kcButtonClass"))}
id={`revoke-${application.client.clientId}`}
name="clientId"
value={application.client.id}
>
{msg("revoke")}
</button>
) : null}
</td>
</tr>
))}
</tbody>
</table>
</form>
</div>
</Template>
);
}

View File

@ -0,0 +1,58 @@
import { PageProps } from "keycloakify/account";
import { I18n } from "keycloakify/account/i18n";
import { KcContext } from "keycloakify/account/kcContext";
export default function FederatedIdentity(props: PageProps<Extract<KcContext, { pageId: "federatedIdentity.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
const { url, federatedIdentity, stateChecker } = kcContext;
const { msg } = i18n;
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="federatedIdentity">
<div className="main-layout social">
<div className="row">
<div className="col-md-10">
<h2>{msg("federatedIdentitiesHtmlTitle")}</h2>
</div>
</div>
<div id="federated-identities">
{federatedIdentity.identities.map(identity => (
<div key={identity.providerId} className="row margin-bottom">
<div className="col-sm-2 col-md-2">
<label htmlFor={identity.providerId} className="control-label">
{identity.displayName}
</label>
</div>
<div className="col-sm-5 col-md-5">
<input disabled className="form-control" value={identity.userName} />
</div>
<div className="col-sm-5 col-md-5">
{identity.connected ? (
federatedIdentity.removeLinkPossible && (
<form action={url.socialUrl} method="post" className="form-inline">
<input type="hidden" name="stateChecker" value={stateChecker} />
<input type="hidden" name="action" value="remove" />
<input type="hidden" name="providerId" value={identity.providerId} />
<button id={`remove-link-${identity.providerId}`} className="btn btn-default">
{msg("doRemove")}
</button>
</form>
)
) : (
<form action={url.socialUrl} method="post" className="form-inline">
<input type="hidden" name="stateChecker" value={stateChecker} />
<input type="hidden" name="action" value="add" />
<input type="hidden" name="providerId" value={identity.providerId} />
<button id={`add-link-${identity.providerId}`} className="btn btn-default">
{msg("doAdd")}
</button>
</form>
)}
</div>
</div>
))}
</div>
</div>
</Template>
);
}

70
src/account/pages/Log.tsx Normal file
View File

@ -0,0 +1,70 @@
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
import { Key } from "react";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
export default function Log(props: PageProps<Extract<KcContext, { pageId: "log.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
classes
});
const { log } = kcContext;
const { msg } = i18n;
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="log">
<div className={getClassName("kcContentWrapperClass")}>
<div className="col-md-10">
<h2>{msg("accountLogHtmlTitle")}</h2>
</div>
<table className="table table-striped table-bordered">
<thead>
<tr>
<td>{msg("date")}</td>
<td>{msg("event")}</td>
<td>{msg("ip")}</td>
<td>{msg("client")}</td>
<td>{msg("details")}</td>
</tr>
</thead>
<tbody>
{log.events.map(
(
event: {
date: string | number | Date;
event: string;
ipAddress: string;
client: any;
details: any[];
},
index: Key | null | undefined
) => (
<tr key={index}>
<td>{event.date ? new Date(event.date).toLocaleString() : ""}</td>
<td>{event.event}</td>
<td>{event.ipAddress}</td>
<td>{event.client || ""}</td>
<td>
{event.details.map((detail, detailIndex) => (
<span key={detailIndex}>
{`${detail.key} = ${detail.value}`}
{detailIndex < event.details.length - 1 && ", "}
</span>
))}
</td>
</tr>
)
)}
</tbody>
</table>
</div>
</Template>
);
}

View File

@ -0,0 +1,65 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function Sessions(props: PageProps<Extract<KcContext, { pageId: "sessions.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
classes
});
const { url, stateChecker, sessions } = kcContext;
const { msg } = i18n;
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="sessions">
<div className={getClassName("kcContentWrapperClass")}>
<div className="col-md-10">
<h2>{msg("sessionsHtmlTitle")}</h2>
</div>
</div>
<table className="table table-striped table-bordered">
<thead>
<tr>
<th>{msg("ip")}</th>
<th>{msg("started")}</th>
<th>{msg("lastAccess")}</th>
<th>{msg("expires")}</th>
<th>{msg("clients")}</th>
</tr>
</thead>
<tbody role="rowgroup">
{sessions.sessions.map((session, index: number) => (
<tr key={index}>
<td>{session.ipAddress}</td>
<td>{session?.started}</td>
<td>{session?.lastAccess}</td>
<td>{session?.expires}</td>
<td>
{session.clients.map((client: string, clientIndex: number) => (
<div key={clientIndex}>
{client}
<br />
</div>
))}
</td>
</tr>
))}
</tbody>
</table>
<form action={url.sessionsUrl} method="post">
<input type="hidden" id="stateChecker" name="stateChecker" value={stateChecker} />
<button id="logout-all-sessions" type="submit" className={clsx(getClassName("kcButtonDefaultClass"), getClassName("kcButtonClass"))}>
{msg("doLogOutAllSessions")}
</button>
</form>
</Template>
);
}

235
src/account/pages/Totp.tsx Normal file
View File

@ -0,0 +1,235 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
import { MessageKey } from "keycloakify/account/i18n/i18n";
export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
classes
});
const { totp, mode, url, messagesPerField, stateChecker } = kcContext;
const { msg, msgStr } = i18n;
const algToKeyUriAlg: Record<(typeof kcContext)["totp"]["policy"]["algorithm"], string> = {
"HmacSHA1": "SHA1",
"HmacSHA256": "SHA256",
"HmacSHA512": "SHA512"
};
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="totp">
<>
<div className="row">
<div className="col-md-10">
<h2>{msg("authenticatorTitle")}</h2>
</div>
{totp.otpCredentials.length === 0 && (
<div className="subtitle col-md-2">
<span className="required">*</span>
{msg("requiredFields")}
</div>
)}
</div>
{totp.enabled && (
<table className="table table-bordered table-striped">
<thead>
{totp.otpCredentials.length > 1 ? (
<tr>
<th colSpan={4}>{msg("configureAuthenticators")}</th>
</tr>
) : (
<tr>
<th colSpan={3}>{msg("configureAuthenticators")}</th>
</tr>
)}
</thead>
<tbody>
{totp.otpCredentials.map((credential, index) => (
<tr key={index}>
<td className="provider">{msg("mobile")}</td>
{totp.otpCredentials.length > 1 && <td className="provider">{credential.id}</td>}
<td className="provider">{credential.userLabel || ""}</td>
<td className="action">
<form action={url.totpUrl} method="post" className="form-inline">
<input type="hidden" id="stateChecker" name="stateChecker" value={stateChecker} />
<input type="hidden" id="submitAction" name="submitAction" value="Delete" />
<input type="hidden" id="credentialId" name="credentialId" value={credential.id} />
<button id={`remove-mobile-${index}`} className="btn btn-default">
<i className="pficon pficon-delete"></i>
</button>
</form>
</td>
</tr>
))}
</tbody>
</table>
)}
{!totp.enabled && (
<div>
<hr />
<ol id="kc-totp-settings">
<li>
<p>{msg("totpStep1")}</p>
<ul id="kc-totp-supported-apps">
{totp.supportedApplications?.map(app => (
<li key={app}>{msg(app as MessageKey)}</li>
))}
</ul>
</li>
{mode && mode == "manual" ? (
<>
<li>
<p>{msg("totpManualStep2")}</p>
<p>
<span id="kc-totp-secret-key">{totp.totpSecretEncoded}</span>
</p>
<p>
<a href={totp.qrUrl} id="mode-barcode">
{msg("totpScanBarcode")}
</a>
</p>
</li>
<li>
<p>{msg("totpManualStep3")}</p>
<ul>
<li id="kc-totp-type">
{msg("totpType")}: {msg(`totp.${totp.policy.type}`)}
</li>
<li id="kc-totp-algorithm">
{msg("totpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
</li>
<li id="kc-totp-digits">
{msg("totpDigits")}: {totp.policy.digits}
</li>
{totp.policy.type === "totp" ? (
<li id="kc-totp-period">
{msg("totpInterval")}: {totp.policy.period}
</li>
) : (
<li id="kc-totp-counter">
{msg("totpCounter")}: {totp.policy.initialCounter}
</li>
)}
</ul>
</li>
</>
) : (
<li>
<p>{msg("totpStep2")}</p>
<p>
<img
id="kc-totp-secret-qr-code"
src={`data:image/png;base64, ${totp.totpSecretQrCode}`}
alt="Figure: Barcode"
/>
</p>
<p>
<a href={totp.manualUrl} id="mode-manual">
{msg("totpUnableToScan")}
</a>
</p>
</li>
)}
<li>
<p>{msg("totpStep3")}</p>
<p>{msg("totpStep3DeviceName")}</p>
</li>
</ol>
<hr />
<form action={url.totpUrl} className={getClassName("kcFormClass")} id="kc-totp-settings-form" method="post">
<input type="hidden" id="stateChecker" name="stateChecker" value={stateChecker} />
<div className={getClassName("kcFormGroupClass")}>
<div className="col-sm-2 col-md-2">
<label htmlFor="totp" className="control-label">
{msg("authenticatorCode")}
</label>
<span className="required">*</span>
</div>
<div className="col-sm-10 col-md-10">
<input
type="text"
id="totp"
name="totp"
autoComplete="off"
className={getClassName("kcInputClass")}
aria-invalid={messagesPerField.existsError("totp")}
/>
{messagesPerField.existsError("totp") && (
<span id="input-error-otp-code" className={getClassName("kcInputErrorMessageClass")} aria-live="polite">
{messagesPerField.get("totp")}
</span>
)}
</div>
<input type="hidden" id="totpSecret" name="totpSecret" value={totp.totpSecret} />
{mode && <input type="hidden" id="mode" value={mode} />}
</div>
<div className={getClassName("kcFormGroupClass")}>
<div className="col-sm-2 col-md-2">
<label htmlFor="userLabel" className={getClassName("kcLabelClass")}>
{msg("totpDeviceName")}
</label>
{totp.otpCredentials.length >= 1 && <span className="required">*</span>}
</div>
<div className="col-sm-10 col-md-10">
<input
type="text"
id="userLabel"
name="userLabel"
autoComplete="off"
className={getClassName("kcInputClass")}
aria-invalid={messagesPerField.existsError("userLabel")}
/>
{messagesPerField.existsError("userLabel") && (
<span id="input-error-otp-label" className={getClassName("kcInputErrorMessageClass")} aria-live="polite">
{messagesPerField.get("userLabel")}
</span>
)}
</div>
</div>
<div id="kc-form-buttons" className={clsx(getClassName("kcFormGroupClass"), "text-right")}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="submit"
className={clsx(
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonLargeClass")
)}
id="saveTOTPBtn"
value={msgStr("doSave")}
/>
<button
type="submit"
className={clsx(
getClassName("kcButtonClass"),
getClassName("kcButtonDefaultClass"),
getClassName("kcButtonLargeClass"),
getClassName("kcButtonLargeClass")
)}
id="cancelTOTPBtn"
name="submitAction"
value="Cancel"
>
{msg("doCancel")}
</button>
</div>
</div>
</form>
</div>
)}
</>
</Template>
);
}

View File

@ -50,6 +50,62 @@ export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: st
});
}
repatriate_common_resources_from_base_login_theme: {
const baseLoginThemeResourceDir = pathJoin(destDirPath, "base", "login", "resources");
if (!fs.existsSync(baseLoginThemeResourceDir)) {
break repatriate_common_resources_from_base_login_theme;
}
transformCodebase({
"srcDirPath": baseLoginThemeResourceDir,
"destDirPath": pathJoin(destDirPath, "keycloak", "login", "resources")
});
}
install_and_move_to_common_resources_generated_in_keycloak_v2: {
if (!fs.readFileSync(pathJoin(destDirPath, "keycloak", "login", "theme.properties")).toString("utf8").includes("web_modules")) {
break install_and_move_to_common_resources_generated_in_keycloak_v2;
}
const accountV2DirSrcDirPath = pathJoin(destDirPath, "keycloak.v2", "account", "src");
if (!fs.existsSync(accountV2DirSrcDirPath)) {
break install_and_move_to_common_resources_generated_in_keycloak_v2;
}
const packageManager = fs.existsSync(pathJoin(accountV2DirSrcDirPath, "pnpm-lock.yaml")) ? "pnpm" : "npm";
if (packageManager === "pnpm") {
try {
child_process.execSync(`which pnpm`);
} catch {
console.log(`Installing pnpm globally`);
child_process.execSync(`npm install -g pnpm`);
}
}
child_process.execSync(`${packageManager} install`, { "cwd": accountV2DirSrcDirPath, "stdio": "ignore" });
const packageJsonFilePath = pathJoin(accountV2DirSrcDirPath, "package.json");
const packageJsonRaw = fs.readFileSync(packageJsonFilePath);
const parsedPackageJson = JSON.parse(packageJsonRaw.toString("utf8"));
parsedPackageJson.scripts.build = parsedPackageJson.scripts.build
.replace(`${packageManager} run check-types`, "true")
.replace(`${packageManager} run babel`, "true");
fs.writeFileSync(packageJsonFilePath, Buffer.from(JSON.stringify(parsedPackageJson, null, 2), "utf8"));
child_process.execSync(`${packageManager} run build`, { "cwd": accountV2DirSrcDirPath, "stdio": "ignore" });
fs.writeFileSync(packageJsonFilePath, packageJsonRaw);
fs.rmSync(pathJoin(accountV2DirSrcDirPath, "node_modules"), { "recursive": true });
}
remove_keycloak_v2: {
const keycloakV2DirPath = pathJoin(destDirPath, "keycloak.v2");
@ -148,7 +204,7 @@ export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: st
break remove_unused_lib;
}
const toDeletePerfixes = ["ui-ace", "filesaver", "fileupload", "angular", "ui-ace", "pficon"];
const toDeletePerfixes = ["ui-ace", "filesaver", "fileupload", "angular", "ui-ace"];
transformCodebase({
"srcDirPath": libDirPath,
@ -181,34 +237,6 @@ export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: st
);
}
{
const totpFtlFilePath = pathJoin(destDirPath, "base", "account", "totp.ftl");
fs.writeFileSync(
totpFtlFilePath,
Buffer.from(
fs
.readFileSync(totpFtlFilePath)
.toString("utf8")
.replace(
[
" <#list totp.policy.supportedApplications as app>",
" <li>${app}</li>",
" </#list>"
].join("\n"),
[
" <#if totp.policy.supportedApplications?has_content>",
" <#list totp.policy.supportedApplications as app>",
" <li>${app}</li>",
" </#list>",
" </#if>"
].join("\n")
),
"utf8"
)
);
}
// Note, this is an optimization for reducing the size of the jar,
// For this version we know exactly which resources are used.
{

View File

@ -26,7 +26,7 @@ export function getNpmWorkspaceRootDirPath(params: { reactAppRootDirPath: string
const cwd = pathResolve(pathJoin(...[reactAppRootDirPath, ...Array(depth).fill("..")]));
try {
child_process.execSync("npm config get", { cwd: cwd });
child_process.execSync("npm config get", { cwd, "stdio": ["pipe", "pipe", "pipe"] });
} catch (error) {
if (String(error).includes("ENOWORKSPACES")) {
assert(cwd !== pathSep, "NPM workspace not found");

View File

@ -497,6 +497,14 @@
) || (
are_same_path(path, ["user"]) &&
key == "delegateForUpdate"
) || (
<#-- Security audit forwarded by Garth (Gmail) -->
are_same_path(path, ["client", "attributes"]) &&
key == "saml.signing.private.key"
) || (
<#-- See: https://github.com/keycloakify/keycloakify/issues/534 -->
are_same_path(path, ["login"]) &&
key == "password"
)
>
<#local out_seq += ["/*If you need '" + path?join(".") + "." + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>

View File

@ -27,7 +27,15 @@ export const loginThemePageIds = [
"saml-post-form.ftl"
] as const;
export const accountThemePageIds = ["password.ftl", "account.ftl"] as const;
export const accountThemePageIds = [
"password.ftl",
"account.ftl",
"sessions.ftl",
"totp.ftl",
"applications.ftl",
"log.ftl",
"federatedIdentity.ftl"
] as const;
export type LoginThemePageId = (typeof loginThemePageIds)[number];
export type AccountThemePageId = (typeof accountThemePageIds)[number];

View File

@ -10,8 +10,11 @@ import { getThemeSrcDirPath } from "../getThemeSrcDirPath";
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
import { readThisNpmProjectVersion } from "../tools/readThisNpmProjectVersion";
import { keycloakifyBuildOptionsForPostPostBuildScriptEnvName } from "../constants";
import { assertNoPnpmDlx } from "../tools/assertNoPnpmDlx";
export async function main() {
assertNoPnpmDlx();
const buildOptions = readBuildOptions({
"processArgv": process.argv.slice(2)
});
@ -49,13 +52,19 @@ export async function main() {
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8"));
child_process.execSync("npx vite", {
"cwd": buildOptions.reactAppRootDirPath,
"env": {
...process.env,
[keycloakifyBuildOptionsForPostPostBuildScriptEnvName]: JSON.stringify(buildOptions)
run_post_build_script: {
if (buildOptions.bundler !== "vite") {
break run_post_build_script;
}
});
child_process.execSync("npx vite", {
"cwd": buildOptions.reactAppRootDirPath,
"env": {
...process.env,
[keycloakifyBuildOptionsForPostPostBuildScriptEnvName]: JSON.stringify(buildOptions)
}
});
}
create_jar: {
if (!buildOptions.doCreateJar) {

View File

@ -22,11 +22,12 @@ export async function promptKeycloakVersion() {
const tags = [
...(await getLatestsSemVersionedTag({
"count": 10,
"count": 15,
"owner": "keycloak",
"repo": "keycloak"
}).then(arr => arr.map(({ tag }) => tag))),
lastKeycloakVersionWithAccountV1,
"19.0.1",
"11.0.3"
];

View File

@ -0,0 +1,14 @@
import { sep as pathSep } from "path";
export function assertNoPnpmDlx() {
if (__dirname.includes(`${pathSep}pnpm${pathSep}dlx${pathSep}`)) {
console.log(
[
"Please don't use `pnpm dlx keycloakify`.",
"\nUse `npx keycloakify` or `pnpm exec keycloakify` instead since you want to use the",
"keycloakify version that is installed in your project and not the latest version on NPM."
].join(" ")
);
process.exit(1);
}
}

View File

@ -40,5 +40,5 @@ export const BASE_URL = (() => {
return BASE_URL === "" ? "/" : `${BASE_URL}/`;
}
throw new Error("Bundler not supported");
return "/";
})();

View File

@ -1,6 +1,7 @@
import { useReducer, useEffect } from "react";
import { headInsert } from "keycloakify/tools/headInsert";
import { clsx } from "keycloakify/tools/clsx";
import { assert } from "tsafe/assert";
export function usePrepareTemplate(params: {
doFetchDefaultThemeResources: boolean;
@ -8,11 +9,31 @@ export function usePrepareTemplate(params: {
scripts?: string[];
htmlClassName: string | undefined;
bodyClassName: string | undefined;
htmlLangProperty?: string | undefined;
documentTitle?: string;
}) {
const { doFetchDefaultThemeResources, styles = [], scripts = [], htmlClassName, bodyClassName } = params;
const { doFetchDefaultThemeResources, styles = [], scripts = [], htmlClassName, bodyClassName, htmlLangProperty, documentTitle } = params;
const [isReady, setReady] = useReducer(() => true, !doFetchDefaultThemeResources);
useEffect(() => {
if (htmlLangProperty === undefined) {
return;
}
const html = document.querySelector("html");
assert(html !== null);
html.lang = htmlLangProperty;
}, [htmlLangProperty]);
useEffect(() => {
if (documentTitle === undefined) {
return;
}
document.title = documentTitle;
}, [documentTitle]);
useEffect(() => {
if (!doFetchDefaultThemeResources) {
return;

View File

@ -38,15 +38,15 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
`${url.resourcesPath}/css/login.css`
],
"htmlClassName": getClassName("kcHtmlClass"),
"bodyClassName": getClassName("kcBodyClass")
"bodyClassName": getClassName("kcBodyClass"),
"htmlLangProperty": locale?.currentLanguageTag,
"documentTitle": i18n.msgStr("loginTitle", kcContext.realm.displayName)
});
if (!isReady) {
return null;
}
document.title = i18n.msgStr("loginTitle", kcContext.realm.displayName);
return (
<div className={getClassName("kcLoginClass")}>
<div id="kc-header" className={getClassName("kcHeaderClass")}>

View File

@ -116,6 +116,7 @@ export declare namespace KcContext {
*/
exists: (fieldName: string) => boolean;
};
properties: Record<string, string | undefined>;
};
export type SamlPostForm = Common & {
@ -244,6 +245,17 @@ export declare namespace KcContext {
export type Terms = Common & {
pageId: "terms.ftl";
//NOTE: Optional because maybe it wasn't defined in older keycloak versions.
user?: {
id: string;
username: string;
attributes: Record<string, string[]>;
email: string;
emailVerified: boolean;
firstName?: string;
lastName?: string;
markedForEviction?: boolean;
};
};
export type LoginDeviceVerifyUserCode = Common & {

View File

@ -12,8 +12,9 @@ import { symToStr } from "tsafe/symToStr";
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
mockProperties?: Record<string, string>;
}) {
const { mockData } = params ?? {};
const { mockData, mockProperties } = params ?? {};
function getKcContext<PageId extends ExtendKcContext<KcContextExtension>["pageId"] | undefined = undefined>(params?: {
mockPageId?: PageId;
@ -141,6 +142,13 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
}
}
if (mockProperties !== undefined) {
deepAssign({
"target": kcContext.properties,
"source": mockProperties
});
}
return { kcContext };
}

View File

@ -236,7 +236,127 @@ export const kcContextCommonMock: KcContext.Common = {
"attributes": {}
},
"scripts": [],
"isAppInitiatedAction": false
"isAppInitiatedAction": false,
"properties": {
"kcLogoIdP-facebook": "fa fa-facebook",
"parent": "keycloak",
"kcAuthenticatorOTPClass": "fa fa-mobile list-view-pf-icon-lg",
"kcLogoIdP-bitbucket": "fa fa-bitbucket",
"kcAuthenticatorWebAuthnClass": "fa fa-key list-view-pf-icon-lg",
"kcWebAuthnDefaultIcon": "pficon pficon-key",
"kcLogoIdP-stackoverflow": "fa fa-stack-overflow",
"kcSelectAuthListItemClass": "pf-l-stack__item select-auth-box-parent pf-l-split",
"kcLogoIdP-microsoft": "fa fa-windows",
"kcLocaleItemClass": "pf-c-dropdown__menu-item",
"kcLoginOTPListItemHeaderClass": "pf-c-tile__header",
"kcLoginOTPListItemIconBodyClass": "pf-c-tile__icon",
"kcInputHelperTextAfterClass": "pf-c-form__helper-text pf-c-form__helper-text-after",
"kcFormClass": "form-horizontal",
"kcSelectAuthListClass": "pf-l-stack select-auth-container",
"kcInputClassRadioCheckboxLabelDisabled": "pf-m-disabled",
"kcSelectAuthListItemIconClass": "pf-l-split__item select-auth-box-icon",
"kcRecoveryCodesWarning": "kc-recovery-codes-warning",
"kcFormSettingClass": "login-pf-settings",
"kcWebAuthnBLE": "fa fa-bluetooth-b",
"kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcSelectAuthListItemArrowIconClass": "fa fa-angle-right fa-lg",
"meta": "viewport==width=device-width,initial-scale=1",
"styles": "css/login.css css/tile.css",
"kcFeedbackAreaClass": "col-md-12",
"kcLogoIdP-google": "fa fa-google",
"kcCheckLabelClass": "pf-c-check__label",
"kcSelectAuthListItemFillClass": "pf-l-split__item pf-m-fill",
"kcAuthenticatorDefaultClass": "fa fa-list list-view-pf-icon-lg",
"kcLogoIdP-gitlab": "fa fa-gitlab",
"kcFormAreaClass": "col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2",
"kcFormButtonsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcInputClassRadioLabel": "pf-c-radio__label",
"kcAuthenticatorWebAuthnPasswordlessClass": "fa fa-key list-view-pf-icon-lg",
"kcSelectAuthListItemHeadingClass": "pf-l-stack__item select-auth-box-headline pf-c-title",
"kcInfoAreaClass": "col-xs-12 col-sm-4 col-md-4 col-lg-5 details",
"kcLogoLink": "http://www.keycloak.org",
"kcContainerClass": "container-fluid",
"kcSelectAuthListItemTitle": "select-auth-box-paragraph",
"kcHtmlClass": "login-pf",
"kcLoginOTPListItemTitleClass": "pf-c-tile__title",
"locales": "ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN",
"serviceTitle": "CodeGouv",
"kcLogoIdP-openshift-v4": "pf-icon pf-icon-openshift",
"kcWebAuthnUnknownIcon": "pficon pficon-key unknown-transport-class",
"kcFormSocialAccountNameClass": "kc-social-provider-name",
"kcLogoIdP-openshift-v3": "pf-icon pf-icon-openshift",
"kcLoginOTPListInputClass": "pf-c-tile__input",
"kcWebAuthnUSB": "fa fa-usb",
"kcInputClassRadio": "pf-c-radio",
"kcWebAuthnKeyIcon": "pficon pficon-key",
"kcFeedbackInfoIcon": "fa fa-fw fa-info-circle",
"kcCommonLogoIdP": "kc-social-provider-logo kc-social-gray",
"stylesCommon":
"web_modules/@patternfly/react-core/dist/styles/base.css web_modules/@patternfly/react-core/dist/styles/app.css node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css lib/pficon/pficon.css",
"kcRecoveryCodesActions": "kc-recovery-codes-actions",
"kcFormGroupHeader": "pf-c-form__group",
"kcFormSocialAccountSectionClass": "kc-social-section kc-social-gray",
"kcLogoIdP-instagram": "fa fa-instagram",
"kcAlertClass": "pf-c-alert pf-m-inline",
"kcHeaderClass": "login-pf-page-header",
"kcLabelWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcFormSocialAccountLinkClass": "pf-c-login__main-footer-links-item-link",
"kcLocaleMainClass": "pf-c-dropdown",
"kcTextareaClass": "form-control",
"kcButtonBlockClass": "pf-m-block",
"kcButtonClass": "pf-c-button",
"kcWebAuthnNFC": "fa fa-wifi",
"kcLocaleClass": "col-xs-12 col-sm-1",
"kcInputClassCheckboxInput": "pf-c-check__input",
"kcFeedbackErrorIcon": "fa fa-fw fa-exclamation-circle",
"kcInputLargeClass": "input-lg",
"kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text",
"kcRecoveryCodesList": "kc-recovery-codes-list",
"kcFormSocialAccountListClass": "pf-c-login__main-footer-links kc-social-links",
"kcAlertTitleClass": "pf-c-alert__title kc-feedback-text",
"kcAuthenticatorPasswordClass": "fa fa-unlock list-view-pf-icon-lg",
"kcCheckInputClass": "pf-c-check__input",
"kcLogoIdP-linkedin": "fa fa-linkedin",
"kcLogoIdP-twitter": "fa fa-twitter",
"kcFeedbackWarningIcon": "fa fa-fw fa-exclamation-triangle",
"kcResetFlowIcon": "pficon pficon-arrow fa",
"kcSelectAuthListItemIconPropertyClass": "fa-2x select-auth-box-icon-properties",
"kcFeedbackSuccessIcon": "fa fa-fw fa-check-circle",
"kcLoginOTPListClass": "pf-c-tile",
"kcSrOnlyClass": "sr-only",
"kcFormSocialAccountListGridClass": "pf-l-grid kc-social-grid",
"kcButtonDefaultClass": "btn-default",
"kcFormGroupErrorClass": "has-error",
"kcSelectAuthListItemDescriptionClass": "pf-l-stack__item select-auth-box-desc",
"kcSelectAuthListItemBodyClass": "pf-l-split__item pf-l-stack",
"import": "common/keycloak",
"kcWebAuthnInternal": "pficon pficon-key",
"kcSelectAuthListItemArrowClass": "pf-l-split__item select-auth-box-arrow",
"kcCheckClass": "pf-c-check",
"kcContentClass": "col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3",
"kcLogoClass": "login-pf-brand",
"kcLoginOTPListItemIconClass": "fa fa-mobile",
"kcLoginClass": "login-pf-page",
"kcSignUpClass": "login-pf-signup",
"kcButtonLargeClass": "btn-lg",
"kcFormCardClass": "card-pf",
"kcLocaleListClass": "pf-c-dropdown__menu pf-m-align-right",
"kcInputClass": "pf-c-form-control",
"kcFormGroupClass": "form-group",
"kcLogoIdP-paypal": "fa fa-paypal",
"kcInputClassCheckbox": "pf-c-check",
"kcRecoveryCodesConfirmation": "kc-recovery-codes-confirmation",
"kcInputClassRadioInput": "pf-c-radio__input",
"kcFormSocialAccountListButtonClass": "pf-c-button pf-m-control pf-m-block kc-social-item kc-social-gray",
"kcInputClassCheckboxLabel": "pf-c-check__label",
"kcFormOptionsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcFormHeaderClass": "login-pf-header",
"kcFormSocialAccountGridItem": "pf-l-grid__item",
"kcButtonPrimaryClass": "pf-m-primary",
"kcInputHelperTextBeforeClass": "pf-c-form__helper-text pf-c-form__helper-text-before",
"kcLogoIdP-github": "fa fa-github",
"kcLabelClass": "pf-c-form__label pf-c-form__label-text"
}
};
const loginUrl = {

View File

@ -1,24 +1,19 @@
import React from "react";
import type { ComponentMeta } from "@storybook/react";
import { Meta, StoryObj } from "@storybook/react";
import { createPageStory } from "../createPageStory";
const pageId = "account.ftl";
const { PageStory } = createPageStory({ pageId });
const meta: ComponentMeta<any> = {
title: `account/${pageId}`,
component: PageStory,
parameters: {
viewMode: "story",
previewTabs: {
"storybook/docs/panel": {
hidden: true
}
}
}
};
const meta = {
title: "account/Account",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;
export const Default = () => <PageStory />;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => <PageStory />
};

View File

@ -0,0 +1,173 @@
import React from "react";
import { Meta } from "@storybook/react";
import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({
pageId: "totp.ftl"
});
const meta = {
title: "account/Authenticator",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;
export const Default = () => (
<PageStory
kcContext={{
totp: {
enabled: false,
totpSecretEncoded: "HE4W MSTC OBKU CY2M ONXF OV3Q NYYU I3SH",
totpSecret: "99fJbpUAcLsnWWpn1DnG",
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACk0lEQVR4Xu2YQY6DMAxFjViw5AjcBC6GBBIXozfJEbpkger53wEKqOpmFvaikQYNeVRyHPvbiejXscp95jp+/D5zHT9+n7kO8qeIFDqKzjJo9dC1wUSPP7yG4IPq41lq9ZK+keLZSwXDGwMhOCZgdX4sBVD1qld+GYg/h6ScreBuIDo5FKfVM7Z8aWs9PB2E2/73DdOlwUrK9Ck+HDnzB7ziR8fjlD/OPI8pVQwCi899TkNw2M+tp9XSLFKPIq2UySIhBB906fCQTicFwiv1EUG6+d+bl4zPIYnUk5oIcS69/evPYStUp6P0dJhD/mhauijcth76mOsfw+GFrbfXKJx7LW2N15kijuWIMCYicLQOCEimDp1c0L8PzCLTs3/d+ZQLyl6VqeSIT9nz25szf2ZybHgC31yrXEQIbqaPjX0k9GqWy0N/nLkagsHWNXR0LZwsR357c0pjC6fm+meu5f6f6oszz/qj7GpYCdHf0LVH/gTgtJ/5bVavPJ9svwnBS9qaqwoHOh3G7Ln++HIIDgpKYpFW00dlkX7ruz836THBWQpzd23/xeDsFVroz15fRjsfMyaC8JX2Y8PZf+VIoKff+uTO6WSIUIfSkrl9/rbfnbPr30R8hnMtXA/98ea5lx4ZlSMgQlMsEnb73XnP+yNl/SuR3/lzTSZHMTirMpMcXjWr0U5Mp/rnzmk/TsXkC2/iKEJ5TRG4DZ5KrP/C0RiVmkp+5I8zN1uh2vv9Vs+bzJ4947Y+bz6wl6ZIcv87ZaU2+6PwnoKdb7VYmrf9Z02MxCmNdmparbVJtrA4nA+e9LgIS6dzfvly7j+4XWIuPJp8iE9PbvkzJHYNabt/o5MP+535t/Hj95nr+PH7zHX8m/8B+RAnloz5pi4AAAAASUVORK5CYII=",
policy: {
type: "totp"
},
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
otpCredentials: []
},
messagesPerField: {},
stateChecker: "ihTeSAMfNsobnPjYiktV8DY-5T4sVzVdrEZRdwfMm8Y",
realm: {
userManagedAccessAllowed: true,
internationalizationEnabled: false
},
url: {
totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
},
keycloakifyVersion: "9.6.1",
themeVersion: "1.0.10",
themeType: "account",
themeName: "keycloakify",
pageId: "totp.ftl"
}}
/>
);
export const WithTotpEnabled = () => (
<PageStory
kcContext={{
totp: {
enabled: true,
totpSecretEncoded: "G55E MZKC JFUD MQLT MFIF EVSB JFLG M6SO",
totpSecret: "7zFeBIh6AsaPRVAIVfzN",
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACo0lEQVR4Xu2YPY6DMBCFJ6JwyRF8k+RiSCBxsXATjkDpAmX2vTGBwErbbDFTZIps4s8rmfl9RvRPW+W6crYvv66c7cuvK2cjX0Tkho9yW/q5PHSc5QYA62PwXnWqmzrRSUdNL+mygRC8kzQZWhqVO1CRds3YHopnfUkzp2c7ZAY+GIdXywOb0qsdJMXiFn9serYrncxNv/PDkdfUzObk/eNaX368mnl1kML8RH1vFoGzargA1DM/VeWhOpf9+by5iL5Q0NaEUETslHiSIz+dOc4q0tqBrcg7IsnpnZ8BeLmjqjFa4Fps4vlR3484nFHH6OP8o1cTc4I/Q3D4Uqw1TjpkeHqc2R/Rjvb89OUUDAL/CpycOf/o6fUjP505/phrOf8wn+tolsxyD8GZnzyrJSScrNyEcXhHJwrBh2yj2fShPlFB2PQxn935aK1HIB1G1nczm8+P+nbmC7si+zell53a4i97fnhz5Gddxc9iSgLPpPifGn9vDqN0YBL0lpozdx7nd+dDHSiFXkV+NlZO85Efzvzda8yrwkylvlEbhxE4bTJpiCEIkWNHbxD/w/++fJMOVX8p5Q70F0V2EI4LsUWd+ov6Wtgu5aM/OXNIf6jWbKq6zmekA77t88WZr5lXO6vvWaj6kbNo4nv/ceaon0TpYPqrmNJhue/x9+ZKLchbO+cLPrb+aI09BLeob1en2nqkKsUYfOvatSGa/ircmD7i78rNmJoYzXwIKh228z3+ztzef+Cb6S/lSxoWOXM2CO/ZuvlqARtLvX8u1Ie6+d+bd/X9pdS3lrrF/8jPCPytv9AVIbfvddxE4iNFLKL+hH/xCNudKgTvGX/r33ars/y062gQjljfWN8cyKm+f2NPOvqTL//Lvvy6crYvv66c7d/8B/9RFjk6Tp30AAAAAElFTkSuQmCC",
policy: {
type: "totp",
algorithm: "HmacSHA1"
},
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
otpCredentials: [
{
id: "7afaaf7d-f2d5-44f5-a966-e5297f0b2b7a",
userLabel: "mobile"
}
]
},
message: {
summary: "Mobile authenticator configured.",
type: "success"
},
url: {
totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
},
messagesPerField: {},
stateChecker: "0UvyCNJHRJXmdahtRmn0tTPCU2nwLtWBUfPaaX1qb4g",
realm: {
userManagedAccessAllowed: true,
internationalizationEnabled: false
},
keycloakifyVersion: "9.6.1",
themeVersion: "1.0.10",
themeType: "account",
themeName: "keycloakify",
pageId: "totp.ftl"
}}
/>
);
export const WithManualMode = () => (
<PageStory
kcContext={{
mode: "manual",
totp: {
enabled: false,
totpSecretEncoded: "KZ5H CYTW GBVV ASDE JRXG MMCK HAZU E6TX",
totpSecret: "Vzqbv0kPHdLnf0J83Bzw",
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACpklEQVR4Xu2YQa6rMAxFXTFgyBLYSbsxJJC6sb6dZAkdMkDNv8cBStHTm/yBM2ikIpqTgePY1w6W/xyLnWc+x5efZz7Hl59nPgf8aWaXPFl+2ZhbzfWaGPTT3yr4mPPPs8nty4ZeKxfzRQ6q4IO1P8zq0c/iffvqtIlLTfw5psxsK3f7JirjTHDqWpQ3T9fC/fytn2956u32bNJv8RHIyZ/n0MvJh8cpvwJ5GffkQaBNYPo2auCyv30YVmtitm4yu1qT5mtXCR9svsqXeih1/I1IbZHLKniTskxPOvCGSB3Wud2/0Vz+5YH9uHZAvzORUAlXaXmY9FHxyZuWI0L5sfs3lkt1vDTbtVtM8bmovrCT26o/0bxozVAWIY3IuTLpsvk3mDNeRv9QqrJWEp+25Xc01/uMVudHpySiE3PXklN1cLSm8yCgKmuWICUIxip4vqM6Y+kalNX3hJNtz+9orgOXQ60noZPrd/H5u74E86I/pfXXm/obXPvOr2juVW8o9nsTS77T5Ix18CZ71sh+qQ7n3+LzY32J5WptXt291Bdaf8tcVw76Hcvpqr31R3CUOri7Q79r4ap61+5O12XoT1leOrFK+HZ/asga/sr0tz5F85wozWq4aMKcP1DK3f54Ttfv+a0iqG1wCU2H/iGWl156IionQYWmngTpan84H9aGy+8nl7I8J5ejOnjP0SNCC/0/lVpydKyPwZz7u/Xef80ouaRHHt7PP5j74BJFfBpJ3vLp460/wdxtxX5KM6XPMvktJ6/7i+YjvfRS/Gs3za3218LJH5qwzKKf7fzd3fXwEWmkf5WTKS3JN1YRTxKhiY9IC6mzUKmP/g3knL8cqoeUiKvJL/EZyT1/sJ/vg+X7G07e7Q/mf40vP898ji8/z3yO/+b/ANUwOXCzdQgqAAAAAElFTkSuQmCC",
policy: {
type: "totp",
algorithm: "HmacSHA1"
},
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
otpCredentials: []
},
messagesPerField: {},
stateChecker: "HiBl2ADzLwKwQS813LOEig1Ymm4xpEu_NacYtWJIuHU",
realm: {
userManagedAccessAllowed: true,
internationalizationEnabled: false
},
url: {
totpUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual"
},
keycloakifyVersion: "9.6.1",
themeVersion: "1.0.10",
themeType: "account",
themeName: "keycloakify",
pageId: "totp.ftl"
}}
/>
);
export const MoreThanOneTotpProviders = () => (
<PageStory
kcContext={{
totp: {
enabled: true,
totpSecretEncoded: "G55E MZKC JFUD MQLT MFIF EVSB JFLG M6SO",
totpSecret: "7zFeBIh6AsaPRVAIVfzN",
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACo0lEQVR4Xu2YPY6DMBCFJ6JwyRF8k+RiSCBxsXATjkDpAmX2vTGBwErbbDFTZIps4s8rmfl9RvRPW+W6crYvv66c7cuvK2cjX0Tkho9yW/q5PHSc5QYA62PwXnWqmzrRSUdNL+mygRC8kzQZWhqVO1CRds3YHopnfUkzp2c7ZAY+GIdXywOb0qsdJMXiFn9serYrncxNv/PDkdfUzObk/eNaX368mnl1kML8RH1vFoGzargA1DM/VeWhOpf9+by5iL5Q0NaEUETslHiSIz+dOc4q0tqBrcg7IsnpnZ8BeLmjqjFa4Fps4vlR3484nFHH6OP8o1cTc4I/Q3D4Uqw1TjpkeHqc2R/Rjvb89OUUDAL/CpycOf/o6fUjP505/phrOf8wn+tolsxyD8GZnzyrJSScrNyEcXhHJwrBh2yj2fShPlFB2PQxn935aK1HIB1G1nczm8+P+nbmC7si+zell53a4i97fnhz5Gddxc9iSgLPpPifGn9vDqN0YBL0lpozdx7nd+dDHSiFXkV+NlZO85Efzvzda8yrwkylvlEbhxE4bTJpiCEIkWNHbxD/w/++fJMOVX8p5Q70F0V2EI4LsUWd+ov6Wtgu5aM/OXNIf6jWbKq6zmekA77t88WZr5lXO6vvWaj6kbNo4nv/ceaon0TpYPqrmNJhue/x9+ZKLchbO+cLPrb+aI09BLeob1en2nqkKsUYfOvatSGa/ircmD7i78rNmJoYzXwIKh228z3+ztzef+Cb6S/lSxoWOXM2CO/ZuvlqARtLvX8u1Ie6+d+bd/X9pdS3lrrF/8jPCPytv9AVIbfvddxE4iNFLKL+hH/xCNudKgTvGX/r33ars/y062gQjljfWN8cyKm+f2NPOvqTL//Lvvy6crYvv66c7d/8B/9RFjk6Tp30AAAAAElFTkSuQmCC",
policy: {
type: "totp",
algorithm: "HmacSHA1"
},
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
otpCredentials: [
{
id: "7afaaf7d-f2d5-44f5-a966-e5297f0b2b7a",
userLabel: "Samsung S23"
},
{
id: "fbe22500-d979-45a3-9666-84c99e27958e",
userLabel: "Apple Iphone 15"
}
]
},
url: {
totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
},
messagesPerField: {},
stateChecker: "0UvyCNJHRJXmdahtRmn0tTPCU2nwLtWBUfPaaX1qb4g",
realm: {
userManagedAccessAllowed: true,
internationalizationEnabled: false
},
keycloakifyVersion: "9.6.1",
themeVersion: "1.0.10",
themeType: "account",
themeName: "keycloakify",
pageId: "totp.ftl"
}}
/>
);

View File

@ -0,0 +1,34 @@
import React from "react";
import { Meta } from "@storybook/react";
import { createPageStory } from "../createPageStory";
const pageId = "federatedIdentity.ftl";
const { PageStory } = createPageStory({ pageId });
const meta = {
title: "account/FederatedIdentity",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;
export const Default = () => <PageStory />;
export const NotConnected = () => (
<PageStory
kcContext={{
pageId: "federatedIdentity.ftl",
federatedIdentity: {
identities: [
{
providerId: "google",
displayName: "keycloak-oidc",
connected: false
}
],
removeLinkPossible: true
}
}}
/>
);

View File

@ -0,0 +1,354 @@
import React from "react";
import { Meta } from "@storybook/react";
import { createPageStory } from "../createPageStory";
const pageId = "log.ftl";
const { PageStory } = createPageStory({
pageId
});
const meta = {
title: "account/Log",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;
export const Default = () => (
<PageStory
kcContext={{
log: {
events: [
{
date: "2024-04-26T12:29:08Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T12:10:56Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T11:57:34Z",
ipAddress: "127.0.0.1",
client: "account",
details: [],
event: "update totp"
},
{
date: "2024-04-26T11:57:21Z",
ipAddress: "127.0.0.1",
client: "account",
details: [],
event: "update totp"
},
{
date: "2024-04-26T11:56:56Z",
ipAddress: "127.0.0.1",
client: "account",
details: [],
event: "remove totp"
},
{
date: "2024-04-26T11:56:55Z",
ipAddress: "127.0.0.1",
client: "account",
details: [],
event: "remove totp"
},
{
date: "2024-04-26T11:56:41Z",
ipAddress: "127.0.0.1",
client: "account",
details: [],
event: "update totp"
},
{
date: "2024-04-26T11:56:36Z",
ipAddress: "127.0.0.1",
client: "account",
details: [],
event: "update totp"
},
{
date: "2024-04-26T11:32:54Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T09:42:54Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T09:42:52Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T09:42:40Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T09:42:09Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "false",
key: "remember_me"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T09:24:17Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [],
event: "logout"
},
{
date: "2024-04-26T09:23:54Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T09:23:50Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T09:23:47Z",
ipAddress: "127.0.0.1",
client: "account",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T09:23:15Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [],
event: "logout"
},
{
date: "2024-04-26T09:23:06Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T09:22:53Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [],
event: "logout"
},
{
date: "2024-04-26T09:21:29Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "false",
key: "remember_me"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-26T09:17:32Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-18T11:19:09Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-18T11:18:50Z",
ipAddress: "127.0.0.1",
client: "keycloakify-frontend",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
},
{
date: "2024-04-18T11:18:24Z",
ipAddress: "127.0.0.1",
client: "account",
details: [
{
value: "openid-connect",
key: "auth_method"
},
{
value: "john.doe",
key: "username"
}
],
event: "login"
}
]
}
}}
/>
);

View File

@ -1,23 +1,15 @@
import React from "react";
import type { ComponentMeta } from "@storybook/react";
import { Meta } from "@storybook/react";
import { createPageStory } from "../createPageStory";
const pageId = "password.ftl";
const { PageStory } = createPageStory({ pageId });
const meta: ComponentMeta<any> = {
title: `account/${pageId}`,
component: PageStory,
parameters: {
viewMode: "story",
previewTabs: {
"storybook/docs/panel": {
hidden: true
}
}
}
};
const meta = {
title: "account/Password",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -0,0 +1,55 @@
import React from "react";
import { Meta } from "@storybook/react";
import { createPageStory } from "../createPageStory";
const pageId = "sessions.ftl";
const { PageStory } = createPageStory({ pageId });
const meta = {
title: "account/Sessions",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;
export const Default = () => (
<PageStory
kcContext={{
sessions: {
sessions: [
{
expires: "2024-04-26T18:14:19Z",
clients: ["account"],
ipAddress: "172.20.0.1",
started: "2024-04-26T08:14:19Z",
lastAccess: "2024-04-26T08:30:54Z",
id: "af835e30-4821-43b1-b4f7-e732d3cc15d2"
},
{
expires: "2024-04-26T18:14:09Z",
clients: ["security-admin-console", "account"],
ipAddress: "172.20.0.1",
started: "2024-04-26T08:14:09Z",
lastAccess: "2024-04-26T08:15:14Z",
id: "60a9d8b8-617d-441e-8643-08c3fe30e231"
}
]
},
stateChecker: "xQ7EOgFrLi4EvnJ8dbXKhwFGWk_bkOp0X89mhilt1os"
}}
/>
);
export const WithError = () => (
<PageStory
kcContext={{
url: { passwordUrl: "/auth/realms/keycloakify/account/password" },
stateChecker: "xQ7EOgFrLi4EvnJ8dbXKhwFGWk_bkOp0X89mhilt1os",
message: {
summary: "Invalid existing password.",
type: "error"
}
}}
/>
);

View File

@ -0,0 +1,105 @@
import React from "react";
import { Meta } from "@storybook/react";
import { createPageStory } from "../createPageStory";
const pageId = "totp.ftl";
const { PageStory } = createPageStory({ pageId });
const meta = {
title: "account/Authenticator",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;
export const Default = () => (
<PageStory
kcContext={{
totp: {
enabled: false,
totpSecretEncoded: "HB2W ESCK KJKF K5DC GJQX S5RQ I5AX CZ2U",
totpSecret: "8ubHJRTUtb2ayv0GAqgT",
manualUrl: "http://localhost:8080/realms/master/account/totp?mode=manual",
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACoUlEQVR4Xu2YPW7DMAxGGWTImCP4JvbFAtiAL+bcxEfI6MEI+z3Kzl+BLh2oIRwEV08FJIr8SMX8T1vtc+bdvvxz5t2+/HPm3eA3Mzvc+jm+fDoPzXIQkPV18N79ejvOWnRpTlcf/XTVV4Aq+MU0uzZaZI12rZVml4YzVcQHDTrEYufBKuQaZEfX1TvDWhEv98+ty79dGX7HRx438ofQfB04Th08jNS+us+n+1l/XbfZKrgcumj/trRnpfak0Dw54Xp/nC+Xy5bOB7x6dDxt1sq5j/tP52vkd5Ee+Xc1JfnKmergxKdcOyOSCgLik5xXEtXBtXVVvTFcC+pdV6+YqIVT+rpNf4hKjqMgXdo9frO5ldAM1dFJfA7+1O9srqiM4W6otuYQIZ0pvivg8mWUvo6q14VImuTocf/JXFq4cP8lPifld/jXidQqOL0CX0V66L9a6Y9Pu34nc7XW8qoQQ9GfcghVwiq4kStqDdl1hGZpZ3f/ZnN9qyCHkTrWq4nl/l/8n8tVmieL1lpFhiDw0uQ84jeXl4ahp+tay/1r6/ai3/kchxKQI6njyCX64zg8n2s4RZEhIDkJ5ZD9b/mTzZnVq2mmMFP/RjJpwNObf7M5qa0Lj9K837/kvKuEu1ov6p9EiEXkd8ei+57f+VwJPSCSfSnNWkR8PvefytFvK/1riPiIEkXM1sGl39qrWlcEm9K3OXnXn2wO4qcFhWZ02uFk2dO/uTwMJx9ostn6Q4mq4LzvDmoSqXqzE5+8BHiOVsJ7arH661ZFuixCGp/+z+ZsWF+ROuR3I9faS39YBS82F9d2rpWkUzWchqFFdWitS5C+9F+5vC/3T/9Fkl8Y+H3BN/9mc/KHRwrxGa9iePnHGvhf9uWfM+/25Z8z7/Zv/gPV7u6J7fyCcQAAAABJRU5ErkJggg==",
qrUrl: "http://localhost:8080/realms/master/account/totp?mode=qr",
otpCredentials: []
},
url: {
resourcesPath: "/resources/ueycc/account/keycloakify-starter",
resourceUrl: "http://localhost:8080/realms/master/account/resource",
resourcesCommonPath: "/resources/ueycc/account/keycloakify-starter/resources-common",
logUrl: "http://localhost:8080/realms/master/account/log",
socialUrl: "http://localhost:8080/realms/master/account/identity",
accountUrl: "http://localhost:8080/realms/master/account/",
sessionsUrl: "http://localhost:8080/realms/master/account/sessions",
totpUrl: "http://localhost:8080/realms/master/account/totp",
applicationsUrl: "http://localhost:8080/realms/master/account/applications",
passwordUrl: "http://localhost:8080/realms/master/account/password"
}
}}
/>
);
export const WithTotpEnabled = () => (
<PageStory
kcContext={{
totp: {
enabled: true,
totpSecretEncoded: "HB2W ESCK KJKF K5DC GJQX S5RQ I5AX CZ2U",
totpSecret: "8ubHJRTUtb2ayv0GAqgT",
manualUrl: "http://localhost:8080/realms/master/account/totp?mode=manual",
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACoUlEQVR4Xu2YPW7DMAxGGWTImCP4JvbFAtiAL+bcxEfI6MEI+z3Kzl+BLh2oIRwEV08FJIr8SMX8T1vtc+bdvvxz5t2+/HPm3eA3Mzvc+jm+fDoPzXIQkPV18N79ejvOWnRpTlcf/XTVV4Aq+MU0uzZaZI12rZVml4YzVcQHDTrEYufBKuQaZEfX1TvDWhEv98+ty79dGX7HRx438ofQfB04Th08jNS+us+n+1l/XbfZKrgcumj/trRnpfak0Dw54Xp/nC+Xy5bOB7x6dDxt1sq5j/tP52vkd5Ee+Xc1JfnKmergxKdcOyOSCgLik5xXEtXBtXVVvTFcC+pdV6+YqIVT+rpNf4hKjqMgXdo9frO5ldAM1dFJfA7+1O9srqiM4W6otuYQIZ0pvivg8mWUvo6q14VImuTocf/JXFq4cP8lPifld/jXidQqOL0CX0V66L9a6Y9Pu34nc7XW8qoQQ9GfcghVwiq4kStqDdl1hGZpZ3f/ZnN9qyCHkTrWq4nl/l/8n8tVmieL1lpFhiDw0uQ84jeXl4ahp+tay/1r6/ai3/kchxKQI6njyCX64zg8n2s4RZEhIDkJ5ZD9b/mTzZnVq2mmMFP/RjJpwNObf7M5qa0Lj9K837/kvKuEu1ov6p9EiEXkd8ei+57f+VwJPSCSfSnNWkR8PvefytFvK/1riPiIEkXM1sGl39qrWlcEm9K3OXnXn2wO4qcFhWZ02uFk2dO/uTwMJx9ostn6Q4mq4LzvDmoSqXqzE5+8BHiOVsJ7arH661ZFuixCGp/+z+ZsWF+ROuR3I9faS39YBS82F9d2rpWkUzWchqFFdWitS5C+9F+5vC/3T/9Fkl8Y+H3BN/9mc/KHRwrxGa9iePnHGvhf9uWfM+/25Z8z7/Zv/gPV7u6J7fyCcQAAAABJRU5ErkJggg==",
qrUrl: "http://localhost:8080/realms/master/account/totp?mode=qr",
otpCredentials: []
},
url: {
resourcesPath: "/resources/ueycc/account/keycloakify-starter",
resourceUrl: "http://localhost:8080/realms/master/account/resource",
resourcesCommonPath: "/resources/ueycc/account/keycloakify-starter/resources-common",
logUrl: "http://localhost:8080/realms/master/account/log",
socialUrl: "http://localhost:8080/realms/master/account/identity",
accountUrl: "http://localhost:8080/realms/master/account/",
sessionsUrl: "http://localhost:8080/realms/master/account/sessions",
totpUrl: "http://localhost:8080/realms/master/account/totp",
applicationsUrl: "http://localhost:8080/realms/master/account/applications",
passwordUrl: "http://localhost:8080/realms/master/account/password"
}
}}
/>
);
export const WithManualMode = () => (
<PageStory
kcContext={{
mode: "manual",
totp: {
enabled: false,
totpSecretEncoded: "HB2W ESCK KJKF K5DC GJQX S5RQ I5AX CZ2U",
totpSecret: "8ubHJRTUtb2ayv0GAqgT",
manualUrl: "http://localhost:8080/realms/master/account/totp?mode=manual",
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACoUlEQVR4Xu2YPW7DMAxGGWTImCP4JvbFAtiAL+bcxEfI6MEI+z3Kzl+BLh2oIRwEV08FJIr8SMX8T1vtc+bdvvxz5t2+/HPm3eA3Mzvc+jm+fDoPzXIQkPV18N79ejvOWnRpTlcf/XTVV4Aq+MU0uzZaZI12rZVml4YzVcQHDTrEYufBKuQaZEfX1TvDWhEv98+ty79dGX7HRx438ofQfB04Th08jNS+us+n+1l/XbfZKrgcumj/trRnpfak0Dw54Xp/nC+Xy5bOB7x6dDxt1sq5j/tP52vkd5Ee+Xc1JfnKmergxKdcOyOSCgLik5xXEtXBtXVVvTFcC+pdV6+YqIVT+rpNf4hKjqMgXdo9frO5ldAM1dFJfA7+1O9srqiM4W6otuYQIZ0pvivg8mWUvo6q14VImuTocf/JXFq4cP8lPifld/jXidQqOL0CX0V66L9a6Y9Pu34nc7XW8qoQQ9GfcghVwiq4kStqDdl1hGZpZ3f/ZnN9qyCHkTrWq4nl/l/8n8tVmieL1lpFhiDw0uQ84jeXl4ahp+tay/1r6/ai3/kchxKQI6njyCX64zg8n2s4RZEhIDkJ5ZD9b/mTzZnVq2mmMFP/RjJpwNObf7M5qa0Lj9K837/kvKuEu1ov6p9EiEXkd8ei+57f+VwJPSCSfSnNWkR8PvefytFvK/1riPiIEkXM1sGl39qrWlcEm9K3OXnXn2wO4qcFhWZ02uFk2dO/uTwMJx9ostn6Q4mq4LzvDmoSqXqzE5+8BHiOVsJ7arH661ZFuixCGp/+z+ZsWF+ROuR3I9faS39YBS82F9d2rpWkUzWchqFFdWitS5C+9F+5vC/3T/9Fkl8Y+H3BN/9mc/KHRwrxGa9iePnHGvhf9uWfM+/25Z8z7/Zv/gPV7u6J7fyCcQAAAABJRU5ErkJggg==",
qrUrl: "http://localhost:8080/realms/master/account/totp?mode=qr",
otpCredentials: []
},
url: {
resourcesPath: "/resources/ueycc/account/keycloakify-starter",
resourceUrl: "http://localhost:8080/realms/master/account/resource",
resourcesCommonPath: "/resources/ueycc/account/keycloakify-starter/resources-common",
logUrl: "http://localhost:8080/realms/master/account/log",
socialUrl: "http://localhost:8080/realms/master/account/identity",
accountUrl: "http://localhost:8080/realms/master/account/",
sessionsUrl: "http://localhost:8080/realms/master/account/sessions",
totpUrl: "http://localhost:8080/realms/master/account/totp",
applicationsUrl: "http://localhost:8080/realms/master/account/applications",
passwordUrl: "http://localhost:8080/realms/master/account/password"
}
}}
/>
);

112
yarn.lock
View File

@ -4744,30 +4744,29 @@ check-error@^1.0.2:
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==
cheerio-select@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4"
integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==
cheerio-select-tmp@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz#55bbef02a4771710195ad736d5e346763ca4e646"
integrity sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ==
dependencies:
boolbase "^1.0.0"
css-select "^5.1.0"
css-what "^6.1.0"
domelementtype "^2.3.0"
domhandler "^5.0.3"
domutils "^3.0.1"
css-select "^3.1.2"
css-what "^4.0.0"
domelementtype "^2.1.0"
domhandler "^4.0.0"
domutils "^2.4.4"
cheerio@^1.0.0-rc.5:
version "1.0.0-rc.12"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683"
integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==
cheerio@1.0.0-rc.5:
version "1.0.0-rc.5"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.5.tgz#88907e1828674e8f9fee375188b27dadd4f0fa2f"
integrity sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw==
dependencies:
cheerio-select "^2.1.0"
dom-serializer "^2.0.0"
domhandler "^5.0.3"
domutils "^3.0.1"
htmlparser2 "^8.0.1"
parse5 "^7.0.0"
parse5-htmlparser2-tree-adapter "^7.0.0"
cheerio-select-tmp "^0.1.0"
dom-serializer "~1.2.0"
domhandler "^4.0.0"
entities "~2.1.0"
htmlparser2 "^6.0.0"
parse5 "^6.0.0"
parse5-htmlparser2-tree-adapter "^6.0.0"
chokidar@^2.1.8:
version "2.1.8"
@ -5349,6 +5348,17 @@ css-loader@^5.0.1:
schema-utils "^3.0.0"
semver "^7.3.5"
css-select@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-3.1.2.tgz#d52cbdc6fee379fba97fb0d3925abbd18af2d9d8"
integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==
dependencies:
boolbase "^1.0.0"
css-what "^4.0.0"
domhandler "^4.0.0"
domutils "^2.4.3"
nth-check "^2.0.0"
css-select@^4.1.3:
version "4.3.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
@ -5360,18 +5370,12 @@ css-select@^4.1.3:
domutils "^2.8.0"
nth-check "^2.0.1"
css-select@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
dependencies:
boolbase "^1.0.0"
css-what "^6.1.0"
domhandler "^5.0.2"
domutils "^3.0.1"
nth-check "^2.0.1"
css-what@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233"
integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==
css-what@^6.0.1, css-what@^6.1.0:
css-what@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
@ -5656,6 +5660,15 @@ dom-serializer@^2.0.0:
domhandler "^5.0.2"
entities "^4.2.0"
dom-serializer@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1"
integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.0.0"
entities "^2.0.0"
dom-walk@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
@ -5666,7 +5679,7 @@ domain-browser@^1.1.1:
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
domelementtype@^2.0.1, domelementtype@^2.1.0, domelementtype@^2.2.0, domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
@ -5685,7 +5698,7 @@ domhandler@^5.0, domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3:
dependencies:
domelementtype "^2.3.0"
domutils@^2.5.2, domutils@^2.8.0:
domutils@^2.4.3, domutils@^2.4.4, domutils@^2.5.2, domutils@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
@ -5843,6 +5856,11 @@ entities@^4.2.0, entities@^4.4.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
entities@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
err-code@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
@ -7206,7 +7224,7 @@ html-webpack-plugin@^5.0.0:
pretty-error "^4.0.0"
tapable "^2.0.0"
htmlparser2@^6.1.0:
htmlparser2@^6.0.0, htmlparser2@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
@ -7216,7 +7234,7 @@ htmlparser2@^6.1.0:
domutils "^2.5.2"
entities "^2.0.0"
htmlparser2@^8.0, htmlparser2@^8.0.1:
htmlparser2@^8.0:
version "8.0.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
@ -9054,7 +9072,7 @@ npmlog@^5.0.1:
gauge "^3.0.0"
set-blocking "^2.0.0"
nth-check@^2.0.1:
nth-check@^2.0.0, nth-check@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
@ -9407,26 +9425,18 @@ parse-json@^5.0.0:
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
parse5-htmlparser2-tree-adapter@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1"
integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==
parse5-htmlparser2-tree-adapter@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==
dependencies:
domhandler "^5.0.2"
parse5 "^7.0.0"
parse5 "^6.0.1"
parse5@^6.0.0:
parse5@^6.0.0, parse5@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
parse5@^7.0.0:
version "7.1.2"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
dependencies:
entities "^4.4.0"
parseurl@~1.3.2, parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"