fix #12: remove all auth.api.getSession() calls
- middleware.ts: cookie-presence check only (Edge Runtime can't use DB), skip auth for API routes entirely - compare/route.ts: manual session token parsing + db.select() queries - user/comparisons/route.ts: same manual auth bypass - user/stats/route.ts: same manual auth bypass Root cause: Drizzle 0.45.2 queryWithCache bug triggers when auth.api.getSession() is called from non-route-handler contexts. Bypass entirely with direct db.select() on sessions/users tables.
This commit is contained in:
@@ -1,22 +1,46 @@
|
|||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { comparisons, comparisonItems } from "@/lib/db/schema";
|
import { comparisons, comparisonItems, sessions, users } from "@/lib/db/schema";
|
||||||
import { eq, desc, sql, inArray } from "drizzle-orm";
|
import { eq, desc, sql, inArray, and, gt } from "drizzle-orm";
|
||||||
import { auth } from "@/lib/auth";
|
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
const session = await auth.api.getSession({ headers: await headers() });
|
// Bypass auth.api.getSession() — Drizzle queryWithCache bug (#12)
|
||||||
|
const hdrs = await headers();
|
||||||
if (!session?.user) {
|
const cookieHeader = hdrs.get("cookie") ?? "";
|
||||||
|
const tokenMatch = cookieHeader
|
||||||
|
.split(";")
|
||||||
|
.find((c) => c.trim().startsWith("better-auth.session_token="));
|
||||||
|
const token = tokenMatch?.split("=")?.[1]?.trim();
|
||||||
|
if (!token) {
|
||||||
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sessionRows = await db
|
||||||
|
.select()
|
||||||
|
.from(sessions)
|
||||||
|
.where(and(eq(sessions.token, token), gt(sessions.expiresAt, new Date())))
|
||||||
|
.limit(1);
|
||||||
|
if (!sessionRows.length) {
|
||||||
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userRows = await db
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, sessionRows[0].userId))
|
||||||
|
.limit(1);
|
||||||
|
if (!userRows.length) {
|
||||||
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = userRows[0].id;
|
||||||
|
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const page = Math.max(1, Number(searchParams.get("page")) || 1);
|
const page = Math.max(1, Number(searchParams.get("page")) || 1);
|
||||||
const limit = Math.min(100, Math.max(1, Number(searchParams.get("limit")) || 20));
|
const limit = Math.min(100, Math.max(1, Number(searchParams.get("limit")) || 20));
|
||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
const where = eq(comparisons.userId, session.user.id);
|
const where = eq(comparisons.userId, userId);
|
||||||
|
|
||||||
const [result, countResult] = await Promise.all([
|
const [result, countResult] = await Promise.all([
|
||||||
db
|
db
|
||||||
|
|||||||
@@ -1,23 +1,47 @@
|
|||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { comparisons } from "@/lib/db/schema";
|
import { comparisons, sessions, users } from "@/lib/db/schema";
|
||||||
import { eq, sql } from "drizzle-orm";
|
import { eq, sql, and, gt } from "drizzle-orm";
|
||||||
import { auth } from "@/lib/auth";
|
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const session = await auth.api.getSession({ headers: await headers() });
|
// Bypass auth.api.getSession() — Drizzle queryWithCache bug (#12)
|
||||||
|
const hdrs = await headers();
|
||||||
if (!session?.user) {
|
const cookieHeader = hdrs.get("cookie") ?? "";
|
||||||
|
const tokenMatch = cookieHeader
|
||||||
|
.split(";")
|
||||||
|
.find((c) => c.trim().startsWith("better-auth.session_token="));
|
||||||
|
const token = tokenMatch?.split("=")?.[1]?.trim();
|
||||||
|
if (!token) {
|
||||||
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sessionRows = await db
|
||||||
|
.select()
|
||||||
|
.from(sessions)
|
||||||
|
.where(and(eq(sessions.token, token), gt(sessions.expiresAt, new Date())))
|
||||||
|
.limit(1);
|
||||||
|
if (!sessionRows.length) {
|
||||||
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userRows = await db
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, sessionRows[0].userId))
|
||||||
|
.limit(1);
|
||||||
|
if (!userRows.length) {
|
||||||
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = userRows[0].id;
|
||||||
|
|
||||||
const result = await db
|
const result = await db
|
||||||
.select({
|
.select({
|
||||||
totalComparisons: sql<number>`count(*)`,
|
totalComparisons: sql<number>`count(*)`,
|
||||||
totalViews: sql<number>`coalesce(sum(${comparisons.viewCount}), 0)`,
|
totalViews: sql<number>`coalesce(sum(${comparisons.viewCount}), 0)`,
|
||||||
})
|
})
|
||||||
.from(comparisons)
|
.from(comparisons)
|
||||||
.where(eq(comparisons.userId, session.user.id));
|
.where(eq(comparisons.userId, userId));
|
||||||
|
|
||||||
return Response.json(result[0]);
|
return Response.json(result[0]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { auth } from "@/lib/auth";
|
|
||||||
|
|
||||||
const publicPaths = ["/", "/explore", "/sign-in", "/sign-up", "/api/auth"];
|
const publicPaths = ["/", "/explore", "/sign-in", "/sign-up", "/api/auth"];
|
||||||
const protectedPaths = ["/compare", "/profile"];
|
const protectedPaths = ["/compare", "/profile"];
|
||||||
|
|
||||||
|
function hasSessionCookie(headers: Headers): boolean {
|
||||||
|
const cookieHeader = headers.get("cookie") ?? "";
|
||||||
|
return cookieHeader
|
||||||
|
.split(";")
|
||||||
|
.some((c) => c.trim().startsWith("better-auth.session_token="));
|
||||||
|
}
|
||||||
|
|
||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
const { pathname } = request.nextUrl;
|
const { pathname } = request.nextUrl;
|
||||||
|
|
||||||
@@ -15,6 +21,11 @@ export async function middleware(request: NextRequest) {
|
|||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API routes handle their own auth — skip middleware session check
|
||||||
|
if (pathname.startsWith("/api/")) {
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
const isPublic = publicPaths.some(
|
const isPublic = publicPaths.some(
|
||||||
(path) => pathname === path || pathname.startsWith(path + "/"),
|
(path) => pathname === path || pathname.startsWith(path + "/"),
|
||||||
);
|
);
|
||||||
@@ -27,11 +38,9 @@ export async function middleware(request: NextRequest) {
|
|||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await auth.api.getSession({
|
// Cookie-presence check only — real auth happens in route handlers.
|
||||||
headers: request.headers,
|
// auth.api.getSession() bypassed due to Drizzle queryWithCache bug (#12).
|
||||||
});
|
if (!hasSessionCookie(request.headers) && isProtected) {
|
||||||
|
|
||||||
if (!session && isProtected) {
|
|
||||||
const signInUrl = new URL("/sign-in", request.url);
|
const signInUrl = new URL("/sign-in", request.url);
|
||||||
signInUrl.searchParams.set("callbackUrl", pathname);
|
signInUrl.searchParams.set("callbackUrl", pathname);
|
||||||
return NextResponse.redirect(signInUrl);
|
return NextResponse.redirect(signInUrl);
|
||||||
|
|||||||
Reference in New Issue
Block a user