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 { getBrandingSettings, getLoginSettings, server } from "#/lib/zitadel";
|
||||||
import DynamicTheme from "#/ui/DynamicTheme";
|
import DynamicTheme from "#/ui/DynamicTheme";
|
||||||
import TOTPForm from "#/ui/TOTPForm";
|
import LoginOTP from "#/ui/LoginOTP";
|
||||||
import VerifyU2F from "#/ui/VerifyU2F";
|
import VerifyU2F from "#/ui/VerifyU2F";
|
||||||
|
|
||||||
export default async function Page({
|
export default async function Page({
|
||||||
@@ -15,8 +15,6 @@ export default async function Page({
|
|||||||
|
|
||||||
const { method } = params;
|
const { method } = params;
|
||||||
|
|
||||||
console.log(method);
|
|
||||||
|
|
||||||
const branding = await getBrandingSettings(server, organization);
|
const branding = await getBrandingSettings(server, organization);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -37,15 +35,13 @@ export default async function Page({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{method && ["time-based", "sms", "email"].includes(method) ? (
|
{method && ["time-based", "sms", "email"].includes(method) ? (
|
||||||
<TOTPForm
|
<LoginOTP
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
code={code}
|
|
||||||
method={method}
|
|
||||||
authRequestId={authRequestId}
|
authRequestId={authRequestId}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
submit={submit === "true"}
|
method={method}
|
||||||
/>
|
></LoginOTP>
|
||||||
) : (
|
) : (
|
||||||
<VerifyU2F
|
<VerifyU2F
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { server, deleteSession, listHumanAuthFactors } from "#/lib/zitadel";
|
import {
|
||||||
|
server,
|
||||||
|
deleteSession,
|
||||||
|
listHumanAuthFactors,
|
||||||
|
getSession,
|
||||||
|
} from "#/lib/zitadel";
|
||||||
import {
|
import {
|
||||||
SessionCookie,
|
SessionCookie,
|
||||||
getMostRecentSessionCookie,
|
getMostRecentSessionCookie,
|
||||||
@@ -67,11 +72,10 @@ export async function PUT(request: NextRequest) {
|
|||||||
loginName,
|
loginName,
|
||||||
sessionId,
|
sessionId,
|
||||||
organization,
|
organization,
|
||||||
password,
|
checks,
|
||||||
webAuthN,
|
|
||||||
authRequestId,
|
authRequestId,
|
||||||
|
challenges,
|
||||||
} = body;
|
} = body;
|
||||||
const challenges: RequestChallenges = body.challenges;
|
|
||||||
|
|
||||||
const recentPromise: Promise<SessionCookie> = sessionId
|
const recentPromise: Promise<SessionCookie> = sessionId
|
||||||
? getSessionCookieById(sessionId).catch((error) => {
|
? getSessionCookieById(sessionId).catch((error) => {
|
||||||
@@ -93,16 +97,6 @@ export async function PUT(request: NextRequest) {
|
|||||||
|
|
||||||
return recentPromise
|
return recentPromise
|
||||||
.then((recent) => {
|
.then((recent) => {
|
||||||
const checks: Checks = {};
|
|
||||||
if (password) {
|
|
||||||
checks.password = {
|
|
||||||
password,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (webAuthN) {
|
|
||||||
checks.webAuthN = webAuthN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return setSessionAndUpdateCookie(
|
return setSessionAndUpdateCookie(
|
||||||
recent,
|
recent,
|
||||||
checks,
|
checks,
|
||||||
@@ -111,7 +105,7 @@ export async function PUT(request: NextRequest) {
|
|||||||
).then(async (session) => {
|
).then(async (session) => {
|
||||||
// if password, check if user has MFA methods
|
// if password, check if user has MFA methods
|
||||||
let authFactors;
|
let authFactors;
|
||||||
if (password && session.factors?.user?.id) {
|
if (checks.password && session.factors?.user?.id) {
|
||||||
const response = await listHumanAuthFactors(
|
const response = await listHumanAuthFactors(
|
||||||
server,
|
server,
|
||||||
session.factors?.user?.id
|
session.factors?.user?.id
|
||||||
|
|||||||
@@ -1,35 +1,44 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Button, ButtonVariants } from "./Button";
|
|
||||||
import { TextInput } from "./Input";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { useRouter } from "next/navigation";
|
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 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 = {
|
type Inputs = {
|
||||||
code: string;
|
code: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
export default function LoginOTP({
|
||||||
loginName: string | undefined;
|
|
||||||
sessionId: string | undefined;
|
|
||||||
code: string | undefined;
|
|
||||||
method: string;
|
|
||||||
authRequestId?: string;
|
|
||||||
organization?: string;
|
|
||||||
submit: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function TOTPForm({
|
|
||||||
loginName,
|
loginName,
|
||||||
code,
|
sessionId,
|
||||||
method,
|
|
||||||
authRequestId,
|
authRequestId,
|
||||||
organization,
|
organization,
|
||||||
submit,
|
method,
|
||||||
|
code,
|
||||||
}: Props) {
|
}: 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>({
|
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||||
mode: "onBlur",
|
mode: "onBlur",
|
||||||
defaultValues: {
|
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);
|
// setLoading(false);
|
||||||
const [error, setError] = useState<string>("");
|
// })
|
||||||
|
// .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) {
|
async function submitCode(values: Inputs, organization?: string) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -58,12 +150,29 @@ export default function TOTPForm({
|
|||||||
body.authRequestId = authRequestId;
|
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",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify({
|
||||||
|
loginName,
|
||||||
|
sessionId,
|
||||||
|
organization,
|
||||||
|
checks,
|
||||||
|
authRequestId,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -112,13 +221,6 @@ export default function TOTPForm({
|
|||||||
|
|
||||||
const { errors } = formState;
|
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 (
|
return (
|
||||||
<form className="w-full">
|
<form className="w-full">
|
||||||
<div className="">
|
<div className="">
|
||||||
@@ -6,6 +6,7 @@ import { coerceToArrayBuffer, coerceToBase64Url } from "#/utils/base64";
|
|||||||
import { Button, ButtonVariants } from "./Button";
|
import { Button, ButtonVariants } from "./Button";
|
||||||
import Alert from "./Alert";
|
import Alert from "./Alert";
|
||||||
import { Spinner } from "./Spinner";
|
import { Spinner } from "./Spinner";
|
||||||
|
import { Checks } from "@zitadel/server";
|
||||||
|
|
||||||
// either loginName or sessionId must be provided
|
// either loginName or sessionId must be provided
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -100,7 +101,9 @@ export default function LoginPasskey({
|
|||||||
loginName,
|
loginName,
|
||||||
sessionId,
|
sessionId,
|
||||||
organization,
|
organization,
|
||||||
webAuthN: { credentialAssertionData: data },
|
checks: {
|
||||||
|
webAuthN: { credentialAssertionData: data },
|
||||||
|
} as Checks,
|
||||||
authRequestId,
|
authRequestId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useForm } from "react-hook-form";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Spinner } from "./Spinner";
|
import { Spinner } from "./Spinner";
|
||||||
import Alert from "./Alert";
|
import Alert from "./Alert";
|
||||||
import { LoginSettings, AuthFactor } from "@zitadel/server";
|
import { LoginSettings, AuthFactor, Checks } from "@zitadel/server";
|
||||||
|
|
||||||
type Inputs = {
|
type Inputs = {
|
||||||
password: string;
|
password: string;
|
||||||
@@ -52,7 +52,9 @@ export default function PasswordForm({
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName,
|
loginName,
|
||||||
organization,
|
organization,
|
||||||
password: values.password,
|
checks: {
|
||||||
|
password: { password: values.password },
|
||||||
|
} as Checks,
|
||||||
authRequestId,
|
authRequestId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user