import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL ?? 'http://localhost:3000' const DASHBOARD_PUBLIC_URL = process.env.NEXT_PUBLIC_DASHBOARD_URL ?? `${WEBSITE_URL.replace(/\/$/, '')}/dashboard` const DASHBOARD_BASE_PATH = '/dashboard' const DEFAULT_SIGN_IN_LOCALE = 'en' const DEFAULT_SIGN_IN_THEME = 'dark' function toDashboardAppPath(pathname: string): string { let normalized = pathname || '/' while (normalized === DASHBOARD_BASE_PATH || normalized.startsWith(`${DASHBOARD_BASE_PATH}/`)) { normalized = normalized.slice(DASHBOARD_BASE_PATH.length) || '/' } return normalized } function toPublicDashboardPath(pathname: string): string { const appPath = toDashboardAppPath(pathname) return appPath === '/' ? DASHBOARD_BASE_PATH : `${DASHBOARD_BASE_PATH}${appPath}` } function resolveSignInPreference(value: string | undefined, allowed: string[], fallback: string) { return value && allowed.includes(value) ? value : fallback } function resolveHomepageSignInUrl(req: NextRequest, redirectPath?: string): URL { const locale = resolveSignInPreference( req.nextUrl.searchParams.get('lang') ?? req.cookies.get('rentaldrivego-language')?.value, ['en', 'fr', 'ar'], DEFAULT_SIGN_IN_LOCALE, ) const theme = resolveSignInPreference( req.nextUrl.searchParams.get('theme') ?? req.cookies.get('rentaldrivego-theme')?.value ?? req.cookies.get('hpc-theme')?.value, ['light', 'dark'], DEFAULT_SIGN_IN_THEME, ) const signInUrl = new URL(`/${locale}/${theme}/sign-in`, new URL(WEBSITE_URL).origin) if (redirectPath) signInUrl.searchParams.set('redirect', toPublicDashboardPath(redirectPath)) for (const key of ['portal', 'embedded']) { const value = req.nextUrl.searchParams.get(key) if (value) signInUrl.searchParams.set(key, value) } return signInUrl } function deduplicatePublicDashboardPath(pathname: string): string | null { if (!pathname.startsWith(`${DASHBOARD_BASE_PATH}${DASHBOARD_BASE_PATH}`)) return null let normalized = pathname while (normalized.startsWith(`${DASHBOARD_BASE_PATH}${DASHBOARD_BASE_PATH}`)) { normalized = normalized.slice(DASHBOARD_BASE_PATH.length) } return normalized || DASHBOARD_BASE_PATH } function resolveProxyUrl(_req: NextRequest, pathname: string): URL { const canonicalDashboard = new URL(DASHBOARD_PUBLIC_URL) const publicPath = pathname.startsWith(DASHBOARD_BASE_PATH) ? pathname : toPublicDashboardPath(pathname) return new URL(publicPath, canonicalDashboard.origin) } function hasExplicitPort(host: string): boolean { return /^[^:]+:\d+$/.test(host) } function isInternalAppPort(port: string): boolean { return ['3000', '3001', '3002', '3004', '4000'].includes(port) } function isInternalHost(host: string | null): boolean { if (!host) return false const hostname = host.split(':')[0]?.toLowerCase() return ['host.docker.internal', 'dashboard', 'admin', 'api', 'homepage', 'carplace'].includes(hostname) } function isProtectedRoute(req: NextRequest) { const pathname = toDashboardAppPath(req.nextUrl.pathname) if (pathname === '/api' || pathname.startsWith('/api/') || pathname === '/trpc' || pathname.startsWith('/trpc/')) return false if (['/sign-in', '/sign-up', '/forgot-password', '/reset-password', '/onboarding', '/verify-email'].some((p) => pathname === p || pathname.startsWith(p + '/'))) return false return pathname === '/' || (pathname.startsWith('/') && !pathname.startsWith('/_next')) } function localJwtMiddleware(req: NextRequest): NextResponse { const token = req.cookies.get('employee_session')?.value const pathname = toDashboardAppPath(req.nextUrl.pathname) if (pathname === '/sign-in') { if (token) { const dashboardUrl = resolveProxyUrl(req, DASHBOARD_BASE_PATH) return NextResponse.redirect(dashboardUrl) } return NextResponse.redirect(resolveHomepageSignInUrl(req, req.nextUrl.searchParams.get('redirect') ?? undefined)) } if (!isProtectedRoute(req)) return NextResponse.next() if (!token) { return NextResponse.redirect(resolveHomepageSignInUrl(req, req.nextUrl.pathname)) } return NextResponse.next() } export default function middleware(req: NextRequest) { if (req.headers.has('x-middleware-subrequest')) { return new NextResponse('Unsupported internal request header', { status: 400 }) } const dedupedPath = deduplicatePublicDashboardPath(req.nextUrl.pathname) if (dedupedPath) { const redirectUrl = req.nextUrl.clone() redirectUrl.pathname = dedupedPath return NextResponse.redirect(redirectUrl) } return localJwtMiddleware(req) } export const config = { matcher: [ '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', '/(api|trpc)(.*)', ], }