mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 18:02:33 +00:00
combined otp form
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { getBrandingSettings, getLoginSettings, server } from "#/lib/zitadel";
|
||||
import DynamicTheme from "#/ui/DynamicTheme";
|
||||
import TOTPForm from "#/ui/TOTPForm";
|
||||
import LoginOTP from "#/ui/LoginOTP";
|
||||
import VerifyU2F from "#/ui/VerifyU2F";
|
||||
|
||||
export default async function Page({
|
||||
@@ -15,8 +15,6 @@ export default async function Page({
|
||||
|
||||
const { method } = params;
|
||||
|
||||
console.log(method);
|
||||
|
||||
const branding = await getBrandingSettings(server, organization);
|
||||
|
||||
return (
|
||||
@@ -37,15 +35,13 @@ export default async function Page({
|
||||
)}
|
||||
|
||||
{method && ["time-based", "sms", "email"].includes(method) ? (
|
||||
<TOTPForm
|
||||
<LoginOTP
|
||||
loginName={loginName}
|
||||
sessionId={sessionId}
|
||||
code={code}
|
||||
method={method}
|
||||
authRequestId={authRequestId}
|
||||
organization={organization}
|
||||
submit={submit === "true"}
|
||||
/>
|
||||
method={method}
|
||||
></LoginOTP>
|
||||
) : (
|
||||
<VerifyU2F
|
||||
loginName={loginName}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { server, deleteSession, listHumanAuthFactors } from "#/lib/zitadel";
|
||||
import {
|
||||
server,
|
||||
deleteSession,
|
||||
listHumanAuthFactors,
|
||||
getSession,
|
||||
} from "#/lib/zitadel";
|
||||
import {
|
||||
SessionCookie,
|
||||
getMostRecentSessionCookie,
|
||||
@@ -67,11 +72,10 @@ export async function PUT(request: NextRequest) {
|
||||
loginName,
|
||||
sessionId,
|
||||
organization,
|
||||
password,
|
||||
webAuthN,
|
||||
checks,
|
||||
authRequestId,
|
||||
challenges,
|
||||
} = body;
|
||||
const challenges: RequestChallenges = body.challenges;
|
||||
|
||||
const recentPromise: Promise<SessionCookie> = sessionId
|
||||
? getSessionCookieById(sessionId).catch((error) => {
|
||||
@@ -93,16 +97,6 @@ export async function PUT(request: NextRequest) {
|
||||
|
||||
return recentPromise
|
||||
.then((recent) => {
|
||||
const checks: Checks = {};
|
||||
if (password) {
|
||||
checks.password = {
|
||||
password,
|
||||
};
|
||||
}
|
||||
if (webAuthN) {
|
||||
checks.webAuthN = webAuthN;
|
||||
}
|
||||
|
||||
return setSessionAndUpdateCookie(
|
||||
recent,
|
||||
checks,
|
||||
@@ -111,7 +105,7 @@ export async function PUT(request: NextRequest) {
|
||||
).then(async (session) => {
|
||||
// if password, check if user has MFA methods
|
||||
let authFactors;
|
||||
if (password && session.factors?.user?.id) {
|
||||
if (checks.password && session.factors?.user?.id) {
|
||||
const response = await listHumanAuthFactors(
|
||||
server,
|
||||
session.factors?.user?.id
|
||||
|
||||
@@ -1,35 +1,44 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button, ButtonVariants } from "./Button";
|
||||
import { TextInput } from "./Input";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Spinner } from "./Spinner";
|
||||
import { coerceToArrayBuffer, coerceToBase64Url } from "#/utils/base64";
|
||||
import { Button, ButtonVariants } from "./Button";
|
||||
import Alert from "./Alert";
|
||||
import { Spinner } from "./Spinner";
|
||||
import { Checks } from "@zitadel/server";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { TextInput } from "./Input";
|
||||
|
||||
// either loginName or sessionId must be provided
|
||||
type Props = {
|
||||
loginName?: string;
|
||||
sessionId?: string;
|
||||
authRequestId?: string;
|
||||
organization?: string;
|
||||
method?: string;
|
||||
code?: string;
|
||||
};
|
||||
|
||||
type Inputs = {
|
||||
code: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
loginName: string | undefined;
|
||||
sessionId: string | undefined;
|
||||
code: string | undefined;
|
||||
method: string;
|
||||
authRequestId?: string;
|
||||
organization?: string;
|
||||
submit: boolean;
|
||||
};
|
||||
|
||||
export default function TOTPForm({
|
||||
export default function LoginOTP({
|
||||
loginName,
|
||||
code,
|
||||
method,
|
||||
sessionId,
|
||||
authRequestId,
|
||||
organization,
|
||||
submit,
|
||||
method,
|
||||
code,
|
||||
}: Props) {
|
||||
const [error, setError] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const initialized = useRef(false);
|
||||
|
||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
@@ -37,10 +46,93 @@ export default function TOTPForm({
|
||||
},
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
if (!initialized.current) {
|
||||
initialized.current = true;
|
||||
setLoading(true);
|
||||
updateSessionForOTPChallenge();
|
||||
// .then((response) => {
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
// setLoading(false);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// setError(error);
|
||||
// setLoading(false);
|
||||
// });
|
||||
}
|
||||
}, []);
|
||||
|
||||
async function updateSessionForOTPChallenge() {
|
||||
setLoading(true);
|
||||
const res = await fetch("/api/session", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
loginName,
|
||||
sessionId,
|
||||
organization,
|
||||
challenges:
|
||||
method === "email"
|
||||
? {
|
||||
otpEmail: true,
|
||||
}
|
||||
: method === "sms"
|
||||
? { otpSms: true }
|
||||
: {},
|
||||
authRequestId,
|
||||
}),
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
if (!res.ok) {
|
||||
const error = await res.json();
|
||||
throw error.details.details;
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// async function submitLogin(inputs: Inputs) {
|
||||
// setLoading(true);
|
||||
|
||||
// const checks: Checks = {};
|
||||
|
||||
// if (method === "email") {
|
||||
// checks.otpEmail = {
|
||||
// code: inputs.code,
|
||||
// };
|
||||
// }
|
||||
|
||||
// if (method === "sms") {
|
||||
// checks.otpSms = {
|
||||
// code: inputs.code,
|
||||
// };
|
||||
// }
|
||||
|
||||
// const res = await fetch("/api/session", {
|
||||
// method: "PUT",
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// body: JSON.stringify({
|
||||
// loginName,
|
||||
// sessionId,
|
||||
// organization,
|
||||
// authRequestId,
|
||||
// checks,
|
||||
// }),
|
||||
// });
|
||||
|
||||
// const response = await res.json();
|
||||
|
||||
// setLoading(false);
|
||||
// if (!res.ok) {
|
||||
// setError(response.details);
|
||||
// return Promise.reject(response.details);
|
||||
// }
|
||||
// return response;
|
||||
// }
|
||||
|
||||
async function submitCode(values: Inputs, organization?: string) {
|
||||
setLoading(true);
|
||||
@@ -58,12 +150,29 @@ export default function TOTPForm({
|
||||
body.authRequestId = authRequestId;
|
||||
}
|
||||
|
||||
const res = await fetch("/api/otp/verify", {
|
||||
const checks: Checks = {};
|
||||
if (method === "sms") {
|
||||
checks.otpSms = { code: values.code };
|
||||
}
|
||||
if (method === "email") {
|
||||
checks.otpEmail = { code: values.code };
|
||||
}
|
||||
if (method === "time-based") {
|
||||
checks.totp = { code: values.code };
|
||||
}
|
||||
|
||||
const res = await fetch("/api/session", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
body: JSON.stringify({
|
||||
loginName,
|
||||
sessionId,
|
||||
organization,
|
||||
checks,
|
||||
authRequestId,
|
||||
}),
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
@@ -112,13 +221,6 @@ export default function TOTPForm({
|
||||
|
||||
const { errors } = formState;
|
||||
|
||||
useEffect(() => {
|
||||
if (submit && code) {
|
||||
// When we navigate to this page, we always want to be redirected if submit is true and the parameters are valid.
|
||||
setCodeAndContinue({ code }, organization);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<form className="w-full">
|
||||
<div className="">
|
||||
@@ -6,6 +6,7 @@ import { coerceToArrayBuffer, coerceToBase64Url } from "#/utils/base64";
|
||||
import { Button, ButtonVariants } from "./Button";
|
||||
import Alert from "./Alert";
|
||||
import { Spinner } from "./Spinner";
|
||||
import { Checks } from "@zitadel/server";
|
||||
|
||||
// either loginName or sessionId must be provided
|
||||
type Props = {
|
||||
@@ -100,7 +101,9 @@ export default function LoginPasskey({
|
||||
loginName,
|
||||
sessionId,
|
||||
organization,
|
||||
webAuthN: { credentialAssertionData: data },
|
||||
checks: {
|
||||
webAuthN: { credentialAssertionData: data },
|
||||
} as Checks,
|
||||
authRequestId,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useForm } from "react-hook-form";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Spinner } from "./Spinner";
|
||||
import Alert from "./Alert";
|
||||
import { LoginSettings, AuthFactor } from "@zitadel/server";
|
||||
import { LoginSettings, AuthFactor, Checks } from "@zitadel/server";
|
||||
|
||||
type Inputs = {
|
||||
password: string;
|
||||
@@ -52,7 +52,9 @@ export default function PasswordForm({
|
||||
body: JSON.stringify({
|
||||
loginName,
|
||||
organization,
|
||||
password: values.password,
|
||||
checks: {
|
||||
password: { password: values.password },
|
||||
} as Checks,
|
||||
authRequestId,
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user