Disable storybook, for now

This commit is contained in:
garronej 2023-03-18 18:16:41 +01:00
parent 9d704eaaa7
commit 39ae948f24
40 changed files with 14 additions and 1885 deletions

View file

@ -130,18 +130,18 @@ jobs:
- uses: actions/setup-node@v3.6.0
- run: npx -y -p gh-pages@3.0.0 gh-pages -u "github-actions-bot <actions@github.com>" -d build
github_pages_storybook:
runs-on: ubuntu-latest
needs:
- create_github_release
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3.6.0
- uses: bahmutov/npm-install@v1
- run: yarn build-keycloak-theme # Only for the assets in public
- run: yarn build-storybook
- run: git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/${{github.repository}}.git
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: npx -y -p gh-pages@3.0.0 gh-pages -u "github-actions-bot <actions@github.com>" -d storybook-static --dest storybook --add
# github_pages_storybook:
# runs-on: ubuntu-latest
# needs:
# - create_github_release
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v3.6.0
# - uses: bahmutov/npm-install@v1
# - run: yarn build-keycloak-theme # Only for the assets in public
# - run: yarn build-storybook
# - run: git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/${{github.repository}}.git
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# - run: npx -y -p gh-pages@3.0.0 gh-pages -u "github-actions-bot <actions@github.com>" -d storybook-static --dest storybook --add

View file

@ -1,32 +0,0 @@
import type {ComponentMeta} from '@storybook/react';
import KcApp from './KcApp';
import {template} from '../../.storybook/util'
const bind = template('my-extra-page-1.ftl');
export default {
title: 'Theme/Template',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})
export const InFrench = bind({locale: {currentLanguageTag: 'fr'}})
export const RealmDisplayNameIsHtml = bind({
realm: {
displayNameHtml: '<marquee>my realm</marquee>'
}
})
export const NoInternationalization = bind({
realm: {
internationalizationEnabled: false,
}
})
export const WithGlobalError = bind({
message: {type: "error", summary: "This is an error"}
})

View file

@ -1,16 +0,0 @@
import {ComponentMeta} from '@storybook/react';
import KcApp from '../KcApp';
import {template} from '../../../.storybook/util'
const bind = template('error.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Notification/Error',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({message: {type: 'error', summary: 'Something went wrong'}})

View file

@ -1,34 +0,0 @@
// copied and adapted from https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/pages/Error.tsx
import React from "react";
import type { PageProps } from "keycloakify"
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { message, client } = kcContext;
const { msg } = i18n;
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
displayMessage={false}
headerNode={msg("errorTitle")}
formNode={
<div id="kc-error-message">
<p className="instruction">{message.summary}</p>
{client !== undefined && client.baseUrl !== undefined && (
<p>
<a id="backToApplication" href={client.baseUrl}>
{msg("backToApplication")}
</a>
</p>
)}
</div>
}
/>
);
}

View file

@ -1,17 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('idp-review-user-profile.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/IDP/Review User Profile',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,47 +0,0 @@
import React, { useState } from "react";
import { clsx } from "keycloakify/lib/tools/clsx";
import { UserProfileFormFields } from "keycloakify/lib/pages/shared/UserProfileCommons";
import type { PageProps } from "keycloakify";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function IdpReviewUserProfile(props: PageProps<Extract<KcContext, { pageId: "idp-review-user-profile.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { msg, msgStr } = i18n;
const { url } = kcContext;
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("loginIdpReviewProfileTitle")}
formNode={
<form id="kc-idp-review-profile-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)} />
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
value={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/>
</div>
</div>
</form>
}
/>
);
}

View file

@ -1,38 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('info.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Notification/Info',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({
messageHeader: 'Yo, get this:',
message: {
summary: 'You look good today'
}
})
export const WithLinkBack = bind({
messageHeader: 'Yo, get this:',
message: {
summary: 'You look good today'
},
actionUri: undefined
})
export const WithRequiredActions = bind({
messageHeader: 'Yo, get this:',
message: {
summary: 'Before you can carry on, you need to do this: '
},
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PROFILE", "VERIFY_EMAIL"]
})

View file

@ -1,49 +0,0 @@
import React from "react";
import {assert} from "keycloakify/lib/tools/assert";
import type {PageProps} from "keycloakify";
import type {KcContext} from "../kcContext";
import type {I18n} from "../i18n";
export default function Info(props: PageProps<Extract<KcContext, { pageId: "info.ftl" }>, I18n>) {
const {kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps} = props;
const {msgStr, msg} = i18n;
assert(kcContext.message !== undefined);
const {messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client} = kcContext;
return (
<Template
{...{kcContext, i18n, doFetchDefaultThemeResources, ...kcProps}}
displayMessage={false}
headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
formNode={
<div id="kc-info-message">
<p className="instruction">
{message.summary}
{requiredActions !== undefined && (
<b>{requiredActions.map(requiredAction => msgStr(`requiredAction.${requiredAction}` as const)).join(",")}</b>
)}
</p>
{!skipLink && pageRedirectUri !== undefined ? (
<p>
<a href={pageRedirectUri}>{msg("backToApplication")}</a>
</p>
) : actionUri !== undefined ? (
<p>
<a href={actionUri}>{msg("proceedWithAction")}</a>
</p>
) : (
client.baseUrl !== undefined && (
<p>
<a href={client.baseUrl}>{msg("backToApplication")}</a>
</p>
)
)}
</div>
}
/>
);
}

View file

@ -1,27 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { socialProviders, template } from '../../../.storybook/util'
const bind = template('login.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Login/Login',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})
export const WithoutPasswordField = bind({ realm: { password: false } })
export const WithoutRegistration = bind({ realm: { registrationAllowed: false } })
export const WithoutRememberMe = bind({ realm: { rememberMe: false } })
export const WithoutPasswordReset = bind({ realm: { resetPasswordAllowed: false } })
export const WithEmailAsUsername = bind({ realm: { loginWithEmailAllowed: false } })
export const WithPresetUsername = bind({ login: { username: 'max.mustermann@mail.com' } })
export const WithImmutablePresetUsername = bind({
login: { username: 'max.mustermann@mail.com' },
usernameEditDisabled: true
})
export const WithSocialProviders = bind({ social: { displayInfo: true, providers: socialProviders } })

View file

@ -1,27 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('login-config-totp.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Actions/Configure TOTP',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})
export const WithManualSetUp = bind({ mode: 'manual' })
export const WithError = bind({
messagesPerField: {
get: (fieldName: string) => fieldName === 'totp' ? 'Invalid TOTP' : undefined,
exists: (fieldName: string) => fieldName === 'totp',
existsError: (fieldName: string) => fieldName === 'totp',
printIfExists: <T,>(fieldName: string, x: T) => fieldName === 'totp' ? x : undefined
}
})

View file

@ -1,186 +0,0 @@
import React from "react";
import {clsx} from "keycloakify/lib/tools/clsx";
import type {PageProps, KcContextBase} from "keycloakify";
import type {KcContext} from "../kcContext";
import type {I18n} from "../i18n";
export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pageId: "login-config-totp.ftl" }>, I18n>) {
const {kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps} = props;
const {url, isAppInitiatedAction, totp, mode, messagesPerField} = kcContext;
const {msg, msgStr} = i18n;
const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = {
"HmacSHA1": "SHA1",
"HmacSHA256": "SHA256",
"HmacSHA512": "SHA512"
};
return (
<Template
{...{kcContext, i18n, doFetchDefaultThemeResources, ...kcProps}}
headerNode={msg("loginTotpTitle")}
formNode={
<>
<ol id="kc-totp-settings">
<li>
<p>{msg("loginTotpStep1")}</p>
<ul id="kc-totp-supported-apps">
{totp.policy.supportedApplications.map(app => (
<li>{app}</li>
))}
</ul>
</li>
{mode && mode == "manual" ? (
<>
<li>
<p>{msg("loginTotpManualStep2")}</p>
<p>
<span id="kc-totp-secret-key">{totp.totpSecretEncoded}</span>
</p>
<p>
<a href={totp.qrUrl} id="mode-barcode">
{msg("loginTotpScanBarcode")}
</a>
</p>
</li>
<li>
<p>{msg("loginTotpManualStep3")}</p>
<p>
<ul>
<li id="kc-totp-type">
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
</li>
<li id="kc-totp-algorithm">
{msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
</li>
<li id="kc-totp-digits">
{msg("loginTotpDigits")}: {totp.policy.digits}
</li>
{totp.policy.type === "totp" ? (
<li id="kc-totp-period">
{msg("loginTotpInterval")}: {totp.policy.period}
</li>
) : (
<li id="kc-totp-counter">
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
</li>
)}
</ul>
</p>
</li>
</>
) : (
<li>
<p>{msg("loginTotpStep2")}</p>
<img id="kc-totp-secret-qr-code" src={`data:image/png;base64, ${totp.totpSecretQrCode}`}
alt="Figure: Barcode"/>
<br/>
<p>
<a href={totp.manualUrl} id="mode-manual">
{msg("loginTotpUnableToScan")}
</a>
</p>
</li>
)}
<li>
<p>{msg("loginTotpStep3")}</p>
<p>{msg("loginTotpStep3DeviceName")}</p>
</li>
</ol>
<form action={url.loginAction} className={clsx(kcProps.kcFormClass)} id="kc-totp-settings-form"
method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<label htmlFor="totp" className={clsx(kcProps.kcLabelClass)}>
{msg("authenticatorCode")}
</label>{" "}
<span className="required">*</span>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<input
type="text"
id="totp"
name="totp"
autoComplete="off"
className={clsx(kcProps.kcInputClass)}
aria-invalid={messagesPerField.existsError("totp")}
/>
{messagesPerField.existsError("totp") && (
<span id="input-error-otp-code" className={clsx(kcProps.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={clsx(kcProps.kcFormGroupClass)}>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<label htmlFor="userLabel" className={clsx(kcProps.kcLabelClass)}>
{msg("loginTotpDeviceName")}
</label>{" "}
{totp.otpCredentials.length >= 1 && <span className="required">*</span>}
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<input
type="text"
id="userLabel"
name="userLabel"
autoComplete="off"
className={clsx(kcProps.kcInputClass)}
aria-invalid={messagesPerField.existsError("userLabel")}
/>
{messagesPerField.existsError("userLabel") && (
<span id="input-error-otp-label" className={clsx(kcProps.kcInputErrorMessageClass)}
aria-live="polite">
{messagesPerField.get("userLabel")}
</span>
)}
</div>
</div>
{isAppInitiatedAction ? (
<>
<input
type="submit"
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>
<button
type="submit"
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonDefaultClass,
kcProps.kcButtonLargeClass,
kcProps.kcButtonLargeClass
)}
id="cancelTOTPBtn"
name="cancel-aia"
value="true"
>
${msg("doCancel")}
</button>
</>
) : (
<input
type="submit"
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>
)}
</form>
</>
}
/>
);
}

View file

@ -1,17 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('login-idp-link-confirm.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/IDP/Confirm Link',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,54 +0,0 @@
import React from "react";
import {clsx} from "keycloakify/lib/tools/clsx";
import type {PageProps} from "keycloakify";
import type {KcContext} from "../kcContext";
import type {I18n} from "../i18n";
export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-confirm.ftl" }>, I18n>) {
const {kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps} = props;
const {url, idpAlias} = kcContext;
const {msg} = i18n;
return (
<Template
{...{kcContext, i18n, doFetchDefaultThemeResources, ...kcProps}}
headerNode={msg("confirmLinkIdpTitle")}
formNode={
<form id="kc-register-form" action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<button
type="submit"
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonDefaultClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="submitAction"
id="updateProfile"
value="updateProfile"
>
{msg("confirmLinkIdpReviewProfile")}
</button>
<button
type="submit"
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonDefaultClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="submitAction"
id="linkAccount"
value="linkAccount"
>
{msg("confirmLinkIdpContinue", idpAlias)}
</button>
</div>
</form>
}
/>
);
}

View file

@ -1,17 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('login-idp-link-email.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/IDP/Confirm Email',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,32 +0,0 @@
import React from "react";
import type {PageProps} from "keycloakify";
import type {KcContext} from "../kcContext";
import type {I18n} from "../i18n";
export default function LoginIdpLinkEmail(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-email.ftl" }>, I18n>) {
const {kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps} = props;
const {url, realm, brokerContext, idpAlias} = kcContext;
const {msg} = i18n;
return (
<Template
{...{kcContext, i18n, doFetchDefaultThemeResources, ...kcProps}}
headerNode={msg("emailLinkIdpTitle", idpAlias)}
formNode={
<>
<p id="instruction1" className="instruction">
{msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)}
</p>
<p id="instruction2" className="instruction">
{msg("emailLinkIdp2")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp3")}
</p>
<p id="instruction3" className="instruction">
{msg("emailLinkIdp4")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp5")}
</p>
</>
}
/>
);
}

View file

@ -1,16 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('login-otp.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Login/OTP',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,114 +0,0 @@
import React, { useEffect } from "react";
import { headInsert } from "keycloakify/lib/tools/headInsert";
import { pathJoin } from "keycloakify/bin/tools/pathJoin";
import { clsx } from "keycloakify/lib/tools/clsx";
import type { PageProps } from "keycloakify";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "login-otp.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { otpLogin, url } = kcContext;
const { msg, msgStr } = i18n;
useEffect(() => {
let isCleanedUp = false;
headInsert({
"type": "javascript",
"src": pathJoin(kcContext.url.resourcesCommonPath, "node_modules/jquery/dist/jquery.min.js")
}).then(() => {
if (isCleanedUp) return;
evaluateInlineScript();
});
return () => {
isCleanedUp = true;
};
}, []);
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("doLogIn")}
formNode={
<form id="kc-otp-login-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
{otpLogin.userOtpCredentials.length > 1 && (
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={clsx(kcProps.kcInputWrapperClass)}>
{otpLogin.userOtpCredentials.map(otpCredential => (
<div key={otpCredential.id} className={clsx(kcProps.kcSelectOTPListClass)}>
<input type="hidden" value="${otpCredential.id}" />
<div className={clsx(kcProps.kcSelectOTPListItemClass)}>
<span className={clsx(kcProps.kcAuthenticatorOtpCircleClass)} />
<h2 className={clsx(kcProps.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2>
</div>
</div>
))}
</div>
</div>
)}
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="otp" className={clsx(kcProps.kcLabelClass)}>
{msg("loginOtpOneTime")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<input id="otp" name="otp" autoComplete="off" type="text" className={clsx(kcProps.kcInputClass)} autoFocus />
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)} />
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
/>
</div>
</div>
</form>
}
/>
);
}
declare const $: any;
function evaluateInlineScript() {
$(document).ready(function () {
// Card Single Select
$(".card-pf-view-single-select").click(function (this: any) {
if ($(this).hasClass("active")) {
$(this).removeClass("active");
$(this).children().removeAttr("name");
} else {
$(".card-pf-view-single-select").removeClass("active");
$(".card-pf-view-single-select").children().removeAttr("name");
$(this).addClass("active");
$(this).children().attr("name", "selectedCredentialId");
}
});
var defaultCred = $(".card-pf-view-single-select")[0];
if (defaultCred) {
defaultCred.click();
}
});
}

View file

@ -1,16 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('login-page-expired.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Login/Login Page Expired',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,36 +0,0 @@
import React from "react";
import type { PageProps } from "keycloakify";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function LoginPageExpired(props: PageProps<Extract<KcContext, { pageId: "login-page-expired.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { url } = kcContext;
const { msg } = i18n;
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
displayMessage={false}
headerNode={msg("pageExpiredTitle")}
formNode={
<>
<p id="instruction1" className="instruction">
{msg("pageExpiredMsg1")}
<a id="loginRestartLink" href={url.loginRestartFlowUrl}>
{msg("doClickHere")}
</a>{" "}
.<br />
{msg("pageExpiredMsg2")}{" "}
<a id="loginContinueLink" href={url.loginAction}>
{msg("doClickHere")}
</a>{" "}
.
</p>
</>
}
/>
);
}

View file

@ -1,16 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('login-password.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Login/Password',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,86 +0,0 @@
import React, { useState } from "react";
import { clsx } from "keycloakify/lib/tools/clsx";
import { useConstCallback } from "keycloakify/lib/tools/useConstCallback";
import type { FormEventHandler } from "react";
import type { PageProps } from "keycloakify";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function LoginPassword(props: PageProps<Extract<KcContext, { "pageId": "login-password.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { realm, url, login } = kcContext;
const { msg, msgStr } = i18n;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
e.preventDefault();
setIsLoginButtonDisabled(true);
const formElement = e.target as HTMLFormElement;
formElement.submit();
});
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("doLogIn")}
formNode={
<div id="kc-form">
<div id="kc-form-wrapper">
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<hr />
<label htmlFor="password" className={clsx(kcProps.kcLabelClass)}>
{msg("password")}
</label>
<input
tabIndex={2}
id="password"
className={clsx(kcProps.kcInputClass)}
name="password"
type="password"
autoFocus={true}
autoComplete="on"
defaultValue={login.password ?? ""}
/>
</div>
<div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options" />
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
{realm.resetPasswordAllowed && (
<span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
{msg("doForgotPassword")}
</a>
</span>
)}
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<input
tabIndex={4}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
disabled={isLoginButtonDisabled}
/>
</div>
</form>
</div>
</div>
}
/>
);
}

View file

@ -1,17 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('login-reset-password.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Login/Reset Password',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})
export const WithEmailAsUsername = bind({ realm: { loginWithEmailAllowed: true, registrationEmailAsUsername: true } })

View file

@ -1,69 +0,0 @@
import React from "react";
import { clsx } from "keycloakify/lib/tools/clsx";
import type { PageProps } from "keycloakify/lib/KcProps";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function LoginResetPassword(props: PageProps<Extract<KcContext, { pageId: "login-reset-password.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { url, realm, auth } = kcContext;
const { msg, msgStr } = i18n;
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
displayMessage={false}
headerNode={msg("emailForgotTitle")}
formNode={
<form id="kc-reset-password-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="username" className={clsx(kcProps.kcLabelClass)}>
{!realm.loginWithEmailAllowed
? msg("username")
: !realm.registrationEmailAsUsername
? msg("usernameOrEmail")
: msg("email")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<input
type="text"
id="username"
name="username"
className={clsx(kcProps.kcInputClass)}
autoFocus
defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined}
/>
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
value={msgStr("doSubmit")}
/>
</div>
</div>
</form>
}
infoNode={msg("emailInstruction")}
/>
);
}

View file

@ -1,16 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('login-update-password.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Actions/Update Password',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,117 +0,0 @@
import React from "react";
import { clsx } from "keycloakify/lib/tools/clsx";
import type { PageProps } from "keycloakify/lib/KcProps";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function LoginUpdatePassword(props: PageProps<Extract<KcContext, { pageId: "login-update-password.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { msg, msgStr } = i18n;
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("updatePasswordTitle")}
formNode={
<form id="kc-passwd-update-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<input
type="text"
id="username"
name="username"
value={username}
readOnly={true}
autoComplete="username"
style={{ display: "none" }}
/>
<input type="password" id="password" name="password" autoComplete="current-password" style={{ display: "none" }} />
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password", kcProps.kcFormGroupErrorClass))}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password-new" className={clsx(kcProps.kcLabelClass)}>
{msg("passwordNew")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<input
type="password"
id="password-new"
name="password-new"
autoFocus
autoComplete="new-password"
className={clsx(kcProps.kcInputClass)}
/>
</div>
</div>
<div
className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", kcProps.kcFormGroupErrorClass))}
>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={clsx(kcProps.kcLabelClass)}>
{msg("passwordConfirm")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<input
type="password"
id="password-confirm"
name="password-confirm"
autoComplete="new-password"
className={clsx(kcProps.kcInputClass)}
/>
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
{isAppInitiatedAction && (
<div className="checkbox">
<label>
<input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked />
{msgStr("logoutOtherSessions")}
</label>
</div>
)}
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
{isAppInitiatedAction ? (
<>
<input
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
<button
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
type="submit"
name="cancel-aia"
value="true"
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
)}
</div>
</div>
</form>
}
/>
);
}

View file

@ -1,17 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('login-username.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Login/Username',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})
export const WithEmailAsUsername = bind({ realm: { loginWithEmailAllowed: true, registrationEmailAsUsername: true } })

View file

@ -1,158 +0,0 @@
import React, { useState } from "react";
import { clsx } from "keycloakify/lib/tools/clsx";
import { useConstCallback } from "keycloakify/lib/tools/useConstCallback";
import type { FormEventHandler } from "react";
import type { PageProps } from "keycloakify";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function LoginUsername(props: PageProps<Extract<KcContext, { pageId: "login-username.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { social, realm, url, usernameHidden, login, registrationDisabled } = kcContext;
const { msg, msgStr } = i18n;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
e.preventDefault();
setIsLoginButtonDisabled(true);
const formElement = e.target as HTMLFormElement;
//NOTE: Even if we login with email Keycloak expect username and password in
//the POST request.
formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
formElement.submit();
});
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
displayInfo={social.displayInfo}
displayWide={realm.password && social.providers !== undefined}
headerNode={msg("doLogIn")}
formNode={
<div id="kc-form" className={clsx(realm.password && social.providers !== undefined && kcProps.kcContentWrapperClass)}>
<div
id="kc-form-wrapper"
className={clsx(
realm.password && social.providers && [kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass]
)}
>
{realm.password && (
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
{!usernameHidden &&
(() => {
const label = !realm.loginWithEmailAllowed
? "username"
: realm.registrationEmailAsUsername
? "email"
: "usernameOrEmail";
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
return (
<>
<label htmlFor={autoCompleteHelper} className={clsx(kcProps.kcLabelClass)}>
{msg(label)}
</label>
<input
tabIndex={1}
id={autoCompleteHelper}
className={clsx(kcProps.kcInputClass)}
// NOTE: This is used by Google Chrome auto fill so we use it to tell
// the browser how to pre fill the form but before submit we put it back
// to username because it is what keycloak expects.
name={autoCompleteHelper}
defaultValue={login.username ?? ""}
type="text"
autoFocus={true}
autoComplete="off"
/>
</>
);
})()}
</div>
<div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options">
{realm.rememberMe && !usernameHidden && (
<div className="checkbox">
<label>
<input
tabIndex={3}
id="rememberMe"
name="rememberMe"
type="checkbox"
{...(login.rememberMe
? {
"checked": true
}
: {})}
/>
{msg("rememberMe")}
</label>
</div>
)}
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<input
tabIndex={4}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
disabled={isLoginButtonDisabled}
/>
</div>
</form>
)}
</div>
{realm.password && social.providers !== undefined && (
<div id="kc-social-providers" className={clsx(kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass)}>
<ul
className={clsx(
kcProps.kcFormSocialAccountListClass,
social.providers.length > 4 && kcProps.kcFormSocialAccountDoubleListClass
)}
>
{social.providers.map(p => (
<li key={p.providerId} className={clsx(kcProps.kcFormSocialAccountListLinkClass)}>
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={clsx("zocial", p.providerId)}>
<span>{p.displayName}</span>
</a>
</li>
))}
</ul>
</div>
)}
</div>
}
infoNode={
realm.password &&
realm.registrationAllowed &&
!registrationDisabled && (
<div id="kc-registration">
<span>
{msg("noAccount")}
<a tabIndex={6} href={url.registrationUrl}>
{msg("doRegister")}
</a>
</span>
</div>
)
}
/>
);
}

View file

@ -1,16 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('login-verify-email.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Login/Verify Email',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,32 +0,0 @@
import React from "react";
import type { PageProps } from "keycloakify";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function LoginVerifyEmail(props: PageProps<Extract<KcContext, { pageId: "login-verify-email.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { msg } = i18n;
const { url, user } = kcContext;
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
displayMessage={false}
headerNode={msg("emailVerifyTitle")}
formNode={
<>
<p className="instruction">{msg("emailVerifyInstruction1", user?.email)}</p>
<p className="instruction">
{msg("emailVerifyInstruction2")}
<br />
<a href={url.loginAction}>{msg("doClickHere")}</a>
&nbsp;
{msg("emailVerifyInstruction3")}
</p>
</>
}
/>
);
}

View file

@ -1,16 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('logout-confirm.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Login/Logout Confirmation',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,58 +0,0 @@
import React from "react";
import { clsx } from "keycloakify/lib/tools/clsx";
import type { PageProps } from "keycloakify/lib/KcProps";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function LogoutConfirm(props: PageProps<Extract<KcContext, { pageId: "logout-confirm.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { url, client, logoutConfirm } = kcContext;
const { msg, msgStr } = i18n;
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
displayMessage={false}
headerNode={msg("logoutConfirmTitle")}
formNode={
<>
<div id="kc-logout-confirm" className="content-area">
<p className="instruction">{msg("logoutConfirmHeader")}</p>
<form className="form-actions" action={url.logoutConfirmAction} method="POST">
<input type="hidden" name="session_code" value={logoutConfirm.code} />
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options">
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}></div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<input
tabIndex={4}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="confirmLogout"
id="kc-logout"
type="submit"
value={msgStr("doLogout")}
/>
</div>
</div>
</form>
<div id="kc-info-message">
{!logoutConfirm.skipLink && client.baseUrl && (
<p>
<a href={client.baseUrl} dangerouslySetInnerHTML={{ __html: msgStr("backToApplication") }} />
</p>
)}
</div>
</div>
</>
}
/>
);
}

View file

@ -1,16 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('my-extra-page-1.ftl')
export default {
kind: 'Page',
title: 'Theme/Pages/Custom/My Extra Page 1',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,18 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('my-extra-page-2.ftl')
export default {
kind: 'Page',
title: 'Theme/Pages/Custom/My Extra Page 2',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})
export const WithCustomValue = bind({ someCustomValue: 'Foo Bar Baz' })

View file

@ -1,54 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('register.ftl')
export default {
kind: 'Page',
title: 'Theme/Pages/Register/Legacy',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})
export const WithFieldError = bind({
register: {
formData: {
email: 'max.mustermann@mail.com'
}
},
messagesPerField: {
existsError: (fieldName: string) => fieldName === "email",
exists: (fieldName: string) => fieldName === "email",
get: (fieldName: string) => fieldName === "email" ? "I don't like your email address" : undefined,
printIfExists: <T,>(fieldName: string, x: T) => fieldName === "email" ? x : undefined,
}
})
export const WithEmailAsUsername = bind({
realm: { registrationEmailAsUsername: true }
})
export const WithoutPassword = bind({
passwordRequired: false
})
export const WithRecaptcha = bind({
recaptchaRequired: true,
recaptchaSiteKey: 'foobar'
})
export const WithPresets = bind({
register: {
formData: {
firstName: 'Max',
lastName: 'Mustermann',
email: 'max.mustermann@mail.com',
username: 'max.mustermann'
}
}
})

View file

@ -1,82 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('register-user-profile.ftl')
export default {
kind: 'Page',
title: 'Theme/Pages/Register/Modern',
component: KcApp,
parameters: { layout: 'fullscreen' },
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})
/*
export const WithFieldError = bind({
profile: {
attributes: [
{
name: "email",
value: "max.mustermann@mail.com",
}
]
}
})
export const WithPresets = bind({
profile: {
attributes: [
{
name: "username",
value: "max.mustermann"
},
{
name: "email",
value: "max.mustermann@gmail.com",
},
{
name: "firstName",
required: false,
value: "Max"
},
{
name: "lastName",
required: false,
value: "Mustermann"
}
]
}
})
export const WithImmutablePresets = bind({
profile: {
attributes: [
{
name: "username",
value: "max.mustermann",
readOnly: true,
},
{
name: "email",
value: "max.mustermann@gmail.com",
readOnly: true,
},
{
name: "firstName",
required: true,
value: "Max",
readOnly: true,
},
{
name: "lastName",
required: true,
value: "Mustermann",
readOnly: true,
}
]
}
})
*/

View file

@ -1,16 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('terms.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Actions/Terms',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,16 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('update-user-profile.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Actions/Update User Profile',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,67 +0,0 @@
import React, { useState } from "react";
import { clsx } from "keycloakify/lib/tools/clsx";
import { UserProfileFormFields } from "keycloakify/lib/pages/shared/UserProfileCommons";
import type { PageProps } from "keycloakify/lib/KcProps";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function UpdateUserProfile(props: PageProps<Extract<KcContext, { pageId: "update-user-profile.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { msg, msgStr } = i18n;
const { url, isAppInitiatedAction } = kcContext;
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("loginProfileTitle")}
formNode={
<form id="kc-update-profile-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}></div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
{isAppInitiatedAction ? (
<>
<input
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
type="submit"
value={msgStr("doSubmit")}
/>
<button
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
type="submit"
name="cancel-aia"
value="true"
formNoValidate
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/>
)}
</div>
</div>
</form>
}
/>
);
}

View file

@ -1,16 +0,0 @@
import { ComponentMeta } from '@storybook/react';
import KcApp from '../KcApp';
import { template } from '../../../.storybook/util'
const bind = template('webauthn-authenticate.ftl');
export default {
kind: 'Page',
title: 'Theme/Pages/Login/Webauthn',
component: KcApp,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof KcApp>;
export const Default = bind({})

View file

@ -1,193 +0,0 @@
import React, { useRef, useState } from "react";
import { clsx } from "keycloakify/lib/tools/clsx";
import type { MessageKeyBase } from "keycloakify/lib/i18n";
import { base64url } from "rfc4648";
import { useConstCallback } from "keycloakify/lib/tools/useConstCallback";
import type { PageProps } from "keycloakify/lib/KcProps";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext, { pageId: "webauthn-authenticate.ftl" }>, I18n>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
const { url } = kcContext;
const { msg, msgStr } = i18n;
const { authenticators, challenge, shouldDisplayAuthenticators, userVerification, rpId } = kcContext;
const createTimeout = Number(kcContext.createTimeout);
const isUserIdentified = kcContext.isUserIdentified == "true";
const webAuthnAuthenticate = useConstCallback(async () => {
if (!isUserIdentified) {
return;
}
const allowCredentials = authenticators.authenticators.map(
authenticator =>
({
id: base64url.parse(authenticator.credentialId, { loose: true }),
type: "public-key"
} as PublicKeyCredentialDescriptor)
);
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
setError(msgStr("webauthn-unsupported-browser-text"));
submitForm();
return;
}
const publicKey: PublicKeyCredentialRequestOptions = {
rpId,
challenge: base64url.parse(challenge, { loose: true })
};
if (createTimeout !== 0) {
publicKey.timeout = createTimeout * 1000;
}
if (allowCredentials.length) {
publicKey.allowCredentials = allowCredentials;
}
if (userVerification !== "not specified") {
publicKey.userVerification = userVerification;
}
try {
const resultRaw = await navigator.credentials.get({ publicKey });
if (!resultRaw || resultRaw.type != "public-key") return;
const result = resultRaw as PublicKeyCredential;
if (!("authenticatorData" in result.response)) return;
const response = result.response as AuthenticatorAssertionResponse;
const clientDataJSON = response.clientDataJSON;
const authenticatorData = response.authenticatorData;
const signature = response.signature;
setClientDataJSON(base64url.stringify(new Uint8Array(clientDataJSON), { pad: false }));
setAuthenticatorData(base64url.stringify(new Uint8Array(authenticatorData), { pad: false }));
setSignature(base64url.stringify(new Uint8Array(signature), { pad: false }));
setCredentialId(result.id);
setUserHandle(base64url.stringify(new Uint8Array(response.userHandle!), { pad: false }));
submitForm();
} catch (err) {
setError(String(err));
submitForm();
}
});
const webAuthForm = useRef<HTMLFormElement>(null);
const submitForm = useConstCallback(() => {
webAuthForm.current!.submit();
});
const [clientDataJSON, setClientDataJSON] = useState("");
const [authenticatorData, setAuthenticatorData] = useState("");
const [signature, setSignature] = useState("");
const [credentialId, setCredentialId] = useState("");
const [userHandle, setUserHandle] = useState("");
const [error, setError] = useState("");
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("webauthn-login-title")}
formNode={
<div id="kc-form-webauthn" className={clsx(kcProps.kcFormClass)}>
<form id="webauth" action={url.loginAction} ref={webAuthForm} method="post">
<input type="hidden" id="clientDataJSON" name="clientDataJSON" value={clientDataJSON} />
<input type="hidden" id="authenticatorData" name="authenticatorData" value={authenticatorData} />
<input type="hidden" id="signature" name="signature" value={signature} />
<input type="hidden" id="credentialId" name="credentialId" value={credentialId} />
<input type="hidden" id="userHandle" name="userHandle" value={userHandle} />
<input type="hidden" id="error" name="error" value={error} />
</form>
<div className={clsx(kcProps.kcFormGroupClass)}>
{authenticators &&
(() => (
<form id="authn_select" className={clsx(kcProps.kcFormClass)}>
{authenticators.authenticators.map(authenticator => (
<input
type="hidden"
name="authn_use_chk"
value={authenticator.credentialId}
key={authenticator.credentialId}
/>
))}
</form>
))()}
{authenticators &&
shouldDisplayAuthenticators &&
(() => (
<>
{authenticators.authenticators.length > 1 && (
<p className={clsx(kcProps.kcSelectAuthListItemTitle)}>{msg("webauthn-available-authenticators")}</p>
)}
<div className={clsx(kcProps.kcFormClass)}>
{authenticators.authenticators.map(authenticator => (
<div id="kc-webauthn-authenticator" className={clsx(kcProps.kcSelectAuthListItemClass)}>
<div className={clsx(kcProps.kcSelectAuthListItemIconClass)}>
<i
className={clsx(
kcProps[authenticator.transports.iconClass] ?? kcProps.kcWebAuthnDefaultIcon,
kcProps.kcSelectAuthListItemIconPropertyClass
)}
/>
</div>
<div className={clsx(kcProps.kcSelectAuthListItemBodyClass)}>
<div
id="kc-webauthn-authenticator-label"
className={clsx(kcProps.kcSelectAuthListItemHeadingClass)}
>
{authenticator.label}
</div>
{authenticator.transports && authenticator.transports.displayNameProperties.length && (
<div
id="kc-webauthn-authenticator-transport"
className={clsx(kcProps.kcSelectAuthListItemDescriptionClass)}
>
{authenticator.transports.displayNameProperties.map(
(transport: MessageKeyBase, index: number) => (
<>
<span>{msg(transport)}</span>
{index < authenticator.transports.displayNameProperties.length - 1 && (
<span>{", "}</span>
)}
</>
)
)}
</div>
)}
<div className={clsx(kcProps.kcSelectAuthListItemDescriptionClass)}>
<span id="kc-webauthn-authenticator-created-label">{msg("webauthn-createdAt-label")}</span>
<span id="kc-webauthn-authenticator-created">{authenticator.createdAt}</span>
</div>
</div>
<div className={clsx(kcProps.kcSelectAuthListItemFillClass)} />
</div>
))}
</div>
</>
))()}
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<input
id="authenticateWebAuthnButton"
type="button"
onClick={webAuthnAuthenticate}
autoFocus={true}
value={msgStr("webauthn-doAuthenticate")}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
/>
</div>
</div>
</div>
}
/>
);
}