Files
Order-Loop/app/actions/auth.ts
2026-06-02 10:23:09 +03:00

203 lines
5.1 KiB
TypeScript

'use server';
import { redirect } from "next/navigation";
import { prisma } from "@/lib/prisma";
import { hashPassword, verifyPassword, generateToken } from "@/lib/crypto";
import {
createSession,
deleteSession,
requireCurrentUser,
} from "@/lib/auth";
import { Resend } from "resend";
import type { FormState } from "@/lib/types";
export async function login(
prevState: FormState | undefined,
formData: FormData,
): Promise<FormState> {
const email = formData.get("email") as string;
const password = formData.get("password") as string;
if (!email || !password) {
return { message: "Email and password are required" };
}
const user = await prisma.user.findUnique({ where: { email } });
if (!user) {
return { message: "Invalid email or password" };
}
const valid = await verifyPassword(password, user.passwordHash);
if (!valid) {
return { message: "Invalid email or password" };
}
await createSession(user.id);
redirect("/");
}
export async function logout(): Promise<never> {
await deleteSession();
redirect("/login");
}
export async function changePassword(
prevState: FormState | undefined,
formData: FormData,
): Promise<FormState> {
const currentUser = await requireCurrentUser();
const currentPassword = formData.get("currentPassword") as string;
const newPassword = formData.get("newPassword") as string;
const confirmPassword = formData.get("confirmPassword") as string;
if (!currentPassword || !newPassword || !confirmPassword) {
return { message: "All fields are required" };
}
if (newPassword.length < 8) {
return {
errors: { newPassword: ["Password must be at least 8 characters"] },
message: "Password must be at least 8 characters",
};
}
if (newPassword !== confirmPassword) {
return {
errors: { confirmPassword: ["Passwords do not match"] },
message: "Passwords do not match",
};
}
const user = await prisma.user.findUnique({
where: { id: currentUser.id },
});
if (!user) {
return { message: "User not found" };
}
const valid = await verifyPassword(currentPassword, user.passwordHash);
if (!valid) {
return {
errors: { currentPassword: ["Current password is incorrect"] },
message: "Current password is incorrect",
};
}
const newHash = await hashPassword(newPassword);
await prisma.user.update({
where: { id: user.id },
data: { passwordHash: newHash },
});
return { message: "Password changed successfully", success: true };
}
export async function forgotPassword(
prevState: FormState | undefined,
formData: FormData,
): Promise<FormState> {
const email = formData.get("email") as string;
if (!email) {
return { message: "Email is required" };
}
const user = await prisma.user.findUnique({ where: { email } });
if (user) {
const token = generateToken();
await prisma.passwordResetToken.create({
data: {
token,
userId: user.id,
expiresAt: new Date(Date.now() + 60 * 60 * 1000),
},
});
const resetLink = `${process.env.NEXT_PUBLIC_APP_URL}/reset-password?token=${token}`;
if (process.env.RESEND_API_KEY) {
try {
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: "Order Loop <onboarding@resend.dev>",
to: email,
subject: "Reset your password",
html: `<p>Click <a href="${resetLink}">here</a> to reset your password. This link expires in 1 hour.</p>`,
});
} catch (error) {
console.error("Failed to send reset email:", error);
console.log("Password reset link:", resetLink);
}
} else {
console.log("Password reset link:", resetLink);
}
}
return {
message: "If an account exists, a reset link has been sent",
success: true,
};
}
export async function resetPassword(
prevState: FormState | undefined,
formData: FormData,
): Promise<FormState> {
const token = formData.get("token") as string;
const password = formData.get("password") as string;
const confirmPassword = formData.get("confirmPassword") as string;
if (!token || !password || !confirmPassword) {
return { message: "All fields are required" };
}
if (password.length < 8) {
return {
errors: { password: ["Password must be at least 8 characters"] },
message: "Password must be at least 8 characters",
};
}
if (password !== confirmPassword) {
return {
errors: { confirmPassword: ["Passwords do not match"] },
message: "Passwords do not match",
};
}
const resetToken = await prisma.passwordResetToken.findUnique({
where: { token },
});
if (!resetToken || resetToken.used || resetToken.expiresAt < new Date()) {
return { message: "Invalid or expired reset token" };
}
const newHash = await hashPassword(password);
await prisma.user.update({
where: { id: resetToken.userId },
data: { passwordHash: newHash },
});
await prisma.passwordResetToken.update({
where: { id: resetToken.id },
data: { used: true },
});
return {
message: "Password reset successfully. You can now log in.",
success: true,
};
}