import { NextResponse } from "next/server"; import { cookies } from "next/headers"; import { randomUUID } from "crypto"; import { and, eq, gt, isNull } from "drizzle-orm"; import { db } from "@/lib/db"; import { loginTokens, sessions, users } from "@/lib/schema"; import { hashToken } from "@/lib/auth/tokens"; export async function POST(req: Request) { // -------------------------------------------------- // 1️⃣ Parse token from request // -------------------------------------------------- const { token } = await req.json(); if (!token) { return NextResponse.json( { error: "Missing token" }, { status: 400 } ); } // -------------------------------------------------- // 2️⃣ Validate login token (magic link) // -------------------------------------------------- const tokenHash = hashToken(token); const loginToken = await db .select() .from(loginTokens) .where( and( eq(loginTokens.tokenHash, tokenHash), isNull(loginTokens.usedAt), gt(loginTokens.expiresAt, new Date()) ) ) .limit(1) .then((rows) => rows[0]); if (!loginToken) { return NextResponse.json( { error: "Invalid or expired token" }, { status: 401 } ); } // -------------------------------------------------- // 3️⃣ Ensure user still exists // -------------------------------------------------- const user = await db .select() .from(users) .where(eq(users.id, loginToken.userId)) .limit(1) .then((rows) => rows[0]); if (!user) { return NextResponse.json( { error: "User not found" }, { status: 404 } ); } // -------------------------------------------------- // 4️⃣ Consume login token (one-time use) // -------------------------------------------------- await db .update(loginTokens) .set({ usedAt: new Date() }) .where(eq(loginTokens.id, loginToken.id)); // -------------------------------------------------- // 5️⃣ Create DB-backed session // -------------------------------------------------- const sessionId = randomUUID(); const expiresAt = new Date( Date.now() + 1000 * 60 * 60 * 24 * 14 // 14 days ); await db.insert(sessions).values({ id: sessionId, userId: user.id, expiresAt, }); // -------------------------------------------------- // 6️⃣ Set secure session cookie // -------------------------------------------------- const cookieStore = await cookies(); cookieStore.set("session", sessionId, { httpOnly: true, sameSite: "strict", secure: process.env.NODE_ENV === "production", path: "/", maxAge: 60 * 60 * 24 * 14, // 14 days }); // -------------------------------------------------- // 7️⃣ Done // -------------------------------------------------- return NextResponse.json({ ok: true }); }