mfa, otp i18n

This commit is contained in:
peintnermax
2024-10-09 17:03:03 +02:00
parent b2e8384902
commit b5e81d426f
7 changed files with 48 additions and 23 deletions

View File

@@ -59,6 +59,25 @@
"description": "Choose one of the following second factors."
}
},
"otp": {
"verify": {
"title": "Verify 2-Factor",
"totpDescription": "Enter the code from your authenticator app.",
"smsDescription": "Enter the code you received via SMS.",
"emailDescription": "Enter the code you received via email.",
"noCodeReceived": "Didn't receive a code?",
"resendCode": "Resend code",
"submit": "Continue"
},
"set": {
"title": "Set up 2-Factor",
"totpDescription": "Scan the QR code with your authenticator app.",
"smsDescription": "Enter your phone number to receive a code via SMS.",
"emailDescription": "Enter your email address to receive a code via email.",
"totpRegisterDescription": "Scan the QR Code or navigate to the URL manually.",
"submit": "Continue"
}
},
"error": {
"unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.",
"sessionExpired": "You need to have a valid session in order to continue."

View File

@@ -85,7 +85,7 @@ export default async function Page({
)}
{!(loginName || sessionId) && (
<Alert>{t("error.unknownContext")}</Alert>
<Alert>{t("error:unknownContext")}</Alert>
)}
{sessionFactors ? (

View File

@@ -122,7 +122,7 @@ export default async function Page({
)}
{!(loginName || sessionId) && (
<Alert>{t("error.unknownContext")}</Alert>
<Alert>{t("error:unknownContext")}</Alert>
)}
{!valid && <Alert>{t("error.sessionExpired")}</Alert>}

View File

@@ -4,6 +4,7 @@ import { LoginOTP } from "@/components/login-otp";
import { UserAvatar } from "@/components/user-avatar";
import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings } from "@/lib/zitadel";
import { getLocale, getTranslations } from "next-intl/server";
export default async function Page({
searchParams,
@@ -12,6 +13,9 @@ export default async function Page({
searchParams: Record<string | number | symbol, string | undefined>;
params: Record<string | number | symbol, string | undefined>;
}) {
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "otp" });
const { loginName, authRequestId, sessionId, organization, code, submit } =
searchParams;
@@ -27,23 +31,20 @@ export default async function Page({
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Verify 2-Factor</h1>
<h1>{t("verify.title")}</h1>
{method === "time-based" && (
<p className="ztdl-p">Enter the code from your authenticator app.</p>
<p className="ztdl-p">{t("verify.totpDescription")}</p>
)}
{method === "sms" && (
<p className="ztdl-p">Enter the code you got on your phone.</p>
<p className="ztdl-p">{t("verify.smsDescription")}</p>
)}
{method === "email" && (
<p className="ztdl-p">Enter the code you got via your email.</p>
<p className="ztdl-p">{t("verify.emailDescription")}</p>
)}
{!session && (
<div className="py-4">
<Alert>
Could not get the context of the user. Make sure to enter the
username first or provide a loginName as searchParam.
</Alert>
<Alert>{t("error:unknownContext")}</Alert>
</div>
)}

View File

@@ -12,6 +12,7 @@ import {
registerTOTP,
} from "@/lib/zitadel";
import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { getLocale, getTranslations } from "next-intl/server";
import Link from "next/link";
import { redirect } from "next/navigation";
@@ -22,6 +23,9 @@ export default async function Page({
searchParams: Record<string | number | symbol, string | undefined>;
params: Record<string | number | symbol, string | undefined>;
}) {
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "otp" });
const { loginName, organization, sessionId, authRequestId, checkAfter } =
searchParams;
const { method } = params;
@@ -98,13 +102,10 @@ export default async function Page({
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Register 2-factor</h1>
<h1>{t("set.title")}</h1>
{!session && (
<div className="py-4">
<Alert>
Could not get the context of the user. Make sure to enter the
username first or provide a loginName as searchParam.
</Alert>
<Alert>{t("error:unknownContext")}</Alert>
</div>
)}
@@ -125,9 +126,7 @@ export default async function Page({
{totpResponse && "uri" in totpResponse && "secret" in totpResponse ? (
<>
<p className="ztdl-p">
Scan the QR Code or navigate to the URL manually.
</p>
<p className="ztdl-p">{t("set.totpRegisterDescription")}</p>
<div>
<TotpRegister
uri={totpResponse.uri as string}
@@ -160,7 +159,7 @@ export default async function Page({
className="self-end"
variant={ButtonVariants.Primary}
>
continue
{t("set.submit")}
</Button>
</Link>
</div>

View File

@@ -4,6 +4,7 @@ import { updateSession } from "@/lib/server/session";
import { create } from "@zitadel/client";
import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
@@ -35,6 +36,8 @@ export function LoginOTP({
method,
code,
}: Props) {
const t = useTranslations("otp");
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
@@ -191,7 +194,7 @@ export function LoginOTP({
<Alert type={AlertType.INFO}>
<div className="flex flex-row">
<span className="flex-1 mr-auto text-left">
Did not get the Code?
{t("noCodeReceived")}
</span>
<button
aria-label="Resend OTP Code"
@@ -209,7 +212,7 @@ export function LoginOTP({
});
}}
>
Resend
{t("resendCode")}
</button>
</div>
</Alert>
@@ -241,7 +244,7 @@ export function LoginOTP({
})}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
continue
{t("submit")}
</Button>
</div>
</form>

View File

@@ -1,5 +1,6 @@
"use client";
import { verifyTOTP } from "@/lib/server-actions";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { QRCodeSVG } from "qrcode.react";
@@ -33,6 +34,8 @@ export function TotpRegister({
organization,
checkAfter,
}: Props) {
const t = useTranslations("otp");
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const router = useRouter();
@@ -138,7 +141,7 @@ export function TotpRegister({
onClick={handleSubmit(continueWithCode)}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
continue
{t("set.submit")}
</Button>
</div>
</form>