"use server"; import { auth } from "@/auth"; import { db } from "@/lib/db"; import { buildSignatureKey, uploadBuffer } from "@/lib/storage"; import { revalidatePath } from "next/cache"; import bcrypt from "bcryptjs"; import { z } from "zod"; type Result = { ok: true } | { error: string }; // ── Change password ─────────────────────────────────────────────────────────── const changePasswordSchema = z.object({ currentPassword: z.string().min(1, "Current password is required"), newPassword: z.string().min(8, "New password must be at least 8 characters"), }); export async function changePassword(formData: FormData): Promise { const session = await auth(); if (!session?.user) return { error: "Unauthorized" }; const parsed = changePasswordSchema.safeParse({ currentPassword: formData.get("currentPassword"), newPassword: formData.get("newPassword"), }); if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" }; const user = await db.user.findUnique({ where: { id: session.user.id }, select: { passwordHash: true }, }); if (!user) return { error: "User not found" }; const valid = await bcrypt.compare(parsed.data.currentPassword, user.passwordHash); if (!valid) return { error: "Current password is incorrect" }; const newHash = await bcrypt.hash(parsed.data.newPassword, 12); await db.user.update({ where: { id: session.user.id }, data: { passwordHash: newHash }, }); return { ok: true }; } // ── Upload signature ────────────────────────────────────────────────────────── export async function saveSignature(formData: FormData): Promise { const session = await auth(); if (!session?.user) return { error: "Unauthorized" }; if (session.user.role !== "MANAGER" && session.user.role !== "SUPERUSER") { return { error: "Only managers and superusers can upload a signature" }; } const file = formData.get("signature") as File | null; if (!file || file.size === 0) return { error: "No file provided" }; if (file.size > 2 * 1024 * 1024) return { error: "Signature must be under 2 MB" }; const allowed = ["image/png", "image/jpeg", "image/jpg", "image/webp"]; if (!allowed.includes(file.type)) { return { error: "Signature must be a PNG, JPG, or WebP image" }; } const ext = file.type === "image/png" ? "png" : file.type === "image/webp" ? "webp" : "jpg"; const key = buildSignatureKey(session.user.id, ext); const buffer = Buffer.from(await file.arrayBuffer()); await uploadBuffer(key, buffer, file.type); await db.user.update({ where: { id: session.user.id }, data: { signatureKey: key }, }); revalidatePath("/profile"); revalidatePath("/approvals"); return { ok: true }; } // ── Remove signature ────────────────────────────────────────────────────────── export async function removeSignature(): Promise { const session = await auth(); if (!session?.user) return { error: "Unauthorized" }; await db.user.update({ where: { id: session.user.id }, data: { signatureKey: null }, }); revalidatePath("/profile"); revalidatePath("/approvals"); return { ok: true }; } // ── Request SuperUser access ────────────────────────────────────────────────── export async function requestSuperUser(formData: FormData): Promise { const session = await auth(); if (!session?.user) return { error: "Unauthorized" }; if (session.user.role === "SUPERUSER" || session.user.role === "ADMIN") { return { error: "You already have elevated access" }; } const reason = (formData.get("reason") as string | null)?.trim() ?? ""; // Check for an existing pending request const existing = await db.superUserRequest.findFirst({ where: { userId: session.user.id, status: "PENDING" }, }); if (existing) return { error: "You already have a pending SuperUser request" }; await db.superUserRequest.create({ data: { userId: session.user.id, reason: reason || null, }, }); revalidatePath("/profile"); return { ok: true }; }