82 lines
2.9 KiB
TypeScript
82 lines
2.9 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import type { NextRequest } from 'next/server'
|
|
|
|
const PUBLIC_DASHBOARD_PREFIXES = ['/dashboard/sign-in', '/dashboard/forgot-password']
|
|
const MARKETPLACE_URL = process.env.NEXT_PUBLIC_MARKETPLACE_URL ?? 'http://localhost:3000'
|
|
|
|
|
|
function rejectInternalSubrequest(req: NextRequest): NextResponse | null {
|
|
if (!req.headers.has('x-middleware-subrequest')) return null
|
|
return new NextResponse('Unsupported internal request header', { status: 400 })
|
|
}
|
|
|
|
function dedupeDashboardPath(pathname: string): string {
|
|
return pathname.replace(/^\/dashboard\/dashboard(?=\/|$)/, '/dashboard')
|
|
}
|
|
|
|
function isInternalHost(host: string | null): boolean {
|
|
if (!host) return false
|
|
const hostname = host.split(':')[0]?.toLowerCase()
|
|
return ['host.docker.internal', 'dashboard', 'admin', 'api', 'marketplace'].includes(hostname)
|
|
}
|
|
|
|
function isProtectedRoute(req: NextRequest) {
|
|
const { pathname } = req.nextUrl
|
|
if (PUBLIC_DASHBOARD_PREFIXES.some((p) => pathname === p || pathname.startsWith(p + '/'))) return false
|
|
return pathname === '/dashboard' || pathname.startsWith('/dashboard/')
|
|
}
|
|
|
|
function localJwtMiddleware(req: NextRequest): NextResponse {
|
|
const token = req.cookies.get('employee_session')?.value
|
|
if (token && req.nextUrl.pathname === '/dashboard/sign-in') {
|
|
const dashboardUrl = req.nextUrl.clone()
|
|
dashboardUrl.pathname = '/dashboard'
|
|
dashboardUrl.search = ''
|
|
return NextResponse.redirect(dashboardUrl)
|
|
}
|
|
|
|
if (!isProtectedRoute(req)) return NextResponse.next()
|
|
|
|
if (!token) {
|
|
const forwardedHost = req.headers.get('x-forwarded-host')
|
|
const forwardedProto = req.headers.get('x-forwarded-proto')
|
|
const marketplaceOrigin = new URL(MARKETPLACE_URL)
|
|
|
|
const signInUrl = req.nextUrl.clone()
|
|
if (forwardedHost && !isInternalHost(forwardedHost)) {
|
|
signInUrl.host = forwardedHost
|
|
signInUrl.protocol = (forwardedProto ?? 'http') + ':'
|
|
} else if (isInternalHost(signInUrl.host)) {
|
|
signInUrl.host = marketplaceOrigin.host
|
|
signInUrl.protocol = marketplaceOrigin.protocol
|
|
}
|
|
|
|
const isMarketplaceHost = signInUrl.host === marketplaceOrigin.host
|
|
signInUrl.pathname = isMarketplaceHost ? '/' : '/dashboard/sign-in'
|
|
signInUrl.searchParams.set('redirect', req.nextUrl.pathname)
|
|
return NextResponse.redirect(signInUrl)
|
|
}
|
|
return NextResponse.next()
|
|
}
|
|
|
|
export default function middleware(req: NextRequest) {
|
|
const rejected = rejectInternalSubrequest(req)
|
|
if (rejected) return rejected
|
|
|
|
const dedupedPath = dedupeDashboardPath(req.nextUrl.pathname)
|
|
if (dedupedPath !== req.nextUrl.pathname) {
|
|
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)(.*)',
|
|
],
|
|
}
|