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: [