register user api, verify email, resend, set password

This commit is contained in:
peintnermax
2024-09-03 14:15:42 +02:00
parent 2672978a50
commit 07f360a749
12 changed files with 210 additions and 281 deletions

View File

@@ -31,7 +31,7 @@ export default async function Page({
<h1>{sessionFactors?.factors?.user?.displayName ?? "Password"}</h1>
<p className="ztdl-p mb-6 block">Enter your password.</p>
{!sessionFactors && (
{(!sessionFactors || !loginName) && (
<div className="py-4">
<Alert>
Could not get the context of the user. Make sure to enter the
@@ -49,14 +49,16 @@ export default async function Page({
></UserAvatar>
)}
<PasswordForm
loginName={loginName}
authRequestId={authRequestId}
organization={organization}
loginSettings={loginSettings}
promptPasswordless={promptPasswordless === "true"}
isAlternative={alt === "true"}
/>
{loginName && (
<PasswordForm
loginName={loginName}
authRequestId={authRequestId}
organization={organization}
loginSettings={loginSettings}
promptPasswordless={promptPasswordless === "true"}
isAlternative={alt === "true"}
/>
)}
</div>
</DynamicTheme>
);

View File

@@ -1,47 +0,0 @@
import { addHumanUser } from "@/lib/zitadel";
import {
createSessionAndUpdateCookie,
createSessionForUserIdAndUpdateCookie,
} from "@/utils/session";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const body = await request.json();
if (body) {
const {
email,
password,
firstName,
lastName,
organization,
authRequestId,
} = body;
return addHumanUser({
email: email,
firstName,
lastName,
password: password ? password : undefined,
organization,
})
.then((user) => {
return createSessionForUserIdAndUpdateCookie(
user.userId,
password,
undefined,
authRequestId,
).then((session) => {
return NextResponse.json({
userId: user.userId,
sessionId: session.id,
factors: session.factors,
});
});
})
.catch((error) => {
return NextResponse.json(error, { status: 500 });
});
} else {
return NextResponse.error();
}
}

View File

@@ -1,20 +0,0 @@
import { resendEmailCode } from "@/lib/zitadel";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const body = await request.json();
if (body) {
const { userId } = body;
// replace with resend Mail method once its implemented
return resendEmailCode(userId)
.then((resp) => {
return NextResponse.json(resp);
})
.catch((error) => {
return NextResponse.json(error, { status: 500 });
});
} else {
return NextResponse.error();
}
}

View File

@@ -1,31 +0,0 @@
import { listUsers, passwordReset } from "@/lib/zitadel";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const body = await request.json();
if (body) {
const { loginName, organization } = body;
return listUsers({
userName: loginName,
organizationId: organization,
}).then((users) => {
if (
users.details &&
Number(users.details.totalResult) == 1 &&
users.result[0].userId
) {
const userId = users.result[0].userId;
return passwordReset(userId)
.then((resp) => {
return NextResponse.json(resp);
})
.catch((error) => {
return NextResponse.json(error, { status: 500 });
});
} else {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
});
}
}

View File

@@ -1,19 +0,0 @@
import { verifyEmail } from "@/lib/zitadel";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const body = await request.json();
if (body) {
const { userId, code } = body;
return verifyEmail(userId, code)
.then((resp) => {
return NextResponse.json(resp);
})
.catch((error) => {
return NextResponse.json(error, { status: 500 });
});
} else {
return NextResponse.error();
}
}

View File

@@ -0,0 +1,24 @@
"use server";
import { resendEmailCode, verifyEmail } from "@/lib/zitadel";
type VerifyUserByEmailCommand = {
userId: string;
code: string;
};
export async function verifyUserByEmail(command: VerifyUserByEmailCommand) {
const { userId, code } = command;
return verifyEmail(userId, code);
}
type resendVerifyEmailCommand = {
userId: string;
};
export async function resendVerifyEmail(command: resendVerifyEmailCommand) {
const { userId } = command;
// replace with resend Mail method once its implemented
return resendEmailCode(userId);
}

View File

@@ -0,0 +1,27 @@
"use server";
import { listUsers, passwordReset } from "@/lib/zitadel";
type ResetPasswordCommand = {
loginName: string;
organization?: string;
};
export async function resetPassword(command: ResetPasswordCommand) {
const { loginName, organization } = command;
const users = await listUsers({
userName: loginName,
organizationId: organization,
});
if (
!users.details ||
Number(users.details.totalResult) !== 1 ||
users.result[0].userId
) {
throw Error("Could not find user");
}
const userId = users.result[0].userId;
return passwordReset(userId);
}

View File

@@ -0,0 +1,44 @@
"use server";
import { addHumanUser } from "@/lib/zitadel";
import {
createSessionAndUpdateCookie,
createSessionForUserIdAndUpdateCookie,
} from "@/utils/session";
type RegisterUserCommand = {
email: string;
firstName: string;
lastName: string;
password?: string;
organization?: string;
authRequestId?: string;
};
export async function registerUser(command: RegisterUserCommand) {
const { email, password, firstName, lastName, organization, authRequestId } =
command;
const human = await addHumanUser({
email: email,
firstName,
lastName,
password: password ? password : undefined,
organization,
});
if (!human) {
throw Error("Could not create user");
}
return createSessionForUserIdAndUpdateCookie(
human.userId,
password,
undefined,
authRequestId,
).then((session) => {
return {
userId: human.userId,
sessionId: session.id,
factors: session.factors,
};
});
}

View File

@@ -17,6 +17,7 @@ import {
import { create } from "@zitadel/client";
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { updateSession } from "@/lib/server/session";
import { resetPassword } from "@/lib/server/password";
type Inputs = {
password: string;
@@ -24,7 +25,7 @@ type Inputs = {
type Props = {
loginSettings: LoginSettings | undefined;
loginName?: string;
loginName: string;
organization?: string;
authRequestId?: string;
isAlternative?: boolean; // whether password was requested as alternative auth method
@@ -69,30 +70,20 @@ export default function PasswordForm({
return response;
}
async function resetPassword() {
async function resetPasswordAndContinue() {
setError("");
setLoading(true);
const res = await fetch("/api/resetpassword", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
loginName,
organization,
authRequestId,
}),
const response = await resetPassword({
loginName,
organization,
}).catch((error: Error) => {
setLoading(false);
setError(error.message ?? "Could not reset password");
});
const response = await res.json();
setLoading(false);
if (!res.ok) {
console.log(response.details.details);
setError(response.details?.details ?? "Could not verify password");
return Promise.reject(response.details);
}
return response;
}
@@ -237,7 +228,7 @@ export default function PasswordForm({
/>
<button
className="transition-all text-sm hover:text-primary-light-500 dark:hover:text-primary-dark-500"
onClick={() => resetPassword()}
onClick={() => resetPasswordAndContinue()}
type="button"
disabled={loading}
>

View File

@@ -13,7 +13,7 @@ import AuthenticationMethodRadio, {
import Alert from "./Alert";
import BackButton from "./BackButton";
import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb";
import { first } from "node_modules/cypress/types/lodash";
import { registerUser } from "@/lib/server/register";
type Inputs =
| {
@@ -57,24 +57,19 @@ export default function RegisterFormWithoutPassword({
async function submitAndRegister(values: Inputs) {
setLoading(true);
const res = await fetch("/api/registeruser", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: values.email,
firstName: values.firstname,
lastName: values.lastname,
organization: organization,
}),
const response = await registerUser({
email: values.email,
firstName: values.firstname,
lastName: values.lastname,
organization: organization,
}).catch((error) => {
setError(error.message ?? "Could not register user");
setLoading(false);
});
setLoading(false);
if (!res.ok) {
const error = await res.json();
throw new Error(error.details);
}
return res.json();
return response;
}
async function submitAndContinue(
@@ -91,28 +86,28 @@ export default function RegisterFormWithoutPassword({
registerParams.authRequestId = authRequestId;
}
return withPassword
? router.push(`/register?` + new URLSearchParams(registerParams))
: submitAndRegister(value)
.then((session) => {
setError("");
if (withPassword) {
return router.push(`/register?` + new URLSearchParams(registerParams));
} else {
const session = await submitAndRegister(value).catch((error) => {
setError(error.message ?? "Could not register user");
});
const params: any = { loginName: session.factors.user.loginName };
const params = new URLSearchParams({});
if (session?.factors?.user?.loginName) {
params.set("loginName", session.factors?.user?.loginName);
}
if (organization) {
params.organization = organization;
}
if (organization) {
params.set("organization", organization);
}
if (authRequestId) {
params.authRequestId = authRequestId;
}
if (authRequestId) {
params.set("authRequestId", authRequestId);
}
return router.push(`/passkey/add?` + new URLSearchParams(params));
})
.catch((errorDetails: Error) => {
setLoading(false);
setError(errorDetails.message);
});
return router.push(`/passkey/add?` + new URLSearchParams(params));
}
}
const { errors } = formState;

View File

@@ -15,6 +15,7 @@ import { useRouter } from "next/navigation";
import { Spinner } from "./Spinner";
import Alert from "./Alert";
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
import { registerUser } from "@/lib/server/register";
type Inputs =
| {
@@ -56,52 +57,36 @@ export default function SetPasswordForm({
async function submitRegister(values: Inputs) {
setLoading(true);
const res = await fetch("/api/registeruser", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
firstName: firstname,
lastName: lastname,
organization: organization,
authRequestId: authRequestId,
password: values.password,
}),
const response = await registerUser({
email: email,
firstName: firstname,
lastName: lastname,
organization: organization,
authRequestId: authRequestId,
password: values.password,
}).catch((error: Error) => {
setError(error.message ?? "Could not register user");
});
setLoading(false);
if (!res.ok) {
const error = await res.json();
throw new Error(error.details);
if (!response) {
setError("Could not register user");
return;
}
return res.json();
}
const params: any = { userId: response.userId };
function submitAndLink(value: Inputs): Promise<boolean | void> {
return submitRegister(value)
.then((registerResponse) => {
setError("");
if (authRequestId) {
params.authRequestId = authRequestId;
}
if (organization) {
params.organization = organization;
}
if (response && response.sessionId) {
params.sessionId = response.sessionId;
}
setLoading(false);
const params: any = { userId: registerResponse.userId };
if (authRequestId) {
params.authRequestId = authRequestId;
}
if (organization) {
params.organization = organization;
}
if (registerResponse && registerResponse.sessionId) {
params.sessionId = registerResponse.sessionId;
}
return router.push(`/verify?` + new URLSearchParams(params));
})
.catch((errorDetails: Error) => {
setLoading(false);
setError(errorDetails.message);
});
return router.push(`/verify?` + new URLSearchParams(params));
}
const { errors } = formState;
@@ -177,7 +162,7 @@ export default function SetPasswordForm({
!formState.isValid ||
watchPassword !== watchConfirmPassword
}
onClick={handleSubmit(submitAndLink)}
onClick={handleSubmit(submitRegister)}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
continue

View File

@@ -7,6 +7,7 @@ import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
import { Spinner } from "./Spinner";
import Alert from "@/ui/Alert";
import { resendVerifyEmail, verifyUserByEmail } from "@/lib/server/email";
type Inputs = {
code: string;
@@ -50,71 +51,48 @@ export default function VerifyEmailForm({
const router = useRouter();
async function submitCode(values: Inputs) {
setLoading(true);
const res = await fetch("/api/verifyemail", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
code: values.code,
userId,
organization,
}),
});
const response = await res.json();
setLoading(false);
if (!res.ok) {
setError(response.rawMessage);
return Promise.reject(response);
} else {
return response;
}
}
async function resendCode() {
setLoading(true);
const res = await fetch("/api/resendverifyemail", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId,
}),
const response = await resendVerifyEmail({
userId,
}).catch((error: Error) => {
setLoading(false);
setError(error.message);
});
const response = await res.json();
if (!res.ok) {
setLoading(false);
setError(response.details);
return Promise.reject(response);
} else {
setLoading(false);
return response;
}
setLoading(false);
return response;
}
function submitCodeAndContinue(value: Inputs): Promise<boolean | void> {
return submitCode(value).then((resp: any) => {
const params = new URLSearchParams({});
if (organization) {
params.set("organization", organization);
}
if (authRequestId && sessionId) {
params.set("authRequest", authRequestId);
params.set("sessionId", sessionId);
return router.push(`/login?` + params);
} else {
return router.push(`/loginname?` + params);
}
async function submitCodeAndContinue(value: Inputs): Promise<boolean | void> {
setLoading(true);
const verifyResponse = await verifyUserByEmail({
code: value.code,
userId,
}).catch((error: Error) => {
setLoading(false);
setError(error.message);
});
setLoading(false);
if (!verifyResponse) {
setError("Could not verify email");
}
const params = new URLSearchParams({});
if (organization) {
params.set("organization", organization);
}
if (authRequestId && sessionId) {
params.set("authRequest", authRequestId);
params.set("sessionId", sessionId);
return router.push(`/login?` + params);
} else {
return router.push(`/loginname?` + params);
}
}
return (