From 271dbe4fb7dcf1c71f800ea83402184496ced163 Mon Sep 17 00:00:00 2001
From: Aidan Gilmore
Date: Tue, 28 Jun 2022 14:37:17 -0400
Subject: [PATCH 1/2] Add totp config support
---
.../generateFtl/generateFtl.ts | 1 +
src/lib/components/KcApp.tsx | 3 +
src/lib/components/LoginConfigTotp.tsx | 183 ++++++++++++++++++
src/lib/getKcContext/KcContextBase.ts | 31 ++-
.../kcContextMocks/kcContextMocks.ts | 21 ++
5 files changed, 238 insertions(+), 1 deletion(-)
create mode 100644 src/lib/components/LoginConfigTotp.tsx
diff --git a/src/bin/build-keycloak-theme/generateFtl/generateFtl.ts b/src/bin/build-keycloak-theme/generateFtl/generateFtl.ts
index cdb0977e..1c625b36 100644
--- a/src/bin/build-keycloak-theme/generateFtl/generateFtl.ts
+++ b/src/bin/build-keycloak-theme/generateFtl/generateFtl.ts
@@ -20,6 +20,7 @@ export const pageIds = [
"login-idp-link-confirm.ftl",
"login-idp-link-email.ftl",
"login-page-expired.ftl",
+ "login-config-totp.ftl",
] as const;
export type PageId = typeof pageIds[number];
diff --git a/src/lib/components/KcApp.tsx b/src/lib/components/KcApp.tsx
index e060506c..e157c5ec 100644
--- a/src/lib/components/KcApp.tsx
+++ b/src/lib/components/KcApp.tsx
@@ -15,6 +15,7 @@ import { LoginUpdateProfile } from "./LoginUpdateProfile";
import { LoginIdpLinkConfirm } from "./LoginIdpLinkConfirm";
import { LoginPageExpired } from "./LoginPageExpired";
import { LoginIdpLinkEmail } from "./LoginIdpLinkEmail";
+import { LoginConfigTotp } from "./LoginConfigTotp";
export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase } & KcProps) => {
switch (kcContext.pageId) {
@@ -46,5 +47,7 @@ export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase }
return ;
case "login-page-expired.ftl":
return ;
+ case "login-config-totp.ftl":
+ return ;
}
});
diff --git a/src/lib/components/LoginConfigTotp.tsx b/src/lib/components/LoginConfigTotp.tsx
new file mode 100644
index 00000000..63072c7e
--- /dev/null
+++ b/src/lib/components/LoginConfigTotp.tsx
@@ -0,0 +1,183 @@
+import { memo } from "react";
+import { Template } from "./Template";
+import type { KcProps } from "./KcProps";
+import type { KcContextBase } from "../getKcContext/KcContextBase";
+import { getMsg } from "../i18n";
+import { useCssAndCx } from "tss-react";
+
+export const LoginConfigTotp = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginConfigTotp } & KcProps) => {
+ const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
+
+ const { cx } = useCssAndCx();
+
+ const { msg, msgStr } = getMsg(kcContext);
+ const algToKeyUriAlg: Record = {
+ HmacSHA1: "SHA1",
+ HmacSHA256: "SHA256",
+ HmacSHA512: "SHA512",
+ };
+
+ return (
+
+
+ -
+
{msg("loginTotpStep1")}
+
+
+ {totp.policy.supportedApplications.map(app => (
+ - {app}
+ ))}
+
+
+
+ {mode && mode == "manual" ? (
+ <>
+ -
+
{msg("loginTotpManualStep2")}
+
+ {totp.totpSecretEncoded}
+
+
+
+ {msg("loginTotpScanBarcode")}
+
+
+
+ -
+
{msg("loginTotpManualStep3")}
+
+
+ -
+ {msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
+
+ -
+ {msg("loginTotpAlgorithm")}: {algToKeyUriAlg[totp.policy.algorithm]}
+
+ -
+ {msg("loginTotpDigits")}: {totp.policy.digits}
+
+ {totp.policy.type === "totp" ? (
+ -
+ {msg("loginTotpInterval")}: {totp.policy.period}
+
+ ) : (
+ -
+ {msg("loginTotpCounter")}: {totp.policy.initialCounter}
+
+ )}
+
+
+
+ >
+ ) : (
+
+ {msg("loginTotpStep2")}
+
+
+
+
+ {msg("loginTotpUnableToScan")}
+
+
+
+ )}
+
+ {msg("loginTotpStep3")}
+ {msg("loginTotpStep3DeviceName")}
+
+
+
+
+ >
+ }
+ />
+ );
+});
diff --git a/src/lib/getKcContext/KcContextBase.ts b/src/lib/getKcContext/KcContextBase.ts
index e95a56bc..ff57af20 100644
--- a/src/lib/getKcContext/KcContextBase.ts
+++ b/src/lib/getKcContext/KcContextBase.ts
@@ -24,7 +24,8 @@ export type KcContextBase =
| KcContextBase.LoginUpdateProfile
| KcContextBase.LoginIdpLinkConfirm
| KcContextBase.LoginIdpLinkEmail
- | KcContextBase.LoginPageExpired;
+ | KcContextBase.LoginPageExpired
+ | KcContextBase.LoginConfigTotp;
export declare namespace KcContextBase {
export type Common = {
@@ -223,6 +224,34 @@ export declare namespace KcContextBase {
export type LoginPageExpired = Common & {
pageId: "login-page-expired.ftl";
};
+
+ export type LoginConfigTotp = Common & {
+ pageId: "login-config-totp.ftl";
+ mode?: "qr" | "manual" | undefined | null;
+ totp: {
+ totpSecretEncoded: string;
+ qrUrl: string;
+ policy: {
+ supportedApplications: string[];
+ algorithm: "HmacSHA1" | "HmacSHA256" | "HmacSHA512";
+ digits: number;
+ lookAheadWindow: number;
+ } & (
+ | {
+ type: "totp";
+ period: number;
+ }
+ | {
+ type: "hotp";
+ initialCounter: number;
+ }
+ );
+ totpSecretQrCode: string;
+ manualUrl: string;
+ totpSecret: string;
+ otpCredentials: { id: string; userLabel: string }[];
+ };
+ };
}
export type Attribute = {
diff --git a/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts b/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts
index 592be5fb..7db78b0f 100644
--- a/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts
+++ b/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts
@@ -387,4 +387,25 @@ export const kcContextMocks: KcContextBase[] = [
"username": "anUsername",
},
}),
+ id({
+ ...kcContextCommonMock,
+ "pageId": "login-config-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: [],
+ policy: {
+ supportedApplications: ["FreeOTP", "Google Authenticator"],
+ algorithm: "HmacSHA1",
+ digits: 6,
+ lookAheadWindow: 1,
+ type: "totp",
+ period: 30,
+ },
+ },
+ }),
];
From cdeb575ec65a55f6aa2bdcf09b925128912aaeb5 Mon Sep 17 00:00:00 2001
From: Aidan Gilmore
Date: Tue, 28 Jun 2022 15:21:09 -0400
Subject: [PATCH 2/2] Fix unknown algorithm name lookup in LoginConfigTotp
---
src/lib/components/LoginConfigTotp.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/lib/components/LoginConfigTotp.tsx b/src/lib/components/LoginConfigTotp.tsx
index 63072c7e..6e7d8787 100644
--- a/src/lib/components/LoginConfigTotp.tsx
+++ b/src/lib/components/LoginConfigTotp.tsx
@@ -56,7 +56,7 @@ export const LoginConfigTotp = memo(({ kcContext, ...props }: { kcContext: KcCon
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
- {msg("loginTotpAlgorithm")}: {algToKeyUriAlg[totp.policy.algorithm]}
+ {msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
{msg("loginTotpDigits")}: {totp.policy.digits}