fix: validate Theia auth via direct Postgres session lookup

Avoid importing authOptions/PrismaClient in the forwardAuth endpoint.
Under --network host (Coolify's build flag), routes that import Prisma
at module evaluation time are silently dropped from the build output.

Instead, read the NextAuth session-token cookie directly and verify
it with a raw SQL query against the sessions table - the same pattern
used by other working API routes via @/lib/db-postgres.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-18 16:33:47 -08:00
parent b9baefed0b
commit efcf20b8f6

View File

@@ -5,29 +5,79 @@
* *
* Traefik calls this URL for every request to the Theia IDE, forwarding * Traefik calls this URL for every request to the Theia IDE, forwarding
* the user's Cookie header via authRequestHeaders. We validate the * the user's Cookie header via authRequestHeaders. We validate the
* NextAuth session and return: * NextAuth database session (strategy: "database") by looking up the
* 200 — session valid, Traefik lets the request through * session token directly in Postgres — avoiding Prisma / authOptions
* 302 — no session, redirect browser to Vibn login * imports that cause build-time issues under --network host.
*
* Returns:
* 200 — valid session, Traefik lets the request through
* 302 — no/expired session, redirect browser to Vibn login
*/ */
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth'; import { query } from '@/lib/db-postgres';
import { authOptions } from '@/lib/auth/authOptions';
export const dynamic = 'force-dynamic';
const APP_URL = process.env.NEXTAUTH_URL ?? 'https://vibnai.com'; const APP_URL = process.env.NEXTAUTH_URL ?? 'https://vibnai.com';
const THEIA_URL = 'https://theia.vibnai.com'; const THEIA_URL = 'https://theia.vibnai.com';
export async function GET(request: NextRequest) { // NextAuth v4 uses these cookie names for database sessions
let session: Awaited<ReturnType<typeof getServerSession>> = null; const SESSION_COOKIE_NAMES = [
'__Secure-next-auth.session-token', // HTTPS (production)
'next-auth.session-token', // HTTP fallback
];
try { export async function GET(request: NextRequest) {
session = await getServerSession(authOptions); // Extract session token from cookies
} catch { let sessionToken: string | null = null;
// Treat any session-validation errors as unauthenticated for (const name of SESSION_COOKIE_NAMES) {
const val = request.cookies.get(name)?.value;
if (val) { sessionToken = val; break; }
} }
if (!session?.user) { if (!sessionToken) {
// Build a callbackUrl so the user lands back in Theia after login return redirectToLogin(request);
}
// Look up session in Postgres (NextAuth stores sessions in the "sessions" table)
let userEmail: string | null = null;
let userName: string | null = null;
try {
const result = await query<{ email: string; name: string }>(
`SELECT u.email, u.name
FROM sessions s
JOIN users u ON u.id = s."userId"
WHERE s."sessionToken" = $1
AND s.expires > NOW()
LIMIT 1`,
[sessionToken],
);
if (result.rows.length > 0) {
userEmail = result.rows[0].email;
userName = result.rows[0].name;
}
} catch {
// DB error → treat as unauthenticated
return redirectToLogin(request);
}
if (!userEmail) {
return redirectToLogin(request);
}
// Session valid — pass user identity to Theia via response headers
return new NextResponse(null, {
status: 200,
headers: {
'X-Auth-Email': userEmail,
'X-Auth-Name': userName ?? '',
},
});
}
function redirectToLogin(request: NextRequest): NextResponse {
const forwardedHost = request.headers.get('x-forwarded-host'); const forwardedHost = request.headers.get('x-forwarded-host');
const forwardedProto = request.headers.get('x-forwarded-proto') ?? 'https'; const forwardedProto = request.headers.get('x-forwarded-proto') ?? 'https';
const forwardedUri = request.headers.get('x-forwarded-uri') ?? '/'; const forwardedUri = request.headers.get('x-forwarded-uri') ?? '/';
@@ -37,16 +87,5 @@ export async function GET(request: NextRequest) {
: THEIA_URL; : THEIA_URL;
const loginUrl = `${APP_URL}/auth?callbackUrl=${encodeURIComponent(destination)}`; const loginUrl = `${APP_URL}/auth?callbackUrl=${encodeURIComponent(destination)}`;
return NextResponse.redirect(loginUrl, { status: 302 }); return NextResponse.redirect(loginUrl, { status: 302 });
} }
// Session is valid — forward user identity to Theia via response headers
return new NextResponse(null, {
status: 200,
headers: {
'X-Auth-Email': session.user.email ?? '',
'X-Auth-Name': session.user.name ?? '',
},
});
}