import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' const PUBLIC_PREFIXES = [ '/login', '/api/auth', '/api/trpc/stellen.listPublic', '/api/setup', '/registrierung', '/impressum', '/datenschutz', ] const PUBLIC_EXACT_PATHS = ['/'] // Reserved subdomains that shouldn't be treated as tenant slugs const RESERVED_SUBDOMAINS = ['www', 'app', 'admin', 'localhost', 'superadmin', 'api'] export function middleware(request: NextRequest) { const url = request.nextUrl const pathname = url.pathname // 1. Subdomain Extraction const hostname = request.headers.get('host') || '' const domainParts = hostname.split(':')[0].split('.') let slug = null // For localhost: tischler.localhost -> parts: ['tischler', 'localhost'] // For production: tischler.innungsapp.de -> parts: ['tischler', 'innungsapp', 'de'] if ( domainParts.length > 2 || (domainParts.length === 2 && domainParts[1] === 'localhost') ) { const potentialSlug = domainParts[0] if (!RESERVED_SUBDOMAINS.includes(potentialSlug)) { slug = potentialSlug } } // Allow static files from /public const isStaticFile = pathname.includes('.') && !pathname.startsWith('/api') const isPublic = isStaticFile || PUBLIC_EXACT_PATHS.includes(pathname) || PUBLIC_PREFIXES.some((p) => pathname.startsWith(p)) // 2. Auth Check const sessionToken = request.cookies.get('better-auth.session_token') ?? request.cookies.get('__Secure-better-auth.session_token') if (!isPublic && !sessionToken) { const loginUrl = new URL('/login', request.url) loginUrl.searchParams.set('callbackUrl', pathname) return NextResponse.redirect(loginUrl) } // 3. Subdomain Redirection / Rewrite if (slug) { // Paths that should not be rewritten into the slug folder // because they are shared across the entire app const SHARED_PATHS = ['/login', '/api', '/superadmin', '/registrierung', '/impressum', '/datenschutz', '/passwort-aendern'] const isSharedPath = SHARED_PATHS.some((p) => pathname.startsWith(p)) || pathname.startsWith('/_next') if (!isSharedPath && !pathname.startsWith(`/${slug}`)) { const rewriteUrl = request.nextUrl.clone() rewriteUrl.pathname = `/${slug}${pathname === '/' ? '' : pathname}` return NextResponse.rewrite(rewriteUrl) } } else { // Check if the user is trying to access a path that starts with a potential slug // but they are on the root domain. // Example: localhost/tischler/... should redirect to tischler.localhost/... const pathParts = pathname.split('/') if (pathParts.length > 1) { const potentialSlug = pathParts[1] // Check if it's a known non-reserved path but could be an organization slug // We don't want to redirect /login, /api, etc. const SHARED_PATHS = ['login', 'api', 'superadmin', 'dashboard', 'registrierung', 'impressum', 'datenschutz', '_next', 'uploads', 'favicon.ico', 'passwort-aendern'] if (potentialSlug && !SHARED_PATHS.includes(potentialSlug)) { // This looks like a tenant path being accessed from the root domain. // Redirect to subdomain. const baseHost = hostname.split('.').slice(-2).join('.') // Simplistic, assumes domain.tld or localhost // For localhost it's special const isLocalhost = hostname.includes('localhost') const newHost = isLocalhost ? `${potentialSlug}.localhost${hostname.includes(':') ? `:${hostname.split(':')[1]}` : ''}` : `${potentialSlug}.${baseHost}` const remainingPath = '/' + pathParts.slice(2).join('/') return NextResponse.redirect(new URL(remainingPath, `${url.protocol}//${newHost}`)) } } } return NextResponse.next() } export const config = { matcher: [ '/((?!_next/static|_next/image|favicon.ico|uploads).*)', ], }