add magic link back
This commit is contained in:
parent
b864da766b
commit
0504460f5b
2 changed files with 101 additions and 20 deletions
|
|
@ -1,20 +1,104 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { cookies } from "next/headers";
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import { sessions } from "@/lib/schema";
|
import { and, eq, gt, isNull } from "drizzle-orm";
|
||||||
|
|
||||||
const sessionId = randomUUID();
|
import { db } from "@/lib/db";
|
||||||
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 14); // 14 days
|
import { loginTokens, sessions, users } from "@/lib/schema";
|
||||||
|
import { hashToken } from "@/lib/auth/tokens";
|
||||||
|
|
||||||
await db.insert(sessions).values({
|
export async function POST(req: Request) {
|
||||||
id: sessionId,
|
// --------------------------------------------------
|
||||||
userId: loginToken.userId,
|
// 1️⃣ Parse token from request
|
||||||
expiresAt,
|
// --------------------------------------------------
|
||||||
});
|
const { token } = await req.json();
|
||||||
|
|
||||||
const cookieStore = await cookies();
|
if (!token) {
|
||||||
cookieStore.set("session", sessionId, {
|
return NextResponse.json(
|
||||||
httpOnly: true,
|
{ error: "Missing token" },
|
||||||
sameSite: "strict",
|
{ status: 400 }
|
||||||
secure: process.env.NODE_ENV === "production",
|
);
|
||||||
path: "/",
|
}
|
||||||
maxAge: 60 * 60 * 24 * 14,
|
|
||||||
});
|
// --------------------------------------------------
|
||||||
|
// 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 });
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'standalone',
|
output: 'standalone'
|
||||||
experimental: {
|
|
||||||
turbo: false, // disables Turbopack in prod builds
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
Loading…
Add table
Reference in a new issue