Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
bc44eadcec | |||
a3e3136600 | |||
c7bfcee8d2 | |||
12ebd19716 | |||
aec9ffa5db | |||
2e6321342e | |||
6e71da62f0 | |||
5bf33aae75 | |||
06b2dc63ff | |||
1bb0c9dfc2 | |||
79e25e69bb | |||
b95c12772d | |||
de47525d7c | |||
f49d20e47c | |||
33b9917229 | |||
319d7dbe94 | |||
feb8eaf95a | |||
e88be30fc8 | |||
22496e36eb |
@ -63,12 +63,12 @@ Their dedicated support helps us continue the development and maintenance of thi
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
<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/>
|
<br/>
|
||||||
<i>5% of your annual subscription will be donated to us, and you'll get 5% off too.</i>
|
<i>5% of your annual subscription will be donated to us, and you'll get 5% off too.</i>
|
||||||
</p>
|
</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 ✨
|
## Contributors ✨
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "9.5.6",
|
"version": "9.6.1",
|
||||||
"description": "Create Keycloak themes using React",
|
"description": "Create Keycloak themes using React",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -6,6 +6,10 @@ import { assert, type Equals } from "tsafe/assert";
|
|||||||
|
|
||||||
const Password = lazy(() => import("keycloakify/account/pages/Password"));
|
const Password = lazy(() => import("keycloakify/account/pages/Password"));
|
||||||
const Account = lazy(() => import("keycloakify/account/pages/Account"));
|
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>) {
|
export default function Fallback(props: PageProps<KcContext, I18n>) {
|
||||||
const { kcContext, ...rest } = props;
|
const { kcContext, ...rest } = props;
|
||||||
@ -16,8 +20,16 @@ export default function Fallback(props: PageProps<KcContext, I18n>) {
|
|||||||
switch (kcContext.pageId) {
|
switch (kcContext.pageId) {
|
||||||
case "password.ftl":
|
case "password.ftl":
|
||||||
return <Password kcContext={kcContext} {...rest} />;
|
return <Password kcContext={kcContext} {...rest} />;
|
||||||
|
case "sessions.ftl":
|
||||||
|
return <Sessions kcContext={kcContext} {...rest} />;
|
||||||
case "account.ftl":
|
case "account.ftl":
|
||||||
return <Account kcContext={kcContext} {...rest} />;
|
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} />;
|
||||||
}
|
}
|
||||||
assert<Equals<typeof kcContext, never>>(false);
|
assert<Equals<typeof kcContext, never>>(false);
|
||||||
})()}
|
})()}
|
||||||
|
@ -11,4 +11,17 @@ export type TemplateProps<KcContext extends KcContext.Common, I18nExtended exten
|
|||||||
children: ReactNode;
|
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";
|
||||||
|
@ -3,7 +3,7 @@ import { assert } from "tsafe/assert";
|
|||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
import { type ThemeType } from "keycloakify/bin/constants";
|
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;
|
||||||
|
|
||||||
export declare namespace KcContext {
|
export declare namespace KcContext {
|
||||||
export type Common = {
|
export type Common = {
|
||||||
@ -90,6 +90,16 @@ export declare namespace KcContext {
|
|||||||
lastName?: string;
|
lastName?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
};
|
};
|
||||||
|
properties: Record<string, string | undefined>;
|
||||||
|
sessions: {
|
||||||
|
sessions: {
|
||||||
|
ipAddress: string;
|
||||||
|
started?: any;
|
||||||
|
lastAccess?: any;
|
||||||
|
expires?: any;
|
||||||
|
clients: string[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Password = Common & {
|
export type Password = Common & {
|
||||||
@ -111,6 +121,144 @@ export declare namespace KcContext {
|
|||||||
};
|
};
|
||||||
stateChecker: string;
|
stateChecker: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Sessions = Common & {
|
||||||
|
pageId: "sessions.ftl";
|
||||||
|
sessions: {
|
||||||
|
sessions: {
|
||||||
|
ipAddress: string;
|
||||||
|
started?: any;
|
||||||
|
lastAccess?: any;
|
||||||
|
expires?: any;
|
||||||
|
clients: 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 }[];
|
||||||
|
resourceRolesAvailable: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
roleName: string;
|
||||||
|
roleDescription: string;
|
||||||
|
clientName: string;
|
||||||
|
clientId: string;
|
||||||
|
}[]
|
||||||
|
>;
|
||||||
|
additionalGrants: string[];
|
||||||
|
clientScopesGranted: string[];
|
||||||
|
effectiveUrl?: string;
|
||||||
|
client: {
|
||||||
|
consentScreenText: string;
|
||||||
|
surrogateAuthRequired: boolean;
|
||||||
|
bearerOnly: boolean;
|
||||||
|
id: string;
|
||||||
|
protocolMappersStream: Record<string, unknown>;
|
||||||
|
includeInTokenScope: boolean;
|
||||||
|
redirectUris: string[];
|
||||||
|
fullScopeAllowed: boolean;
|
||||||
|
registeredNodes: Record<string, unknown>;
|
||||||
|
enabled: boolean;
|
||||||
|
clientAuthenticatorType: string;
|
||||||
|
realmScopeMappingsStream: Record<string, unknown>;
|
||||||
|
scopeMappingsStream: Record<string, unknown>;
|
||||||
|
displayOnConsentScreen: boolean;
|
||||||
|
clientId: string;
|
||||||
|
rootUrl: string;
|
||||||
|
authenticationFlowBindingOverrides: Record<string, unknown>;
|
||||||
|
standardFlowEnabled: boolean;
|
||||||
|
attributes: Record<string, unknown>;
|
||||||
|
publicClient: boolean;
|
||||||
|
alwaysDisplayInConsole: boolean;
|
||||||
|
consentRequired: boolean;
|
||||||
|
notBefore: string;
|
||||||
|
rolesStream: Record<string, unknown>;
|
||||||
|
protocol: string;
|
||||||
|
dynamicScope: boolean;
|
||||||
|
directAccessGrantsEnabled: boolean;
|
||||||
|
name: string;
|
||||||
|
serviceAccountsEnabled: boolean;
|
||||||
|
frontchannelLogout: boolean;
|
||||||
|
nodeReRegistrationTimeout: string;
|
||||||
|
implicitFlowEnabled: boolean;
|
||||||
|
baseUrl: string;
|
||||||
|
webOrigins: string[];
|
||||||
|
realm: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Log = Common & {
|
||||||
|
pageId: "log.ftl";
|
||||||
|
log: {
|
||||||
|
events: {
|
||||||
|
date: string | number | Date;
|
||||||
|
event: string;
|
||||||
|
ipAddress: string;
|
||||||
|
client: any;
|
||||||
|
details: any[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -8,8 +8,9 @@ import { kcContextMocks, kcContextCommonMock } from "keycloakify/account/kcConte
|
|||||||
|
|
||||||
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
||||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
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?: {
|
function getKcContext<PageId extends ExtendKcContext<KcContextExtension>["pageId"] | undefined = undefined>(params?: {
|
||||||
mockPageId?: PageId;
|
mockPageId?: PageId;
|
||||||
@ -82,6 +83,13 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mockProperties !== undefined) {
|
||||||
|
deepAssign({
|
||||||
|
"target": kcContext.properties,
|
||||||
|
"source": mockProperties
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return { kcContext };
|
return { kcContext };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +145,28 @@ export const kcContextCommonMock: KcContext.Common = {
|
|||||||
"lastName": "doe",
|
"lastName": "doe",
|
||||||
"email": "john.doe@code.gouv.fr",
|
"email": "john.doe@code.gouv.fr",
|
||||||
"username": "doe_j"
|
"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"
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"sessions": [
|
||||||
|
{
|
||||||
|
"ipAddress": "127.0.0.1",
|
||||||
|
"started": new Date().toString(),
|
||||||
|
"lastAccess": new Date().toString(),
|
||||||
|
"expires": new Date().toString(),
|
||||||
|
"clients": ["Chrome", "Firefox"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -171,5 +193,62 @@ export const kcContextMocks: KcContext[] = [
|
|||||||
"editUsernameAllowed": true
|
"editUsernameAllowed": true
|
||||||
},
|
},
|
||||||
"stateChecker": ""
|
"stateChecker": ""
|
||||||
|
}),
|
||||||
|
id<KcContext.Sessions>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "sessions.ftl",
|
||||||
|
"sessions": {
|
||||||
|
"sessions": [
|
||||||
|
{
|
||||||
|
...kcContextCommonMock.sessions,
|
||||||
|
"ipAddress": "127.0.0.1",
|
||||||
|
"started": new Date().toString(),
|
||||||
|
"lastAccess": new Date().toString(),
|
||||||
|
"expires": new Date().toString(),
|
||||||
|
"clients": ["Chrome", "Firefox"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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": ["auth_method = openid-connect, username = admin"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
@ -6,8 +6,15 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
|
|||||||
"kcHtmlClass": undefined,
|
"kcHtmlClass": undefined,
|
||||||
"kcBodyClass": undefined,
|
"kcBodyClass": undefined,
|
||||||
"kcButtonClass": "btn",
|
"kcButtonClass": "btn",
|
||||||
|
"kcContentWrapperClass": "row",
|
||||||
"kcButtonPrimaryClass": "btn-primary",
|
"kcButtonPrimaryClass": "btn-primary",
|
||||||
"kcButtonLargeClass": "btn-lg",
|
"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"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
138
src/account/pages/Applications.tsx
Normal file
138
src/account/pages/Applications.tsx
Normal 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 => (
|
||||||
|
<span key={role.name}>
|
||||||
|
{role.description ? advancedMsg(role.description) : advancedMsg(role.name)}
|
||||||
|
{role !== application.realmRolesAvailable[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>
|
||||||
|
);
|
||||||
|
}
|
70
src/account/pages/Log.tsx
Normal file
70
src/account/pages/Log.tsx
Normal 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 "../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>
|
||||||
|
);
|
||||||
|
}
|
68
src/account/pages/Sessions.tsx
Normal file
68
src/account/pages/Sessions.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log({ kcContext });
|
||||||
|
const { url, stateChecker, sessions } = kcContext;
|
||||||
|
|
||||||
|
const { msg } = i18n;
|
||||||
|
console.log({ sdf: kcContext.locale?.supported });
|
||||||
|
console.log({ asdf: "asdf" });
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
236
src/account/pages/Totp.tsx
Normal file
236
src/account/pages/Totp.tsx
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
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>
|
||||||
|
<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>
|
||||||
|
</p>
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
@ -27,7 +27,7 @@ export const loginThemePageIds = [
|
|||||||
"saml-post-form.ftl"
|
"saml-post-form.ftl"
|
||||||
] as const;
|
] 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"] as const;
|
||||||
|
|
||||||
export type LoginThemePageId = (typeof loginThemePageIds)[number];
|
export type LoginThemePageId = (typeof loginThemePageIds)[number];
|
||||||
export type AccountThemePageId = (typeof accountThemePageIds)[number];
|
export type AccountThemePageId = (typeof accountThemePageIds)[number];
|
||||||
|
@ -49,13 +49,19 @@ export async function main() {
|
|||||||
|
|
||||||
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8"));
|
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8"));
|
||||||
|
|
||||||
child_process.execSync("npx vite", {
|
run_post_build_script: {
|
||||||
"cwd": buildOptions.reactAppRootDirPath,
|
if (buildOptions.bundler !== "vite") {
|
||||||
"env": {
|
break run_post_build_script;
|
||||||
...process.env,
|
|
||||||
[keycloakifyBuildOptionsForPostPostBuildScriptEnvName]: JSON.stringify(buildOptions)
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
child_process.execSync("npx vite", {
|
||||||
|
"cwd": buildOptions.reactAppRootDirPath,
|
||||||
|
"env": {
|
||||||
|
...process.env,
|
||||||
|
[keycloakifyBuildOptionsForPostPostBuildScriptEnvName]: JSON.stringify(buildOptions)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
create_jar: {
|
create_jar: {
|
||||||
if (!buildOptions.doCreateJar) {
|
if (!buildOptions.doCreateJar) {
|
||||||
|
@ -116,6 +116,7 @@ export declare namespace KcContext {
|
|||||||
*/
|
*/
|
||||||
exists: (fieldName: string) => boolean;
|
exists: (fieldName: string) => boolean;
|
||||||
};
|
};
|
||||||
|
properties: Record<string, string | undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SamlPostForm = Common & {
|
export type SamlPostForm = Common & {
|
||||||
|
@ -12,8 +12,9 @@ import { symToStr } from "tsafe/symToStr";
|
|||||||
|
|
||||||
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
||||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
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?: {
|
function getKcContext<PageId extends ExtendKcContext<KcContextExtension>["pageId"] | undefined = undefined>(params?: {
|
||||||
mockPageId?: PageId;
|
mockPageId?: PageId;
|
||||||
@ -141,6 +142,13 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mockProperties !== undefined) {
|
||||||
|
deepAssign({
|
||||||
|
"target": kcContext.properties,
|
||||||
|
"source": mockProperties
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return { kcContext };
|
return { kcContext };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +236,127 @@ export const kcContextCommonMock: KcContext.Common = {
|
|||||||
"attributes": {}
|
"attributes": {}
|
||||||
},
|
},
|
||||||
"scripts": [],
|
"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 = {
|
const loginUrl = {
|
||||||
|
26
stories/account/pages/Sessions.stories.tsx
Normal file
26
stories/account/pages/Sessions.stories.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from "react";
|
||||||
|
import type { ComponentMeta } from "@storybook/react";
|
||||||
|
import { createPageStory } from "../createPageStory";
|
||||||
|
|
||||||
|
const pageId = "sessions.ftl";
|
||||||
|
|
||||||
|
const { PageStory } = createPageStory({ pageId });
|
||||||
|
|
||||||
|
const meta: ComponentMeta<any> = {
|
||||||
|
title: `account/${pageId}`,
|
||||||
|
component: PageStory,
|
||||||
|
parameters: {
|
||||||
|
viewMode: "story",
|
||||||
|
previewTabs: {
|
||||||
|
"storybook/docs/panel": {
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
export const Default = () => <PageStory />;
|
||||||
|
|
||||||
|
export const WithMessage = () => <PageStory kcContext={{}} />;
|
113
stories/account/pages/Totp.stories.tsx
Normal file
113
stories/account/pages/Totp.stories.tsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import React from "react";
|
||||||
|
import type { ComponentMeta } from "@storybook/react";
|
||||||
|
import { createPageStory } from "../createPageStory";
|
||||||
|
|
||||||
|
const pageId = "totp.ftl";
|
||||||
|
|
||||||
|
const { PageStory } = createPageStory({ pageId });
|
||||||
|
|
||||||
|
const meta: ComponentMeta<any> = {
|
||||||
|
title: `account/${pageId}`,
|
||||||
|
component: PageStory,
|
||||||
|
parameters: {
|
||||||
|
viewMode: "story",
|
||||||
|
previewTabs: {
|
||||||
|
"storybook/docs/panel": {
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
Reference in New Issue
Block a user