mission-control/app/api/auth/reset-password/route.ts

105 lines
2.9 KiB
TypeScript

import { NextResponse } from "next/server";
import { createHash } from "crypto";
import { getServiceSupabase } from "@/lib/supabase/client";
export const runtime = "nodejs";
function hashToken(token: string): string {
return createHash("sha256").update(token).digest("hex");
}
export async function POST(request: Request) {
try {
const body = (await request.json()) as {
token?: string;
email?: string;
password?: string;
};
const token = body.token || "";
const email = (body.email || "").trim().toLowerCase();
const password = body.password || "";
if (!token || !email || !password) {
return NextResponse.json(
{ error: "Token, email, and password are required" },
{ status: 400 }
);
}
if (password.length < 8) {
return NextResponse.json(
{ error: "Password must be at least 8 characters" },
{ status: 400 }
);
}
const supabase = getServiceSupabase();
const tokenHash = hashToken(token);
const now = new Date().toISOString();
// Find valid token with user info
const { data: resetTokenRaw } = await supabase
.from("password_reset_tokens")
.select("id, user_id, users(email, name)")
.eq("token_hash", tokenHash)
.eq("used", false)
.gt("expires_at", now)
.maybeSingle();
const resetToken = resetTokenRaw as
| {
id: string;
user_id: string;
users?:
| {
email?: string;
name?: string;
}
| Array<{
email?: string;
name?: string;
}>;
}
| null;
if (!resetToken) {
return NextResponse.json(
{ error: "Invalid or expired reset token" },
{ status: 400 }
);
}
// Get user email from the nested users object
const userEmail = Array.isArray(resetToken.users)
? resetToken.users[0]?.email
: resetToken.users?.email;
if (userEmail?.toLowerCase() !== email) {
return NextResponse.json({ error: "Invalid reset token" }, { status: 400 });
}
// Strict mode: reset requires an existing Supabase Auth user.
const { error: updateError } = await supabase.auth.admin.updateUserById(resetToken.user_id, {
password,
});
if (updateError) throw updateError;
// Mark token as used
await supabase
.from("password_reset_tokens")
.update({ used: true })
.eq("id", resetToken.id);
// Delete all sessions for this user (force re-login)
await supabase.from("sessions").delete().eq("user_id", resetToken.user_id);
return NextResponse.json({
success: true,
message: "Password reset successfully",
});
} catch (error) {
console.error("Reset password error:", error);
return NextResponse.json({ error: "Failed to reset password" }, { status: 500 });
}
}