Files
prowler/ui/proxy.ts
T

146 lines
4.3 KiB
TypeScript

import { NextResponse } from "next/server";
import type { NextAuthRequest } from "next-auth";
import { auth } from "@/auth.config";
import { getCspHeader } from "@/lib/csp";
import {
GATED_INTEGRATIONS,
isGatedIntegrationEnabled,
readGatedEnv,
} from "@/lib/integrations";
import {
SLACK_CALLBACK_PATH,
SLACK_EXPIRED_CALLBACK_URL,
} from "@/lib/integrations/slack-connect-status";
import { REGISTRY_ACCESS } from "@/lib/registry/access";
import { evaluateRegistryAccess } from "@/lib/registry/access.server";
import { readRegistryPresentation } from "@/lib/registry/presentation";
import { readEnv } from "@/lib/runtime-env";
import { isCloud } from "@/lib/shared/env";
import { copyAttributionParams } from "@/lib/utm";
const publicRoutes = [
"/sign-in",
"/sign-up",
"/invitation/accept",
// In Cloud uncomment the following lines:
// "/reset-password",
// "/email-verification",
// "/set-password",
];
const isPublicRoute = (pathname: string): boolean => {
return publicRoutes.some((route) => pathname.startsWith(route));
};
const withSecurityHeaders = (response: NextResponse): NextResponse => {
response.headers.set(
"Content-Security-Policy",
getCspHeader({
cloudEnabled: isCloud(),
registryImageOrigins: readRegistryPresentation().imageOrigins,
posthogEnabled: isGatedIntegrationEnabled(GATED_INTEGRATIONS.posthog),
posthogKey: readGatedEnv(
"UI_POSTHOG_ENABLED",
"UI_POSTHOG_KEY",
"POSTHOG_KEY",
),
posthogIngestionHost: readGatedEnv(
"UI_POSTHOG_ENABLED",
"UI_POSTHOG_HOST",
"POSTHOG_HOST",
),
posthogUiHost: readGatedEnv("UI_POSTHOG_ENABLED", "UI_POSTHOG_UI_HOST"),
posthogToolbarEnabled: process.env.NODE_ENV === "development",
}),
);
return response;
};
const redirect = (url: URL): NextResponse =>
withSecurityHeaders(NextResponse.redirect(url));
// NextAuth's auth() wrapper - renamed from middleware to proxy
export default auth(async (req: NextAuthRequest) => {
const { pathname } = req.nextUrl;
const user = req.auth?.user;
const sessionError = req.auth?.error;
const cloudBillingEnabled =
(readEnv("CLOUD_BILLING_ENABLED") ?? "false") !== "false";
// If there's a session error (e.g., RefreshAccessTokenError), redirect to login with error info
if (sessionError && !isPublicRoute(pathname)) {
const signInUrl = new URL("/sign-in", req.url);
signInUrl.searchParams.set("error", sessionError);
signInUrl.searchParams.set(
"callbackUrl",
pathname === SLACK_CALLBACK_PATH
? SLACK_EXPIRED_CALLBACK_URL
: pathname + req.nextUrl.search,
);
if (pathname !== SLACK_CALLBACK_PATH) {
copyAttributionParams(req.nextUrl.searchParams, signInUrl.searchParams);
}
return redirect(signInUrl);
}
if (!user && !isPublicRoute(pathname)) {
const signInUrl = new URL("/sign-in", req.url);
signInUrl.searchParams.set(
"callbackUrl",
pathname === SLACK_CALLBACK_PATH
? SLACK_EXPIRED_CALLBACK_URL
: pathname + req.nextUrl.search,
);
if (pathname !== SLACK_CALLBACK_PATH) {
copyAttributionParams(req.nextUrl.searchParams, signInUrl.searchParams);
}
return redirect(signInUrl);
}
if (
pathname.startsWith("/billing") &&
(!isCloud() ||
!cloudBillingEnabled ||
user?.permissions?.manage_billing !== true)
) {
return redirect(new URL("/profile", req.url));
}
if (
(pathname === "/registry" || pathname.startsWith("/registry/")) &&
(await evaluateRegistryAccess(req.auth?.accessToken)).status !==
REGISTRY_ACCESS.ELIGIBLE
) {
return redirect(new URL("/profile", req.url));
}
if (user?.permissions) {
const permissions = user.permissions;
if (
pathname.startsWith("/integrations") &&
!permissions.manage_integrations
) {
return redirect(new URL("/profile", req.url));
}
}
return withSecurityHeaders(NextResponse.next());
});
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - *.png, *.jpg, *.jpeg, *.svg, *.ico (image files)
*/
"/((?!api|_next/static|_next/image|favicon.ico|.*\\.(?:png|jpg|jpeg|svg|ico|css|js)$).*)",
],
};