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:
@@ -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 ?? '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user