From 22bf34c4e030bb91db6bceb80b30a0f737be01fc Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Wed, 18 Feb 2026 15:10:56 -0800 Subject: [PATCH] feat: add Traefik forwardAuth endpoint for Theia IDE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /api/auth/theia-check validates the NextAuth session cookie forwarded by Traefik. Returns 200 for authenticated users with X-Auth-User/Email/Name headers, or 302 redirect to /auth for unauthenticated requests — preserving the original Theia URL as callbackUrl so users land back in the IDE after login. Co-authored-by: Cursor --- app/api/auth/theia-check/route.ts | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 app/api/auth/theia-check/route.ts diff --git a/app/api/auth/theia-check/route.ts b/app/api/auth/theia-check/route.ts new file mode 100644 index 0000000..8a06a9e --- /dev/null +++ b/app/api/auth/theia-check/route.ts @@ -0,0 +1,56 @@ +/** + * GET /api/auth/theia-check + * + * Traefik ForwardAuth endpoint for theia.vibnai.com. + * + * Traefik calls this URL for every request to the Theia IDE, forwarding + * the user's Cookie header via authRequestHeaders. We validate the + * NextAuth session token and return: + * 200 — session valid, Traefik lets the request through + * 302 — no session, redirect browser to Vibn login + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { getToken } from 'next-auth/jwt'; + +const APP_URL = process.env.NEXTAUTH_URL ?? 'https://vibnai.com'; +const THEIA_URL = 'https://theia.vibnai.com'; + +export async function GET(request: NextRequest) { + let token: Awaited> = null; + + try { + token = await getToken({ + req: request, + secret: process.env.NEXTAUTH_SECRET, + }); + } catch { + // If token validation throws, treat as unauthenticated + } + + if (!token) { + // Build a callbackUrl so after login the user lands back in Theia + const forwardedHost = request.headers.get('x-forwarded-host'); + const forwardedProto = request.headers.get('x-forwarded-proto') ?? 'https'; + const forwardedUri = request.headers.get('x-forwarded-uri') ?? '/'; + + const destination = forwardedHost + ? `${forwardedProto}://${forwardedHost}${forwardedUri}` + : THEIA_URL; + + const loginUrl = `${APP_URL}/auth?callbackUrl=${encodeURIComponent(destination)}`; + + return NextResponse.redirect(loginUrl, { status: 302 }); + } + + // Session is valid — pass user identity to Theia via response headers + // (Traefik forwards these to the upstream if authResponseHeaders is set) + return new NextResponse(null, { + status: 200, + headers: { + 'X-Auth-User': token.sub ?? '', + 'X-Auth-Email': (token.email as string) ?? '', + 'X-Auth-Name': (token.name as string) ?? '', + }, + }); +}