feat: update to zeusina keycloak
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "code.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/code.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
export const WithErrorCode: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
code: {
|
||||
success: false,
|
||||
error: "Failed to generate code"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithFrenchLanguage: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
locale: {
|
||||
currentLanguageTag: "fr"
|
||||
},
|
||||
code: {
|
||||
success: true,
|
||||
code: "XYZ789"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithHtmlErrorMessage: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
code: {
|
||||
success: false,
|
||||
error: "Something went wrong. <a href='https://example.com'>Try again</a>"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "error.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/error.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
|
||||
export const WithAnotherMessage: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: { summary: "With another error message" }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithHtmlErrorMessage: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "<strong>Error:</strong> Something went wrong. <a href='https://example.com'>Go back</a>"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const FrenchError: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
locale: { currentLanguageTag: "fr" },
|
||||
message: { summary: "Une erreur s'est produite" }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithSkipLink: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: { summary: "An error occurred" },
|
||||
skipLink: true,
|
||||
client: {
|
||||
baseUrl: "https://example.com"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "info.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/info.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
messageHeader: "Message header",
|
||||
message: {
|
||||
summary: "Server info message"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithLinkBack: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
messageHeader: "Message header",
|
||||
message: {
|
||||
summary: "Server message"
|
||||
},
|
||||
actionUri: undefined
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithRequiredActions: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
messageHeader: "Message header",
|
||||
message: {
|
||||
summary: "Required actions:"
|
||||
},
|
||||
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PROFILE", "VERIFY_EMAIL", "CUSTOM_ACTION"],
|
||||
"x-keycloakify": {
|
||||
messages: {
|
||||
"requiredAction.CUSTOM_ACTION": "Custom action"
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,407 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "login.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/login.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
|
||||
export const WithInvalidCredential: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
login: {
|
||||
username: "johndoe"
|
||||
},
|
||||
messagesPerField: {
|
||||
// NOTE: The other functions of messagesPerField are derived from get() and
|
||||
// existsError() so they are the only ones that need to mock.
|
||||
existsError: (fieldName: string, ...otherFieldNames: string[]) => {
|
||||
const fieldNames = [fieldName, ...otherFieldNames];
|
||||
return fieldNames.includes("username") || fieldNames.includes("password");
|
||||
},
|
||||
get: (fieldName: string) => {
|
||||
if (fieldName === "username" || fieldName === "password") {
|
||||
return "Invalid username or password.";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutRegistration: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: { registrationAllowed: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutRememberMe: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: { rememberMe: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutPasswordReset: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: { resetPasswordAllowed: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithEmailAsUsername: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: { loginWithEmailAllowed: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithPresetUsername: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
login: { username: "max.mustermann@mail.com" }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithImmutablePresetUsername: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
auth: {
|
||||
attemptedUsername: "max.mustermann@mail.com",
|
||||
showUsername: true
|
||||
},
|
||||
usernameHidden: true,
|
||||
message: {
|
||||
type: "info",
|
||||
summary: "Please re-authenticate to continue"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithSocialProviders: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google",
|
||||
iconClasses: "fa fa-google"
|
||||
},
|
||||
{
|
||||
loginUrl: "microsoft",
|
||||
alias: "microsoft",
|
||||
providerId: "microsoft",
|
||||
displayName: "Microsoft",
|
||||
iconClasses: "fa fa-windows"
|
||||
},
|
||||
{
|
||||
loginUrl: "facebook",
|
||||
alias: "facebook",
|
||||
providerId: "facebook",
|
||||
displayName: "Facebook",
|
||||
iconClasses: "fa fa-facebook"
|
||||
},
|
||||
{
|
||||
loginUrl: "instagram",
|
||||
alias: "instagram",
|
||||
providerId: "instagram",
|
||||
displayName: "Instagram",
|
||||
iconClasses: "fa fa-instagram"
|
||||
},
|
||||
{
|
||||
loginUrl: "twitter",
|
||||
alias: "twitter",
|
||||
providerId: "twitter",
|
||||
displayName: "Twitter",
|
||||
iconClasses: "fa fa-twitter"
|
||||
},
|
||||
{
|
||||
loginUrl: "yandex",
|
||||
alias: "yandex",
|
||||
providerId: "yandex",
|
||||
displayName: "Yandex",
|
||||
iconClasses: "fa fa-yandex"
|
||||
},
|
||||
{
|
||||
loginUrl: "vk",
|
||||
alias: "vk",
|
||||
providerId: "vk",
|
||||
displayName: "VK",
|
||||
iconClasses: "fa fa-vk"
|
||||
},
|
||||
{
|
||||
loginUrl: "linkedin",
|
||||
alias: "linkedin",
|
||||
providerId: "linkedin",
|
||||
displayName: "LinkedIn",
|
||||
iconClasses: "fa fa-linkedin"
|
||||
},
|
||||
{
|
||||
loginUrl: "stackoverflow",
|
||||
alias: "stackoverflow",
|
||||
providerId: "stackoverflow",
|
||||
displayName: "Stackoverflow",
|
||||
iconClasses: "fa fa-stack-overflow"
|
||||
},
|
||||
{
|
||||
loginUrl: "github",
|
||||
alias: "github",
|
||||
providerId: "github",
|
||||
displayName: "Github",
|
||||
iconClasses: "fa fa-github"
|
||||
},
|
||||
{
|
||||
loginUrl: "gitlab",
|
||||
alias: "gitlab",
|
||||
providerId: "gitlab",
|
||||
displayName: "Gitlab",
|
||||
iconClasses: "fa fa-gitlab"
|
||||
},
|
||||
{
|
||||
loginUrl: "bitbucket",
|
||||
alias: "bitbucket",
|
||||
providerId: "bitbucket",
|
||||
displayName: "Bitbucket",
|
||||
iconClasses: "fa fa-bitbucket"
|
||||
},
|
||||
{
|
||||
loginUrl: "paypal",
|
||||
alias: "paypal",
|
||||
providerId: "paypal",
|
||||
displayName: "PayPal",
|
||||
iconClasses: "fa fa-paypal"
|
||||
},
|
||||
{
|
||||
loginUrl: "openshift",
|
||||
alias: "openshift",
|
||||
providerId: "openshift",
|
||||
displayName: "OpenShift",
|
||||
iconClasses: "fa fa-cloud"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutPasswordField: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: { password: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithErrorMessage: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "The time allotted for the connection has elapsed.<br/>The login process will restart from the beginning.",
|
||||
type: "error"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithOneSocialProvider: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google",
|
||||
iconClasses: "fa fa-google"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithTwoSocialProviders: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google",
|
||||
iconClasses: "fa fa-google"
|
||||
},
|
||||
{
|
||||
loginUrl: "microsoft",
|
||||
alias: "microsoft",
|
||||
providerId: "microsoft",
|
||||
displayName: "Microsoft",
|
||||
iconClasses: "fa fa-windows"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithNoSocialProviders: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: []
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithMoreThanTwoSocialProviders: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google",
|
||||
iconClasses: "fa fa-google"
|
||||
},
|
||||
{
|
||||
loginUrl: "microsoft",
|
||||
alias: "microsoft",
|
||||
providerId: "microsoft",
|
||||
displayName: "Microsoft",
|
||||
iconClasses: "fa fa-windows"
|
||||
},
|
||||
{
|
||||
loginUrl: "facebook",
|
||||
alias: "facebook",
|
||||
providerId: "facebook",
|
||||
displayName: "Facebook",
|
||||
iconClasses: "fa fa-facebook"
|
||||
},
|
||||
{
|
||||
loginUrl: "twitter",
|
||||
alias: "twitter",
|
||||
providerId: "twitter",
|
||||
displayName: "Twitter",
|
||||
iconClasses: "fa fa-twitter"
|
||||
},
|
||||
{
|
||||
loginUrl: "yandex",
|
||||
alias: "yandex",
|
||||
providerId: "yandex",
|
||||
displayName: "Yandex",
|
||||
iconClasses: "fa fa-yandex"
|
||||
},
|
||||
{
|
||||
loginUrl: "vk",
|
||||
alias: "vk",
|
||||
providerId: "vk",
|
||||
displayName: "VK",
|
||||
iconClasses: "fa fa-vk"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithSocialProvidersAndWithoutRememberMe: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google",
|
||||
iconClasses: "fa fa-google"
|
||||
}
|
||||
]
|
||||
},
|
||||
realm: { rememberMe: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
/**
|
||||
* WithAuthPassKey:
|
||||
* - Purpose: Test usage of Sign In With Pass Key integration
|
||||
* - Scenario: Simulates a scenario where the `Sign In with Passkey` button is rendered below `Sign In` button.
|
||||
* - Key Aspect: Ensure that it is displayed correctly.
|
||||
*/
|
||||
export const WithAuthPassKey: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
},
|
||||
enableWebAuthnConditionalUI: true
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,127 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "login-otp.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/login-otp.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
|
||||
/**
|
||||
* MultipleOtpCredentials:
|
||||
* - Purpose: Tests the behavior when the user has multiple OTP credentials to choose from.
|
||||
* - Scenario: Simulates the scenario where the user is presented with multiple OTP credentials and must select one to proceed.
|
||||
* - Key Aspect: Ensures that multiple OTP credentials are listed and selectable, and the correct credential is selected by default.
|
||||
*/
|
||||
export const MultipleOtpCredentials: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
otpLogin: {
|
||||
userOtpCredentials: [
|
||||
{ id: "credential1", userLabel: "Device 1" },
|
||||
{ id: "credential2", userLabel: "Device 2" },
|
||||
{ id: "credential3", userLabel: "Device 3" },
|
||||
{ id: "credential4", userLabel: "Device 4" },
|
||||
{ id: "credential5", userLabel: "Device 5" },
|
||||
{ id: "credential6", userLabel: "Device 6" }
|
||||
],
|
||||
selectedCredentialId: "credential1"
|
||||
},
|
||||
url: {
|
||||
loginAction: "/login-action"
|
||||
},
|
||||
messagesPerField: {
|
||||
existsError: () => false
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* WithOtpError:
|
||||
* - Purpose: Tests the behavior when an error occurs with the OTP field (e.g., invalid OTP code).
|
||||
* - Scenario: Simulates an invalid OTP code scenario where an error message is displayed.
|
||||
* - Key Aspect: Ensures that the OTP input displays error messages correctly and the error is visible.
|
||||
*/
|
||||
export const WithOtpError: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
otpLogin: {
|
||||
userOtpCredentials: []
|
||||
},
|
||||
url: {
|
||||
loginAction: "/login-action"
|
||||
},
|
||||
messagesPerField: {
|
||||
existsError: (field: string) => field === "totp",
|
||||
get: () => "Invalid OTP code"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* NoOtpCredentials:
|
||||
* - Purpose: Tests the behavior when no OTP credentials are provided for the user.
|
||||
* - Scenario: Simulates the scenario where the user is not presented with any OTP credentials, and only the OTP input is displayed.
|
||||
* - Key Aspect: Ensures that the component handles cases where there are no user OTP credentials, and the user is only prompted for the OTP code.
|
||||
*/
|
||||
export const NoOtpCredentials: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
otpLogin: {
|
||||
userOtpCredentials: []
|
||||
},
|
||||
url: {
|
||||
loginAction: "/login-action"
|
||||
},
|
||||
messagesPerField: {
|
||||
existsError: () => false
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* WithErrorAndMultipleOtpCredentials:
|
||||
* - Purpose: Tests behavior when there is both an error in the OTP field and multiple OTP credentials.
|
||||
* - Scenario: Simulates the case where the user has multiple OTP credentials and encounters an error with the OTP input.
|
||||
* - Key Aspect: Ensures that the component can handle both multiple OTP credentials and display an error message simultaneously.
|
||||
*/
|
||||
export const WithErrorAndMultipleOtpCredentials: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
otpLogin: {
|
||||
userOtpCredentials: [
|
||||
{ id: "credential1", userLabel: "Device 1" },
|
||||
{ id: "credential2", userLabel: "Device 2" }
|
||||
],
|
||||
selectedCredentialId: "credential1"
|
||||
},
|
||||
url: {
|
||||
loginAction: "/login-action"
|
||||
},
|
||||
messagesPerField: {
|
||||
existsError: (field: string) => field === "totp",
|
||||
get: () => "Invalid OTP code"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "login-passkeys-conditional-authenticate.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/login-passkeys-conditional-authenticate.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "login-reset-password.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/login-reset-password.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
|
||||
export const WithEmailAsUsername: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: {
|
||||
loginWithEmailAllowed: true,
|
||||
registrationEmailAsUsername: true
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
/**
|
||||
* WithUsernameError:
|
||||
* - Purpose: Tests behavior when an error occurs with the username input (e.g., invalid username).
|
||||
* - Scenario: The component displays an error message next to the username input field.
|
||||
* - Key Aspect: Ensures the username input shows error messages when validation fails.
|
||||
*/
|
||||
export const WithUsernameError: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: {
|
||||
loginWithEmailAllowed: false,
|
||||
registrationEmailAsUsername: false,
|
||||
duplicateEmailsAllowed: false
|
||||
},
|
||||
url: {
|
||||
loginAction: "/mock-login-action",
|
||||
loginUrl: "/mock-login-url"
|
||||
},
|
||||
messagesPerField: {
|
||||
existsError: (field: string) => field === "username",
|
||||
get: () => "Invalid username"
|
||||
},
|
||||
auth: {
|
||||
attemptedUsername: "invalid_user"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "login-verify-email.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/login-verify-email.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "You need to verify your email to activate your account.",
|
||||
type: "warning"
|
||||
},
|
||||
user: {
|
||||
email: "john.doe@gmail.com"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* WithSuccessMessage:
|
||||
* - Purpose: Tests when the email verification is successful, and the user receives a confirmation message.
|
||||
* - Scenario: The component renders a success message instead of a warning or error.
|
||||
* - Key Aspect: Ensures the success message is displayed correctly when the email is successfully verified.
|
||||
*/
|
||||
export const WithSuccessMessage: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "Your email has been successfully verified.",
|
||||
type: "success"
|
||||
},
|
||||
user: {
|
||||
email: "john.doe@gmail.com"
|
||||
},
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* WithErrorMessage:
|
||||
* - Purpose: Tests when there is an error during the email verification process.
|
||||
* - Scenario: The component renders an error message indicating the email verification failed.
|
||||
* - Key Aspect: Ensures the error message is shown correctly when the verification process encounters an issue.
|
||||
*/
|
||||
export const WithErrorMessage: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "There was an error verifying your email. Please try again.",
|
||||
type: "error"
|
||||
},
|
||||
user: {
|
||||
email: "john.doe@gmail.com"
|
||||
},
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* WithInfoMessage:
|
||||
* - Purpose: Tests when the user is prompted to verify their email without any urgency.
|
||||
* - Scenario: The component renders with an informational message for email verification.
|
||||
* - Key Aspect: Ensures the informational message is displayed properly.
|
||||
*/
|
||||
export const WithInfoMessage: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "Please verify your email to continue using our services.",
|
||||
type: "info"
|
||||
},
|
||||
user: {
|
||||
email: "john.doe@gmail.com"
|
||||
},
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "qr-login-canceled.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/qr-login-canceled.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
export default function QrLoginCanceled(
|
||||
props: PageProps<Extract<KcContext, { pageId: "qr-login-canceled.ftl" }>, I18n>
|
||||
) {
|
||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||
const { msg } = i18n;
|
||||
|
||||
return (
|
||||
<Template
|
||||
kcContext={kcContext}
|
||||
i18n={i18n}
|
||||
doUseDefaultCss={doUseDefaultCss}
|
||||
classes={classes}
|
||||
displayInfo={false}
|
||||
headerNode={msg("consentDenied")}
|
||||
>
|
||||
<div id="kc-qr-canceled-message" className={classes?.kcFormClass} />
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "qr-login-scan.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/qr-login-scan.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const qrCodePlaceholder =
|
||||
"iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAElJREFUOI1jZGBgYJj9X/U/IwMDw3wmBgaG+YwMDAwM8xkYGBgY5jMwMDAwzGdgYGBgmM/AwMDAMB8HBwb5DAwMDAzIZmBgYGBgAAD9/wX9eWNG0QAAAABJRU5ErkJggg==";
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
QRauthImage: qrCodePlaceholder,
|
||||
QRauthExecId: "8f16a04f-6610-4555-8607-4bf88f4a2d92",
|
||||
QRauthToken: "link",
|
||||
tabId: "GvAAhBUeVqw",
|
||||
alignment: "Center",
|
||||
refreshRate: 15,
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithShortCode: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
QRauthImage: qrCodePlaceholder,
|
||||
QRauthExecId: "8f16a04f-6610-4555-8607-4bf88f4a2d92",
|
||||
QRauthToken: "link",
|
||||
tabId: "GvAAhBUeVqw",
|
||||
alignment: "Center",
|
||||
refreshRate: 0,
|
||||
ShortCode: "123456",
|
||||
ShortCodeLink: "#",
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutAutoSubmit: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
QRauthImage: qrCodePlaceholder,
|
||||
QRauthExecId: "8f16a04f-6610-4555-8607-4bf88f4a2d92",
|
||||
QRauthToken: "link",
|
||||
tabId: "GvAAhBUeVqw",
|
||||
alignment: "Center",
|
||||
refreshRate: 0,
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const LoadingWithoutQrCode: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
QRauthExecId: "8f16a04f-6610-4555-8607-4bf88f4a2d92",
|
||||
QRauthToken: "link",
|
||||
tabId: "GvAAhBUeVqw",
|
||||
alignment: "Center",
|
||||
refreshRate: 0,
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
export default function QrLoginScan(
|
||||
props: PageProps<Extract<KcContext, { pageId: "qr-login-scan.ftl" }>, I18n>
|
||||
) {
|
||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||
const { url, QRauthImage, QRauthExecId, QRauthToken, tabId, alignment, ShortCode, ShortCodeLink, refreshRate = 15 } = kcContext;
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [showShortCode, setShowShortCode] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
formRef.current?.requestSubmit();
|
||||
}, refreshRate * 1000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [refreshRate]);
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get("qr_code_originated") === "true") {
|
||||
const el = document.getElementById("com-hadleyso-qr-auth");
|
||||
if (el) el.style.display = "none";
|
||||
}
|
||||
}, []);
|
||||
|
||||
const qrContainerStyle: React.CSSProperties = {
|
||||
width: "45%",
|
||||
marginLeft: alignment === "Center" ? "auto" : alignment === "Right" ? "auto" : undefined,
|
||||
marginRight: alignment === "Center" || alignment === "Left" ? "auto" : undefined
|
||||
};
|
||||
|
||||
return (
|
||||
<Template
|
||||
kcContext={kcContext}
|
||||
i18n={i18n}
|
||||
doUseDefaultCss={doUseDefaultCss}
|
||||
classes={classes}
|
||||
displayInfo={false}
|
||||
headerNode={msg("doQrCodeLogin")}
|
||||
>
|
||||
<div id="com-hadleyso-qr-auth" className={classes?.kcFormClass}>
|
||||
<div
|
||||
id="com-hadleyso-qr-auth-js-target"
|
||||
className="kcQrCodeContainer"
|
||||
style={qrContainerStyle}
|
||||
onClick={() => formRef.current?.requestSubmit()}
|
||||
>
|
||||
<span className="kcQrTokenHidden">{QRauthToken}</span>
|
||||
{QRauthImage ? (
|
||||
<img
|
||||
id="com-hadleyso-qr-auth-qr-code"
|
||||
src={`data:image/png;base64,${QRauthImage}`}
|
||||
alt={msgStr("ext-qr-code-login-display-name")}
|
||||
className="kcQrCodeImage"
|
||||
/>
|
||||
) : (
|
||||
<div className="kcQrCodePlaceholder">{msg("qrLoginLoading")}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{tabId && (
|
||||
<p className="kcQrSessionClass">
|
||||
<b>{msg("qrLoginSessionLabel")}</b> {tabId}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{ShortCode && (
|
||||
<p id="com-hadleyso-qr-auth-short-zone" className="kcQrShortCodeZone">
|
||||
<a
|
||||
href="#"
|
||||
id="com-hadleyso-qr-auth-short-start"
|
||||
onClick={event => {
|
||||
setShowShortCode(true);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{msg("CannotScan")}
|
||||
</a>{" "}
|
||||
|{" "}
|
||||
<a href={ShortCodeLink}>{msg("UseShortCode")}</a>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{ShortCode && showShortCode && (
|
||||
<p id="com-hadleyso-qr-auth-short-message" className="kcQrShortCodeMessage">
|
||||
{msg("doShortCodeSteps")}
|
||||
<br />
|
||||
<br />
|
||||
<b>{msg("doShortCode")}:</b> {ShortCode}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<form
|
||||
ref={formRef}
|
||||
id={`com-hadleyso-qrcode-${QRauthExecId ?? "default"}`}
|
||||
className={classes?.kcFormClass}
|
||||
action={url.loginAction}
|
||||
method="post"
|
||||
>
|
||||
<input type="hidden" name="authenticationExecution" value={QRauthExecId ?? ""} />
|
||||
<input
|
||||
type="submit"
|
||||
value={msgStr("doLogIn")}
|
||||
className={`${classes?.kcButtonClass ?? ""} ${classes?.kcButtonPrimaryClass ?? ""} ${classes?.kcButtonLargeClass ?? ""}`}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "qr-login-short-start.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/qr-login-short-start.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
export default function QrLoginShortStart(
|
||||
props: PageProps<Extract<KcContext, { pageId: "qr-login-short-start.ftl" }>, I18n>
|
||||
) {
|
||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
return (
|
||||
<Template
|
||||
kcContext={kcContext}
|
||||
i18n={i18n}
|
||||
doUseDefaultCss={doUseDefaultCss}
|
||||
classes={classes}
|
||||
displayInfo={false}
|
||||
headerNode={msg("doShortCodeLogin")}
|
||||
>
|
||||
<form id="com-hadleyso-qrcode-short" className={classes?.kcFormClass} method="post">
|
||||
<div className={classes?.kcFormGroupClass}>
|
||||
<p className="kcQrShortCodeInfoClass">{msg("doShortCodeInfo")}</p>
|
||||
|
||||
<label htmlFor="shortCode" className={classes?.kcLabelClass}>
|
||||
{msg("doShortCode")}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="shortCode"
|
||||
name="shortCode"
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
pattern="\d{6}"
|
||||
min={100000}
|
||||
max={999999}
|
||||
autoFocus
|
||||
required
|
||||
className={classes?.kcInputClass}
|
||||
placeholder={msgStr("doShortCode")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={classes?.kcFormGroupClass}>
|
||||
<input
|
||||
type="submit"
|
||||
value={msgStr("doSubmit")}
|
||||
className={`${classes?.kcButtonClass ?? ""} ${classes?.kcButtonPrimaryClass ?? ""}`}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "qr-login-success.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/qr-login-success.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
export default function QrLoginSuccess(
|
||||
props: PageProps<Extract<KcContext, { pageId: "qr-login-success.ftl" }>, I18n>
|
||||
) {
|
||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||
const { msg } = i18n;
|
||||
|
||||
return (
|
||||
<Template
|
||||
kcContext={kcContext}
|
||||
i18n={i18n}
|
||||
doUseDefaultCss={doUseDefaultCss}
|
||||
classes={classes}
|
||||
displayInfo={false}
|
||||
headerNode={msg("successQrCodeLoginTitle")}
|
||||
>
|
||||
<div id="kc-qr-success-message" className={classes?.kcFormClass}>
|
||||
<p className="kcQrSuccessMessageClass">{msg("successQrCodeLoginMessage")}</p>
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "qr-login-verify.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/qr-login-verify.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
ua_device: "Desktop",
|
||||
ua_os: "macOS",
|
||||
ua_agent: "Chrome",
|
||||
local_localized: "English",
|
||||
tabId: "GvAAhBUeVqw",
|
||||
approveURL: "#approve",
|
||||
rejectURL: "#reject"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
export default function QrLoginVerify(
|
||||
props: PageProps<Extract<KcContext, { pageId: "qr-login-verify.ftl" }>, I18n>
|
||||
) {
|
||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||
const { ua_device, ua_os, ua_agent, local_localized, tabId, approveURL, rejectURL } = kcContext;
|
||||
|
||||
const { msg } = i18n;
|
||||
|
||||
return (
|
||||
<Template
|
||||
kcContext={kcContext}
|
||||
i18n={i18n}
|
||||
doUseDefaultCss={doUseDefaultCss}
|
||||
classes={classes}
|
||||
displayInfo={false}
|
||||
headerNode={msg("doQrCodeVerify")}
|
||||
>
|
||||
<div id="kc-qr-verify-form" className={classes?.kcFormClass}>
|
||||
<p className="kcQrWarningClass">{msg("doQrCodeWarning")}</p>
|
||||
|
||||
<p className="kcQrDeviceInfoClass">
|
||||
You are authorizing a session on a <b>{ua_device}</b> running <b>{ua_os}</b> / <b>{ua_agent}</b> in
|
||||
locale <b>{local_localized}</b>.
|
||||
</p>
|
||||
|
||||
{tabId && (
|
||||
<p className="kcQrSessionClass">
|
||||
Session: <b>{tabId}</b>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="kcQrVerifyButtonsClass">
|
||||
<a
|
||||
href={approveURL}
|
||||
type="button"
|
||||
className={`${classes?.kcButtonClass ?? ""} ${classes?.kcButtonPrimaryClass ?? ""} ${classes?.kcButtonLargeClass ?? ""}`}
|
||||
>
|
||||
{msg("doAccept")}
|
||||
</a>
|
||||
<a
|
||||
href={rejectURL}
|
||||
type="button"
|
||||
className={`${classes?.kcButtonClass ?? ""} ${classes?.kcButtonDefaultClass ?? ""} ${classes?.kcButtonLargeClass ?? ""}`}
|
||||
>
|
||||
{msg("doDecline")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "select-authenticator.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/select-authenticator.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
|
||||
export const WithDifferentAuthenticationMethods: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
auth: {
|
||||
authenticationSelections: [
|
||||
{
|
||||
authExecId: "25697c4e-0c80-4f2c-8eb7-2c16347e8e8d",
|
||||
displayName: "auth-username-password-form-display-name",
|
||||
helpText: "auth-username-password-form-help-text",
|
||||
iconCssClass: "kcAuthenticatorPasswordClass"
|
||||
},
|
||||
{
|
||||
authExecId: "4cb60872-ce0d-4c8f-a806-e651ed77994b",
|
||||
displayName: "webauthn-passwordless-display-name",
|
||||
helpText: "webauthn-passwordless-help-text",
|
||||
iconCssClass: "kcAuthenticatorWebAuthnPasswordlessClass"
|
||||
},
|
||||
{
|
||||
authExecId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
displayName: "ext-qr-code-login-display-name",
|
||||
helpText: "ext-qr-code-login-help-text",
|
||||
iconCssClass: "kcAuthenticatorDefaultClass"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithRealmTranslations: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
auth: {
|
||||
authenticationSelections: [
|
||||
{
|
||||
authExecId: "f0c22855-eda7-4092-8565-0c22f77d2ffb",
|
||||
displayName: "home-idp-discovery-display-name",
|
||||
helpText: "home-idp-discovery-help-text",
|
||||
iconCssClass: "kcAuthenticatorDefaultClass"
|
||||
},
|
||||
{
|
||||
authExecId: "20456f5a-8b2b-45f3-98e0-551dcb27e3e1",
|
||||
displayName: "identity-provider-redirctor-display-name",
|
||||
helpText: "identity-provider-redirctor-help-text",
|
||||
iconCssClass: "kcAuthenticatorDefaultClass"
|
||||
},
|
||||
{
|
||||
authExecId: "eb435db9-474e-473a-8da7-c184fa510b96",
|
||||
displayName: "auth-username-password-form-display-name",
|
||||
helpText: "auth-username-password-help-text",
|
||||
iconCssClass: "kcAuthenticatorPasswordClass"
|
||||
}
|
||||
]
|
||||
},
|
||||
"x-keycloakify": {
|
||||
messages: {
|
||||
"home-idp-discovery-display-name": "Home identity provider",
|
||||
"home-idp-discovery-help-text":
|
||||
"Sign in via your home identity provider which will be automatically determined based on your provided email address.",
|
||||
"identity-provider-redirctor-display-name": "Identity Provider Redirector",
|
||||
"identity-provider-redirctor-help-text": "Sign in via your identity provider.",
|
||||
"auth-username-password-help-text": "Sign in via your username and password."
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* WithoutAuthenticationSelections:
|
||||
* - Purpose: Tests when no authentication methods are available for selection.
|
||||
* - Scenario: The component renders without any authentication options, providing a default message or fallback.
|
||||
* - Key Aspect: Ensures that the component gracefully handles the absence of available authentication methods.
|
||||
*/
|
||||
export const WithoutAuthenticationSelections: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
},
|
||||
auth: {
|
||||
authenticationSelections: [] // No authentication methods available
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "webauthn-authenticate.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/webauthn-authenticate.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
|
||||
/**
|
||||
* WithMultipleAuthenticators:
|
||||
* - Purpose: Tests when multiple WebAuthn authenticators are available for selection.
|
||||
* - Scenario: The component renders multiple authenticators, allowing the user to choose between them.
|
||||
* - Key Aspect: Ensures that the available authenticators are displayed, and the user can select one.
|
||||
*/
|
||||
export const WithMultipleAuthenticators: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
},
|
||||
authenticators: {
|
||||
authenticators: [
|
||||
{
|
||||
credentialId: "authenticator-1",
|
||||
label: "Security Key 1",
|
||||
transports: {
|
||||
iconClass: "kcAuthenticatorUsbIcon",
|
||||
displayNameProperties: ["USB"]
|
||||
},
|
||||
createdAt: "2023-01-01"
|
||||
},
|
||||
{
|
||||
credentialId: "authenticator-2",
|
||||
label: "Security Key 2",
|
||||
transports: {
|
||||
iconClass: "kcAuthenticatorNfcIcon",
|
||||
displayNameProperties: ["NFC"]
|
||||
},
|
||||
createdAt: "2023-02-01"
|
||||
}
|
||||
]
|
||||
},
|
||||
shouldDisplayAuthenticators: true
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* WithSingleAuthenticator:
|
||||
* - Purpose: Tests when only one WebAuthn authenticator is available.
|
||||
* - Scenario: The component renders the WebAuthn form with a single available authenticator.
|
||||
* - Key Aspect: Ensures the form renders correctly when there is only one authenticator available.
|
||||
*/
|
||||
export const WithSingleAuthenticator: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
},
|
||||
authenticators: {
|
||||
authenticators: [
|
||||
{
|
||||
credentialId: "authenticator-1",
|
||||
label: "My Security Key",
|
||||
transports: {
|
||||
iconClass: "kcAuthenticatorUsbIcon",
|
||||
displayNameProperties: ["USB"]
|
||||
},
|
||||
createdAt: "2023-01-01"
|
||||
}
|
||||
]
|
||||
},
|
||||
shouldDisplayAuthenticators: true
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* WithErrorDuringAuthentication:
|
||||
* - Purpose: Tests the behavior when an error occurs during WebAuthn authentication.
|
||||
* - Scenario: The component renders with an error message displayed to the user.
|
||||
* - Key Aspect: Ensures the form handles authentication errors and displays a relevant message.
|
||||
*/
|
||||
export const WithErrorDuringAuthentication: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
},
|
||||
authenticators: {
|
||||
authenticators: [
|
||||
{
|
||||
credentialId: "authenticator-1",
|
||||
label: "My Security Key",
|
||||
transports: {
|
||||
iconClass: "kcAuthenticatorUsbIcon",
|
||||
displayNameProperties: ["USB"]
|
||||
},
|
||||
createdAt: "2023-01-01"
|
||||
}
|
||||
]
|
||||
},
|
||||
shouldDisplayAuthenticators: true,
|
||||
message: {
|
||||
summary: "An error occurred during WebAuthn authentication.",
|
||||
type: "error"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* WithJavaScriptDisabled:
|
||||
* - Purpose: Tests the behavior when JavaScript is disabled or not functioning.
|
||||
* - Scenario: The component renders a fallback message prompting the user to enable JavaScript for WebAuthn authentication.
|
||||
* - Key Aspect: Ensures the form provides a clear message when JavaScript is required but unavailable.
|
||||
*/
|
||||
export const WithJavaScriptDisabled: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
},
|
||||
authenticators: {
|
||||
authenticators: [
|
||||
{
|
||||
credentialId: "authenticator-1",
|
||||
label: "My Security Key",
|
||||
transports: {
|
||||
iconClass: "kcAuthenticatorUsbIcon",
|
||||
displayNameProperties: ["USB"]
|
||||
},
|
||||
createdAt: "2023-01-01"
|
||||
}
|
||||
]
|
||||
},
|
||||
shouldDisplayAuthenticators: true
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "webauthn-register.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/webauthn-register.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
|
||||
/**
|
||||
* WithRetryAvailable:
|
||||
* - Purpose: Tests when the user is allowed to retry WebAuthn registration after a failure.
|
||||
* - Scenario: The component renders the form with a retry option.
|
||||
* - Key Aspect: Ensures the retry functionality is available and the form allows the user to retry.
|
||||
*/
|
||||
export const WithRetryAvailable: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
},
|
||||
isSetRetry: true,
|
||||
isAppInitiatedAction: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* WithErrorDuringRegistration:
|
||||
* - Purpose: Tests when an error occurs during WebAuthn registration.
|
||||
* - Scenario: The component displays an error message related to WebAuthn registration failure.
|
||||
* - Key Aspect: Ensures the error message is displayed correctly, informing the user of the registration failure.
|
||||
*/
|
||||
export const WithErrorDuringRegistration: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
url: {
|
||||
loginAction: "/mock-login-action"
|
||||
},
|
||||
isSetRetry: false,
|
||||
isAppInitiatedAction: false,
|
||||
message: {
|
||||
summary: "An error occurred during WebAuthn registration. Please try again.",
|
||||
type: "error"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
Reference in New Issue
Block a user