fix(dashboard): proxy API auth on custom domains

This commit is contained in:
Rotimi Best
2026-05-28 08:57:28 +01:00
parent 730bcee553
commit 7ee544230f
2 changed files with 81 additions and 0 deletions
+6
View File
@@ -1,6 +1,7 @@
import { getSessionData } from '$lib/utils/services/auth/session';
import { getHasCioCookies } from '$lib/utils/functions/cookies';
import { applyCspExtensions } from '$lib/utils/csp';
import { proxyRequestToApi, shouldForwardToApi } from '$lib/utils/proxy-api-request';
import { type Handle, type HandleServerError, redirect } from '@sveltejs/kit';
import { isPublicApiRoute, isPublicRoute } from '$lib/utils/functions/routes/isPublicRoute';
import { ROUTE } from '$lib/utils/constants/routes';
@@ -35,6 +36,11 @@ function ensureAnalyticsSessionCookie(cookies: Parameters<Handle>[0]['event']['c
export const handle: Handle = async (args) => {
const { event } = args;
if (shouldForwardToApi(event.url.pathname)) {
return proxyRequestToApi(event.request);
}
const sessionData = await getSessionData(event.cookies);
if (sessionData) {
@@ -0,0 +1,75 @@
/**
* Mirrors `apps/tenant-router` path splitting for BYOD custom domains.
*
* Approximated forwards customer domains straight to the dashboard Render
* service, so `/proxy/*` and `/api/auth/*` never reach the API unless we
* forward them here. Tenant subdomains on `*.myclassroomio.com` hit the
* Cloudflare Worker first and normally never exercise this path.
*/
const PROXY_PREFIX = '/proxy';
const AUTH_PREFIX = '/api/auth';
export function shouldForwardToApi(pathname: string): boolean {
return (
pathname === PROXY_PREFIX ||
pathname.startsWith(`${PROXY_PREFIX}/`) ||
pathname === AUTH_PREFIX ||
pathname.startsWith(`${AUTH_PREFIX}/`)
);
}
function resolveApiUpstreamPath(pathname: string): string {
if (pathname === PROXY_PREFIX || pathname.startsWith(`${PROXY_PREFIX}/`)) {
return pathname.slice(PROXY_PREFIX.length) || '/';
}
return pathname;
}
function resolveApiUpstreamBase(): string | null {
const base = process.env.PRIVATE_SERVER_URL || process.env.PUBLIC_SERVER_URL;
if (!base) return null;
return base.replace(/\/$/, '');
}
function resolveOriginalHost(request: Request, url: URL): string {
return request.headers.get('x-forwarded-host') ?? request.headers.get('host') ?? url.host;
}
export async function proxyRequestToApi(request: Request): Promise<Response> {
const url = new URL(request.url);
const apiBase = resolveApiUpstreamBase();
if (!apiBase) {
console.error('proxyRequestToApi: missing PRIVATE_SERVER_URL / PUBLIC_SERVER_URL');
return new Response('API upstream not configured', { status: 502 });
}
const upstreamPath = resolveApiUpstreamPath(url.pathname);
const upstreamUrl = new URL(`${upstreamPath}${url.search}`, apiBase);
const originalHost = resolveOriginalHost(request, url);
const upstreamHeaders = new Headers(request.headers);
upstreamHeaders.set('host', upstreamUrl.host);
upstreamHeaders.set('x-forwarded-host', originalHost);
upstreamHeaders.set('x-forwarded-proto', url.protocol.replace(':', ''));
const forwardedFor = request.headers.get('cf-connecting-ip') ?? request.headers.get('x-forwarded-for');
if (forwardedFor) {
upstreamHeaders.set('x-forwarded-for', forwardedFor);
}
const init: RequestInit = {
method: request.method,
headers: upstreamHeaders,
redirect: 'manual'
};
if (request.method !== 'GET' && request.method !== 'HEAD') {
init.body = await request.arrayBuffer();
}
return fetch(upstreamUrl, init);
}