From b5d42a0ac27aeb48682ca14792c778d5f8cfc12e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 24 Apr 2025 10:58:53 +0200 Subject: [PATCH 01/27] fix: use default org if no context detected --- .../(login)/idp/[provider]/success/page.tsx | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 193622fa20..999fabe7ac 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -4,7 +4,6 @@ import { linkingFailed } from "@/components/idps/pages/linking-failed"; import { linkingSuccess } from "@/components/idps/pages/linking-success"; import { loginFailed } from "@/components/idps/pages/login-failed"; import { loginSuccess } from "@/components/idps/pages/login-success"; -import { idpTypeToIdentityProviderType } from "@/lib/idp"; import { getServiceUrlFromHeaders } from "@/lib/service"; import { addHuman, @@ -19,7 +18,10 @@ import { import { create } from "@zitadel/client"; import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb"; import { OrganizationSchema } from "@zitadel/proto/zitadel/object/v2/object_pb"; -import { AddHumanUserRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { + AddHumanUserRequest, + AddHumanUserRequestSchema, +} from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; @@ -83,8 +85,6 @@ export default async function Page(props: { throw new Error("IDP not found"); } - const providerType = idpTypeToIdentityProviderType(idp.type); - if (link) { if (!options?.isLinkingAllowed) { // linking was probably disallowed since the invitation was created @@ -205,15 +205,23 @@ export default async function Page(props: { } } - if (addHumanUser && orgToRegisterOn) { - const organizationSchema = create(OrganizationSchema, { - org: { case: "orgId", value: orgToRegisterOn }, - }); + if (addHumanUser) { + let addHumanUserWithOrganization: AddHumanUserRequest; + if (orgToRegisterOn) { + const organizationSchema = create(OrganizationSchema, { + org: { case: "orgId", value: orgToRegisterOn }, + }); - const addHumanUserWithOrganization = create(AddHumanUserRequestSchema, { - ...addHumanUser, - organization: organizationSchema, - }); + addHumanUserWithOrganization = create(AddHumanUserRequestSchema, { + ...addHumanUser, + organization: organizationSchema, + }); + } else { + addHumanUserWithOrganization = create( + AddHumanUserRequestSchema, + addHumanUser, + ); + } newUser = await addHuman({ serviceUrl, From 682b5017b5c3763ad82be2948c9f2c3a658dfea4 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 24 Apr 2025 14:39:38 +0200 Subject: [PATCH 02/27] error handling --- .../(login)/idp/[provider]/success/page.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 999fabe7ac..735c4ecfef 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -15,7 +15,7 @@ import { listUsers, retrieveIDPIntent, } from "@/lib/zitadel"; -import { create } from "@zitadel/client"; +import { ConnectError, create } from "@zitadel/client"; import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb"; import { OrganizationSchema } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { @@ -223,10 +223,20 @@ export default async function Page(props: { ); } - newUser = await addHuman({ - serviceUrl, - request: addHumanUserWithOrganization, - }); + try { + newUser = await addHuman({ + serviceUrl, + request: addHumanUserWithOrganization, + }); + } catch (error: unknown) { + console.error(error); + return loginFailed( + branding, + (error as ConnectError).message + ? (error as ConnectError).message + : "Could not create user", + ); + } } if (newUser) { From 39359224a553a1efcf1363d7f9c82e03bc14d805 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 24 Apr 2025 14:43:16 +0200 Subject: [PATCH 03/27] logs --- apps/login/src/app/(login)/idp/[provider]/success/page.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 735c4ecfef..00fd9593c0 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -229,7 +229,11 @@ export default async function Page(props: { request: addHumanUserWithOrganization, }); } catch (error: unknown) { - console.error(error); + console.error( + "An error occurred while creating the user:", + error, + addHumanUser, + ); return loginFailed( branding, (error as ConnectError).message From 7a25dce936a0bd699d2287ba82d00634e2ee8631 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 09:14:11 +0200 Subject: [PATCH 04/27] fix: override csp for allowed iframe --- apps/login/next.config.mjs | 5 ++- .../app/(login)/authenticator/set/page.tsx | 11 ++--- apps/login/src/app/login/route.ts | 44 +++++++++++++++---- apps/login/src/lib/csp.ts | 2 + apps/login/src/lib/zitadel.ts | 15 +++++++ apps/login/src/middleware.ts | 18 ++++++++ 6 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 apps/login/src/lib/csp.ts diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index 00fa1e19c4..2795854114 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -1,4 +1,5 @@ import createNextIntlPlugin from "next-intl/plugin"; +import { DEFAULT_CSP } from "./src/lib/csp"; const withNextIntl = createNextIntlPlugin(); @@ -29,9 +30,9 @@ const secureHeaders = [ // script-src va.vercel-scripts.com for analytics/vercel scripts { key: "Content-Security-Policy", - value: - "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com; connect-src 'self'; child-src; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; img-src 'self' https://vercel.com;", + value: `${DEFAULT_CSP} frame-ancestors 'none'`, }, + { key: "X-Frame-Options", value: "deny" }, ]; const imageRemotePatterns = [ diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 8240023c2d..8904eff963 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -33,8 +33,8 @@ export default async function Page(props: { const { serviceUrl } = getServiceUrlFromHeaders(_headers); const sessionWithData = sessionId - ? await loadSessionById(serviceUrl, sessionId, organization) - : await loadSessionByLoginname(serviceUrl, loginName, organization); + ? await loadSessionById(sessionId, organization) + : await loadSessionByLoginname(loginName, organization); async function getAuthMethodsAndUser( serviceUrl: string, @@ -67,7 +67,6 @@ export default async function Page(props: { } async function loadSessionByLoginname( - host: string, loginName?: string, organization?: string, ) { @@ -82,11 +81,7 @@ export default async function Page(props: { }); } - async function loadSessionById( - host: string, - sessionId: string, - organization?: string, - ) { + async function loadSessionById(sessionId: string, organization?: string) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ serviceUrl, diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index e3834e5a27..b3da6f863e 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -1,4 +1,5 @@ import { getAllSessions } from "@/lib/cookies"; +import { DEFAULT_CSP } from "@/lib/csp"; import { idpTypeToSlug } from "@/lib/idp"; import { loginWithOIDCandSession } from "@/lib/oidc"; import { loginWithSAMLandSession } from "@/lib/saml"; @@ -12,6 +13,7 @@ import { getAuthRequest, getOrgsByDomain, getSAMLRequest, + getSecuritySettings, listSessions, startIdentityProviderFlow, } from "@/lib/zitadel"; @@ -293,17 +295,32 @@ export async function GET(request: NextRequest) { * This means that the user should not be prompted to enter their password again. * Instead, the server attempts to silently authenticate the user using an existing session or other authentication mechanisms that do not require user interaction **/ + const securitySettings = await getSecuritySettings({ + serviceUrl, + }); + const selectedSession = await findValidSession({ serviceUrl, sessions, authRequest, }); - if (!selectedSession || !selectedSession.id) { - return NextResponse.json( - { error: "No active session found" }, - { status: 400 }, + const noSessionResponse = NextResponse.json( + { error: "No active session found" }, + { status: 400 }, + ); + + if (securitySettings?.embeddedIframe?.enabled) { + securitySettings.embeddedIframe.allowedOrigins; + noSessionResponse.headers.set( + "Content-Security-Policy", + `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, ); + noSessionResponse.headers.delete("X-Frame-Options"); + } + + if (!selectedSession || !selectedSession.id) { + return noSessionResponse; } const cookie = sessionCookies.find( @@ -311,10 +328,7 @@ export async function GET(request: NextRequest) { ); if (!cookie || !cookie.id || !cookie.token) { - return NextResponse.json( - { error: "No active session found" }, - { status: 400 }, - ); + return noSessionResponse; } const session = { @@ -332,7 +346,19 @@ export async function GET(request: NextRequest) { }, }), }); - return NextResponse.redirect(callbackUrl); + + const callbackResponse = NextResponse.redirect(callbackUrl); + + if (securitySettings?.embeddedIframe?.enabled) { + securitySettings.embeddedIframe.allowedOrigins; + callbackResponse.headers.set( + "Content-Security-Policy", + `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, + ); + callbackResponse.headers.delete("X-Frame-Options"); + } + + return callbackResponse; } else { // check for loginHint, userId hint and valid sessions let selectedSession = await findValidSession({ diff --git a/apps/login/src/lib/csp.ts b/apps/login/src/lib/csp.ts new file mode 100644 index 0000000000..5cc1e254f3 --- /dev/null +++ b/apps/login/src/lib/csp.ts @@ -0,0 +1,2 @@ +export const DEFAULT_CSP = + "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com; connect-src 'self'; child-src; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; img-src 'self' https://vercel.com;"; diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 0511eaaf0d..a5abc0dcb1 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -92,6 +92,21 @@ export async function getLoginSettings({ return useCache ? cacheWrapper(callback) : callback; } +export async function getSecuritySettings({ + serviceUrl, +}: { + serviceUrl: string; +}) { + const settingsService: Client = + await createServiceForHost(SettingsService, serviceUrl); + + const callback = settingsService + .getSecuritySettings({}) + .then((resp) => (resp.settings ? resp.settings : undefined)); + + return useCache ? cacheWrapper(callback) : callback; +} + export async function getLockoutSettings({ serviceUrl, orgId, diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 8d4080cddf..0572fe43b6 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -1,6 +1,8 @@ import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; +import { DEFAULT_CSP } from "./lib/csp"; import { getServiceUrlFromHeaders } from "./lib/service"; +import { getSecuritySettings } from "./lib/zitadel"; export const config = { matcher: [ @@ -22,6 +24,8 @@ export async function middleware(request: NextRequest) { const { serviceUrl } = getServiceUrlFromHeaders(_headers); + const securitySettings = await getSecuritySettings({ serviceUrl }); + const instanceHost = `${serviceUrl}` .replace("https://", "") .replace("http://", ""); @@ -39,6 +43,20 @@ export async function middleware(request: NextRequest) { responseHeaders.set("Access-Control-Allow-Origin", "*"); responseHeaders.set("Access-Control-Allow-Headers", "*"); + responseHeaders.set( + "Content-Security-Policy", + `${DEFAULT_CSP} frame-ancestors 'none'`, + ); + + if (securitySettings?.embeddedIframe?.enabled) { + securitySettings.embeddedIframe.allowedOrigins; + responseHeaders.set( + "Content-Security-Policy", + `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, + ); + responseHeaders.delete("X-Frame-Options"); + } + request.nextUrl.href = `${serviceUrl}${request.nextUrl.pathname}${request.nextUrl.search}`; return NextResponse.rewrite(request.nextUrl, { request: { From 231ecdc5c54f3872cbcc4e1eb270943d224010cd Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 09:18:01 +0200 Subject: [PATCH 05/27] change import --- apps/login/next.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index 2795854114..7fb0f5b65d 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -1,5 +1,5 @@ import createNextIntlPlugin from "next-intl/plugin"; -import { DEFAULT_CSP } from "./src/lib/csp"; +import { DEFAULT_CSP } from "src/lib/csp"; const withNextIntl = createNextIntlPlugin(); From a31c17f5fa4c424bab251d92f2db869617c9e20e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 09:20:26 +0200 Subject: [PATCH 06/27] @ ns --- apps/login/next.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index 7fb0f5b65d..0820c0c555 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -1,5 +1,5 @@ import createNextIntlPlugin from "next-intl/plugin"; -import { DEFAULT_CSP } from "src/lib/csp"; +import { DEFAULT_CSP } from "@/lib/csp"; const withNextIntl = createNextIntlPlugin(); From a690b254c4858e3419f14968a621936ecc7cebed Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 09:23:56 +0200 Subject: [PATCH 07/27] import --- apps/login/next.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index 0820c0c555..2795854114 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -1,5 +1,5 @@ import createNextIntlPlugin from "next-intl/plugin"; -import { DEFAULT_CSP } from "@/lib/csp"; +import { DEFAULT_CSP } from "./src/lib/csp"; const withNextIntl = createNextIntlPlugin(); From 77e9f6f2e90505236a2dbdd9125f6ba8aa06ce6e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 09:41:00 +0200 Subject: [PATCH 08/27] override cookie sameSite settings --- apps/login/src/lib/cookies.ts | 60 ++++++++++++++------- apps/login/src/lib/server/cookie.ts | 79 ++++++++++++++++++---------- apps/login/src/lib/server/session.ts | 11 +++- 3 files changed, 99 insertions(+), 51 deletions(-) diff --git a/apps/login/src/lib/cookies.ts b/apps/login/src/lib/cookies.ts index cf762b904f..76f5580a16 100644 --- a/apps/login/src/lib/cookies.ts +++ b/apps/login/src/lib/cookies.ts @@ -20,7 +20,10 @@ export type Cookie = { type SessionCookie = Cookie & T; -async function setSessionHttpOnlyCookie(sessions: SessionCookie[]) { +async function setSessionHttpOnlyCookie( + sessions: SessionCookie[], + sameSite: boolean | "lax" | "strict" | "none" = true, +) { const cookiesList = await cookies(); return cookiesList.set({ @@ -28,6 +31,7 @@ async function setSessionHttpOnlyCookie(sessions: SessionCookie[]) { value: JSON.stringify(sessions), httpOnly: true, path: "/", + sameSite, }); } @@ -42,10 +46,15 @@ export async function setLanguageCookie(language: string) { }); } -export async function addSessionToCookie( - session: SessionCookie, - cleanup: boolean = false, -): Promise { +export async function addSessionToCookie({ + session, + cleanup, + sameSite, +}: { + session: SessionCookie; + cleanup?: boolean; + sameSite?: boolean | "lax" | "strict" | "none" | undefined; +}): Promise { const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); @@ -79,17 +88,23 @@ export async function addSessionToCookie( ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now : true, ); - return setSessionHttpOnlyCookie(filteredSessions); + return setSessionHttpOnlyCookie(filteredSessions, sameSite); } else { - return setSessionHttpOnlyCookie(currentSessions); + return setSessionHttpOnlyCookie(currentSessions, sameSite); } } -export async function updateSessionCookie( - id: string, - session: SessionCookie, - cleanup: boolean = false, -): Promise { +export async function updateSessionCookie({ + id, + session, + cleanup, + sameSite, +}: { + id: string; + session: SessionCookie; + cleanup?: boolean; + sameSite?: boolean | "lax" | "strict" | "none" | undefined; +}): Promise { const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); @@ -108,19 +123,24 @@ export async function updateSessionCookie( ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now : true, ); - return setSessionHttpOnlyCookie(filteredSessions); + return setSessionHttpOnlyCookie(filteredSessions, sameSite); } else { - return setSessionHttpOnlyCookie(sessions); + return setSessionHttpOnlyCookie(sessions, sameSite); } } else { throw "updateSessionCookie: session id now found"; } } -export async function removeSessionFromCookie( - session: SessionCookie, - cleanup: boolean = false, -): Promise { +export async function removeSessionFromCookie({ + session, + cleanup, + sameSite, +}: { + session: SessionCookie; + cleanup?: boolean; + sameSite?: boolean | "lax" | "strict" | "none" | undefined; +}): Promise { const cookiesList = await cookies(); const stringifiedCookie = cookiesList.get("sessions"); @@ -136,9 +156,9 @@ export async function removeSessionFromCookie( ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now : true, ); - return setSessionHttpOnlyCookie(filteredSessions); + return setSessionHttpOnlyCookie(filteredSessions, sameSite); } else { - return setSessionHttpOnlyCookie(reducedSessions); + return setSessionHttpOnlyCookie(reducedSessions, sameSite); } } diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index d54a4047b1..7cc86e9337 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -4,6 +4,7 @@ import { addSessionToCookie, updateSessionCookie } from "@/lib/cookies"; import { createSessionForUserIdAndIdpIntent, createSessionFromChecks, + getSecuritySettings, getSession, setSession, } from "@/lib/zitadel"; @@ -65,7 +66,7 @@ export async function createSessionAndUpdateCookie(command: { serviceUrl, sessionId: createdSession.sessionId, sessionToken: createdSession.sessionToken, - }).then((response) => { + }).then(async (response) => { if (response?.session && response.session?.factors?.user?.loginName) { const sessionCookie: CustomCookieData = { id: createdSession.sessionId, @@ -91,9 +92,14 @@ export async function createSessionAndUpdateCookie(command: { response.session.factors.user.organizationId; } - return addSessionToCookie(sessionCookie).then(() => { - return response.session as Session; - }); + const securitySettings = await getSecuritySettings({ serviceUrl }); + const sameSite = securitySettings?.embeddedIframe?.enabled + ? "none" + : true; + + await addSessionToCookie({ session: sessionCookie, sameSite }); + + return response.session as Session; } else { throw "could not get session or session does not have loginName"; } @@ -167,7 +173,10 @@ export async function createSessionForIdpAndUpdateCookie( sessionCookie.organization = session.factors.user.organizationId; } - return addSessionToCookie(sessionCookie).then(() => { + const securitySettings = await getSecuritySettings({ serviceUrl }); + const sameSite = securitySettings?.embeddedIframe?.enabled ? "none" : true; + + return addSessionToCookie({ session: sessionCookie, sameSite }).then(() => { return session as Session; }); } @@ -217,32 +226,44 @@ export async function setSessionAndUpdateCookie( serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, - }).then((response) => { - if (response?.session && response.session.factors?.user?.loginName) { - const { session } = response; - const newCookie: CustomCookieData = { - id: sessionCookie.id, - token: updatedSession.sessionToken, - creationTs: sessionCookie.creationTs, - expirationTs: sessionCookie.expirationTs, - // just overwrite the changeDate with the new one - changeTs: updatedSession.details?.changeDate - ? `${timestampMs(updatedSession.details.changeDate)}` - : "", - loginName: session.factors?.user?.loginName ?? "", - organization: session.factors?.user?.organizationId ?? "", - }; - - if (sessionCookie.requestId) { - newCookie.requestId = sessionCookie.requestId; - } - - return updateSessionCookie(sessionCookie.id, newCookie).then(() => { - return { challenges: updatedSession.challenges, ...session }; - }); - } else { + }).then(async (response) => { + if ( + !response?.session || + !response.session.factors?.user?.loginName + ) { throw "could not get session or session does not have loginName"; } + + const { session } = response; + const newCookie: CustomCookieData = { + id: sessionCookie.id, + token: updatedSession.sessionToken, + creationTs: sessionCookie.creationTs, + expirationTs: sessionCookie.expirationTs, + // just overwrite the changeDate with the new one + changeTs: updatedSession.details?.changeDate + ? `${timestampMs(updatedSession.details.changeDate)}` + : "", + loginName: session.factors?.user?.loginName ?? "", + organization: session.factors?.user?.organizationId ?? "", + }; + + if (sessionCookie.requestId) { + newCookie.requestId = sessionCookie.requestId; + } + + const securitySettings = await getSecuritySettings({ serviceUrl }); + const sameSite = securitySettings?.embeddedIframe?.enabled + ? "none" + : true; + + return updateSessionCookie({ + id: sessionCookie.id, + session: newCookie, + sameSite, + }).then(() => { + return { challenges: updatedSession.challenges, ...session }; + }); }); } else { throw "Session not be set"; diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 66688bf415..3ff3d14017 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -4,6 +4,7 @@ import { setSessionAndUpdateCookie } from "@/lib/server/cookie"; import { deleteSession, getLoginSettings, + getSecuritySettings, humanMFAInitSkipped, listAuthenticationMethodTypes, } from "@/lib/zitadel"; @@ -209,8 +210,11 @@ export async function clearSession(options: ClearSessionOptions) { sessionToken: session.token, }); + const securitySettings = await getSecuritySettings({ serviceUrl }); + const sameSite = securitySettings?.embeddedIframe?.enabled ? "none" : true; + if (deletedSession) { - return removeSessionFromCookie(session); + return removeSessionFromCookie({ session, sameSite }); } } @@ -230,9 +234,12 @@ export async function cleanupSession({ sessionId }: CleanupSessionCommand) { sessionToken: sessionCookie.token, }); + const securitySettings = await getSecuritySettings({ serviceUrl }); + const sameSite = securitySettings?.embeddedIframe?.enabled ? "none" : true; + if (!deleteResponse) { throw new Error("Could not delete session"); } - return removeSessionFromCookie(sessionCookie); + return removeSessionFromCookie({ session: sessionCookie, sameSite }); } From 43dff470bd41ef797a3726368b421057d2f06ef8 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 09:47:51 +0200 Subject: [PATCH 09/27] csp import --- apps/login/constants/csp.js | 2 ++ apps/login/next.config.mjs | 2 +- apps/login/src/app/login/route.ts | 2 +- apps/login/src/lib/csp.ts | 2 -- apps/login/src/middleware.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 apps/login/constants/csp.js delete mode 100644 apps/login/src/lib/csp.ts diff --git a/apps/login/constants/csp.js b/apps/login/constants/csp.js new file mode 100644 index 0000000000..21dc869a53 --- /dev/null +++ b/apps/login/constants/csp.js @@ -0,0 +1,2 @@ +export const DEFAULT_CSP = + "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com; connect-src 'self'; child-src; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; img-src 'self' https://vercel.com;"; diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index 2795854114..edf5e54595 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -1,5 +1,5 @@ import createNextIntlPlugin from "next-intl/plugin"; -import { DEFAULT_CSP } from "./src/lib/csp"; +import { DEFAULT_CSP } from "./constants/csp.js"; const withNextIntl = createNextIntlPlugin(); diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index b3da6f863e..fb2f5e5f49 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -1,5 +1,4 @@ import { getAllSessions } from "@/lib/cookies"; -import { DEFAULT_CSP } from "@/lib/csp"; import { idpTypeToSlug } from "@/lib/idp"; import { loginWithOIDCandSession } from "@/lib/oidc"; import { loginWithSAMLandSession } from "@/lib/saml"; @@ -27,6 +26,7 @@ import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; +import { DEFAULT_CSP } from "../../../constants/csp"; export const dynamic = "force-dynamic"; export const revalidate = false; diff --git a/apps/login/src/lib/csp.ts b/apps/login/src/lib/csp.ts deleted file mode 100644 index 5cc1e254f3..0000000000 --- a/apps/login/src/lib/csp.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const DEFAULT_CSP = - "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com; connect-src 'self'; child-src; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; img-src 'self' https://vercel.com;"; diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 0572fe43b6..0621b2f5f2 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -1,6 +1,6 @@ import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; -import { DEFAULT_CSP } from "./lib/csp"; +import { DEFAULT_CSP } from "../constants/csp"; import { getServiceUrlFromHeaders } from "./lib/service"; import { getSecuritySettings } from "./lib/zitadel"; From 0568aed6e0bfc443c53b139453036eae3f009c02 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 09:52:03 +0200 Subject: [PATCH 10/27] lint --- apps/login/constants/csp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/constants/csp.js b/apps/login/constants/csp.js index 21dc869a53..5cc1e254f3 100644 --- a/apps/login/constants/csp.js +++ b/apps/login/constants/csp.js @@ -1,2 +1,2 @@ export const DEFAULT_CSP = - "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com; connect-src 'self'; child-src; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; img-src 'self' https://vercel.com;"; + "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com; connect-src 'self'; child-src; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; img-src 'self' https://vercel.com;"; From a6cf9a6db688aa23875a706716de9e6ecae62a27 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 09:58:29 +0200 Subject: [PATCH 11/27] empty security settings as default for integration tests --- .../initial-stubs/zitadel.settings.v2.SettingsService.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json index 07e9980f9b..3da4ae999f 100644 --- a/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json +++ b/apps/login/mock/initial-stubs/zitadel.settings.v2.SettingsService.json @@ -6,6 +6,13 @@ "data": {} } }, + { + "service": "zitadel.settings.v2.SettingsService", + "method": "GetSecuritySettings", + "out": { + "data": {} + } + }, { "service": "zitadel.settings.v2.SettingsService", "method": "GetLegalAndSupportSettings", From 449e632766c7cb7a8dd95bdafe2c318b7c530ed2 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 10:05:24 +0200 Subject: [PATCH 12/27] remove fallback --- apps/login/src/middleware.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 0621b2f5f2..6184bee182 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -43,11 +43,6 @@ export async function middleware(request: NextRequest) { responseHeaders.set("Access-Control-Allow-Origin", "*"); responseHeaders.set("Access-Control-Allow-Headers", "*"); - responseHeaders.set( - "Content-Security-Policy", - `${DEFAULT_CSP} frame-ancestors 'none'`, - ); - if (securitySettings?.embeddedIframe?.enabled) { securitySettings.embeddedIframe.allowedOrigins; responseHeaders.set( From 9d43c3f6b8c925e28c7c3dfa3a4462f1f0448293 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 10:56:20 +0200 Subject: [PATCH 13/27] log policy, csp --- apps/login/src/middleware.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 6184bee182..dd888f02e8 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -26,6 +26,8 @@ export async function middleware(request: NextRequest) { const securitySettings = await getSecuritySettings({ serviceUrl }); + console.log(securitySettings, DEFAULT_CSP); + const instanceHost = `${serviceUrl}` .replace("https://", "") .replace("http://", ""); From 06e69ace5e0c608ee92469d7ba7445bc0760118e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 11:08:21 +0200 Subject: [PATCH 14/27] log --- apps/login/src/middleware.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index dd888f02e8..4ae8e2a47c 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -24,9 +24,11 @@ export async function middleware(request: NextRequest) { const { serviceUrl } = getServiceUrlFromHeaders(_headers); + console.log("defaultCSP", DEFAULT_CSP); + const securitySettings = await getSecuritySettings({ serviceUrl }); - console.log(securitySettings, DEFAULT_CSP); + console.log("securitySettings", securitySettings); const instanceHost = `${serviceUrl}` .replace("https://", "") From 4cca720f052f19323bb6ef1118726046d205b7d1 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 13:20:03 +0200 Subject: [PATCH 15/27] route handler for middleware --- apps/login/src/app/security/route.ts | 27 +++++++++++++++++++++++++++ apps/login/src/middleware.ts | 14 ++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 apps/login/src/app/security/route.ts diff --git a/apps/login/src/app/security/route.ts b/apps/login/src/app/security/route.ts new file mode 100644 index 0000000000..e89a609e52 --- /dev/null +++ b/apps/login/src/app/security/route.ts @@ -0,0 +1,27 @@ +import { createServiceForHost, getServiceUrlFromHeaders } from "@/lib/service"; +import { Client } from "@zitadel/client"; +import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb"; +import { headers } from "next/headers"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET(request: NextRequest) { + const _headers = await headers(); + const { serviceUrl } = getServiceUrlFromHeaders(_headers); + + const settingsService: Client = + await createServiceForHost(SettingsService, serviceUrl); + + const settings = settingsService + .getSecuritySettings({}) + .then((resp) => (resp.settings ? resp.settings : undefined)); + + const response = NextResponse.json({ settings }, { status: 200 }); + + // Add Cache-Control header to cache the response for up to 1 hour + response.headers.set( + "Cache-Control", + "public, max-age=3600, stale-while-revalidate=86400", + ); + + return response; +} diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 4ae8e2a47c..22dc143790 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -2,7 +2,6 @@ import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; import { DEFAULT_CSP } from "../constants/csp"; import { getServiceUrlFromHeaders } from "./lib/service"; -import { getSecuritySettings } from "./lib/zitadel"; export const config = { matcher: [ @@ -26,8 +25,19 @@ export async function middleware(request: NextRequest) { console.log("defaultCSP", DEFAULT_CSP); - const securitySettings = await getSecuritySettings({ serviceUrl }); + // Call the /security route handler + // TODO check this on cloud run deployment + const securityResponse = await fetch(`${request.nextUrl.origin}/security`); + if (!securityResponse.ok) { + console.error( + "Failed to fetch security settings:", + securityResponse.statusText, + ); + return NextResponse.next(); // Fallback if the request fails + } + + const { settings: securitySettings } = await securityResponse.json(); console.log("securitySettings", securitySettings); const instanceHost = `${serviceUrl}` From 65da744d9a202ac1ad5b6f6778ed69799015ee69 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 13:27:50 +0200 Subject: [PATCH 16/27] fix zlib export --- apps/login/src/app/(login)/accounts/page.tsx | 2 +- .../app/(login)/authenticator/set/page.tsx | 2 +- .../(login)/idp/[provider]/failure/page.tsx | 2 +- .../(login)/idp/[provider]/success/page.tsx | 2 +- apps/login/src/app/(login)/idp/page.tsx | 2 +- apps/login/src/app/(login)/invite/page.tsx | 2 +- .../src/app/(login)/invite/success/page.tsx | 2 +- apps/login/src/app/(login)/loginname/page.tsx | 2 +- apps/login/src/app/(login)/mfa/page.tsx | 2 +- apps/login/src/app/(login)/mfa/set/page.tsx | 2 +- .../src/app/(login)/otp/[method]/page.tsx | 2 +- .../src/app/(login)/otp/[method]/set/page.tsx | 2 +- apps/login/src/app/(login)/passkey/page.tsx | 2 +- .../src/app/(login)/passkey/set/page.tsx | 2 +- .../src/app/(login)/password/change/page.tsx | 2 +- apps/login/src/app/(login)/password/page.tsx | 2 +- .../src/app/(login)/password/set/page.tsx | 2 +- apps/login/src/app/(login)/register/page.tsx | 2 +- .../app/(login)/register/password/page.tsx | 2 +- apps/login/src/app/(login)/signedin/page.tsx | 2 +- apps/login/src/app/(login)/u2f/page.tsx | 2 +- apps/login/src/app/(login)/u2f/set/page.tsx | 2 +- apps/login/src/app/(login)/verify/page.tsx | 2 +- apps/login/src/app/security/route.ts | 3 +- apps/login/src/lib/service-url.ts | 58 +++++++++++++++++++ apps/login/src/lib/service.ts | 44 -------------- apps/login/src/middleware.ts | 2 +- 27 files changed, 84 insertions(+), 69 deletions(-) create mode 100644 apps/login/src/lib/service-url.ts diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index 6a57f05cee..32c88d17bf 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -1,7 +1,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SessionsList } from "@/components/sessions-list"; import { getAllSessionCookieIds } from "@/lib/cookies"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getBrandingSettings, getDefaultOrg, diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 8904eff963..3e1b49eed0 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -5,7 +5,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getActiveIdentityProviders, diff --git a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx index 82c7224a92..de6ad858d9 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -2,7 +2,7 @@ import { Alert, AlertType } from "@/components/alert"; import { ChooseAuthenticatorToLogin } from "@/components/choose-authenticator-to-login"; import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getBrandingSettings, getLoginSettings, diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 00fd9593c0..2c9724f13a 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -4,7 +4,7 @@ import { linkingFailed } from "@/components/idps/pages/linking-failed"; import { linkingSuccess } from "@/components/idps/pages/linking-success"; import { loginFailed } from "@/components/idps/pages/login-failed"; import { loginSuccess } from "@/components/idps/pages/login-success"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { addHuman, addIDPLink, diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index e8c63512d2..db492cb79c 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -1,6 +1,6 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx index ed29624d0e..11e9d732ee 100644 --- a/apps/login/src/app/(login)/invite/page.tsx +++ b/apps/login/src/app/(login)/invite/page.tsx @@ -1,7 +1,7 @@ import { Alert, AlertType } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { InviteForm } from "@/components/invite-form"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getBrandingSettings, getDefaultOrg, diff --git a/apps/login/src/app/(login)/invite/success/page.tsx b/apps/login/src/app/(login)/invite/success/page.tsx index 8a2cfb96e9..1b12a5b903 100644 --- a/apps/login/src/app/(login)/invite/success/page.tsx +++ b/apps/login/src/app/(login)/invite/success/page.tsx @@ -2,7 +2,7 @@ import { Alert, AlertType } from "@/components/alert"; import { Button, ButtonVariants } from "@/components/button"; import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getBrandingSettings, getDefaultOrg, getUserByID } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { getLocale, getTranslations } from "next-intl/server"; diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index e3406b5fee..79372729c4 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -1,7 +1,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { UsernameForm } from "@/components/username-form"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getActiveIdentityProviders, getBrandingSettings, diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index 2838457b30..c65d6d3058 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -4,7 +4,7 @@ import { ChooseSecondFactor } from "@/components/choose-second-factor"; import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index c198ed9f2c..c7f2fa6599 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -4,7 +4,7 @@ import { ChooseSecondFactorToSetup } from "@/components/choose-second-factor-to- import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index f566f2b0e6..ee58420c42 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -3,7 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { LoginOTP } from "@/components/login-otp"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index b16dd177a9..d3fc4c89f7 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -4,7 +4,7 @@ import { Button, ButtonVariants } from "@/components/button"; import { DynamicTheme } from "@/components/dynamic-theme"; import { TotpRegister } from "@/components/totp-register"; import { UserAvatar } from "@/components/user-avatar"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { addOTPEmail, diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index cef7032a54..e24585e7e0 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -3,7 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { LoginPasskey } from "@/components/login-passkey"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index b9745bdd68..dad3749fd7 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -2,7 +2,7 @@ import { Alert, AlertType } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { RegisterPasskey } from "@/components/register-passkey"; import { UserAvatar } from "@/components/user-avatar"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index f0ab94d315..05f8cd6a10 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -2,7 +2,7 @@ import { Alert } from "@/components/alert"; import { ChangePasswordForm } from "@/components/change-password-form"; import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 64f2709009..506454a275 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -2,7 +2,7 @@ import { Alert } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { PasswordForm } from "@/components/password-form"; import { UserAvatar } from "@/components/user-avatar"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index 2205f5e0a5..26e065438c 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -2,7 +2,7 @@ import { Alert, AlertType } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { SetPasswordForm } from "@/components/set-password-form"; import { UserAvatar } from "@/components/user-avatar"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 7a69755f4d..e50511edb1 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -1,6 +1,6 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { RegisterForm } from "@/components/register-form"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getBrandingSettings, getDefaultOrg, diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx index 8a2c0e1649..ee6fa03e59 100644 --- a/apps/login/src/app/(login)/register/password/page.tsx +++ b/apps/login/src/app/(login)/register/password/page.tsx @@ -1,6 +1,6 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SetRegisterPasswordForm } from "@/components/set-register-password-form"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getBrandingSettings, getDefaultOrg, diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index 8c5c5486ac..230890bf88 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -3,7 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SelfServiceMenu } from "@/components/self-service-menu"; import { UserAvatar } from "@/components/user-avatar"; import { getMostRecentCookieWithLoginname } from "@/lib/cookies"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { createCallback, createResponse, diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index bf8bd7ce37..b16dc88f4b 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -3,7 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { LoginPasskey } from "@/components/login-passkey"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index 0119502ffd..b31b87c202 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -2,7 +2,7 @@ import { Alert } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { RegisterU2f } from "@/components/register-u2f"; import { UserAvatar } from "@/components/user-avatar"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 1464d5185a..198a46a5fe 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -4,7 +4,7 @@ import { UserAvatar } from "@/components/user-avatar"; import { VerifyForm } from "@/components/verify-form"; import { VerifyRedirectButton } from "@/components/verify-redirect-button"; import { sendEmailCode } from "@/lib/server/verify"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, diff --git a/apps/login/src/app/security/route.ts b/apps/login/src/app/security/route.ts index e89a609e52..d47daaa40e 100644 --- a/apps/login/src/app/security/route.ts +++ b/apps/login/src/app/security/route.ts @@ -1,4 +1,5 @@ -import { createServiceForHost, getServiceUrlFromHeaders } from "@/lib/service"; +import { createServiceForHost } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { Client } from "@zitadel/client"; import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb"; import { headers } from "next/headers"; diff --git a/apps/login/src/lib/service-url.ts b/apps/login/src/lib/service-url.ts new file mode 100644 index 0000000000..e74ee1f333 --- /dev/null +++ b/apps/login/src/lib/service-url.ts @@ -0,0 +1,58 @@ +import { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers"; +import { NextRequest } from "next/server"; + +/** + * Extracts the service url and region from the headers if used in a multitenant context (host, x-zitadel-forward-host header) + * or falls back to the ZITADEL_API_URL for a self hosting deployment + * or falls back to the host header for a self hosting deployment using custom domains + * @param headers + * @returns the service url and region from the headers + * @throws if the service url could not be determined + * + */ +export function getServiceUrlFromHeaders(headers: ReadonlyHeaders): { + serviceUrl: string; +} { + let instanceUrl; + + const forwardedHost = headers.get("x-zitadel-forward-host"); + // use the forwarded host if available (multitenant), otherwise fall back to the host of the deployment itself + if (forwardedHost) { + instanceUrl = forwardedHost; + instanceUrl = instanceUrl.startsWith("http://") + ? instanceUrl + : `https://${instanceUrl}`; + } else if (process.env.ZITADEL_API_URL) { + instanceUrl = process.env.ZITADEL_API_URL; + } else { + const host = headers.get("host"); + + if (host) { + const [hostname, port] = host.split(":"); + if (hostname !== "localhost") { + instanceUrl = host.startsWith("http") ? host : `https://${host}`; + } + } + } + + if (!instanceUrl) { + throw new Error("Service URL could not be determined"); + } + + return { + serviceUrl: instanceUrl, + }; +} + +export function constructUrl(request: NextRequest, path: string) { + const forwardedProto = request.headers.get("x-forwarded-proto") + ? `${request.headers.get("x-forwarded-proto")}:` + : request.nextUrl.protocol; + + const forwardedHost = + request.headers.get("x-zitadel-forward-host") ?? + request.headers.get("x-forwarded-host") ?? + request.headers.get("host"); + const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; + return new URL(`${basePath}${path}`, `${forwardedProto}//${forwardedHost}`); +} diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index ec01c619f5..97143cf003 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -7,7 +7,6 @@ import { SAMLService } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb"; import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb"; import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers"; import { NextRequest } from "next/server"; import { systemAPIToken } from "./api"; @@ -67,49 +66,6 @@ export async function createServiceForHost( return createClientFor(service)(transport); } -/** - * Extracts the service url and region from the headers if used in a multitenant context (host, x-zitadel-forward-host header) - * or falls back to the ZITADEL_API_URL for a self hosting deployment - * or falls back to the host header for a self hosting deployment using custom domains - * @param headers - * @returns the service url and region from the headers - * @throws if the service url could not be determined - * - */ -export function getServiceUrlFromHeaders(headers: ReadonlyHeaders): { - serviceUrl: string; -} { - let instanceUrl; - - const forwardedHost = headers.get("x-zitadel-forward-host"); - // use the forwarded host if available (multitenant), otherwise fall back to the host of the deployment itself - if (forwardedHost) { - instanceUrl = forwardedHost; - instanceUrl = instanceUrl.startsWith("http://") - ? instanceUrl - : `https://${instanceUrl}`; - } else if (process.env.ZITADEL_API_URL) { - instanceUrl = process.env.ZITADEL_API_URL; - } else { - const host = headers.get("host"); - - if (host) { - const [hostname, port] = host.split(":"); - if (hostname !== "localhost") { - instanceUrl = host.startsWith("http") ? host : `https://${host}`; - } - } - } - - if (!instanceUrl) { - throw new Error("Service URL could not be determined"); - } - - return { - serviceUrl: instanceUrl, - }; -} - export function constructUrl(request: NextRequest, path: string) { const forwardedProto = request.headers.get("x-forwarded-proto") ? `${request.headers.get("x-forwarded-proto")}:` diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 22dc143790..fc02d859c6 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -1,7 +1,7 @@ import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; import { DEFAULT_CSP } from "../constants/csp"; -import { getServiceUrlFromHeaders } from "./lib/service"; +import { getServiceUrlFromHeaders } from "./lib/service-url"; export const config = { matcher: [ From 54cb3b086e1e04006113db73041dad5575c136a8 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 13:31:05 +0200 Subject: [PATCH 17/27] fix imports --- apps/login/src/lib/server/cookie.ts | 2 +- apps/login/src/lib/server/idp.ts | 2 +- apps/login/src/lib/server/invite.ts | 2 +- apps/login/src/lib/server/loginname.ts | 2 +- apps/login/src/lib/server/otp.ts | 2 +- apps/login/src/lib/server/passkeys.ts | 2 +- apps/login/src/lib/server/password.ts | 2 +- apps/login/src/lib/server/register.ts | 2 +- apps/login/src/lib/server/session.ts | 2 +- apps/login/src/lib/server/u2f.ts | 2 +- apps/login/src/lib/server/verify.ts | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 7cc86e9337..88e0b48290 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -21,7 +21,7 @@ import { import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { headers } from "next/headers"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; type CustomCookieData = { id: string; diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index e6861a60c4..5cac537690 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -8,7 +8,7 @@ import { import { headers } from "next/headers"; import { redirect } from "next/navigation"; import { getNextUrl } from "../client"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; import { checkEmailVerification } from "../verify-helper"; import { createSessionForIdpAndUpdateCookie } from "./cookie"; diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 9821336099..c0fc63fef5 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -3,7 +3,7 @@ import { addHumanUser, createInviteCode } from "@/lib/zitadel"; import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { headers } from "next/headers"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; type InviteUserCommand = { email: string; diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 78aa455483..2ea6004fdc 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -8,7 +8,7 @@ import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp"; import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; import { checkInvite } from "../verify-helper"; import { getActiveIdentityProviders, diff --git a/apps/login/src/lib/server/otp.ts b/apps/login/src/lib/server/otp.ts index 77def53af6..f3d4a1536a 100644 --- a/apps/login/src/lib/server/otp.ts +++ b/apps/login/src/lib/server/otp.ts @@ -13,7 +13,7 @@ import { getSessionCookieById, getSessionCookieByLoginName, } from "../cookies"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; import { getLoginSettings } from "../zitadel"; export type SetOTPCommand = { diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index bbed8e9175..73d12043b0 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -22,7 +22,7 @@ import { getSessionCookieById, getSessionCookieByLoginName, } from "../cookies"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; import { checkEmailVerification } from "../verify-helper"; import { setSessionAndUpdateCookie } from "./cookie"; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 3fff193851..9e256c71d9 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -32,7 +32,7 @@ import { import { headers } from "next/headers"; import { getNextUrl } from "../client"; import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; import { checkEmailVerification, checkMFAFactors, diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 59f6d00171..25bea33527 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -10,7 +10,7 @@ import { } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { headers } from "next/headers"; import { getNextUrl } from "../client"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; import { checkEmailVerification } from "../verify-helper"; type RegisterUserCommand = { diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 3ff3d14017..0535a27c35 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -20,7 +20,7 @@ import { getSessionCookieByLoginName, removeSessionFromCookie, } from "../cookies"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; export async function skipMFAAndContinueWithNextUrl({ userId, diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index a13be1176a..3fe5194336 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -6,7 +6,7 @@ import { VerifyU2FRegistrationRequestSchema } from "@zitadel/proto/zitadel/user/ import { headers } from "next/headers"; import { userAgent } from "next/server"; import { getSessionCookieById } from "../cookies"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; type RegisterU2FCommand = { sessionId: string; diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 513aa03d40..e7c9f5e715 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -19,7 +19,7 @@ import { User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { headers } from "next/headers"; import { getNextUrl } from "../client"; import { getSessionCookieByLoginName } from "../cookies"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; import { loadMostRecentSession } from "../session"; import { checkMFAFactors } from "../verify-helper"; import { createSessionAndUpdateCookie } from "./cookie"; From 74dee578c012ef01e7004fe92d7a0268084e06f2 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 13:34:14 +0200 Subject: [PATCH 18/27] import --- apps/login/src/app/login/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index fb2f5e5f49..00998d225d 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -3,7 +3,7 @@ import { idpTypeToSlug } from "@/lib/idp"; import { loginWithOIDCandSession } from "@/lib/oidc"; import { loginWithSAMLandSession } from "@/lib/saml"; import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname"; -import { constructUrl, getServiceUrlFromHeaders } from "@/lib/service"; +import { constructUrl, getServiceUrlFromHeaders } from "@/lib/service-url"; import { findValidSession } from "@/lib/session"; import { createCallback, From 79043c2f34371fe30e7258893c16d3fce5ede292 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 13:39:02 +0200 Subject: [PATCH 19/27] eliminate duplicate constructurl --- apps/login/src/lib/oidc.ts | 2 +- apps/login/src/lib/saml.ts | 2 +- apps/login/src/lib/self.ts | 2 +- apps/login/src/lib/service.ts | 14 -------------- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/apps/login/src/lib/oidc.ts b/apps/login/src/lib/oidc.ts index c1038d90c4..7efab0b254 100644 --- a/apps/login/src/lib/oidc.ts +++ b/apps/login/src/lib/oidc.ts @@ -8,7 +8,7 @@ import { } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { NextRequest, NextResponse } from "next/server"; -import { constructUrl } from "./service"; +import { constructUrl } from "./service-url"; import { isSessionValid } from "./session"; type LoginWithOIDCandSession = { diff --git a/apps/login/src/lib/saml.ts b/apps/login/src/lib/saml.ts index 9b12e48d25..c2664599b5 100644 --- a/apps/login/src/lib/saml.ts +++ b/apps/login/src/lib/saml.ts @@ -5,7 +5,7 @@ import { create } from "@zitadel/client"; import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { NextRequest, NextResponse } from "next/server"; -import { constructUrl } from "./service"; +import { constructUrl } from "./service-url"; import { isSessionValid } from "./session"; type LoginWithSAMLandSession = { diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index d1971c19b1..7375f4f114 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -4,7 +4,7 @@ import { createServerTransport } from "@zitadel/client/node"; import { createUserServiceClient } from "@zitadel/client/v2"; import { headers } from "next/headers"; import { getSessionCookieById } from "./cookies"; -import { getServiceUrlFromHeaders } from "./service"; +import { getServiceUrlFromHeaders } from "./service-url"; import { getSession } from "./zitadel"; const transport = async (serviceUrl: string, token: string) => { diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index 97143cf003..0fbb083b05 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -7,7 +7,6 @@ import { SAMLService } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb"; import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb"; import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { NextRequest } from "next/server"; import { systemAPIToken } from "./api"; type ServiceClass = @@ -65,16 +64,3 @@ export async function createServiceForHost( return createClientFor(service)(transport); } - -export function constructUrl(request: NextRequest, path: string) { - const forwardedProto = request.headers.get("x-forwarded-proto") - ? `${request.headers.get("x-forwarded-proto")}:` - : request.nextUrl.protocol; - - const forwardedHost = - request.headers.get("x-zitadel-forward-host") ?? - request.headers.get("x-forwarded-host") ?? - request.headers.get("host"); - const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; - return new URL(`${basePath}${path}`, `${forwardedProto}//${forwardedHost}`); -} From b49c2be471638411e83915e145a3c9e4042e3d92 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 13:45:15 +0200 Subject: [PATCH 20/27] await response --- apps/login/src/app/security/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/app/security/route.ts b/apps/login/src/app/security/route.ts index d47daaa40e..704e59c8e8 100644 --- a/apps/login/src/app/security/route.ts +++ b/apps/login/src/app/security/route.ts @@ -12,7 +12,7 @@ export async function GET(request: NextRequest) { const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); - const settings = settingsService + const settings = await settingsService .getSecuritySettings({}) .then((resp) => (resp.settings ? resp.settings : undefined)); From 08d34b87a892ff0b0dc5f372f11c452689132f96 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 14:21:23 +0200 Subject: [PATCH 21/27] cleanup --- apps/login/src/app/security/route.ts | 4 ++-- apps/login/src/middleware.ts | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/login/src/app/security/route.ts b/apps/login/src/app/security/route.ts index 704e59c8e8..4a2b6d4854 100644 --- a/apps/login/src/app/security/route.ts +++ b/apps/login/src/app/security/route.ts @@ -3,9 +3,9 @@ import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { Client } from "@zitadel/client"; import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb"; import { headers } from "next/headers"; -import { NextRequest, NextResponse } from "next/server"; +import { NextResponse } from "next/server"; -export async function GET(request: NextRequest) { +export async function GET() { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index fc02d859c6..4d66d0ab39 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -23,8 +23,6 @@ export async function middleware(request: NextRequest) { const { serviceUrl } = getServiceUrlFromHeaders(_headers); - console.log("defaultCSP", DEFAULT_CSP); - // Call the /security route handler // TODO check this on cloud run deployment const securityResponse = await fetch(`${request.nextUrl.origin}/security`); @@ -38,7 +36,6 @@ export async function middleware(request: NextRequest) { } const { settings: securitySettings } = await securityResponse.json(); - console.log("securitySettings", securitySettings); const instanceHost = `${serviceUrl}` .replace("https://", "") @@ -67,6 +64,7 @@ export async function middleware(request: NextRequest) { } request.nextUrl.href = `${serviceUrl}${request.nextUrl.pathname}${request.nextUrl.search}`; + return NextResponse.rewrite(request.nextUrl, { request: { headers: requestHeaders, From 833669792b607ed679c71a63d0d438326b92aaa9 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 30 Apr 2025 14:38:53 +0200 Subject: [PATCH 22/27] also run on qa --- .github/workflows/docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9485e82977..58a48f9882 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - qa workflow_dispatch: permissions: From 12f8c14ff2cf2270c4dbe02c93b4d2f1eb479071 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 5 May 2025 11:12:56 +0200 Subject: [PATCH 23/27] trigger From 4b8b3b4a2ec41032b601820297b41ccd8475e97b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 7 May 2025 09:30:52 +0200 Subject: [PATCH 24/27] fix: turbo dep --- apps/login/turbo.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/login/turbo.json b/apps/login/turbo.json index e8a243feaf..80224125a2 100644 --- a/apps/login/turbo.json +++ b/apps/login/turbo.json @@ -5,6 +5,10 @@ "outputs": ["dist/**", ".next/**", "!.next/cache/**"], "dependsOn": ["^build"] }, + "build:standalone": { + "outputs": ["dist/**", ".next/**", "!.next/cache/**"], + "dependsOn": ["^build"] + }, "test": { "dependsOn": ["@zitadel/client#build"] }, From 15937f51501eb58db39b8d6effd069ec02f70855 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 7 May 2025 09:40:05 +0200 Subject: [PATCH 25/27] driver opts --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 58a48f9882..b8f37c0ce1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -42,7 +42,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: - driver-opts: 'image=moby/buildkit:v0.11.6' + driver: docker-container - name: Login Public uses: docker/login-action@v3 From 5c53069ed858d4ceb1737f80298b1ca5015a831b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 7 May 2025 10:17:55 +0200 Subject: [PATCH 26/27] fix imports --- apps/login/src/app/(login)/device/consent/page.tsx | 2 +- apps/login/src/app/(login)/device/page.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/login/src/app/(login)/device/consent/page.tsx b/apps/login/src/app/(login)/device/consent/page.tsx index 379dad2720..8cc9b4556f 100644 --- a/apps/login/src/app/(login)/device/consent/page.tsx +++ b/apps/login/src/app/(login)/device/consent/page.tsx @@ -1,6 +1,6 @@ import { ConsentScreen } from "@/components/consent"; import { DynamicTheme } from "@/components/dynamic-theme"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getBrandingSettings, getDefaultOrg, diff --git a/apps/login/src/app/(login)/device/page.tsx b/apps/login/src/app/(login)/device/page.tsx index bde104b631..4353ef5be4 100644 --- a/apps/login/src/app/(login)/device/page.tsx +++ b/apps/login/src/app/(login)/device/page.tsx @@ -1,6 +1,6 @@ import { DeviceCodeForm } from "@/components/device-code-form"; import { DynamicTheme } from "@/components/dynamic-theme"; -import { getServiceUrlFromHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; From 41fb472a4ba537c1e07a97df1696f14fa485ddac Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 7 May 2025 10:20:39 +0200 Subject: [PATCH 27/27] imports --- apps/login/src/lib/server/device.ts | 2 +- apps/login/src/lib/server/oidc.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/login/src/lib/server/device.ts b/apps/login/src/lib/server/device.ts index d96059f6a6..5e36facfc8 100644 --- a/apps/login/src/lib/server/device.ts +++ b/apps/login/src/lib/server/device.ts @@ -2,7 +2,7 @@ import { authorizeOrDenyDeviceAuthorization } from "@/lib/zitadel"; import { headers } from "next/headers"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; export async function completeDeviceAuthorization( deviceAuthorizationId: string, diff --git a/apps/login/src/lib/server/oidc.ts b/apps/login/src/lib/server/oidc.ts index 4ae01b4a47..36a31fe419 100644 --- a/apps/login/src/lib/server/oidc.ts +++ b/apps/login/src/lib/server/oidc.ts @@ -2,7 +2,7 @@ import { getDeviceAuthorizationRequest as zitadelGetDeviceAuthorizationRequest } from "@/lib/zitadel"; import { headers } from "next/headers"; -import { getServiceUrlFromHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service-url"; export async function getDeviceAuthorizationRequest(userCode: string) { const _headers = await headers();