spacerr
Version:
This starter pack provides a comprehensive Next.js setup, built on top of `create-next-app`, and includes additional features. Developed by spacerrr.
206 lines (170 loc) • 5.41 kB
text/typescript
"use server";
import { signIn, signOut } from "@/packages/auth";
import {
recoverPasswordSchema,
resetPasswordSchema,
signInFormSchema,
signUpFormSchema,
verifyUserEmailSchema,
} from "@/packages/auth/schemas";
import {
createUser,
findUserByEmail,
findVerificationToken,
storeEmailVerificationToken,
updatePassword,
verifyEmailVerificationToken,
} from "@/packages/database/actions";
import { sendEmailVerificationLink, sendPasswordResetEmailLink } from "@/packages/resend/actions";
import { Prisma } from "@prisma/client";
import { AuthError } from "next-auth";
import { v4 as uuidv4 } from "uuid";
import { type CustomErrorType } from "@/lib/error";
import { routes } from "@/lib/routes";
export const createTokenAndSendVeriticationLink = async (email: string) => {
const token = uuidv4();
await storeEmailVerificationToken(email, token);
return await sendEmailVerificationLink(email, token);
};
export const verifyUserEmail = async (_: string | undefined, formData: FormData) => {
try {
const credentials = verifyUserEmailSchema.safeParse({
token: formData.get("token"),
email: formData.get("email"),
});
if (!credentials.success) {
return credentials.error.errors?.[0]?.message;
}
const { email: identifier, token } = credentials.data;
await verifyEmailVerificationToken({ identifier, token });
return "success";
} catch (error) {
console.error("Email verification failed:", error);
return "failed";
}
};
export async function authenticate(_: string | undefined, formData: FormData) {
try {
const credentials = signInFormSchema.safeParse({
email: formData.get("email"),
password: formData.get("password"),
});
if (!credentials.success) {
return credentials.error.errors?.[0]?.message;
}
await signIn("credentials", {
...credentials.data,
redirectTo: routes.root,
});
return "success";
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case "CredentialsSignin":
if ((error.cause?.type as CustomErrorType) === "EmailVerificationError") {
return "EmailVerificationError";
}
return "Invalid Credentials";
default:
return "Invalid Credentials";
}
}
throw error;
}
}
export const loginWithGoogle = async () => {
try {
await signIn("google", { redirectTo: routes.root });
} catch (error) {
throw error;
}
};
export const logOut = async () => signOut({ redirectTo: routes.root });
export const signUpUser = async (_: string | undefined, formData: FormData) => {
try {
const credentials = signUpFormSchema.safeParse({
email: formData.get("email"),
password: formData.get("password"),
confirmPassword: formData.get("confirm-password"),
name: formData.get("name"),
});
if (!credentials.success) {
return credentials.error.errors?.[0]?.message;
}
await createUser(credentials.data);
const { error } = await createTokenAndSendVeriticationLink(credentials.data.email);
if (error) {
console.error(error);
throw new Error("UnknownError");
}
return "success";
} catch (error) {
if (error instanceof AuthError) {
console.error(error);
switch (error.type) {
case "CredentialsSignin":
return "Invalid Credentials";
default:
return "Invalid Credentials";
}
}
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
if (error.code === "P2002") {
return "User with this email already exists";
}
}
throw error;
}
};
export const sendPasswordRecoveryEmail = async (_: string | undefined, formData: FormData) => {
try {
const credentials = recoverPasswordSchema.safeParse({ email: formData.get("email") });
if (!credentials.success) {
return credentials.error.errors?.[0]?.message;
}
const { email } = credentials.data;
const user = await findUserByEmail(email);
if (!user) {
return "Invalid Credentials";
}
const token = uuidv4();
await storeEmailVerificationToken(email, token);
const { error } = await sendPasswordResetEmailLink({
email,
token,
name: user?.name || "User",
});
if (error) {
console.error(error);
return "Invalid Credentials";
}
return "success";
} catch (err) {
console.error(err);
return "Invalid Credentials";
}
};
export const resetPassword = async (_: string | undefined, formData: FormData) => {
try {
const credentials = resetPasswordSchema.safeParse({
token: formData.get("token"),
password: formData.get("password"),
confirmPassword: formData.get("confirm-password"),
email: formData.get("email"),
});
if (!credentials.success) {
return credentials.error.errors?.[0]?.message;
}
const { token, email, password } = credentials.data;
const verificationToken = await findVerificationToken(email, token);
if (!verificationToken || verificationToken.expires < new Date()) {
return "expired";
}
await updatePassword({ email, password, token, identifier: verificationToken.identifier });
return "success";
} catch (error) {
console.error(error);
return "Invalid Credentials";
}
};