From 49901d5cc52f199bc796db39b609acc01972376c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Jun 2025 10:42:58 +0200 Subject: [PATCH 1/4] fix: middleware to handle csp on /login route --- apps/login/src/middleware.ts | 86 +++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index fa287e2bdd..a1e630a823 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -1,8 +1,9 @@ +import { SecuritySettings } from "@zitadel/proto/zitadel/settings/v2/security_settings_pb"; + import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; import { DEFAULT_CSP } from "../constants/csp"; import { getServiceUrlFromHeaders } from "./lib/service-url"; - export const config = { matcher: [ "/.well-known/:path*", @@ -14,6 +15,29 @@ export const config = { ], }; +async function loadSecuritySettings( + request: NextRequest, +): Promise { + const securityResponse = await fetch(`${request.nextUrl.origin}/security`); + + if (!securityResponse.ok) { + console.error( + "Failed to fetch security settings:", + securityResponse.statusText, + ); + return null; + } + + const response = await securityResponse.json(); + + if (!response || !response.settings) { + console.error("No security settings found in the response."); + return null; + } + + return response.settings; +} + export async function middleware(request: NextRequest) { // Add the original URL as a header to all requests const requestHeaders = new Headers(request.headers); @@ -24,8 +48,31 @@ export async function middleware(request: NextRequest) { requestHeaders.set("x-zitadel-i18n-organization", organization); } + // Check if the request is for the /login route that handles the auth request for OIDC none prompt + let isLoginRouteMatched = request.nextUrl.pathname.startsWith("/login/"); + + let securitySettings; + if (isLoginRouteMatched) { + securitySettings = await loadSecuritySettings(request); + + if (securitySettings?.embeddedIframe?.enabled) { + const responseHeaders = new Headers(); + + responseHeaders.set( + "Content-Security-Policy", + `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, + ); + responseHeaders.delete("X-Frame-Options"); + + return NextResponse.next({ + request: { headers: requestHeaders }, + headers: responseHeaders, + }); + } + } + // Only run the rest of the logic for the original matcher paths - const matchedPaths = [ + const proxyPaths = [ "/.well-known/", "/oauth/", "/oidc/", @@ -33,19 +80,17 @@ export async function middleware(request: NextRequest) { "/saml/", ]; - const isMatched = matchedPaths.some((prefix) => + const isMatched = proxyPaths.some((prefix) => request.nextUrl.pathname.startsWith(prefix), ); - if (!isMatched) { - // For all other routes, just add the header and continue - return NextResponse.next({ - request: { headers: requestHeaders }, - }); - } - // escape proxy if the environment is setup for multitenancy - if (!process.env.ZITADEL_API_URL || !process.env.ZITADEL_SERVICE_USER_TOKEN) { + if ( + !isMatched || + !process.env.ZITADEL_API_URL || + !process.env.ZITADEL_SERVICE_USER_TOKEN + ) { + // For all other routes, just add the header and continue return NextResponse.next({ request: { headers: requestHeaders }, }); @@ -54,21 +99,6 @@ export async function middleware(request: NextRequest) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - // Call the /security route handler - const securityResponse = await fetch(`${request.nextUrl.origin}/security`); - - if (!securityResponse.ok) { - console.error( - "Failed to fetch security settings:", - securityResponse.statusText, - ); - return NextResponse.next({ - request: { headers: requestHeaders }, - }); - } - - const { settings: securitySettings } = await securityResponse.json(); - const instanceHost = `${serviceUrl}` .replace("https://", "") .replace("http://", ""); @@ -81,6 +111,10 @@ export async function middleware(request: NextRequest) { responseHeaders.set("Access-Control-Allow-Origin", "*"); responseHeaders.set("Access-Control-Allow-Headers", "*"); + if (!securitySettings) { + securitySettings = await loadSecuritySettings(request); + } + if (securitySettings?.embeddedIframe?.enabled) { responseHeaders.set( "Content-Security-Policy", From ff29ab834b46d192eecf918df728ceb77b4f6099 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Jun 2025 11:15:51 +0200 Subject: [PATCH 2/4] middleware change --- apps/login/src/middleware.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index a1e630a823..0c157bc03a 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -56,18 +56,16 @@ export async function middleware(request: NextRequest) { securitySettings = await loadSecuritySettings(request); if (securitySettings?.embeddedIframe?.enabled) { - const responseHeaders = new Headers(); + const response = NextResponse.next({ + request: { headers: requestHeaders }, + }); - responseHeaders.set( + response.headers.set( "Content-Security-Policy", `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, ); - responseHeaders.delete("X-Frame-Options"); - - return NextResponse.next({ - request: { headers: requestHeaders }, - headers: responseHeaders, - }); + response.headers.delete("X-Frame-Options"); + return response; } } From ba7bdb7517cd5ad4e8ff4432d823dc2a3395594c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Jun 2025 11:38:37 +0200 Subject: [PATCH 3/4] rm /login handling on the middleware --- apps/login/src/middleware.ts | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 0c157bc03a..ae4193aaef 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -49,25 +49,25 @@ export async function middleware(request: NextRequest) { } // Check if the request is for the /login route that handles the auth request for OIDC none prompt - let isLoginRouteMatched = request.nextUrl.pathname.startsWith("/login/"); + // let isLoginRouteMatched = request.nextUrl.pathname.startsWith("/login/"); - let securitySettings; - if (isLoginRouteMatched) { - securitySettings = await loadSecuritySettings(request); + // let securitySettings; + // if (isLoginRouteMatched) { + // securitySettings = await loadSecuritySettings(request); - if (securitySettings?.embeddedIframe?.enabled) { - const response = NextResponse.next({ - request: { headers: requestHeaders }, - }); + // if (securitySettings?.embeddedIframe?.enabled) { + // const response = NextResponse.next({ + // request: { headers: requestHeaders }, + // }); - response.headers.set( - "Content-Security-Policy", - `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, - ); - response.headers.delete("X-Frame-Options"); - return response; - } - } + // response.headers.set( + // "Content-Security-Policy", + // `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, + // ); + // response.headers.delete("X-Frame-Options"); + // return response; + // } + // } // Only run the rest of the logic for the original matcher paths const proxyPaths = [ @@ -109,9 +109,7 @@ export async function middleware(request: NextRequest) { responseHeaders.set("Access-Control-Allow-Origin", "*"); responseHeaders.set("Access-Control-Allow-Headers", "*"); - if (!securitySettings) { - securitySettings = await loadSecuritySettings(request); - } + const securitySettings = await loadSecuritySettings(request); if (securitySettings?.embeddedIframe?.enabled) { responseHeaders.set( From 49b11375e546e908dfd71949b0edd6b22528f9aa Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 27 Jun 2025 11:38:52 +0200 Subject: [PATCH 4/4] rm comments --- apps/login/src/middleware.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index ae4193aaef..8eca00510e 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -48,27 +48,6 @@ export async function middleware(request: NextRequest) { requestHeaders.set("x-zitadel-i18n-organization", organization); } - // Check if the request is for the /login route that handles the auth request for OIDC none prompt - // let isLoginRouteMatched = request.nextUrl.pathname.startsWith("/login/"); - - // let securitySettings; - // if (isLoginRouteMatched) { - // securitySettings = await loadSecuritySettings(request); - - // if (securitySettings?.embeddedIframe?.enabled) { - // const response = NextResponse.next({ - // request: { headers: requestHeaders }, - // }); - - // response.headers.set( - // "Content-Security-Policy", - // `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, - // ); - // response.headers.delete("X-Frame-Options"); - // return response; - // } - // } - // Only run the rest of the logic for the original matcher paths const proxyPaths = [ "/.well-known/",