mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-13 03:44:39 +00:00
mfa, otp i18n
This commit is contained in:
@@ -59,6 +59,25 @@
|
|||||||
"description": "Choose one of the following second factors."
|
"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": {
|
"error": {
|
||||||
"unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.",
|
"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."
|
"sessionExpired": "You need to have a valid session in order to continue."
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export default async function Page({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!(loginName || sessionId) && (
|
{!(loginName || sessionId) && (
|
||||||
<Alert>{t("error.unknownContext")}</Alert>
|
<Alert>{t("error:unknownContext")}</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{sessionFactors ? (
|
{sessionFactors ? (
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export default async function Page({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!(loginName || sessionId) && (
|
{!(loginName || sessionId) && (
|
||||||
<Alert>{t("error.unknownContext")}</Alert>
|
<Alert>{t("error:unknownContext")}</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!valid && <Alert>{t("error.sessionExpired")}</Alert>}
|
{!valid && <Alert>{t("error.sessionExpired")}</Alert>}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { LoginOTP } from "@/components/login-otp";
|
|||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import { getBrandingSettings } from "@/lib/zitadel";
|
import { getBrandingSettings } from "@/lib/zitadel";
|
||||||
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
export default async function Page({
|
export default async function Page({
|
||||||
searchParams,
|
searchParams,
|
||||||
@@ -12,6 +13,9 @@ export default async function Page({
|
|||||||
searchParams: Record<string | number | symbol, string | undefined>;
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
params: 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 } =
|
const { loginName, authRequestId, sessionId, organization, code, submit } =
|
||||||
searchParams;
|
searchParams;
|
||||||
|
|
||||||
@@ -27,23 +31,20 @@ export default async function Page({
|
|||||||
return (
|
return (
|
||||||
<DynamicTheme branding={branding}>
|
<DynamicTheme branding={branding}>
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<h1>Verify 2-Factor</h1>
|
<h1>{t("verify.title")}</h1>
|
||||||
{method === "time-based" && (
|
{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" && (
|
{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" && (
|
{method === "email" && (
|
||||||
<p className="ztdl-p">Enter the code you got via your email.</p>
|
<p className="ztdl-p">{t("verify.emailDescription")}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!session && (
|
{!session && (
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<Alert>
|
<Alert>{t("error:unknownContext")}</Alert>
|
||||||
Could not get the context of the user. Make sure to enter the
|
|
||||||
username first or provide a loginName as searchParam.
|
|
||||||
</Alert>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
registerTOTP,
|
registerTOTP,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
@@ -22,6 +23,9 @@ export default async function Page({
|
|||||||
searchParams: Record<string | number | symbol, string | undefined>;
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
params: 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 } =
|
const { loginName, organization, sessionId, authRequestId, checkAfter } =
|
||||||
searchParams;
|
searchParams;
|
||||||
const { method } = params;
|
const { method } = params;
|
||||||
@@ -98,13 +102,10 @@ export default async function Page({
|
|||||||
return (
|
return (
|
||||||
<DynamicTheme branding={branding}>
|
<DynamicTheme branding={branding}>
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<h1>Register 2-factor</h1>
|
<h1>{t("set.title")}</h1>
|
||||||
{!session && (
|
{!session && (
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<Alert>
|
<Alert>{t("error:unknownContext")}</Alert>
|
||||||
Could not get the context of the user. Make sure to enter the
|
|
||||||
username first or provide a loginName as searchParam.
|
|
||||||
</Alert>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -125,9 +126,7 @@ export default async function Page({
|
|||||||
|
|
||||||
{totpResponse && "uri" in totpResponse && "secret" in totpResponse ? (
|
{totpResponse && "uri" in totpResponse && "secret" in totpResponse ? (
|
||||||
<>
|
<>
|
||||||
<p className="ztdl-p">
|
<p className="ztdl-p">{t("set.totpRegisterDescription")}</p>
|
||||||
Scan the QR Code or navigate to the URL manually.
|
|
||||||
</p>
|
|
||||||
<div>
|
<div>
|
||||||
<TotpRegister
|
<TotpRegister
|
||||||
uri={totpResponse.uri as string}
|
uri={totpResponse.uri as string}
|
||||||
@@ -160,7 +159,7 @@ export default async function Page({
|
|||||||
className="self-end"
|
className="self-end"
|
||||||
variant={ButtonVariants.Primary}
|
variant={ButtonVariants.Primary}
|
||||||
>
|
>
|
||||||
continue
|
{t("set.submit")}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { updateSession } from "@/lib/server/session";
|
|||||||
import { create } from "@zitadel/client";
|
import { create } from "@zitadel/client";
|
||||||
import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -35,6 +36,8 @@ export function LoginOTP({
|
|||||||
method,
|
method,
|
||||||
code,
|
code,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const t = useTranslations("otp");
|
||||||
|
|
||||||
const [error, setError] = useState<string>("");
|
const [error, setError] = useState<string>("");
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -191,7 +194,7 @@ export function LoginOTP({
|
|||||||
<Alert type={AlertType.INFO}>
|
<Alert type={AlertType.INFO}>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<span className="flex-1 mr-auto text-left">
|
<span className="flex-1 mr-auto text-left">
|
||||||
Did not get the Code?
|
{t("noCodeReceived")}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
aria-label="Resend OTP Code"
|
aria-label="Resend OTP Code"
|
||||||
@@ -209,7 +212,7 @@ export function LoginOTP({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Resend
|
{t("resendCode")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Alert>
|
</Alert>
|
||||||
@@ -241,7 +244,7 @@ export function LoginOTP({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||||
continue
|
{t("submit")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { verifyTOTP } from "@/lib/server-actions";
|
import { verifyTOTP } from "@/lib/server-actions";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { QRCodeSVG } from "qrcode.react";
|
import { QRCodeSVG } from "qrcode.react";
|
||||||
@@ -33,6 +34,8 @@ export function TotpRegister({
|
|||||||
organization,
|
organization,
|
||||||
checkAfter,
|
checkAfter,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const t = useTranslations("otp");
|
||||||
|
|
||||||
const [error, setError] = useState<string>("");
|
const [error, setError] = useState<string>("");
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -138,7 +141,7 @@ export function TotpRegister({
|
|||||||
onClick={handleSubmit(continueWithCode)}
|
onClick={handleSubmit(continueWithCode)}
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||||
continue
|
{t("set.submit")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user