import { Button, PageHeader, SectionCard } from "@/components/ui"; import { getLocale, getTranslator } from "@/lib/i18n"; import { getRequestAuditContext, writeAuditTrail } from "@/lib/audit"; import { AuthTokenType, UserStatus } from "@prisma/client"; import { consumeAuthToken } from "@/lib/auth-tokens"; import { hashPassword } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; async function resetPassword(formData: FormData) { "use server"; const requestContext = await getRequestAuditContext(); const tokenRaw = formData.get("token"); const passwordRaw = formData.get("password"); const confirmRaw = formData.get("confirmPassword"); const token = typeof tokenRaw === "string" ? tokenRaw : ""; const password = typeof passwordRaw === "string" ? passwordRaw : ""; const confirmPassword = typeof confirmRaw === "string" ? confirmRaw : ""; if (!token || !password || !confirmPassword) { redirect(`/reset-password?token=${encodeURIComponent(token)}&error=missing_fields`); } if (password !== confirmPassword) { redirect(`/reset-password?token=${encodeURIComponent(token)}&error=password_mismatch`); } const resolvedToken = await consumeAuthToken(token, AuthTokenType.PASSWORD_RESET); if (!resolvedToken.valid) { const reason = resolvedToken.reason === "expired" ? "expired_token" : "invalid_token"; redirect(`/reset-password?token=${encodeURIComponent(token)}&error=${reason}`); } const user = await prisma.user.findUnique({ where: { id: resolvedToken.token.userId } }); if (!user) { await writeAuditTrail({ tenantId: resolvedToken.token.tenantId, actorUserId: null, entityType: "user", entityId: resolvedToken.token.userId, action: "password_reset_token_used_no_user", metadata: { reason: "user_not_found", tokenType: AuthTokenType.PASSWORD_RESET }, ipAddress: requestContext.ipAddress, userAgent: requestContext.userAgent }); redirect("/reset-password?error=invalid_token"); } if (user.status !== UserStatus.ACTIVE) { await writeAuditTrail({ tenantId: user.tenantId, actorUserId: user.id, entityType: "user", entityId: user.id, action: "password_reset_denied", metadata: { reason: "invalid_status", status: user.status }, ipAddress: requestContext.ipAddress, userAgent: requestContext.userAgent }); redirect("/reset-password?error=invalid_token"); } await prisma.$transaction([ prisma.user.update({ where: { id: user.id }, data: { passwordHash: await hashPassword(password) } }), prisma.authToken.update({ where: { id: resolvedToken.token.id }, data: { consumedAt: new Date() } }) ]); await writeAuditTrail({ tenantId: user.tenantId, actorUserId: user.id, entityType: "user", entityId: user.id, action: "password_reset_completed", metadata: { source: "web" }, ipAddress: requestContext.ipAddress, userAgent: requestContext.userAgent }); revalidatePath("/login"); redirect("/login?success=password_reset_done"); } export default async function ResetPasswordPage({ searchParams }: { searchParams?: Promise<{ token?: string; error?: string }>; }) { const t = getTranslator(await getLocale()); const params = await ( searchParams ?? Promise.resolve({ token: undefined, error: undefined }) ); const token = typeof params.token === "string" ? params.token : ""; const tokenState = params?.error === "invalid_token" ? t("pages", "invalid_token") : params?.error === "expired_token" ? t("pages", "reset_token_expired") : params?.error === "missing_fields" ? t("login", "error_credentials_required") : params?.error === "password_mismatch" ? t("pages", "password_mismatch") : null; return (
{tokenState ? (

{tokenState}

) : null}
); }