From 22496e36eb178b2a0098b20f084911df43953321 Mon Sep 17 00:00:00 2001 From: giorgoslytos Date: Wed, 7 Feb 2024 15:18:27 +0200 Subject: [PATCH 1/7] feat: Addition of Sessions page --- src/account/Fallback.tsx | 3 + src/account/TemplateProps.ts | 9 ++- src/account/kcContext/KcContext.ts | 25 +++++++- src/account/kcContext/kcContextMocks.ts | 28 +++++++++ src/account/lib/useGetClassName.ts | 1 + src/account/pages/Sessions.tsx | 68 ++++++++++++++++++++++ src/bin/keycloakify/generateFtl/pageId.ts | 2 +- stories/account/pages/Sessions.stories.tsx | 32 ++++++++++ 8 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 src/account/pages/Sessions.tsx create mode 100644 stories/account/pages/Sessions.stories.tsx diff --git a/src/account/Fallback.tsx b/src/account/Fallback.tsx index 50e9225a..35a16f49 100644 --- a/src/account/Fallback.tsx +++ b/src/account/Fallback.tsx @@ -6,6 +6,7 @@ import { assert, type Equals } from "tsafe/assert"; const Password = lazy(() => import("keycloakify/account/pages/Password")); const Account = lazy(() => import("keycloakify/account/pages/Account")); +const Sessions = lazy(() => import("keycloakify/account/pages/Sessions")); export default function Fallback(props: PageProps) { const { kcContext, ...rest } = props; @@ -16,6 +17,8 @@ export default function Fallback(props: PageProps) { switch (kcContext.pageId) { case "password.ftl": return ; + case "sessions.ftl": + return ; case "account.ftl": return ; } diff --git a/src/account/TemplateProps.ts b/src/account/TemplateProps.ts index 10ebf9aa..1f20c599 100644 --- a/src/account/TemplateProps.ts +++ b/src/account/TemplateProps.ts @@ -11,4 +11,11 @@ export type TemplateProps({ + ...kcContextCommonMock, + "pageId": "sessions.ftl", + sessions: { + sessions: [ + { + ...kcContextCommonMock.sessions, + ipAddress: "127.0.0.1", + started: new Date().toString(), + lastAccess: new Date().toString(), + expires: new Date().toString(), + clients: ["Chrome", "Firefox"] + } + ] + }, + "stateChecker": "" }) ]; diff --git a/src/account/lib/useGetClassName.ts b/src/account/lib/useGetClassName.ts index 51366ff0..f0ee87af 100644 --- a/src/account/lib/useGetClassName.ts +++ b/src/account/lib/useGetClassName.ts @@ -6,6 +6,7 @@ export const { useGetClassName } = createUseClassName({ "kcHtmlClass": undefined, "kcBodyClass": undefined, "kcButtonClass": "btn", + "kcContentWrapperClass": "row", "kcButtonPrimaryClass": "btn-primary", "kcButtonLargeClass": "btn-lg", "kcButtonDefaultClass": "btn-default" diff --git a/src/account/pages/Sessions.tsx b/src/account/pages/Sessions.tsx new file mode 100644 index 00000000..1bb46355 --- /dev/null +++ b/src/account/pages/Sessions.tsx @@ -0,0 +1,68 @@ +import { clsx } from "keycloakify/tools/clsx"; +import type { PageProps } from "keycloakify/account/pages/PageProps"; +import { useGetClassName } from "keycloakify/account/lib/useGetClassName"; +import type { KcContext } from "../kcContext"; +import type { I18n } from "../i18n"; + +export default function Sessions(props: PageProps, I18n>) { + const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; + + const { getClassName } = useGetClassName({ + doUseDefaultCss, + classes + }); + + console.log({ kcContext }); + const { url, stateChecker, sessions } = kcContext; + + const { msg } = i18n; + console.log({ sdf: kcContext.locale?.supported }); + console.log({ asdf: "asdf" }); + return ( + + ); +} diff --git a/src/bin/keycloakify/generateFtl/pageId.ts b/src/bin/keycloakify/generateFtl/pageId.ts index da3525f6..70062ac0 100644 --- a/src/bin/keycloakify/generateFtl/pageId.ts +++ b/src/bin/keycloakify/generateFtl/pageId.ts @@ -27,7 +27,7 @@ export const loginThemePageIds = [ "saml-post-form.ftl" ] as const; -export const accountThemePageIds = ["password.ftl", "account.ftl"] as const; +export const accountThemePageIds = ["password.ftl", "account.ftl", "sessions.ftl"] as const; export type LoginThemePageId = (typeof loginThemePageIds)[number]; export type AccountThemePageId = (typeof accountThemePageIds)[number]; diff --git a/stories/account/pages/Sessions.stories.tsx b/stories/account/pages/Sessions.stories.tsx new file mode 100644 index 00000000..b5d882aa --- /dev/null +++ b/stories/account/pages/Sessions.stories.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import type { ComponentMeta } from "@storybook/react"; +import { createPageStory } from "../createPageStory"; + +const pageId = "sessions.ftl"; + +const { PageStory } = createPageStory({ pageId }); + +const meta: ComponentMeta = { + title: `account/${pageId}`, + component: PageStory, + parameters: { + viewMode: "story", + previewTabs: { + "storybook/docs/panel": { + hidden: true + } + } + } +}; + +export default meta; + +export const Default = () => ; + +export const WithMessage = () => ( + +); From 319d7dbe94ef840b8291006ac4a45a233e34d834 Mon Sep 17 00:00:00 2001 From: giorgoslytos Date: Fri, 16 Feb 2024 17:40:12 +0200 Subject: [PATCH 2/7] feat: Addition of Totp account page --- src/account/Fallback.tsx | 3 + src/account/kcContext/KcContext.ts | 45 ++++- src/account/kcContext/kcContextMocks.ts | 22 +++ src/account/pages/Totp.tsx | 186 +++++++++++++++++++++ src/bin/keycloakify/generateFtl/pageId.ts | 2 +- stories/account/pages/Sessions.stories.tsx | 8 +- stories/account/pages/Totp.stories.tsx | 51 ++++++ 7 files changed, 308 insertions(+), 9 deletions(-) create mode 100644 src/account/pages/Totp.tsx create mode 100644 stories/account/pages/Totp.stories.tsx diff --git a/src/account/Fallback.tsx b/src/account/Fallback.tsx index 35a16f49..8ef67146 100644 --- a/src/account/Fallback.tsx +++ b/src/account/Fallback.tsx @@ -7,6 +7,7 @@ import { assert, type Equals } from "tsafe/assert"; const Password = lazy(() => import("keycloakify/account/pages/Password")); const Account = lazy(() => import("keycloakify/account/pages/Account")); const Sessions = lazy(() => import("keycloakify/account/pages/Sessions")); +const Totp = lazy(() => import("keycloakify/account/pages/Totp")); export default function Fallback(props: PageProps) { const { kcContext, ...rest } = props; @@ -21,6 +22,8 @@ export default function Fallback(props: PageProps) { return ; case "account.ftl": return ; + case "totp.ftl": + return ; } assert>(false); })()} diff --git a/src/account/kcContext/KcContext.ts b/src/account/kcContext/KcContext.ts index 826a4fa0..9b8a75cd 100644 --- a/src/account/kcContext/KcContext.ts +++ b/src/account/kcContext/KcContext.ts @@ -3,7 +3,7 @@ import { assert } from "tsafe/assert"; import type { Equals } from "tsafe"; import { type ThemeType } from "keycloakify/bin/constants"; -export type KcContext = KcContext.Password | KcContext.Account | KcContext.Sessions; +export type KcContext = KcContext.Password | KcContext.Account | KcContext.Sessions | KcContext.Totp; export declare namespace KcContext { export type Common = { @@ -134,6 +134,49 @@ export declare namespace KcContext { }; stateChecker: string; }; + + export type Totp = Common & { + pageId: "totp.ftl"; + totp: { + totpSecretEncoded: string; + qrUrl: string; + policy: { + algorithm: "HmacSHA1" | "HmacSHA256" | "HmacSHA512"; + digits: number; + lookAheadWindow: number; + } & ( + | { + type: "totp"; + period: number; + } + | { + type: "hotp"; + initialCounter: number; + } + ); + supportedApplications: string[]; + totpSecretQrCode: string; + manualUrl: string; + totpSecret: string; + otpCredentials: { id: string; userLabel: string }[]; + }; + url: { + accountUrl: string; + passwordUrl: string; + totpUrl: string; + socialUrl: string; + sessionsUrl: string; + applicationsUrl: string; + logUrl: string; + resourceUrl: string; + resourcesCommonPath: string; + resourcesPath: string; + /** @deprecated, not present in recent keycloak version apparently, use kcContext.referrer instead */ + referrerURI?: string; + getLogoutUrl: () => string; + }; + stateChecker: string; + }; } { diff --git a/src/account/kcContext/kcContextMocks.ts b/src/account/kcContext/kcContextMocks.ts index 9b13b89b..0d85c572 100644 --- a/src/account/kcContext/kcContextMocks.ts +++ b/src/account/kcContext/kcContextMocks.ts @@ -199,5 +199,27 @@ export const kcContextMocks: KcContext[] = [ ] }, "stateChecker": "" + }), + id({ + ...kcContextCommonMock, + "pageId": "totp.ftl", + totp: { + totpSecretEncoded: "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV", + qrUrl: "#", + totpSecretQrCode: + "iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACM0lEQVR4Xu3OIZJgOQwDUDFd2UxiurLAVnnbHw4YGDKtSiWOn4Gxf81//7r/+q8b4HfLGBZDK9d85NmNR+sB42sXvOYrN5P1DcgYYFTGfOlbzE8gzwy3euweGizw7cfdl34/GRhlkxjKNV+5AebPXPORX1JuB9x8ZfbyyD2y1krWAKsbMq1HnqQDaLfa77p4+MqvzEGSqvSAD/2IHW2yHaigR9tX3m8dDIYGcNf3f+gDpVBZbZU77zyJ6Rlcy+qoTMG887KAPD9hsh6a1Sv3gJUHGHUAxSMzj7zqDDe7Phmt2eG+8UsMxjRGm816MAO+8VMl1R1jGHOrZB/5Zo/WXAPgxixm9Mo96vDGrM1eOto8c4Ax4wF437mifOXlpiPzCnN7Y9l95NnEMxgMY9AAGA8fucH14Y1aVb6N/cqrmyh0BVht7k1e+bU8LK0Cg5vmVq9c5vHIjOfqxDIfeTraNVTwewa4wVe+SW5N+uP1qACeudUZbqGOfA6VZV750Noq2Xx3kpveV44ZelSV1V7KFHzkWyVrrlUwG0Pl9pWnoy3vsQoME6vKI69i5osVgwWzHT7zjmJtMcNUSVn1oYMd7ZodbgowZl45VG0uVuLPUr1yc79uaQBag/mqR34xhlWyHm1prplHboCWdZ4TeZjsK8+dI+jbz1C5hl65mcpgB5dhcj8+dGO+0Ko68+lD37JDD83dpDLzzK+TrQyaVwGj6pUboGV+7+AyN8An/pf84/7rv/4/1l4OCc/1BYMAAAAASUVORK5CYII=", + manualUrl: "#", + totpSecret: "G4nsI8lQagRMUchH8jEG", + otpCredentials: [], + supportedApplications: ["FreeOTP", "Google Authenticator"], + policy: { + algorithm: "HmacSHA1", + digits: 6, + lookAheadWindow: 1, + type: "totp", + period: 30 + } + }, + "stateChecker": "" }) ]; diff --git a/src/account/pages/Totp.tsx b/src/account/pages/Totp.tsx new file mode 100644 index 00000000..66c0594a --- /dev/null +++ b/src/account/pages/Totp.tsx @@ -0,0 +1,186 @@ +import { clsx } from "keycloakify/tools/clsx"; +import type { PageProps } from "keycloakify/account/pages/PageProps"; +import { useGetClassName } from "keycloakify/account/lib/useGetClassName"; +import type { KcContext } from "../kcContext"; +import type { I18n } from "../i18n"; + +export default function Totp(props: PageProps, I18n>) { + const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; + const { getClassName } = useGetClassName({ + doUseDefaultCss, + classes + }); + + const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext; + + const { msg, msgStr } = i18n; + + const algToKeyUriAlg: Record<(typeof kcContext)["totp"]["policy"]["algorithm"], string> = { + "HmacSHA1": "SHA1", + "HmacSHA256": "SHA256", + "HmacSHA512": "SHA512" + }; + + return ( + + ); +} diff --git a/src/bin/keycloakify/generateFtl/pageId.ts b/src/bin/keycloakify/generateFtl/pageId.ts index 70062ac0..3688198c 100644 --- a/src/bin/keycloakify/generateFtl/pageId.ts +++ b/src/bin/keycloakify/generateFtl/pageId.ts @@ -27,7 +27,7 @@ export const loginThemePageIds = [ "saml-post-form.ftl" ] as const; -export const accountThemePageIds = ["password.ftl", "account.ftl", "sessions.ftl"] as const; +export const accountThemePageIds = ["password.ftl", "account.ftl", "sessions.ftl", "totp.ftl"] as const; export type LoginThemePageId = (typeof loginThemePageIds)[number]; export type AccountThemePageId = (typeof accountThemePageIds)[number]; diff --git a/stories/account/pages/Sessions.stories.tsx b/stories/account/pages/Sessions.stories.tsx index b5d882aa..250c53b0 100644 --- a/stories/account/pages/Sessions.stories.tsx +++ b/stories/account/pages/Sessions.stories.tsx @@ -23,10 +23,4 @@ export default meta; export const Default = () => ; -export const WithMessage = () => ( - -); +export const WithMessage = () => ; diff --git a/stories/account/pages/Totp.stories.tsx b/stories/account/pages/Totp.stories.tsx new file mode 100644 index 00000000..4807f799 --- /dev/null +++ b/stories/account/pages/Totp.stories.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import type { ComponentMeta } from "@storybook/react"; +import { createPageStory } from "../createPageStory"; + +const pageId = "totp.ftl"; + +const { PageStory } = createPageStory({ pageId }); + +const meta: ComponentMeta = { + title: `account/${pageId}`, + component: PageStory, + parameters: { + viewMode: "story", + previewTabs: { + "storybook/docs/panel": { + hidden: true + } + } + } +}; + +export default meta; + +export const Default = () => ( + +); From 33b99172296340939683a57ea8a40c8b5f685350 Mon Sep 17 00:00:00 2001 From: George Litos Date: Mon, 19 Feb 2024 08:58:27 +0200 Subject: [PATCH 3/7] fix: locales in account totp page --- src/account/TemplateProps.ts | 8 +- src/account/kcContext/KcContext.ts | 2 + src/account/kcContext/kcContextMocks.ts | 4 +- src/account/lib/useGetClassName.ts | 8 +- src/account/pages/Totp.tsx | 101 ++++++++++++------------ 5 files changed, 68 insertions(+), 55 deletions(-) diff --git a/src/account/TemplateProps.ts b/src/account/TemplateProps.ts index 1f20c599..c19f3312 100644 --- a/src/account/TemplateProps.ts +++ b/src/account/TemplateProps.ts @@ -18,4 +18,10 @@ export type ClassKey = | "kcButtonPrimaryClass" | "kcButtonLargeClass" | "kcButtonDefaultClass" - | "kcContentWrapperClass"; + | "kcContentWrapperClass" + | "kcFormClass" + | "kcFormGroupClass" + | "kcInputWrapperClass" + | "kcLabelClass" + | "kcInputClass" + | "kcInputErrorMessageClass"; diff --git a/src/account/kcContext/KcContext.ts b/src/account/kcContext/KcContext.ts index 9b8a75cd..ca356144 100644 --- a/src/account/kcContext/KcContext.ts +++ b/src/account/kcContext/KcContext.ts @@ -160,6 +160,8 @@ export declare namespace KcContext { totpSecret: string; otpCredentials: { id: string; userLabel: string }[]; }; + mode?: "qr" | "manual" | undefined | null; + isAppInitiatedAction: boolean; url: { accountUrl: string; passwordUrl: string; diff --git a/src/account/kcContext/kcContextMocks.ts b/src/account/kcContext/kcContextMocks.ts index 0d85c572..0c7b7c52 100644 --- a/src/account/kcContext/kcContextMocks.ts +++ b/src/account/kcContext/kcContextMocks.ts @@ -211,7 +211,7 @@ export const kcContextMocks: KcContext[] = [ manualUrl: "#", totpSecret: "G4nsI8lQagRMUchH8jEG", otpCredentials: [], - supportedApplications: ["FreeOTP", "Google Authenticator"], + supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"], policy: { algorithm: "HmacSHA1", digits: 6, @@ -220,6 +220,8 @@ export const kcContextMocks: KcContext[] = [ period: 30 } }, + mode: "qr", + isAppInitiatedAction: false, "stateChecker": "" }) ]; diff --git a/src/account/lib/useGetClassName.ts b/src/account/lib/useGetClassName.ts index f0ee87af..15397ade 100644 --- a/src/account/lib/useGetClassName.ts +++ b/src/account/lib/useGetClassName.ts @@ -9,6 +9,12 @@ export const { useGetClassName } = createUseClassName({ "kcContentWrapperClass": "row", "kcButtonPrimaryClass": "btn-primary", "kcButtonLargeClass": "btn-lg", - "kcButtonDefaultClass": "btn-default" + "kcButtonDefaultClass": "btn-default", + "kcFormClass": "form-horizontal", + "kcFormGroupClass": "form-group", + "kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", + "kcLabelClass": "control-label", + "kcInputClass": "form-control", + "kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text" } }); diff --git a/src/account/pages/Totp.tsx b/src/account/pages/Totp.tsx index 66c0594a..8ce4eb15 100644 --- a/src/account/pages/Totp.tsx +++ b/src/account/pages/Totp.tsx @@ -3,6 +3,7 @@ import type { PageProps } from "keycloakify/account/pages/PageProps"; import { useGetClassName } from "keycloakify/account/lib/useGetClassName"; import type { KcContext } from "../kcContext"; import type { I18n } from "../i18n"; +import { MessageKey } from "keycloakify/account/i18n/i18n"; export default function Totp(props: PageProps, I18n>) { const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; @@ -11,7 +12,7 @@ export default function Totp(props: PageProps <> +
+
+

{msg("changePasswordHtmlTitle")}

+
+
+ {msg("allFieldsRequired")} +
+
  1. -

    {msg("loginTotpStep1")}

    +

    {msg("totpStep1")}

      {totp.supportedApplications.map(app => ( -
    • {msg(app as MessageKey)}
    • +
    • {msg(app as MessageKey)}
    • ))}
  2. @@ -38,36 +47,36 @@ export default function Totp(props: PageProps
  3. -

    {msg("loginTotpManualStep2")}

    +

    {msg("totpManualStep2")}

    {totp.totpSecretEncoded}

    - {msg("loginTotpScanBarcode")} + {msg("totpScanBarcode")}

  4. -

    {msg("loginTotpManualStep3")}

    +

    {msg("totpManualStep3")}

    • - {msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)} + {msg("totpType")}: {msg(`totp.${totp.policy.type}`)}
    • - {msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm} + {msg("totpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
    • - {msg("loginTotpDigits")}: {totp.policy.digits} + {msg("totpDigits")}: {totp.policy.digits}
    • {totp.policy.type === "totp" ? (
    • - {msg("loginTotpInterval")}: {totp.policy.period} + {msg("totpInterval")}: {totp.policy.period}
    • ) : (
    • - {msg("loginTotpCounter")}: {totp.policy.initialCounter} + {msg("totpCounter")}: {totp.policy.initialCounter}
    • )}
    @@ -76,31 +85,32 @@ export default function Totp(props: PageProps ) : (
  5. -

    {msg("loginTotpStep2")}

    +

    {msg("totpStep2")}

    Figure: Barcode

    - {msg("loginTotpUnableToScan")} + {msg("totpUnableToScan")}

  6. )}
  7. -

    {msg("loginTotpStep3")}

    -

    {msg("loginTotpStep3DeviceName")}

    +

    {msg("totpStep3")}

    +

    {msg("totpStep3DeviceName")}

+ {/*
*/} - +
-
-