import { NextResponse } from "next/server"; import { withAuth, NextRequestWithAuth } from "next-auth/middleware"; const rewriteURL = ( request: NextRequestWithAuth, originBaseURL: string, destinationBaseURL: string, headers: any = {}, ) => { let path = request.url.replace(originBaseURL, ""); if (path.startsWith("/")) { path = path.slice(1); } const destinationURL = `${destinationBaseURL}/${path}`; console.info(`Rewriting ${request.url} to ${destinationURL}`); const requestHeaders = new Headers(request.headers); requestHeaders.delete("x-forwarded-user"); requestHeaders.delete("x-forwarded-roles"); requestHeaders.delete("connection"); for (const [key, value] of Object.entries(headers)) { requestHeaders.set(key, value as string); } return NextResponse.rewrite(new URL(destinationURL), { request: { headers: requestHeaders }, }); }; const checkRewrites = async (request: NextRequestWithAuth) => { const linkBaseURL = process.env.LINK_URL ?? "http://localhost:3000"; const zammadURL = process.env.ZAMMAD_URL ?? "http://zammad-nginx:8080"; const opensearchBaseURL = process.env.OPENSEARCH_DASHBOARDS_URL ?? "http://opensearch-dashboards:5601"; const zammadPaths = [ "/zammad", "/auth/sso", "/assets", "/mobile", "/graphql", "/cable", ]; const isSetupMode = process.env.SETUP_MODE === "true"; const { token } = request.nextauth; const email = token?.email?.toLowerCase() ?? "unknown"; const roles = (token?.roles as string[]) ?? []; let headers = { "x-forwarded-user": email, "x-forwarded-roles": roles.join(","), }; if (request.nextUrl.pathname.startsWith("/dashboards")) { return rewriteURL( request, `${linkBaseURL}/dashboards`, opensearchBaseURL, headers, ); } else if (request.nextUrl.pathname.startsWith("/zammad")) { return rewriteURL(request, `${linkBaseURL}/zammad`, zammadURL, headers); } else if (zammadPaths.some((p) => request.nextUrl.pathname.startsWith(p))) { return rewriteURL(request, linkBaseURL, zammadURL, headers); } else if (request.nextUrl.pathname.startsWith("/api/v1")) { if ((email && email !== "unknown") || isSetupMode) { return NextResponse.next(); } else { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } else { const isDev = process.env.NODE_ENV === "development"; const nonce = Buffer.from(crypto.randomUUID()).toString("base64"); const cspHeader = ` default-src 'self'; frame-src 'self' https://digiresilience.org; connect-src 'self'; script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ""}; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests; `; const contentSecurityPolicyHeaderValue = cspHeader .replace(/\s{2,}/g, " ") .trim(); const requestHeaders = new Headers(request.headers); requestHeaders.set("x-nonce", nonce); requestHeaders.set( "Content-Security-Policy", contentSecurityPolicyHeaderValue, ); const response = NextResponse.next({ request: { headers: requestHeaders, }, }); response.headers.set( "Content-Security-Policy", contentSecurityPolicyHeaderValue, ); return response; } }; export default withAuth(checkRewrites, { callbacks: { authorized: ({ token, req }) => { if (process.env.SETUP_MODE === "true") { return true; } const path = req.nextUrl.pathname; const roles: any = token?.roles ?? []; if (path.startsWith("/login")) { return true; } if (path.startsWith("/admin") && !roles.includes("admin")) { return false; } if (roles.includes("admin") || roles.includes("agent")) { return true; } return false; }, }, }); export const config = { matcher: [ "/((?!ws|wss|api/signal|api/whatsapp|_next/static|_next/image|favicon.ico).*)", ], };