diff --git a/stripe_to_invoice/app/api/auth/callback/route.ts b/stripe_to_invoice/app/api/auth/callback/route.ts index 44dbc8f..09097af 100644 --- a/stripe_to_invoice/app/api/auth/callback/route.ts +++ b/stripe_to_invoice/app/api/auth/callback/route.ts @@ -1,20 +1,104 @@ +import { NextResponse } from "next/server"; +import { cookies } from "next/headers"; import { randomUUID } from "crypto"; -import { sessions } from "@/lib/schema"; +import { and, eq, gt, isNull } from "drizzle-orm"; -const sessionId = randomUUID(); -const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 14); // 14 days +import { db } from "@/lib/db"; +import { loginTokens, sessions, users } from "@/lib/schema"; +import { hashToken } from "@/lib/auth/tokens"; -await db.insert(sessions).values({ - id: sessionId, - userId: loginToken.userId, - expiresAt, -}); +export async function POST(req: Request) { + // -------------------------------------------------- + // 1️⃣ Parse token from request + // -------------------------------------------------- + const { token } = await req.json(); -const cookieStore = await cookies(); -cookieStore.set("session", sessionId, { - httpOnly: true, - sameSite: "strict", - secure: process.env.NODE_ENV === "production", - path: "/", - maxAge: 60 * 60 * 24 * 14, -}); + 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 }); +} diff --git a/stripe_to_invoice/next.config.ts b/stripe_to_invoice/next.config.ts index 8843504..803b6e9 100644 --- a/stripe_to_invoice/next.config.ts +++ b/stripe_to_invoice/next.config.ts @@ -1,10 +1,7 @@ import type { NextConfig } from "next"; const nextConfig = { - output: 'standalone', - experimental: { - turbo: false, // disables Turbopack in prod builds - }, + output: 'standalone' }; export default nextConfig; \ No newline at end of file