From 1736a2b3a0c5227c93787944d12fb47f274d4390 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 14 Jan 2025 13:13:57 +0100 Subject: [PATCH 01/67] fix deps, tsconfig, next-env-vars, zitadel api --- apps/login/next-env-vars.d.ts | 33 ++++ apps/login/package.json | 15 +- apps/login/src/lib/api.ts | 36 ++++ apps/login/src/lib/zitadel.ts | 145 ++++++++++++---- apps/login/tsconfig.json | 1 + package.json | 4 +- packages/zitadel-client/src/index.ts | 3 +- pnpm-lock.yaml | 248 +++++++++++++-------------- 8 files changed, 308 insertions(+), 177 deletions(-) create mode 100644 apps/login/next-env-vars.d.ts create mode 100644 apps/login/src/lib/api.ts diff --git a/apps/login/next-env-vars.d.ts b/apps/login/next-env-vars.d.ts new file mode 100644 index 0000000000..dce7ea9065 --- /dev/null +++ b/apps/login/next-env-vars.d.ts @@ -0,0 +1,33 @@ +declare namespace NodeJS { + interface ProcessEnv { + /** + * The system api url + */ + AUDIENCE: string; + + /** + * The system api service user ID + */ + SYSTEM_USER_ID: string; + + /** + * The service user key + */ + SYSTEM_USER_PRIVATE_KEY: string; + + /** + * The instance url + */ + ZITADEL_API_URL: string; + + /** + * The service user id for the instance + */ + ZITADEL_USER_ID: string; + + /** + * The service user token for the instance + */ + ZITADEL_USER_TOKEN: string; + } +} diff --git a/apps/login/package.json b/apps/login/package.json index 6617a416b0..0f913c2553 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -44,14 +44,15 @@ "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", "deepmerge": "^4.3.1", + "jose": "^5.3.0", "moment": "^2.29.4", "next": "15.0.4-canary.23", "next-intl": "^3.25.1", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", "qrcode.react": "^3.1.0", - "react": "19.0.0-rc-66855b96-20241106", - "react-dom": "19.0.0-rc-66855b96-20241106", + "react": "19.0.0", + "react-dom": "19.0.0", "react-hook-form": "7.39.5", "swr": "^2.2.0", "tinycolor2": "1.4.2" @@ -62,19 +63,20 @@ "@testing-library/react": "^16.0.1", "@types/ms": "0.7.34", "@types/node": "22.9.0", - "@types/react": "npm:types-react@19.0.0-rc.1", - "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", + "@types/react": "19.0.2", + "@types/react-dom": "19.0.2", "@types/tinycolor2": "1.4.3", "@types/uuid": "^10.0.0", "@vercel/git-hooks": "1.0.0", + "@zitadel/eslint-config": "workspace:*", "@zitadel/prettier-config": "workspace:*", + "@zitadel/tailwind-config": "workspace:*", "@zitadel/tsconfig": "workspace:*", "autoprefixer": "10.4.20", "concurrently": "^9.1.0", "cypress": "^13.15.2", "del-cli": "6.0.0", "env-cmd": "^10.0.0", - "@zitadel/eslint-config": "workspace:*", "grpc-tools": "1.12.4", "jsdom": "^25.0.1", "lint-staged": "15.2.10", @@ -86,7 +88,6 @@ "start-server-and-test": "^2.0.8", "tailwindcss": "3.4.14", "ts-proto": "^2.2.7", - "typescript": "^5.6.3", - "@zitadel/tailwind-config": "workspace:*" + "typescript": "^5.6.3" } } diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts new file mode 100644 index 0000000000..b6df92f481 --- /dev/null +++ b/apps/login/src/lib/api.ts @@ -0,0 +1,36 @@ +import { importPKCS8, SignJWT } from "jose"; +import { getInstanceByHost } from "./zitadel"; + +export async function getInstanceUrl(host: string): Promise { + const instance = await getInstanceByHost(host); + const generatedDomain = instance.domains.find( + (domain) => domain.generated === true, + ); + + if (!generatedDomain?.domain) { + throw new Error("No generated domain found"); + } + + console.log(`host: ${host}, api: ${generatedDomain?.domain}`); + + return generatedDomain?.domain; +} + +export async function systemAPIToken() { + const audience = process.env.AUDIENCE; + const userID = process.env.SYSTEM_USER_ID; + const key = process.env.SYSTEM_USER_PRIVATE_KEY; + + const decodedToken = Buffer.from(key, "base64").toString("utf-8"); + + const token = new SignJWT({}) + .setProtectedHeader({ alg: "RS256" }) + .setIssuedAt() + .setExpirationTime("1h") + .setIssuer(userID) + .setSubject(userID) + .setAudience(audience) + .sign(await importPKCS8(decodedToken, "RS256")); + + return token; +} diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 835b16a46b..7ba45aca04 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -1,32 +1,22 @@ +import { create, createClientFor, Duration } from "@zitadel/client"; import { createServerTransport } from "@zitadel/client/node"; -import { - createIdpServiceClient, - createOIDCServiceClient, - createOrganizationServiceClient, - createSessionServiceClient, - createSettingsServiceClient, - createUserServiceClient, - makeReqCtx, -} from "@zitadel/client/v2"; -import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; -import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { - AddHumanUserRequest, - ResendEmailCodeRequest, - ResendEmailCodeRequestSchema, - RetrieveIdentityProviderIntentRequest, - SendEmailCodeRequestSchema, - SetPasswordRequest, - SetPasswordRequestSchema, - VerifyPasskeyRegistrationRequest, - VerifyU2FRegistrationRequest, -} from "@zitadel/proto/zitadel/user/v2/user_service_pb"; - -import { create, Duration } from "@zitadel/client"; +import { createSystemServiceClient } from "@zitadel/client/v1"; +import { makeReqCtx } from "@zitadel/client/v2"; +import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; -import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; +import { + CreateCallbackRequest, + OIDCService, +} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; +import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb"; +import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; +import { + Checks, + SessionService, +} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb"; import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb"; import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { @@ -42,19 +32,20 @@ import { User, UserState, } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { + AddHumanUserRequest, + ResendEmailCodeRequest, + ResendEmailCodeRequestSchema, + RetrieveIdentityProviderIntentRequest, + SendEmailCodeRequestSchema, + SetPasswordRequest, + SetPasswordRequestSchema, + UserService, + VerifyPasskeyRegistrationRequest, + VerifyU2FRegistrationRequest, +} from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { unstable_cacheLife as cacheLife } from "next/cache"; - -const transport = createServerTransport( - process.env.ZITADEL_SERVICE_USER_TOKEN!, - { baseUrl: process.env.ZITADEL_API_URL! }, -); - -export const sessionService = createSessionServiceClient(transport); -export const userService = createUserServiceClient(transport); -export const oidcService = createOIDCServiceClient(transport); -export const idpService = createIdpServiceClient(transport); -export const orgService = createOrganizationServiceClient(transport); -export const settingsService = createSettingsServiceClient(transport); +import { systemAPIToken } from "./api"; const useCache = process.env.DEBUG !== "true"; @@ -65,6 +56,86 @@ async function cacheWrapper(callback: Promise) { return callback; } +type ServiceClass = + | typeof IdentityProviderService + | typeof UserService + | typeof OrganizationService + | typeof SessionService + | typeof OIDCService + | typeof SettingsService; + +async function createServiceForHost(service: T) { + // const host = headers().get("X-Forwarded-Host"); + // if (!host) { + // throw new Error("No host header found!"); + // } + + // let instanceUrl; + // try { + // instanceUrl = await getInstanceUrl(host); + // } catch (error) { + // console.error( + // "Could not get instance url, fallback to ZITADEL_API_URL", + // error, + // ); + // instanceUrl = process.env.ZITADEL_API_URL; + // } + + // remove in favor of the above + const instanceUrl = process.env.ZITADEL_API_URL; + + const systemToken = await systemAPIToken(); + + const transport = createServerTransport(systemToken, { + baseUrl: instanceUrl, + }); + + return createClientFor(service)(transport); +} + +const idpService = await createServiceForHost(IdentityProviderService); +const orgService = await createServiceForHost(OrganizationService); +export const sessionService = await createServiceForHost(SessionService); +const userService = await createServiceForHost(UserService); +const oidcService = await createServiceForHost(OIDCService); +const settingsService = await createServiceForHost(SettingsService); + +const systemService = async () => { + const systemToken = await systemAPIToken(); + + const transport = createServerTransport(systemToken, { + baseUrl: process.env.ZITADEL_API_URL, + }); + + return createSystemServiceClient(transport); +}; + +export async function getInstanceByHost(host: string) { + return (await systemService()) + .listInstances( + { + queries: [ + { + query: { + case: "domainQuery", + value: { + domains: [host], + }, + }, + }, + ], + }, + {}, + ) + .then((resp) => { + if (resp.result.length !== 1) { + throw new Error("Could not find instance"); + } + + return resp.result[0]; + }); +} + export async function getBrandingSettings(organization?: string) { const callback = settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) diff --git a/apps/login/tsconfig.json b/apps/login/tsconfig.json index a1efe752c5..c855c43225 100755 --- a/apps/login/tsconfig.json +++ b/apps/login/tsconfig.json @@ -2,6 +2,7 @@ "extends": "@zitadel/tsconfig/nextjs.json", "compilerOptions": { "jsx": "preserve", + "target": "es2022", "baseUrl": ".", "paths": { "@/*": ["./src/*"] diff --git a/package.json b/package.json index 82d893f238..560bf82332 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,7 @@ }, "pnpm": { "overrides": { - "@typescript-eslint/parser": "^7.9.0", - "@types/react": "npm:types-react@19.0.0-rc.1", - "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1" + "@typescript-eslint/parser": "^7.9.0" } }, "devDependencies": { diff --git a/packages/zitadel-client/src/index.ts b/packages/zitadel-client/src/index.ts index 64c3af5050..30fec39464 100644 --- a/packages/zitadel-client/src/index.ts +++ b/packages/zitadel-client/src/index.ts @@ -1,8 +1,9 @@ -export { toDate } from "./helpers"; +export { createClientFor, toDate } from "./helpers"; export { NewAuthorizationBearerInterceptor } from "./interceptors"; // TODO: Move this to `./protobuf.ts` and export it from there export { create, fromJson, toJson } from "@bufbuild/protobuf"; export type { JsonObject } from "@bufbuild/protobuf"; +export type { GenService } from "@bufbuild/protobuf/codegenv1"; export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt"; export type { Duration, Timestamp } from "@bufbuild/protobuf/wkt"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61d01b79c5..b471e9e8ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,6 @@ settings: overrides: '@typescript-eslint/parser': ^7.9.0 - '@types/react': npm:types-react@19.0.0-rc.1 - '@types/react-dom': npm:types-react-dom@19.0.0-rc.1 importers: @@ -78,16 +76,16 @@ importers: dependencies: '@headlessui/react': specifier: ^2.1.9 - version: 2.1.9(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + version: 2.1.9(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@heroicons/react': specifier: 2.1.3 - version: 2.1.3(react@19.0.0-rc-66855b96-20241106) + version: 2.1.3(react@19.0.0) '@tailwindcss/forms': specifier: 0.5.7 version: 0.5.7(tailwindcss@3.4.14) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106) + version: 1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -103,36 +101,39 @@ importers: deepmerge: specifier: ^4.3.1 version: 4.3.1 + jose: + specifier: ^5.3.0 + version: 5.8.0 moment: specifier: ^2.29.4 version: 2.30.1 next: specifier: 15.0.4-canary.23 - version: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) + version: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) next-intl: specifier: ^3.25.1 - version: 3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106) + version: 3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + version: 0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) nice-grpc: specifier: 2.0.1 version: 2.0.1 qrcode.react: specifier: ^3.1.0 - version: 3.1.0(react@19.0.0-rc-66855b96-20241106) + version: 3.1.0(react@19.0.0) react: - specifier: 19.0.0-rc-66855b96-20241106 - version: 19.0.0-rc-66855b96-20241106 + specifier: 19.0.0 + version: 19.0.0 react-dom: - specifier: 19.0.0-rc-66855b96-20241106 - version: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + specifier: 19.0.0 + version: 19.0.0(react@19.0.0) react-hook-form: specifier: 7.39.5 - version: 7.39.5(react@19.0.0-rc-66855b96-20241106) + version: 7.39.5(react@19.0.0) swr: specifier: ^2.2.0 - version: 2.2.5(react@19.0.0-rc-66855b96-20241106) + version: 2.2.5(react@19.0.0) tinycolor2: specifier: 1.4.2 version: 1.4.2 @@ -145,7 +146,7 @@ importers: version: 6.6.3 '@testing-library/react': specifier: ^16.0.1 - version: 16.0.1(@testing-library/dom@10.4.0)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/ms': specifier: 0.7.34 version: 0.7.34 @@ -153,11 +154,11 @@ importers: specifier: 22.9.0 version: 22.9.0 '@types/react': - specifier: npm:types-react@19.0.0-rc.1 - version: types-react@19.0.0-rc.1 + specifier: 19.0.2 + version: 19.0.2 '@types/react-dom': - specifier: npm:types-react-dom@19.0.0-rc.1 - version: types-react-dom@19.0.0-rc.1 + specifier: 19.0.2 + version: 19.0.2(@types/react@19.0.2) '@types/tinycolor2': specifier: 1.4.3 version: 1.4.3 @@ -1513,8 +1514,8 @@ packages: engines: {node: '>=18'} peerDependencies: '@testing-library/dom': ^10.0.0 - '@types/react': npm:types-react@19.0.0-rc.1 - '@types/react-dom': npm:types-react-dom@19.0.0-rc.1 + '@types/react': ^18.0.0 + '@types/react-dom': ^18.0.0 react: ^18.0.0 react-dom: ^18.0.0 peerDependenciesMeta: @@ -1553,11 +1554,13 @@ packages: '@types/node@22.9.0': resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} - '@types/prop-types@15.7.12': - resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + '@types/react-dom@19.0.2': + resolution: {integrity: sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==} + peerDependencies: + '@types/react': ^19.0.0 - '@types/react@18.3.12': - resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + '@types/react@19.0.2': + resolution: {integrity: sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==} '@types/sinonjs__fake-timers@8.1.1': resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} @@ -3947,10 +3950,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@19.0.0-rc-66855b96-20241106: - resolution: {integrity: sha512-D25vdaytZ1wFIRiwNU98NPQ/upS2P8Co4/oNoa02PzHbh8deWdepjm5qwZM/46OdSiGv4WSWwxP55RO9obqJEQ==} + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: - react: 19.0.0-rc-66855b96-20241106 + react: ^19.0.0 react-hook-form@7.39.5: resolution: {integrity: sha512-OE0HKyz5IPc6svN2wd+e+evidZrw4O4WZWAWYzQVZuHi+hYnHFSLnxOq0ddjbdmaLIsLHut/ab7j72y2QT3+KA==} @@ -3968,8 +3971,8 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react@19.0.0-rc-66855b96-20241106: - resolution: {integrity: sha512-klH7xkT71SxRCx4hb1hly5FJB21Hz0ACyxbXYAECEqssUjtJeFUAaI2U1DgJAzkGEnvEm3DkxuBchMC/9K4ipg==} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -4089,8 +4092,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.25.0-rc-66855b96-20241106: - resolution: {integrity: sha512-HQXp/Mnp/MMRSXMQF7urNFla+gmtXW/Gr1KliuR0iboTit4KvZRY8KYaq5ccCTAOJiUqQh2rE2F3wgUekmgdlA==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} @@ -4574,12 +4577,6 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} - types-react-dom@19.0.0-rc.1: - resolution: {integrity: sha512-VSLZJl8VXCD0fAWp7DUTFUDCcZ8DVXOQmjhJMD03odgeFmu14ZQJHCXeETm3BEAhJqfgJaFkLnGkQv88sRx0fQ==} - - types-react@19.0.0-rc.1: - resolution: {integrity: sha512-RshndUfqTW6K3STLPis8BtAYCGOkMbtvYsi90gmVNDZBXUyUc5juf2PE9LfS/JmOlUIRO8cWTS/1MTnmhjDqyQ==} - typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} @@ -5438,18 +5435,18 @@ snapshots: '@floating-ui/core': 1.6.8 '@floating-ui/utils': 0.2.8 - '@floating-ui/react-dom@2.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@floating-ui/dom': 1.6.11 - react: 19.0.0-rc-66855b96-20241106 - react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - '@floating-ui/react@0.26.24(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + '@floating-ui/react@0.26.24(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@floating-ui/utils': 0.2.8 - react: 19.0.0-rc-66855b96-20241106 - react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) tabbable: 6.2.0 '@floating-ui/utils@0.2.8': {} @@ -5501,18 +5498,18 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 - '@headlessui/react@2.1.9(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + '@headlessui/react@2.1.9(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@floating-ui/react': 0.26.24(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) - '@react-aria/focus': 3.18.3(react@19.0.0-rc-66855b96-20241106) - '@react-aria/interactions': 3.22.3(react@19.0.0-rc-66855b96-20241106) - '@tanstack/react-virtual': 3.10.6(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) - react: 19.0.0-rc-66855b96-20241106 - react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + '@floating-ui/react': 0.26.24(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@react-aria/focus': 3.18.3(react@19.0.0) + '@react-aria/interactions': 3.22.3(react@19.0.0) + '@tanstack/react-virtual': 3.10.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - '@heroicons/react@2.1.3(react@19.0.0-rc-66855b96-20241106)': + '@heroicons/react@2.1.3(react@19.0.0)': dependencies: - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 '@humanwhocodes/config-array@0.13.0': dependencies: @@ -5810,45 +5807,45 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@react-aria/focus@3.18.3(react@19.0.0-rc-66855b96-20241106)': + '@react-aria/focus@3.18.3(react@19.0.0)': dependencies: - '@react-aria/interactions': 3.22.3(react@19.0.0-rc-66855b96-20241106) - '@react-aria/utils': 3.25.3(react@19.0.0-rc-66855b96-20241106) - '@react-types/shared': 3.25.0(react@19.0.0-rc-66855b96-20241106) + '@react-aria/interactions': 3.22.3(react@19.0.0) + '@react-aria/utils': 3.25.3(react@19.0.0) + '@react-types/shared': 3.25.0(react@19.0.0) '@swc/helpers': 0.5.5 clsx: 2.1.1 - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 - '@react-aria/interactions@3.22.3(react@19.0.0-rc-66855b96-20241106)': + '@react-aria/interactions@3.22.3(react@19.0.0)': dependencies: - '@react-aria/ssr': 3.9.6(react@19.0.0-rc-66855b96-20241106) - '@react-aria/utils': 3.25.3(react@19.0.0-rc-66855b96-20241106) - '@react-types/shared': 3.25.0(react@19.0.0-rc-66855b96-20241106) + '@react-aria/ssr': 3.9.6(react@19.0.0) + '@react-aria/utils': 3.25.3(react@19.0.0) + '@react-types/shared': 3.25.0(react@19.0.0) '@swc/helpers': 0.5.5 - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 - '@react-aria/ssr@3.9.6(react@19.0.0-rc-66855b96-20241106)': + '@react-aria/ssr@3.9.6(react@19.0.0)': dependencies: '@swc/helpers': 0.5.5 - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 - '@react-aria/utils@3.25.3(react@19.0.0-rc-66855b96-20241106)': + '@react-aria/utils@3.25.3(react@19.0.0)': dependencies: - '@react-aria/ssr': 3.9.6(react@19.0.0-rc-66855b96-20241106) - '@react-stately/utils': 3.10.4(react@19.0.0-rc-66855b96-20241106) - '@react-types/shared': 3.25.0(react@19.0.0-rc-66855b96-20241106) + '@react-aria/ssr': 3.9.6(react@19.0.0) + '@react-stately/utils': 3.10.4(react@19.0.0) + '@react-types/shared': 3.25.0(react@19.0.0) '@swc/helpers': 0.5.5 clsx: 2.1.1 - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 - '@react-stately/utils@3.10.4(react@19.0.0-rc-66855b96-20241106)': + '@react-stately/utils@3.10.4(react@19.0.0)': dependencies: '@swc/helpers': 0.5.13 - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 - '@react-types/shared@3.25.0(react@19.0.0-rc-66855b96-20241106)': + '@react-types/shared@3.25.0(react@19.0.0)': dependencies: - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 '@rollup/rollup-android-arm-eabi@4.25.0': optional: true @@ -5937,11 +5934,11 @@ snapshots: mini-svg-data-uri: 1.4.4 tailwindcss: 3.4.14 - '@tanstack/react-virtual@3.10.6(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + '@tanstack/react-virtual@3.10.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@tanstack/virtual-core': 3.10.6 - react: 19.0.0-rc-66855b96-20241106 - react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) '@tanstack/virtual-core@3.10.6': {} @@ -5966,15 +5963,15 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)': + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.25.6 '@testing-library/dom': 10.4.0 - react: 19.0.0-rc-66855b96-20241106 - react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': types-react@19.0.0-rc.1 - '@types/react-dom': types-react-dom@19.0.0-rc.1 + '@types/react': 19.0.2 + '@types/react-dom': 19.0.2(@types/react@19.0.2) '@types/aria-query@5.0.4': {} @@ -6011,11 +6008,12 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/prop-types@15.7.12': {} - - '@types/react@18.3.12': + '@types/react-dom@19.0.2(@types/react@19.0.2)': + dependencies: + '@types/react': 19.0.2 + + '@types/react@19.0.2': dependencies: - '@types/prop-types': 15.7.12 csstype: 3.1.3 '@types/sinonjs__fake-timers@8.1.1': {} @@ -6142,12 +6140,12 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106)': + '@vercel/analytics@1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) - react: 19.0.0-rc-66855b96-20241106 + next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + react: 19.0.0 '@vercel/git-hooks@1.0.0': {} @@ -8212,21 +8210,21 @@ snapshots: negotiator@1.0.0: {} - next-intl@3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106): + next-intl@3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 1.0.0 - next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) - react: 19.0.0-rc-66855b96-20241106 - use-intl: 3.25.1(react@19.0.0-rc-66855b96-20241106) + next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + react: 19.0.0 + use-intl: 3.25.1(react@19.0.0) - next-themes@0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106): + next-themes@0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7) - react: 19.0.0-rc-66855b96-20241106 - react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7): + next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7): dependencies: '@next/env': 15.0.4-canary.23 '@swc/counter': 0.1.3 @@ -8234,9 +8232,9 @@ snapshots: busboy: 1.6.0 caniuse-lite: 1.0.30001680 postcss: 8.4.31 - react: 19.0.0-rc-66855b96-20241106 - react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) - styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0) optionalDependencies: '@next/swc-darwin-arm64': 15.0.4-canary.23 '@next/swc-darwin-x64': 15.0.4-canary.23 @@ -8603,9 +8601,9 @@ snapshots: punycode@2.3.1: {} - qrcode.react@3.1.0(react@19.0.0-rc-66855b96-20241106): + qrcode.react@3.1.0(react@19.0.0): dependencies: - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 qs@6.13.0: dependencies: @@ -8613,14 +8611,14 @@ snapshots: queue-microtask@1.2.3: {} - react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106): + react-dom@19.0.0(react@19.0.0): dependencies: - react: 19.0.0-rc-66855b96-20241106 - scheduler: 0.25.0-rc-66855b96-20241106 + react: 19.0.0 + scheduler: 0.25.0 - react-hook-form@7.39.5(react@19.0.0-rc-66855b96-20241106): + react-hook-form@7.39.5(react@19.0.0): dependencies: - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 react-is@16.13.1: {} @@ -8628,7 +8626,7 @@ snapshots: react-refresh@0.14.2: {} - react@19.0.0-rc-66855b96-20241106: {} + react@19.0.0: {} read-cache@1.0.0: dependencies: @@ -8782,7 +8780,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.25.0-rc-66855b96-20241106: {} + scheduler@0.25.0: {} semver@6.3.1: {} @@ -9041,10 +9039,10 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(@babel/core@7.26.0)(react@19.0.0-rc-66855b96-20241106): + styled-jsx@5.1.6(@babel/core@7.26.0)(react@19.0.0): dependencies: client-only: 0.0.1 - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 optionalDependencies: '@babel/core': 7.26.0 @@ -9072,11 +9070,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swr@2.2.5(react@19.0.0-rc-66855b96-20241106): + swr@2.2.5(react@19.0.0): dependencies: client-only: 0.0.1 - react: 19.0.0-rc-66855b96-20241106 - use-sync-external-store: 1.2.2(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0 + use-sync-external-store: 1.2.2(react@19.0.0) symbol-tree@3.2.4: {} @@ -9329,14 +9327,6 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 - types-react-dom@19.0.0-rc.1: - dependencies: - '@types/react': 18.3.12 - - types-react@19.0.0-rc.1: - dependencies: - csstype: 3.1.3 - typescript@5.6.3: {} unbox-primitive@1.0.2: @@ -9374,15 +9364,15 @@ snapshots: dependencies: punycode: 2.3.1 - use-intl@3.25.1(react@19.0.0-rc-66855b96-20241106): + use-intl@3.25.1(react@19.0.0): dependencies: '@formatjs/fast-memoize': 2.2.3 intl-messageformat: 10.7.7 - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 - use-sync-external-store@1.2.2(react@19.0.0-rc-66855b96-20241106): + use-sync-external-store@1.2.2(react@19.0.0): dependencies: - react: 19.0.0-rc-66855b96-20241106 + react: 19.0.0 util-deprecate@1.0.2: {} From 098fb7c4e306af5cafde3aeb231c50e2f556d366 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 14 Jan 2025 13:40:36 +0100 Subject: [PATCH 02/67] get instance by host --- apps/login/next-env-vars.d.ts | 18 +++++++++++------- apps/login/src/lib/zitadel.ts | 35 +++++++++++++++++------------------ apps/login/src/middleware.ts | 24 +++++++++++++++--------- turbo.json | 16 +++++++--------- 4 files changed, 50 insertions(+), 43 deletions(-) diff --git a/apps/login/next-env-vars.d.ts b/apps/login/next-env-vars.d.ts index dce7ea9065..773986a7c9 100644 --- a/apps/login/next-env-vars.d.ts +++ b/apps/login/next-env-vars.d.ts @@ -1,33 +1,37 @@ declare namespace NodeJS { interface ProcessEnv { /** - * The system api url + * Multitenancy: The system api url */ AUDIENCE: string; /** - * The system api service user ID + * Multitenancy: The service user id */ SYSTEM_USER_ID: string; /** - * The service user key + * Multitenancy: The service user private key */ SYSTEM_USER_PRIVATE_KEY: string; /** - * The instance url + * Self hosting: The instance url */ ZITADEL_API_URL: string; /** - * The service user id for the instance + * Self hosting: The service user id */ ZITADEL_USER_ID: string; - /** - * The service user token for the instance + * Self hosting: The service user token */ ZITADEL_USER_TOKEN: string; + + /** + * Optional: wheter a user must have verified email + */ + EMAIL_VERIFICATION: string; } } diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 7ba45aca04..2aa4e9b732 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -45,7 +45,8 @@ import { VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { unstable_cacheLife as cacheLife } from "next/cache"; -import { systemAPIToken } from "./api"; +import { headers } from "next/headers"; +import { getInstanceUrl, systemAPIToken } from "./api"; const useCache = process.env.DEBUG !== "true"; @@ -65,24 +66,22 @@ type ServiceClass = | typeof SettingsService; async function createServiceForHost(service: T) { - // const host = headers().get("X-Forwarded-Host"); - // if (!host) { - // throw new Error("No host header found!"); - // } + const _headers = await headers(); + const host = _headers.get("X-Forwarded-Host"); + if (!host) { + throw new Error("No host header found!"); + } - // let instanceUrl; - // try { - // instanceUrl = await getInstanceUrl(host); - // } catch (error) { - // console.error( - // "Could not get instance url, fallback to ZITADEL_API_URL", - // error, - // ); - // instanceUrl = process.env.ZITADEL_API_URL; - // } - - // remove in favor of the above - const instanceUrl = process.env.ZITADEL_API_URL; + let instanceUrl; + try { + instanceUrl = await getInstanceUrl(host); + } catch (error) { + console.error( + "Could not get instance url, fallback to ZITADEL_API_URL", + error, + ); + instanceUrl = process.env.ZITADEL_API_URL; + } const systemToken = await systemAPIToken(); diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 93cb65581c..f8f9199740 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -9,28 +9,34 @@ export const config = { ], }; -const INSTANCE = process.env.ZITADEL_API_URL; -const SERVICE_USER_ID = process.env.ZITADEL_SERVICE_USER_ID as string; +export async function middleware(request: NextRequest) { + // escape proxy if the environment is + if ( + !process.env.ZITADEL_API_URL || + !process.env.ZITADEL_USER_ID || + !process.env.ZITADEL_USER_TOKEN + ) { + return NextResponse.next(); + } + + const INSTANCE_URL = process.env.ZITADEL_API_URL; + const instanceHost = `${INSTANCE_URL}`.replace("https://", ""); -export function middleware(request: NextRequest) { const requestHeaders = new Headers(request.headers); - requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID); + requestHeaders.set("x-zitadel-login-client", process.env.ZITADEL_USER_ID); // this is a workaround for the next.js server not forwarding the host header // requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`); // this is a workaround for the next.js server not forwarding the host header - requestHeaders.set( - "x-zitadel-instance-host", - `${INSTANCE}`.replace(/^https?:\/\//, ""), - ); + requestHeaders.set("x-zitadel-instance-host", instanceHost); const responseHeaders = new Headers(); responseHeaders.set("Access-Control-Allow-Origin", "*"); responseHeaders.set("Access-Control-Allow-Headers", "*"); - request.nextUrl.href = `${INSTANCE}${request.nextUrl.pathname}${request.nextUrl.search}`; + request.nextUrl.href = `${INSTANCE_URL}${request.nextUrl.pathname}${request.nextUrl.search}`; return NextResponse.rewrite(request.nextUrl, { request: { headers: requestHeaders, diff --git a/turbo.json b/turbo.json index 2817c8c157..ca14a23035 100644 --- a/turbo.json +++ b/turbo.json @@ -4,16 +4,14 @@ "globalDependencies": ["**/.env.*local"], "globalEnv": [ "DEBUG", - "ZITADEL_API_URL", - "ZITADEL_SERVICE_USER_ID", - "ZITADEL_SERVICE_USER_TOKEN", - "ZITADEL_SYSTEM_API_URL", - "ZITADEL_SYSTEM_API_USERID", - "ZITADEL_SYSTEM_API_KEY", - "ZITADEL_ISSUER", - "ZITADEL_ADMIN_TOKEN", + "VERCEL_URL", "EMAIL_VERIFICATION", - "VERCEL_URL" + "AUDIENCE", + "SYSTEM_USER_ID", + "SYSTEM_USER_PRIVATE_KEY", + "ZITADEL_API_URL", + "ZITADEL_USER_ID", + "ZITADEL_USER_TOKEN" ], "tasks": { "generate": { From 53df6a8c83f6df2b6f1d2a5ec8c4a73875b5e9b2 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 14 Jan 2025 13:52:15 +0100 Subject: [PATCH 03/67] proxy anyways for the moment --- apps/login/src/middleware.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index f8f9199740..27ad70c448 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -10,14 +10,14 @@ export const config = { }; export async function middleware(request: NextRequest) { - // escape proxy if the environment is - if ( - !process.env.ZITADEL_API_URL || - !process.env.ZITADEL_USER_ID || - !process.env.ZITADEL_USER_TOKEN - ) { - return NextResponse.next(); - } + // escape proxy if the environment is setup for multitenancy + // if ( + // !process.env.ZITADEL_API_URL || + // !process.env.ZITADEL_USER_ID || + // !process.env.ZITADEL_USER_TOKEN + // ) { + // return NextResponse.next(); + // } const INSTANCE_URL = process.env.ZITADEL_API_URL; const instanceHost = `${INSTANCE_URL}`.replace("https://", ""); From 92516e695a8691e268a782d3401867948398ea74 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 14 Jan 2025 13:59:30 +0100 Subject: [PATCH 04/67] settings service cleanup --- apps/login/src/app/(login)/idp/page.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index 30d1c9fab5..fc1af66236 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -1,17 +1,8 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; -import { getBrandingSettings, settingsService } from "@/lib/zitadel"; -import { makeReqCtx } from "@zitadel/client/v2"; +import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; -function getIdentityProviders(orgId?: string) { - return settingsService - .getActiveIdentityProviders({ ctx: makeReqCtx(orgId) }, {}) - .then((resp) => { - return resp.identityProviders; - }); -} - export default async function Page(props: { searchParams: Promise>; }) { @@ -22,7 +13,11 @@ export default async function Page(props: { const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; - const identityProviders = await getIdentityProviders(organization); + const identityProviders = await getActiveIdentityProviders(organization).then( + (resp) => { + return resp.identityProviders; + }, + ); const branding = await getBrandingSettings(organization); From 102dba0e5110d190d771b51bcd5787312e9a8a48 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 14 Jan 2025 16:36:56 +0100 Subject: [PATCH 05/67] singleton service --- apps/login/src/lib/service.ts | 124 +++++++++++++++++++ apps/login/src/lib/zitadel.ts | 170 +++++++++++++++++---------- packages/zitadel-client/src/index.ts | 1 + 3 files changed, 232 insertions(+), 63 deletions(-) create mode 100644 apps/login/src/lib/service.ts diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts new file mode 100644 index 0000000000..b655283315 --- /dev/null +++ b/apps/login/src/lib/service.ts @@ -0,0 +1,124 @@ +import { Client, createClientFor } from "@zitadel/client"; +import { createServerTransport } from "@zitadel/client/node"; +import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb"; +import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; +import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_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 { getInstanceUrl, systemAPIToken } from "./api"; + +type ServiceClass = + | typeof IdentityProviderService + | typeof UserService + | typeof OrganizationService + | typeof SessionService + | typeof OIDCService + | typeof SettingsService; + +export async function createServiceForHost( + service: T, + host: string, +) { + let instanceUrl; + try { + instanceUrl = await getInstanceUrl(host); + } catch (error) { + console.error( + "Could not get instance url, fallback to ZITADEL_API_URL", + error, + ); + instanceUrl = process.env.ZITADEL_API_URL; + } + + const systemToken = await systemAPIToken(); + + const transport = createServerTransport(systemToken, { + baseUrl: instanceUrl, + }); + + return createClientFor(service)(transport); +} + +export class ServiceInitializer { + public idpService: Client | null = null; + public orgService: Client | null = null; + public sessionService: Client | null = null; + public userService: Client | null = null; + public oidcService: Client | null = null; + public settingsService: Client | null = null; + + private static instance: ServiceInitializer; + + constructor(private host: string) { + this.initializeServices(); + } + + public static async getInstance(host: string): Promise { + if (!ServiceInitializer.instance) { + ServiceInitializer.instance = new ServiceInitializer(host); + await ServiceInitializer.instance.initializeServices(); + } + return ServiceInitializer.instance; + } + + async initializeServices() { + this.idpService = await createServiceForHost( + IdentityProviderService, + this.host, + ); + this.orgService = await createServiceForHost( + OrganizationService, + this.host, + ); + this.sessionService = await createServiceForHost(SessionService, this.host); + this.userService = await createServiceForHost(UserService, this.host); + this.oidcService = await createServiceForHost(OIDCService, this.host); + this.settingsService = await createServiceForHost( + SettingsService, + this.host, + ); + } + + public getSettingsService(): Client { + if (!this.settingsService) { + throw new Error("SettingsService is not initialized"); + } + return this.settingsService; + } + + public getUserService(): Client { + if (!this.userService) { + throw new Error("UserService is not initialized"); + } + return this.userService; + } + + public getOrgService(): Client { + if (!this.orgService) { + throw new Error("OrganizationService is not initialized"); + } + return this.orgService; + } + + public getSessionService(): Client { + if (!this.sessionService) { + throw new Error("SessionService is not initialized"); + } + return this.sessionService; + } + + public getIDPService(): Client { + if (!this.idpService) { + throw new Error("IDPService is not initialized"); + } + return this.idpService; + } + + public getOIDCService(): Client { + if (!this.oidcService) { + throw new Error("OIDCService is not initialized"); + } + return this.oidcService; + } +} diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 2aa4e9b732..5b94b95cdc 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -1,22 +1,13 @@ -import { create, createClientFor, Duration } from "@zitadel/client"; +import { create, Duration } from "@zitadel/client"; import { createServerTransport } from "@zitadel/client/node"; import { createSystemServiceClient } from "@zitadel/client/v1"; import { makeReqCtx } from "@zitadel/client/v2"; -import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; -import { - CreateCallbackRequest, - OIDCService, -} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; +import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; -import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; -import { - Checks, - SessionService, -} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb"; import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb"; import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { @@ -40,13 +31,12 @@ import { SendEmailCodeRequestSchema, SetPasswordRequest, SetPasswordRequestSchema, - UserService, VerifyPasskeyRegistrationRequest, VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { unstable_cacheLife as cacheLife } from "next/cache"; -import { headers } from "next/headers"; -import { getInstanceUrl, systemAPIToken } from "./api"; +import { systemAPIToken } from "./api"; +import { ServiceInitializer } from "./service"; const useCache = process.env.DEBUG !== "true"; @@ -57,47 +47,17 @@ async function cacheWrapper(callback: Promise) { return callback; } -type ServiceClass = - | typeof IdentityProviderService - | typeof UserService - | typeof OrganizationService - | typeof SessionService - | typeof OIDCService - | typeof SettingsService; +// Example usage +const serviceInitializer = await ServiceInitializer.getInstance(); -async function createServiceForHost(service: T) { - const _headers = await headers(); - const host = _headers.get("X-Forwarded-Host"); - if (!host) { - throw new Error("No host header found!"); - } - - let instanceUrl; - try { - instanceUrl = await getInstanceUrl(host); - } catch (error) { - console.error( - "Could not get instance url, fallback to ZITADEL_API_URL", - error, - ); - instanceUrl = process.env.ZITADEL_API_URL; - } - - const systemToken = await systemAPIToken(); - - const transport = createServerTransport(systemToken, { - baseUrl: instanceUrl, - }); - - return createClientFor(service)(transport); -} - -const idpService = await createServiceForHost(IdentityProviderService); -const orgService = await createServiceForHost(OrganizationService); -export const sessionService = await createServiceForHost(SessionService); -const userService = await createServiceForHost(UserService); -const oidcService = await createServiceForHost(OIDCService); -const settingsService = await createServiceForHost(SettingsService); +export const { + sessionService, + idpService, + orgService, + settingsService, + oidcService, + userService, +} = serviceInitializer; const systemService = async () => { const systemToken = await systemAPIToken(); @@ -136,6 +96,8 @@ export async function getInstanceByHost(host: string) { } export async function getBrandingSettings(organization?: string) { + const settingsService = serviceInitializer.getSettingsService(); + const callback = settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); @@ -144,6 +106,8 @@ export async function getBrandingSettings(organization?: string) { } export async function getLoginSettings(orgId?: string) { + const settingsService = serviceInitializer.getSettingsService(); + const callback = settingsService .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); @@ -152,6 +116,8 @@ export async function getLoginSettings(orgId?: string) { } export async function listIDPLinks(userId: string) { + const userService = serviceInitializer.getUserService(); + return userService.listIDPLinks( { userId, @@ -161,6 +127,8 @@ export async function listIDPLinks(userId: string) { } export async function addOTPEmail(userId: string) { + const userService = serviceInitializer.getUserService(); + return userService.addOTPEmail( { userId, @@ -170,14 +138,20 @@ export async function addOTPEmail(userId: string) { } export async function addOTPSMS(userId: string) { + const userService = serviceInitializer.getUserService(); + return userService.addOTPSMS({ userId }, {}); } export async function registerTOTP(userId: string) { + const userService = serviceInitializer.getUserService(); + return userService.registerTOTP({ userId }, {}); } export async function getGeneralSettings() { + const settingsService = serviceInitializer.getSettingsService(); + const callback = settingsService .getGeneralSettings({}, {}) .then((resp) => resp.supportedLanguages); @@ -186,6 +160,8 @@ export async function getGeneralSettings() { } export async function getLegalAndSupportSettings(organization?: string) { + const settingsService = serviceInitializer.getSettingsService(); + const callback = settingsService .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); @@ -194,6 +170,8 @@ export async function getLegalAndSupportSettings(organization?: string) { } export async function getPasswordComplexitySettings(organization?: string) { + const settingsService = serviceInitializer.getSettingsService(); + const callback = settingsService .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) .then((resp) => (resp.settings ? resp.settings : undefined)); @@ -206,6 +184,8 @@ export async function createSessionFromChecks( challenges: RequestChallenges | undefined, lifetime?: Duration, ) { + const sessionService = serviceInitializer.getSessionService(); + return sessionService.createSession( { checks: checks, @@ -224,6 +204,8 @@ export async function createSessionForUserIdAndIdpIntent( }, lifetime?: Duration, ) { + const sessionService = serviceInitializer.getSessionService(); + return sessionService.createSession({ checks: { user: { @@ -245,6 +227,8 @@ export async function setSession( checks?: Checks, lifetime?: Duration, ) { + const sessionService = serviceInitializer.getSessionService(); + return sessionService.setSession( { sessionId, @@ -265,14 +249,20 @@ export async function getSession({ sessionId: string; sessionToken: string; }) { + const sessionService = serviceInitializer.getSessionService(); + return sessionService.getSession({ sessionId, sessionToken }, {}); } export async function deleteSession(sessionId: string, sessionToken: string) { + const sessionService = serviceInitializer.getSessionService(); + return sessionService.deleteSession({ sessionId, sessionToken }, {}); } export async function listSessions(ids: string[]) { + const sessionService = serviceInitializer.getSessionService(); + return sessionService.listSessions( { queries: [ @@ -303,6 +293,8 @@ export async function addHumanUser({ password, organization, }: AddHumanUserData) { + const userService = serviceInitializer.getUserService(); + return userService.addHumanUser({ email: { email, @@ -323,14 +315,20 @@ export async function addHumanUser({ } export async function addHuman(request: AddHumanUserRequest) { + const userService = serviceInitializer.getUserService(); + return userService.addHumanUser(request); } export async function verifyTOTPRegistration(code: string, userId: string) { + const userService = serviceInitializer.getUserService(); + return userService.verifyTOTPRegistration({ code, userId }, {}); } export async function getUserByID(userId: string) { + const userService = serviceInitializer.getUserService(); + return userService.getUserByID({ userId }, {}); } @@ -338,10 +336,14 @@ export async function verifyInviteCode( userId: string, verificationCode: string, ) { + const userService = serviceInitializer.getUserService(); + return userService.verifyInviteCode({ userId, verificationCode }, {}); } export async function resendInviteCode(userId: string) { + const userService = serviceInitializer.getUserService(); + return userService.resendInviteCode({ userId }, {}); } @@ -368,6 +370,8 @@ export async function sendEmailCode( }); } + const userService = serviceInitializer.getUserService(); + return userService.sendEmailCode(medium, {}); } @@ -383,6 +387,8 @@ export async function createInviteCode(userId: string, host: string | null) { }; } + const userService = serviceInitializer.getUserService(); + return userService.createInviteCode( { userId, @@ -492,6 +498,8 @@ export async function listUsers({ ); } + const userService = serviceInitializer.getUserService(); + return userService.listUsers({ queries: queries }); } @@ -571,6 +579,8 @@ export async function searchUsers({ ); } + const userService = serviceInitializer.getUserService(); + const loginNameResult = await userService.listUsers({ queries: queries }); if (!loginNameResult || !loginNameResult.details) { @@ -654,6 +664,8 @@ export async function searchUsers({ } export async function getDefaultOrg(): Promise { + const orgService = serviceInitializer.getOrgService(); + return orgService .listOrganizations( { @@ -672,6 +684,8 @@ export async function getDefaultOrg(): Promise { } export async function getOrgsByDomain(domain: string) { + const orgService = serviceInitializer.getOrgService(); + return orgService.listOrganizations( { queries: [ @@ -694,6 +708,8 @@ export async function startIdentityProviderFlow({ idpId: string; urls: RedirectURLsJson; }) { + const userService = serviceInitializer.getUserService(); + return userService.startIdentityProviderIntent({ idpId, content: { @@ -707,6 +723,8 @@ export async function retrieveIdentityProviderInformation({ idpIntentId, idpIntentToken, }: RetrieveIdentityProviderIntentRequest) { + const userService = serviceInitializer.getUserService(); + return userService.retrieveIdentityProviderIntent({ idpIntentId, idpIntentToken, @@ -718,16 +736,22 @@ export async function getAuthRequest({ }: { authRequestId: string; }) { + const oidcService = serviceInitializer.getOIDCService(); + return oidcService.getAuthRequest({ authRequestId, }); } export async function createCallback(req: CreateCallbackRequest) { + const oidcService = serviceInitializer.getOIDCService(); + return oidcService.createCallback(req); } export async function verifyEmail(userId: string, verificationCode: string) { + const userService = serviceInitializer.getUserService(); + return userService.verifyEmail( { userId, @@ -756,10 +780,14 @@ export async function resendEmailCode( request = { ...request, verification: { case: "sendCode", value: medium } }; } + const userService = serviceInitializer.getUserService(); + return userService.resendEmailCode(request, {}); } export function retrieveIDPIntent(id: string, token: string) { + const userService = serviceInitializer.getUserService(); + return userService.retrieveIdentityProviderIntent( { idpIntentId: id, idpIntentToken: token }, {}, @@ -767,6 +795,8 @@ export function retrieveIDPIntent(id: string, token: string) { } export function getIDPByID(id: string) { + const idpService = serviceInitializer.getIDPService(); + return idpService.getIDPByID({ id }, {}).then((resp) => resp.idp); } @@ -778,6 +808,8 @@ export function addIDPLink( }, userId: string, ) { + const userService = serviceInitializer.getUserService(); + return userService.addIDPLink( { idpLink: { @@ -814,6 +846,8 @@ export async function passwordReset( }; } + const userService = serviceInitializer.getUserService(); + return userService.passwordReset( { userId, @@ -869,6 +903,8 @@ export async function setUserPassword( }; } + const userService = serviceInitializer.getUserService(); + return userService.setPassword(payload, {}).catch((error) => { // throw error if failed precondition (ex. User is not yet initialized) if (error.code === 9 && error.message) { @@ -880,6 +916,8 @@ export async function setUserPassword( } export async function setPassword(payload: SetPasswordRequest) { + const userService = serviceInitializer.getUserService(); + return userService.setPassword(payload, {}); } @@ -891,15 +929,9 @@ export async function setPassword(payload: SetPasswordRequest) { */ // TODO check for token requirements! -export async function createPasskeyRegistrationLink( - userId: string, - // token: string, -) { - // const transport = createServerTransport(token, { - // baseUrl: process.env.ZITADEL_API_URL!, - // }); +export async function createPasskeyRegistrationLink(userId: string) { + const userService = serviceInitializer.getUserService(); - // const service = createUserServiceClient(transport); return userService.createPasskeyRegistrationLink({ userId, medium: { @@ -917,6 +949,8 @@ export async function createPasskeyRegistrationLink( */ export async function registerU2F(userId: string, domain: string) { + const userService = serviceInitializer.getUserService(); + return userService.registerU2F({ userId, domain, @@ -932,6 +966,8 @@ export async function registerU2F(userId: string, domain: string) { export async function verifyU2FRegistration( request: VerifyU2FRegistrationRequest, ) { + const userService = serviceInitializer.getUserService(); + return userService.verifyU2FRegistration(request, {}); } @@ -943,6 +979,8 @@ export async function getActiveIdentityProviders( if (linking_allowed) { props.linkingAllowed = linking_allowed; } + const settingsService = serviceInitializer.getSettingsService(); + return settingsService.getActiveIdentityProviders(props, {}); } @@ -954,6 +992,8 @@ export async function getActiveIdentityProviders( export async function verifyPasskeyRegistration( request: VerifyPasskeyRegistrationRequest, ) { + const userService = serviceInitializer.getUserService(); + return userService.verifyPasskeyRegistration(request, {}); } @@ -967,6 +1007,8 @@ export async function registerPasskey( code: { id: string; code: string }, domain: string, ) { + const userService = serviceInitializer.getUserService(); + return userService.registerPasskey({ userId, code, @@ -980,6 +1022,8 @@ export async function registerPasskey( * @returns the newly set email */ export async function listAuthenticationMethodTypes(userId: string) { + const userService = serviceInitializer.getUserService(); + return userService.listAuthenticationMethodTypes({ userId, }); diff --git a/packages/zitadel-client/src/index.ts b/packages/zitadel-client/src/index.ts index 30fec39464..85962bdec0 100644 --- a/packages/zitadel-client/src/index.ts +++ b/packages/zitadel-client/src/index.ts @@ -7,3 +7,4 @@ export type { JsonObject } from "@bufbuild/protobuf"; export type { GenService } from "@bufbuild/protobuf/codegenv1"; export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt"; export type { Duration, Timestamp } from "@bufbuild/protobuf/wkt"; +export type { Client } from "@connectrpc/connect"; From 12fdf38545e4832d5a11f41a4c2b3856c047ca71 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 15 Jan 2025 08:47:32 +0100 Subject: [PATCH 06/67] rm singleton --- apps/login/src/lib/service.ts | 85 +----------- apps/login/src/lib/zitadel.ts | 247 ++++++++++++++++++++++++---------- 2 files changed, 180 insertions(+), 152 deletions(-) diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index b655283315..3cb4752385 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -1,4 +1,4 @@ -import { Client, createClientFor } from "@zitadel/client"; +import { createClientFor } from "@zitadel/client"; import { createServerTransport } from "@zitadel/client/node"; import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb"; import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; @@ -39,86 +39,3 @@ export async function createServiceForHost( return createClientFor(service)(transport); } - -export class ServiceInitializer { - public idpService: Client | null = null; - public orgService: Client | null = null; - public sessionService: Client | null = null; - public userService: Client | null = null; - public oidcService: Client | null = null; - public settingsService: Client | null = null; - - private static instance: ServiceInitializer; - - constructor(private host: string) { - this.initializeServices(); - } - - public static async getInstance(host: string): Promise { - if (!ServiceInitializer.instance) { - ServiceInitializer.instance = new ServiceInitializer(host); - await ServiceInitializer.instance.initializeServices(); - } - return ServiceInitializer.instance; - } - - async initializeServices() { - this.idpService = await createServiceForHost( - IdentityProviderService, - this.host, - ); - this.orgService = await createServiceForHost( - OrganizationService, - this.host, - ); - this.sessionService = await createServiceForHost(SessionService, this.host); - this.userService = await createServiceForHost(UserService, this.host); - this.oidcService = await createServiceForHost(OIDCService, this.host); - this.settingsService = await createServiceForHost( - SettingsService, - this.host, - ); - } - - public getSettingsService(): Client { - if (!this.settingsService) { - throw new Error("SettingsService is not initialized"); - } - return this.settingsService; - } - - public getUserService(): Client { - if (!this.userService) { - throw new Error("UserService is not initialized"); - } - return this.userService; - } - - public getOrgService(): Client { - if (!this.orgService) { - throw new Error("OrganizationService is not initialized"); - } - return this.orgService; - } - - public getSessionService(): Client { - if (!this.sessionService) { - throw new Error("SessionService is not initialized"); - } - return this.sessionService; - } - - public getIDPService(): Client { - if (!this.idpService) { - throw new Error("IDPService is not initialized"); - } - return this.idpService; - } - - public getOIDCService(): Client { - if (!this.oidcService) { - throw new Error("OIDCService is not initialized"); - } - return this.oidcService; - } -} diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 5b94b95cdc..46ca4d6a15 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -1,13 +1,17 @@ -import { create, Duration } from "@zitadel/client"; -import { createServerTransport } from "@zitadel/client/node"; -import { createSystemServiceClient } from "@zitadel/client/v1"; +import { Client, create, Duration } from "@zitadel/client"; import { makeReqCtx } from "@zitadel/client/v2"; +import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; +import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; -import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { + Checks, + SessionService, +} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb"; import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb"; import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { @@ -31,12 +35,12 @@ import { SendEmailCodeRequestSchema, SetPasswordRequest, SetPasswordRequestSchema, + UserService, VerifyPasskeyRegistrationRequest, VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { unstable_cacheLife as cacheLife } from "next/cache"; -import { systemAPIToken } from "./api"; -import { ServiceInitializer } from "./service"; +import { createServiceForHost } from "./service"; const useCache = process.env.DEBUG !== "true"; @@ -47,27 +51,32 @@ async function cacheWrapper(callback: Promise) { return callback; } -// Example usage -const serviceInitializer = await ServiceInitializer.getInstance(); +// const idpService: Client = +// await createServiceForHost(IdentityProviderService, host); +// const orgService: Client = +// await createServiceForHost(OrganizationService, host); +// const sessionService: Client = +// await createServiceForHost(SessionService, host); +// const userService: Client = await createServiceForHost( +// UserService, +// host, +// ); +// const oidcService: Client = await createServiceForHost( +// OIDCService, +// host, +// ); +// const settingsService: Client = +// await createServiceForHost(SettingsService, host); -export const { - sessionService, - idpService, - orgService, - settingsService, - oidcService, - userService, -} = serviceInitializer; +// const systemService = async () => { +// const systemToken = await systemAPIToken(); -const systemService = async () => { - const systemToken = await systemAPIToken(); +// const transport = createServerTransport(systemToken, { +// baseUrl: process.env.ZITADEL_API_URL, +// }); - const transport = createServerTransport(systemToken, { - baseUrl: process.env.ZITADEL_API_URL, - }); - - return createSystemServiceClient(transport); -}; +// return createSystemServiceClient(transport); +// }; export async function getInstanceByHost(host: string) { return (await systemService()) @@ -96,7 +105,8 @@ export async function getInstanceByHost(host: string) { } export async function getBrandingSettings(organization?: string) { - const settingsService = serviceInitializer.getSettingsService(); + const settingsService: Client = + await createServiceForHost(SettingsService, host); const callback = settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) @@ -106,7 +116,8 @@ export async function getBrandingSettings(organization?: string) { } export async function getLoginSettings(orgId?: string) { - const settingsService = serviceInitializer.getSettingsService(); + const settingsService: Client = + await createServiceForHost(SettingsService, host); const callback = settingsService .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) @@ -116,7 +127,10 @@ export async function getLoginSettings(orgId?: string) { } export async function listIDPLinks(userId: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.listIDPLinks( { @@ -127,7 +141,10 @@ export async function listIDPLinks(userId: string) { } export async function addOTPEmail(userId: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.addOTPEmail( { @@ -138,19 +155,26 @@ export async function addOTPEmail(userId: string) { } export async function addOTPSMS(userId: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.addOTPSMS({ userId }, {}); } export async function registerTOTP(userId: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.registerTOTP({ userId }, {}); } export async function getGeneralSettings() { - const settingsService = serviceInitializer.getSettingsService(); + const settingsService: Client = + await createServiceForHost(SettingsService, host); const callback = settingsService .getGeneralSettings({}, {}) @@ -160,7 +184,8 @@ export async function getGeneralSettings() { } export async function getLegalAndSupportSettings(organization?: string) { - const settingsService = serviceInitializer.getSettingsService(); + const settingsService: Client = + await createServiceForHost(SettingsService, host); const callback = settingsService .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) @@ -170,7 +195,8 @@ export async function getLegalAndSupportSettings(organization?: string) { } export async function getPasswordComplexitySettings(organization?: string) { - const settingsService = serviceInitializer.getSettingsService(); + const settingsService: Client = + await createServiceForHost(SettingsService, host); const callback = settingsService .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) @@ -184,7 +210,8 @@ export async function createSessionFromChecks( challenges: RequestChallenges | undefined, lifetime?: Duration, ) { - const sessionService = serviceInitializer.getSessionService(); + const sessionService: Client = + await createServiceForHost(SessionService, host); return sessionService.createSession( { @@ -204,7 +231,8 @@ export async function createSessionForUserIdAndIdpIntent( }, lifetime?: Duration, ) { - const sessionService = serviceInitializer.getSessionService(); + const sessionService: Client = + await createServiceForHost(SessionService, host); return sessionService.createSession({ checks: { @@ -227,7 +255,8 @@ export async function setSession( checks?: Checks, lifetime?: Duration, ) { - const sessionService = serviceInitializer.getSessionService(); + const sessionService: Client = + await createServiceForHost(SessionService, host); return sessionService.setSession( { @@ -249,19 +278,22 @@ export async function getSession({ sessionId: string; sessionToken: string; }) { - const sessionService = serviceInitializer.getSessionService(); + const sessionService: Client = + await createServiceForHost(SessionService, host); return sessionService.getSession({ sessionId, sessionToken }, {}); } export async function deleteSession(sessionId: string, sessionToken: string) { - const sessionService = serviceInitializer.getSessionService(); + const sessionService: Client = + await createServiceForHost(SessionService, host); return sessionService.deleteSession({ sessionId, sessionToken }, {}); } export async function listSessions(ids: string[]) { - const sessionService = serviceInitializer.getSessionService(); + const sessionService: Client = + await createServiceForHost(SessionService, host); return sessionService.listSessions( { @@ -293,7 +325,10 @@ export async function addHumanUser({ password, organization, }: AddHumanUserData) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.addHumanUser({ email: { @@ -315,19 +350,28 @@ export async function addHumanUser({ } export async function addHuman(request: AddHumanUserRequest) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.addHumanUser(request); } export async function verifyTOTPRegistration(code: string, userId: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.verifyTOTPRegistration({ code, userId }, {}); } export async function getUserByID(userId: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.getUserByID({ userId }, {}); } @@ -336,13 +380,19 @@ export async function verifyInviteCode( userId: string, verificationCode: string, ) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.verifyInviteCode({ userId, verificationCode }, {}); } export async function resendInviteCode(userId: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.resendInviteCode({ userId }, {}); } @@ -370,7 +420,10 @@ export async function sendEmailCode( }); } - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.sendEmailCode(medium, {}); } @@ -387,7 +440,10 @@ export async function createInviteCode(userId: string, host: string | null) { }; } - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.createInviteCode( { @@ -498,7 +554,10 @@ export async function listUsers({ ); } - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.listUsers({ queries: queries }); } @@ -579,7 +638,10 @@ export async function searchUsers({ ); } - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); const loginNameResult = await userService.listUsers({ queries: queries }); @@ -664,7 +726,8 @@ export async function searchUsers({ } export async function getDefaultOrg(): Promise { - const orgService = serviceInitializer.getOrgService(); + const orgService: Client = + await createServiceForHost(OrganizationService, host); return orgService .listOrganizations( @@ -684,7 +747,8 @@ export async function getDefaultOrg(): Promise { } export async function getOrgsByDomain(domain: string) { - const orgService = serviceInitializer.getOrgService(); + const orgService: Client = + await createServiceForHost(OrganizationService, host); return orgService.listOrganizations( { @@ -708,7 +772,10 @@ export async function startIdentityProviderFlow({ idpId: string; urls: RedirectURLsJson; }) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.startIdentityProviderIntent({ idpId, @@ -723,7 +790,10 @@ export async function retrieveIdentityProviderInformation({ idpIntentId, idpIntentToken, }: RetrieveIdentityProviderIntentRequest) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.retrieveIdentityProviderIntent({ idpIntentId, @@ -750,7 +820,10 @@ export async function createCallback(req: CreateCallbackRequest) { } export async function verifyEmail(userId: string, verificationCode: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.verifyEmail( { @@ -780,13 +853,19 @@ export async function resendEmailCode( request = { ...request, verification: { case: "sendCode", value: medium } }; } - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.resendEmailCode(request, {}); } export function retrieveIDPIntent(id: string, token: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.retrieveIdentityProviderIntent( { idpIntentId: id, idpIntentToken: token }, @@ -795,7 +874,8 @@ export function retrieveIDPIntent(id: string, token: string) { } export function getIDPByID(id: string) { - const idpService = serviceInitializer.getIDPService(); + const idpService: Client = + await createServiceForHost(IdentityProviderService, host); return idpService.getIDPByID({ id }, {}).then((resp) => resp.idp); } @@ -808,7 +888,10 @@ export function addIDPLink( }, userId: string, ) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.addIDPLink( { @@ -846,7 +929,10 @@ export async function passwordReset( }; } - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.passwordReset( { @@ -903,7 +989,10 @@ export async function setUserPassword( }; } - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.setPassword(payload, {}).catch((error) => { // throw error if failed precondition (ex. User is not yet initialized) @@ -916,7 +1005,10 @@ export async function setUserPassword( } export async function setPassword(payload: SetPasswordRequest) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.setPassword(payload, {}); } @@ -930,7 +1022,10 @@ export async function setPassword(payload: SetPasswordRequest) { // TODO check for token requirements! export async function createPasskeyRegistrationLink(userId: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.createPasskeyRegistrationLink({ userId, @@ -949,7 +1044,10 @@ export async function createPasskeyRegistrationLink(userId: string) { */ export async function registerU2F(userId: string, domain: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.registerU2F({ userId, @@ -966,7 +1064,10 @@ export async function registerU2F(userId: string, domain: string) { export async function verifyU2FRegistration( request: VerifyU2FRegistrationRequest, ) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.verifyU2FRegistration(request, {}); } @@ -979,7 +1080,8 @@ export async function getActiveIdentityProviders( if (linking_allowed) { props.linkingAllowed = linking_allowed; } - const settingsService = serviceInitializer.getSettingsService(); + const settingsService: Client = + await createServiceForHost(SettingsService, host); return settingsService.getActiveIdentityProviders(props, {}); } @@ -992,7 +1094,10 @@ export async function getActiveIdentityProviders( export async function verifyPasskeyRegistration( request: VerifyPasskeyRegistrationRequest, ) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.verifyPasskeyRegistration(request, {}); } @@ -1007,7 +1112,10 @@ export async function registerPasskey( code: { id: string; code: string }, domain: string, ) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.registerPasskey({ userId, @@ -1022,7 +1130,10 @@ export async function registerPasskey( * @returns the newly set email */ export async function listAuthenticationMethodTypes(userId: string) { - const userService = serviceInitializer.getUserService(); + const userService: Client = await createServiceForHost( + UserService, + host, + ); return userService.listAuthenticationMethodTypes({ userId, From 94e14fdbda218550b8a0ad9ecba96f1fc462a8e8 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 15 Jan 2025 09:03:16 +0100 Subject: [PATCH 07/67] extend functions with host --- apps/login/src/lib/zitadel.ts | 511 ++++++++++++++++++++++++---------- 1 file changed, 362 insertions(+), 149 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 46ca4d6a15..3f1d767812 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -2,7 +2,10 @@ import { Client, create, Duration } from "@zitadel/client"; import { makeReqCtx } from "@zitadel/client/v2"; import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; -import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; +import { + CreateCallbackRequest, + OIDCService, +} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; @@ -31,7 +34,6 @@ import { AddHumanUserRequest, ResendEmailCodeRequest, ResendEmailCodeRequestSchema, - RetrieveIdentityProviderIntentRequest, SendEmailCodeRequestSchema, SetPasswordRequest, SetPasswordRequestSchema, @@ -104,7 +106,13 @@ export async function getInstanceByHost(host: string) { }); } -export async function getBrandingSettings(organization?: string) { +export async function getBrandingSettings({ + host, + organization, +}: { + host: string; + organization?: string; +}) { const settingsService: Client = await createServiceForHost(SettingsService, host); @@ -115,7 +123,13 @@ export async function getBrandingSettings(organization?: string) { return useCache ? cacheWrapper(callback) : callback; } -export async function getLoginSettings(orgId?: string) { +export async function getLoginSettings({ + host, + orgId, +}: { + host: string; + orgId?: string; +}) { const settingsService: Client = await createServiceForHost(SettingsService, host); @@ -126,35 +140,43 @@ export async function getLoginSettings(orgId?: string) { return useCache ? cacheWrapper(callback) : callback; } -export async function listIDPLinks(userId: string) { +export async function listIDPLinks({ + host, + userId, +}: { + host: string; + userId: string; +}) { const userService: Client = await createServiceForHost( UserService, host, ); - return userService.listIDPLinks( - { - userId, - }, - {}, - ); + return userService.listIDPLinks({ userId }, {}); } -export async function addOTPEmail(userId: string) { +export async function addOTPEmail({ + host, + userId, +}: { + host: string; + userId: string; +}) { const userService: Client = await createServiceForHost( UserService, host, ); - return userService.addOTPEmail( - { - userId, - }, - {}, - ); + return userService.addOTPEmail({ userId }, {}); } -export async function addOTPSMS(userId: string) { +export async function addOTPSMS({ + host, + userId, +}: { + host: string; + userId: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -163,7 +185,13 @@ export async function addOTPSMS(userId: string) { return userService.addOTPSMS({ userId }, {}); } -export async function registerTOTP(userId: string) { +export async function registerTOTP({ + host, + userId, +}: { + host: string; + userId: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -172,7 +200,7 @@ export async function registerTOTP(userId: string) { return userService.registerTOTP({ userId }, {}); } -export async function getGeneralSettings() { +export async function getGeneralSettings({ host }: { host: string }) { const settingsService: Client = await createServiceForHost(SettingsService, host); @@ -183,7 +211,13 @@ export async function getGeneralSettings() { return useCache ? cacheWrapper(callback) : callback; } -export async function getLegalAndSupportSettings(organization?: string) { +export async function getLegalAndSupportSettings({ + host, + organization, +}: { + host: string; + organization?: string; +}) { const settingsService: Client = await createServiceForHost(SettingsService, host); @@ -194,7 +228,13 @@ export async function getLegalAndSupportSettings(organization?: string) { return useCache ? cacheWrapper(callback) : callback; } -export async function getPasswordComplexitySettings(organization?: string) { +export async function getPasswordComplexitySettings({ + host, + organization, +}: { + host: string; + organization?: string; +}) { const settingsService: Client = await createServiceForHost(SettingsService, host); @@ -205,32 +245,37 @@ export async function getPasswordComplexitySettings(organization?: string) { return useCache ? cacheWrapper(callback) : callback; } -export async function createSessionFromChecks( - checks: Checks, - challenges: RequestChallenges | undefined, - lifetime?: Duration, -) { +export async function createSessionFromChecks({ + host, + checks, + challenges, + lifetime, +}: { + host: string; + checks: Checks; + challenges: RequestChallenges | undefined; + lifetime?: Duration; +}) { const sessionService: Client = await createServiceForHost(SessionService, host); - return sessionService.createSession( - { - checks: checks, - challenges, - lifetime, - }, - {}, - ); + return sessionService.createSession({ checks, challenges, lifetime }, {}); } -export async function createSessionForUserIdAndIdpIntent( - userId: string, +export async function createSessionForUserIdAndIdpIntent({ + host, + userId, + idpIntent, + lifetime, +}: { + host: string; + userId: string; idpIntent: { idpIntentId?: string | undefined; idpIntentToken?: string | undefined; - }, - lifetime?: Duration, -) { + }; + lifetime?: Duration; +}) { const sessionService: Client = await createServiceForHost(SessionService, host); @@ -248,13 +293,21 @@ export async function createSessionForUserIdAndIdpIntent( }); } -export async function setSession( - sessionId: string, - sessionToken: string, - challenges: RequestChallenges | undefined, - checks?: Checks, - lifetime?: Duration, -) { +export async function setSession({ + host, + sessionId, + sessionToken, + challenges, + checks, + lifetime, +}: { + host: string; + sessionId: string; + sessionToken: string; + challenges: RequestChallenges | undefined; + checks?: Checks; + lifetime?: Duration; +}) { const sessionService: Client = await createServiceForHost(SessionService, host); @@ -272,9 +325,11 @@ export async function setSession( } export async function getSession({ + host, sessionId, sessionToken, }: { + host: string; sessionId: string; sessionToken: string; }) { @@ -284,14 +339,28 @@ export async function getSession({ return sessionService.getSession({ sessionId, sessionToken }, {}); } -export async function deleteSession(sessionId: string, sessionToken: string) { +export async function deleteSession({ + host, + sessionId, + sessionToken, +}: { + host: string; + sessionId: string; + sessionToken: string; +}) { const sessionService: Client = await createServiceForHost(SessionService, host); return sessionService.deleteSession({ sessionId, sessionToken }, {}); } -export async function listSessions(ids: string[]) { +export async function listSessions({ + host, + ids, +}: { + host: string; + ids: string[]; +}) { const sessionService: Client = await createServiceForHost(SessionService, host); @@ -301,7 +370,7 @@ export async function listSessions(ids: string[]) { { query: { case: "idsQuery", - value: { ids: ids }, + value: { ids }, }, }, ], @@ -311,6 +380,7 @@ export async function listSessions(ids: string[]) { } export type AddHumanUserData = { + host: string; firstName: string; lastName: string; email: string; @@ -319,6 +389,7 @@ export type AddHumanUserData = { }; export async function addHumanUser({ + host, email, firstName, lastName, @@ -344,12 +415,18 @@ export async function addHumanUser({ ? { org: { case: "orgId", value: organization } } : undefined, passwordType: password - ? { case: "password", value: { password: password } } + ? { case: "password", value: { password } } : undefined, }); } -export async function addHuman(request: AddHumanUserRequest) { +export async function addHuman({ + host, + request, +}: { + host: string; + request: AddHumanUserRequest; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -358,7 +435,15 @@ export async function addHuman(request: AddHumanUserRequest) { return userService.addHumanUser(request); } -export async function verifyTOTPRegistration(code: string, userId: string) { +export async function verifyTOTPRegistration({ + host, + code, + userId, +}: { + host: string; + code: string; + userId: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -367,7 +452,13 @@ export async function verifyTOTPRegistration(code: string, userId: string) { return userService.verifyTOTPRegistration({ code, userId }, {}); } -export async function getUserByID(userId: string) { +export async function getUserByID({ + host, + userId, +}: { + host: string; + userId: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -376,10 +467,15 @@ export async function getUserByID(userId: string) { return userService.getUserByID({ userId }, {}); } -export async function verifyInviteCode( - userId: string, - verificationCode: string, -) { +export async function verifyInviteCode({ + host, + userId, + verificationCode, +}: { + host: string; + userId: string; + verificationCode: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -388,7 +484,13 @@ export async function verifyInviteCode( return userService.verifyInviteCode({ userId, verificationCode }, {}); } -export async function resendInviteCode(userId: string) { +export async function resendInviteCode({ + host, + userId, +}: { + host: string; + userId: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -397,14 +499,16 @@ export async function resendInviteCode(userId: string) { return userService.resendInviteCode({ userId }, {}); } -export async function sendEmailCode( - userId: string, - host: string | null, - authRequestId?: string, -) { - let medium = create(SendEmailCodeRequestSchema, { - userId, - }); +export async function sendEmailCode({ + host, + userId, + authRequestId, +}: { + host: string; + userId: string; + authRequestId?: string; +}) { + let medium = create(SendEmailCodeRequestSchema, { userId }); if (host) { medium = create(SendEmailCodeRequestSchema, { @@ -428,7 +532,13 @@ export async function sendEmailCode( return userService.sendEmailCode(medium, {}); } -export async function createInviteCode(userId: string, host: string | null) { +export async function createInviteCode({ + host, + userId, +}: { + host: string; + userId: string; +}) { let medium = create(SendInviteCodeSchema, { applicationName: "Typescript Login", }); @@ -458,6 +568,7 @@ export async function createInviteCode(userId: string, host: string | null) { } export type ListUsersCommand = { + host: string; loginName?: string; userName?: string; email?: string; @@ -466,6 +577,7 @@ export type ListUsersCommand = { }; export async function listUsers({ + host, loginName, userName, phone, @@ -481,7 +593,7 @@ export async function listUsers({ query: { case: "loginNameQuery", value: { - loginName: loginName, + loginName, method: TextQueryMethod.EQUALS, }, }, @@ -495,7 +607,7 @@ export async function listUsers({ query: { case: "userNameQuery", value: { - userName: userName, + userName, method: TextQueryMethod.EQUALS, }, }, @@ -559,10 +671,11 @@ export async function listUsers({ host, ); - return userService.listUsers({ queries: queries }); + return userService.listUsers({ queries }); } export type SearchUsersCommand = { + host: string; searchValue: string; loginSettings: LoginSettings; organizationId?: string; @@ -606,8 +719,8 @@ const EmailQuery = (searchValue: string) => * this is a dedicated search function to search for users from the loginname page * it searches users based on the loginName or userName and org suffix combination, and falls back to email and phone if no users are found * */ - export async function searchUsers({ + host, searchValue, loginSettings, organizationId, @@ -643,7 +756,7 @@ export async function searchUsers({ host, ); - const loginNameResult = await userService.listUsers({ queries: queries }); + const loginNameResult = await userService.listUsers({ queries }); if (!loginNameResult || !loginNameResult.details) { return { error: "An error occurred." }; @@ -725,7 +838,11 @@ export async function searchUsers({ return { error: "User not found in the system" }; } -export async function getDefaultOrg(): Promise { +export async function getDefaultOrg({ + host, +}: { + host: string; +}): Promise { const orgService: Client = await createServiceForHost(OrganizationService, host); @@ -746,7 +863,13 @@ export async function getDefaultOrg(): Promise { .then((resp) => (resp?.result && resp.result[0] ? resp.result[0] : null)); } -export async function getOrgsByDomain(domain: string) { +export async function getOrgsByDomain({ + host, + domain, +}: { + host: string; + domain: string; +}) { const orgService: Client = await createServiceForHost(OrganizationService, host); @@ -766,9 +889,11 @@ export async function getOrgsByDomain(domain: string) { } export async function startIdentityProviderFlow({ + host, idpId, urls, }: { + host: string; idpId: string; urls: RedirectURLsJson; }) { @@ -787,9 +912,14 @@ export async function startIdentityProviderFlow({ } export async function retrieveIdentityProviderInformation({ + host, idpIntentId, idpIntentToken, -}: RetrieveIdentityProviderIntentRequest) { +}: { + host: string; + idpIntentId: string; + idpIntentToken: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -802,24 +932,40 @@ export async function retrieveIdentityProviderInformation({ } export async function getAuthRequest({ + host, authRequestId, }: { + host: string; authRequestId: string; }) { - const oidcService = serviceInitializer.getOIDCService(); + const oidcService = await createServiceForHost(OIDCService, host); return oidcService.getAuthRequest({ authRequestId, }); } -export async function createCallback(req: CreateCallbackRequest) { - const oidcService = serviceInitializer.getOIDCService(); +export async function createCallback({ + host, + req, +}: { + host: string; + req: CreateCallbackRequest; +}) { + const oidcService = await createServiceForHost(OIDCService, host); return oidcService.createCallback(req); } -export async function verifyEmail(userId: string, verificationCode: string) { +export async function verifyEmail({ + host, + userId, + verificationCode, +}: { + host: string; + userId: string; + verificationCode: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -834,11 +980,15 @@ export async function verifyEmail(userId: string, verificationCode: string) { ); } -export async function resendEmailCode( - userId: string, - host: string | null, - authRequestId?: string, -) { +export async function resendEmailCode({ + host, + userId, + authRequestId, +}: { + host: string; + userId: string; + authRequestId?: string; +}) { let request: ResendEmailCodeRequest = create(ResendEmailCodeRequestSchema, { userId, }); @@ -861,7 +1011,15 @@ export async function resendEmailCode( return userService.resendEmailCode(request, {}); } -export function retrieveIDPIntent(id: string, token: string) { +export async function retrieveIDPIntent({ + host, + id, + token, +}: { + host: string; + id: string; + token: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -873,21 +1031,22 @@ export function retrieveIDPIntent(id: string, token: string) { ); } -export function getIDPByID(id: string) { +export async function getIDPByID({ host, id }: { host: string; id: string }) { const idpService: Client = await createServiceForHost(IdentityProviderService, host); return idpService.getIDPByID({ id }, {}).then((resp) => resp.idp); } -export function addIDPLink( - idp: { - id: string; - userId: string; - userName: string; - }, - userId: string, -) { +export async function addIDPLink({ + host, + idp, + userId, +}: { + host: string; + idp: { id: string; userId: string; userName: string }; + userId: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -906,16 +1065,15 @@ export function addIDPLink( ); } -/** - * - * @param userId the id of the user where the email should be set - * @returns the newly set email - */ -export async function passwordReset( - userId: string, - host: string | null, - authRequestId?: string, -) { +export async function passwordReset({ + host, + userId, + authRequestId, +}: { + host: string; + userId: string; + authRequestId?: string; +}) { let medium = create(SendPasswordResetLinkSchema, { notificationType: NotificationType.Email, }); @@ -946,19 +1104,19 @@ export async function passwordReset( ); } -/** - * - * @param userId userId of the user to set the password for - * @param password the new password - * @param code optional if the password should be set with a code (reset), no code for initial setup of password - * @returns - */ -export async function setUserPassword( - userId: string, - password: string, - user: User, - code?: string, -) { +export async function setUserPassword({ + host, + userId, + password, + user, + code, +}: { + host: string; + userId: string; + password: string; + user: User; + code?: string; +}) { let payload = create(SetPasswordRequestSchema, { userId, newPassword: { @@ -968,7 +1126,7 @@ export async function setUserPassword( // check if the user has no password set in order to set a password if (!code) { - const authmethods = await listAuthenticationMethodTypes(userId); + const authmethods = await listAuthenticationMethodTypes({ host, userId }); // if the user has no authmethods set, we can set a password otherwise we need a code if ( @@ -1004,7 +1162,13 @@ export async function setUserPassword( }); } -export async function setPassword(payload: SetPasswordRequest) { +export async function setPassword({ + host, + payload, +}: { + host: string; + payload: SetPasswordRequest; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -1015,13 +1179,17 @@ export async function setPassword(payload: SetPasswordRequest) { /** * - * @param server + * @param host * @param userId the id of the user where the email should be set * @returns the newly set email */ - -// TODO check for token requirements! -export async function createPasskeyRegistrationLink(userId: string) { +export async function createPasskeyRegistrationLink({ + host, + userId, +}: { + host: string; + userId: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -1038,12 +1206,20 @@ export async function createPasskeyRegistrationLink(userId: string) { /** * + * @param host * @param userId the id of the user where the email should be set * @param domain the domain on which the factor is registered * @returns the newly set email */ - -export async function registerU2F(userId: string, domain: string) { +export async function registerU2F({ + host, + userId, + domain, +}: { + host: string; + userId: string; + domain: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -1057,13 +1233,17 @@ export async function registerU2F(userId: string, domain: string) { /** * - * @param userId the id of the user where the email should be set - * @param domain the domain on which the factor is registered - * @returns the newly set email + * @param host + * @param request the request object for verifying U2F registration + * @returns the result of the verification */ -export async function verifyU2FRegistration( - request: VerifyU2FRegistrationRequest, -) { +export async function verifyU2FRegistration({ + host, + request, +}: { + host: string; + request: VerifyU2FRegistrationRequest; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -1072,10 +1252,22 @@ export async function verifyU2FRegistration( return userService.verifyU2FRegistration(request, {}); } -export async function getActiveIdentityProviders( - orgId?: string, - linking_allowed?: boolean, -) { +/** + * + * @param host + * @param orgId the organization ID + * @param linking_allowed whether linking is allowed + * @returns the active identity providers + */ +export async function getActiveIdentityProviders({ + host, + orgId, + linking_allowed, +}: { + host: string; + orgId?: string; + linking_allowed?: boolean; +}) { const props: any = { ctx: makeReqCtx(orgId) }; if (linking_allowed) { props.linkingAllowed = linking_allowed; @@ -1088,12 +1280,17 @@ export async function getActiveIdentityProviders( /** * - * @param userId the id of the user where the email should be set - * @returns the newly set email + * @param host + * @param request the request object for verifying passkey registration + * @returns the result of the verification */ -export async function verifyPasskeyRegistration( - request: VerifyPasskeyRegistrationRequest, -) { +export async function verifyPasskeyRegistration({ + host, + request, +}: { + host: string; + request: VerifyPasskeyRegistrationRequest; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -1104,14 +1301,23 @@ export async function verifyPasskeyRegistration( /** * + * @param host * @param userId the id of the user where the email should be set + * @param code the code for registering the passkey + * @param domain the domain on which the factor is registered * @returns the newly set email */ -export async function registerPasskey( - userId: string, - code: { id: string; code: string }, - domain: string, -) { +export async function registerPasskey({ + host, + userId, + code, + domain, +}: { + host: string; + userId: string; + code: { id: string; code: string }; + domain: string; +}) { const userService: Client = await createServiceForHost( UserService, host, @@ -1126,10 +1332,17 @@ export async function registerPasskey( /** * + * @param host * @param userId the id of the user where the email should be set - * @returns the newly set email + * @returns the list of authentication method types */ -export async function listAuthenticationMethodTypes(userId: string) { +export async function listAuthenticationMethodTypes({ + host, + userId, +}: { + host: string; + userId: string; +}) { const userService: Client = await createServiceForHost( UserService, host, From c0f66761715563ad116974e2c118f9862b728ff1 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 15 Jan 2025 09:19:54 +0100 Subject: [PATCH 08/67] host everywhere --- apps/login/src/app/(login)/accounts/page.tsx | 16 ++++-- .../app/(login)/authenticator/set/page.tsx | 54 ++++++++++++------- .../(login)/idp/[provider]/failure/page.tsx | 9 +++- .../(login)/idp/[provider]/success/page.tsx | 45 ++++++++++------ apps/login/src/app/(login)/idp/page.tsx | 20 ++++--- .../src/app/(login)/otp/[method]/page.tsx | 31 +++++++---- apps/login/src/lib/zitadel.ts | 23 ++++---- 7 files changed, 132 insertions(+), 66 deletions(-) diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index 49fbad6202..9af2f12848 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -9,6 +9,7 @@ import { import { UserPlusIcon } from "@heroicons/react/24/outline"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; import Link from "next/link"; async function loadSessions() { @@ -35,9 +36,15 @@ export default async function Page(props: { const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg(); + const org: Organization | null = await getDefaultOrg({ host }); if (org) { defaultOrganization = org.id; } @@ -45,9 +52,10 @@ export default async function Page(props: { let sessions = await loadSessions(); - const branding = await getBrandingSettings( - organization ?? defaultOrganization, - ); + const branding = await getBrandingSettings({ + host, + organization: organization ?? defaultOrganization, + }); const params = new URLSearchParams(); diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 7203484df0..82f2cad72b 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -16,6 +16,7 @@ import { } from "@/lib/zitadel"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -27,19 +28,25 @@ export default async function Page(props: { const { loginName, authRequestId, organization, sessionId } = searchParams; - const sessionWithData = sessionId - ? await loadSessionById(sessionId, organization) - : await loadSessionByLoginname(loginName, organization); + const host = (await headers()).get("host"); - async function getAuthMethodsAndUser(session?: Session) { + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const sessionWithData = sessionId + ? await loadSessionById(host, sessionId, organization) + : await loadSessionByLoginname(host, loginName, organization); + + async function getAuthMethodsAndUser(host: string, session?: Session) { const userId = session?.factors?.user?.id; if (!userId) { throw Error("Could not get user id from session"); } - return listAuthenticationMethodTypes(userId).then((methods) => { - return getUserByID(userId).then((user) => { + return listAuthenticationMethodTypes({ host, userId }).then((methods) => { + return getUserByID({ host, userId }).then((user) => { const humanUser = user.user?.type.case === "human" ? user.user?.type.value : undefined; @@ -55,6 +62,7 @@ export default async function Page(props: { } async function loadSessionByLoginname( + host: string, loginName?: string, organization?: string, ) { @@ -62,17 +70,22 @@ export default async function Page(props: { loginName, organization, }).then((session) => { - return getAuthMethodsAndUser(session); + return getAuthMethodsAndUser(host, session); }); } - async function loadSessionById(sessionId: string, organization?: string) { + async function loadSessionById( + host: string, + sessionId: string, + organization?: string, + ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ + host, sessionId: recent.id, sessionToken: recent.token, }).then((sessionResponse) => { - return getAuthMethodsAndUser(sessionResponse.session); + return getAuthMethodsAndUser(host, sessionResponse.session); }); } @@ -80,18 +93,21 @@ export default async function Page(props: { return {tError("unknownContext")}; } - const branding = await getBrandingSettings( - sessionWithData.factors?.user?.organizationId, - ); + const branding = await getBrandingSettings({ + host, + organization: sessionWithData.factors?.user?.organizationId, + }); - const loginSettings = await getLoginSettings( - sessionWithData.factors?.user?.organizationId, - ); + const loginSettings = await getLoginSettings({ + host, + organization: sessionWithData.factors?.user?.organizationId, + }); - const identityProviders = await getActiveIdentityProviders( - sessionWithData.factors?.user?.organizationId, - true, - ).then((resp) => { + const identityProviders = await getActiveIdentityProviders({ + host, + orgId: sessionWithData.factors?.user?.organizationId, + linking_allowed: true, + }).then((resp) => { return resp.identityProviders; }); 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 a3cc0ee883..13bbb44e31 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -2,6 +2,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { getBrandingSettings } from "@/lib/zitadel"; import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; // This configuration shows the given name in the respective IDP button as fallback const PROVIDER_NAME_MAPPING: { @@ -22,7 +23,13 @@ export default async function Page(props: { const { organization } = searchParams; - const branding = await getBrandingSettings(organization); + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const branding = await getBrandingSettings({ host, organization }); return ( 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 0de29fd1ba..0fdc44f6d5 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -23,6 +23,7 @@ import { AddHumanUserRequestSchema, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; @@ -37,13 +38,19 @@ export default async function Page(props: { const { id, token, authRequestId, organization, link } = searchParams; const { provider } = params; - const branding = await getBrandingSettings(organization); + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const branding = await getBrandingSettings({ host, organization }); if (!provider || !id || !token) { return loginFailed(branding, "IDP context missing"); } - const intent = await retrieveIDPIntent(id, token); + const intent = await retrieveIDPIntent({ host, id, token }); const { idpInformation, userId } = intent; @@ -63,7 +70,7 @@ export default async function Page(props: { return loginFailed(branding, "IDP information missing"); } - const idp = await getIDPByID(idpInformation.idpId); + const idp = await getIDPByID({ host, id: idpInformation.idpId }); const options = idp?.config?.options; if (!idp) { @@ -80,14 +87,15 @@ export default async function Page(props: { let idpLink; try { - idpLink = await addIDPLink( - { + idpLink = await addIDPLink({ + host, + idp: { id: idpInformation.idpId, userId: idpInformation.userId, userName: idpInformation.userName, }, userId, - ); + }); } catch (error) { console.error(error); return linkingFailed(branding); @@ -111,19 +119,20 @@ export default async function Page(props: { const email = PROVIDER_MAPPING[providerType](idpInformation).email?.email; if (options.autoLinking === AutoLinkingOption.EMAIL && email) { - foundUser = await listUsers({ email }).then((response) => { + foundUser = await listUsers({ host, email }).then((response) => { return response.result ? response.result[0] : null; }); } else if (options.autoLinking === AutoLinkingOption.USERNAME) { foundUser = await listUsers( options.autoLinking === AutoLinkingOption.USERNAME - ? { userName: idpInformation.userName } - : { email }, + ? { host, userName: idpInformation.userName } + : { host, email }, ).then((response) => { return response.result ? response.result[0] : null; }); } else { foundUser = await listUsers({ + host, userName: idpInformation.userName, email, }).then((response) => { @@ -134,14 +143,15 @@ export default async function Page(props: { if (foundUser) { let idpLink; try { - idpLink = await addIDPLink( - { + idpLink = await addIDPLink({ + host, + idp: { id: idpInformation.idpId, userId: idpInformation.userId, userName: idpInformation.userName, }, - foundUser.userId, - ); + userId: foundUser.userId, + }); } catch (error) { console.error(error); return linkingFailed(branding); @@ -175,11 +185,14 @@ export default async function Page(props: { const suffix = matched?.[1] ?? ""; // this just returns orgs where the suffix is set as primary domain - const orgs = await getOrgsByDomain(suffix); + const orgs = await getOrgsByDomain({ host, domain: suffix }); const orgToCheckForDiscovery = orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; - const orgLoginSettings = await getLoginSettings(orgToCheckForDiscovery); + const orgLoginSettings = await getLoginSettings({ + host, + organization: orgToCheckForDiscovery, + }); if (orgLoginSettings?.allowDomainDiscovery) { orgToRegisterOn = orgToCheckForDiscovery; } @@ -196,7 +209,7 @@ export default async function Page(props: { }); } - const newUser = await addHuman(userData); + const newUser = await addHuman({ host, request: userData }); if (newUser) { return ( diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index fc1af66236..8b91d7c064 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -2,6 +2,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -13,13 +14,20 @@ export default async function Page(props: { const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; - const identityProviders = await getActiveIdentityProviders(organization).then( - (resp) => { - return resp.identityProviders; - }, - ); + const host = (await headers()).get("host"); - const branding = await getBrandingSettings(organization); + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const identityProviders = await getActiveIdentityProviders({ + host, + orgId: organization, + }).then((resp) => { + return resp.identityProviders; + }); + + const branding = await getBrandingSettings({ host, organization }); return ( diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 1755561238..2bcfb5ec53 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -22,6 +22,12 @@ export default async function Page(props: { const t = await getTranslations({ locale, namespace: "otp" }); const tError = await getTranslations({ locale, namespace: "error" }); + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const { loginName, // send from password page userId, // send from email link @@ -35,12 +41,17 @@ export default async function Page(props: { const { method } = params; const session = sessionId - ? await loadSessionById(sessionId, organization) + ? await loadSessionById(host, sessionId, organization) : await loadMostRecentSession({ loginName, organization }); - async function loadSessionById(sessionId: string, organization?: string) { + async function loadSessionById( + host: string, + sessionId: string, + organization?: string, + ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ + host, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { @@ -51,15 +62,15 @@ export default async function Page(props: { } // email links do not come with organization, thus we need to use the session's organization - const branding = await getBrandingSettings( - organization ?? session?.factors?.user?.organizationId, - ); + const branding = await getBrandingSettings({ + host, + organization: organization ?? session?.factors?.user?.organizationId, + }); - const loginSettings = await getLoginSettings( - organization ?? session?.factors?.user?.organizationId, - ); - - const host = (await headers()).get("host"); + const loginSettings = await getLoginSettings({ + host, + organization: organization ?? session?.factors?.user?.organizationId, + }); return ( diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 3f1d767812..a0cf47f450 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -1,4 +1,6 @@ import { Client, create, Duration } from "@zitadel/client"; +import { createServerTransport } from "@zitadel/client/node"; +import { createSystemServiceClient } from "@zitadel/client/v1"; import { makeReqCtx } from "@zitadel/client/v2"; import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; @@ -42,6 +44,7 @@ import { VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { unstable_cacheLife as cacheLife } from "next/cache"; +import { systemAPIToken } from "./api"; import { createServiceForHost } from "./service"; const useCache = process.env.DEBUG !== "true"; @@ -70,15 +73,15 @@ async function cacheWrapper(callback: Promise) { // const settingsService: Client = // await createServiceForHost(SettingsService, host); -// const systemService = async () => { -// const systemToken = await systemAPIToken(); +const systemService = async () => { + const systemToken = await systemAPIToken(); -// const transport = createServerTransport(systemToken, { -// baseUrl: process.env.ZITADEL_API_URL, -// }); + const transport = createServerTransport(systemToken, { + baseUrl: process.env.ZITADEL_API_URL, + }); -// return createSystemServiceClient(transport); -// }; + return createSystemServiceClient(transport); +}; export async function getInstanceByHost(host: string) { return (await systemService()) @@ -125,16 +128,16 @@ export async function getBrandingSettings({ export async function getLoginSettings({ host, - orgId, + organization, }: { host: string; - orgId?: string; + organization?: string; }) { const settingsService: Client = await createServiceForHost(SettingsService, host); const callback = settingsService - .getLoginSettings({ ctx: makeReqCtx(orgId) }, {}) + .getLoginSettings({ ctx: makeReqCtx(organization) }, {}) .then((resp) => (resp.settings ? resp.settings : undefined)); return useCache ? cacheWrapper(callback) : callback; From 4929740e9c09a89c590530e3c0168bcfc7bc1642 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 15 Jan 2025 09:57:24 +0100 Subject: [PATCH 09/67] host everywhere --- apps/login/src/app/(login)/invite/page.tsx | 19 +++++-- .../src/app/(login)/invite/success/page.tsx | 13 +++-- apps/login/src/app/(login)/loginname/page.tsx | 32 ++++++++---- apps/login/src/app/(login)/mfa/page.tsx | 52 +++++++++++++------ apps/login/src/app/(login)/mfa/set/page.tsx | 47 +++++++++++------ .../src/app/(login)/otp/[method]/page.tsx | 5 +- .../src/app/(login)/otp/[method]/set/page.tsx | 36 +++++++++---- apps/login/src/app/(login)/passkey/page.tsx | 27 +++++++--- .../src/app/(login)/passkey/set/page.tsx | 16 ++++-- apps/login/src/app/(login)/password/page.tsx | 30 +++++++---- apps/login/src/app/(login)/register/page.tsx | 21 +++++--- .../app/(login)/register/password/page.tsx | 21 +++++--- apps/login/src/app/(login)/signedin/page.tsx | 29 +++++++---- apps/login/src/app/(login)/u2f/page.tsx | 23 ++++++-- apps/login/src/app/(login)/u2f/set/page.tsx | 16 ++++-- apps/login/src/app/(login)/verify/page.tsx | 18 +++++-- apps/login/src/lib/server/verify.ts | 19 +++++-- apps/login/src/lib/session.ts | 27 +++++++--- 18 files changed, 326 insertions(+), 125 deletions(-) diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx index 18c60eb993..5d99d3e4a4 100644 --- a/apps/login/src/app/(login)/invite/page.tsx +++ b/apps/login/src/app/(login)/invite/page.tsx @@ -8,6 +8,7 @@ import { getPasswordComplexitySettings, } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -18,8 +19,14 @@ export default async function Page(props: { let { firstname, lastname, email, organization } = searchParams; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + if (!organization) { - const org = await getDefaultOrg(); + const org = await getDefaultOrg({ host }); if (!org) { throw new Error("No default organization found"); } @@ -27,12 +34,14 @@ export default async function Page(props: { organization = org.id; } - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings({ host, organization }); - const passwordComplexitySettings = - await getPasswordComplexitySettings(organization); + const passwordComplexitySettings = await getPasswordComplexitySettings({ + host, + organization, + }); - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings({ host, organization }); return ( diff --git a/apps/login/src/app/(login)/invite/success/page.tsx b/apps/login/src/app/(login)/invite/success/page.tsx index 96c4984159..8bb7a68094 100644 --- a/apps/login/src/app/(login)/invite/success/page.tsx +++ b/apps/login/src/app/(login)/invite/success/page.tsx @@ -5,6 +5,7 @@ import { UserAvatar } from "@/components/user-avatar"; 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"; +import { headers } from "next/headers"; import Link from "next/link"; export default async function Page(props: { @@ -16,8 +17,14 @@ export default async function Page(props: { let { userId, organization } = searchParams; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + if (!organization) { - const org = await getDefaultOrg(); + const org = await getDefaultOrg({ host }); if (!org) { throw new Error("No default organization found"); } @@ -25,12 +32,12 @@ export default async function Page(props: { organization = org.id; } - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings({ host, organization }); let user: User | undefined; let human: HumanUser | undefined; if (userId) { - const userResponse = await getUserByID(userId); + const userResponse = await getUserByID({ host, userId }); if (userResponse) { user = userResponse.user; if (user?.type.case === "human") { diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 5400f64b9d..1c513d74a1 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -9,6 +9,7 @@ import { } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -23,29 +24,38 @@ export default async function Page(props: { const suffix = searchParams?.suffix; const submit: boolean = searchParams?.submit === "true"; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg(); + const org: Organization | null = await getDefaultOrg({ host }); if (org) { defaultOrganization = org.id; } } - const loginSettings = await getLoginSettings( - organization ?? defaultOrganization, - ); + const loginSettings = await getLoginSettings({ + host, + organization: organization ?? defaultOrganization, + }); - const contextLoginSettings = await getLoginSettings(organization); + const contextLoginSettings = await getLoginSettings({ host, organization }); - const identityProviders = await getActiveIdentityProviders( - organization ?? defaultOrganization, - ).then((resp) => { + const identityProviders = await getActiveIdentityProviders({ + host, + orgId: organization ?? defaultOrganization, + }).then((resp) => { return resp.identityProviders; }); - const branding = await getBrandingSettings( - organization ?? defaultOrganization, - ); + const branding = await getBrandingSettings({ + host, + organization: organization ?? defaultOrganization, + }); return ( diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index 071806db04..62625c7efa 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -11,6 +11,7 @@ import { listAuthenticationMethodTypes, } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -22,41 +23,58 @@ export default async function Page(props: { const { loginName, authRequestId, organization, sessionId } = searchParams; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const sessionFactors = sessionId - ? await loadSessionById(sessionId, organization) - : await loadSessionByLoginname(loginName, organization); + ? await loadSessionById(host, sessionId, organization) + : await loadSessionByLoginname(host, loginName, organization); async function loadSessionByLoginname( + host: string, loginName?: string, organization?: string, ) { return loadMostRecentSession({ - loginName, - organization, + host, + sessionParams: { + loginName, + organization, + }, }).then((session) => { if (session && session.factors?.user?.id) { - return listAuthenticationMethodTypes(session.factors.user.id).then( - (methods) => { - return { - factors: session?.factors, - authMethods: methods.authMethodTypes ?? [], - }; - }, - ); + return listAuthenticationMethodTypes({ + host, + userId: session.factors.user.id, + }).then((methods) => { + return { + factors: session?.factors, + authMethods: methods.authMethodTypes ?? [], + }; + }); } }); } - async function loadSessionById(sessionId: string, organization?: string) { + async function loadSessionById( + host: string, + sessionId: string, + organization?: string, + ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ + host, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { if (response?.session && response.session.factors?.user?.id) { - return listAuthenticationMethodTypes( - response.session.factors.user.id, - ).then((methods) => { + return listAuthenticationMethodTypes({ + host, + userId: response.session.factors.user.id, + }).then((methods) => { return { factors: response.session?.factors, authMethods: methods.authMethodTypes ?? [], @@ -66,7 +84,7 @@ export default async function Page(props: { }); } - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings({ host, organization }); return ( diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index dce54618dc..d92e1ce820 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -15,6 +15,7 @@ import { import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; function isSessionValid(session: Partial): { valid: boolean; @@ -49,19 +50,25 @@ export default async function Page(props: { sessionId, } = searchParams; - const sessionWithData = sessionId - ? await loadSessionById(sessionId, organization) - : await loadSessionByLoginname(loginName, organization); + const host = (await headers()).get("host"); - async function getAuthMethodsAndUser(session?: Session) { + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const sessionWithData = sessionId + ? await loadSessionById(host, sessionId, organization) + : await loadSessionByLoginname(host, loginName, organization); + + async function getAuthMethodsAndUser(host: string, session?: Session) { const userId = session?.factors?.user?.id; if (!userId) { throw Error("Could not get user id from session"); } - return listAuthenticationMethodTypes(userId).then((methods) => { - return getUserByID(userId).then((user) => { + return listAuthenticationMethodTypes({ host, userId }).then((methods) => { + return getUserByID({ host, userId }).then((user) => { const humanUser = user.user?.type.case === "human" ? user.user?.type.value : undefined; @@ -77,31 +84,41 @@ export default async function Page(props: { } async function loadSessionByLoginname( + host: string, loginName?: string, organization?: string, ) { return loadMostRecentSession({ - loginName, - organization, + host, + sessionParams: { + loginName, + organization, + }, }).then((session) => { - return getAuthMethodsAndUser(session); + return getAuthMethodsAndUser(host, session); }); } - async function loadSessionById(sessionId: string, organization?: string) { + async function loadSessionById( + host: string, + sessionId: string, + organization?: string, + ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ + host, sessionId: recent.id, sessionToken: recent.token, }).then((sessionResponse) => { - return getAuthMethodsAndUser(sessionResponse.session); + return getAuthMethodsAndUser(host, sessionResponse.session); }); } - const branding = await getBrandingSettings(organization); - const loginSettings = await getLoginSettings( - sessionWithData.factors?.user?.organizationId, - ); + const branding = await getBrandingSettings({ host, organization }); + const loginSettings = await getLoginSettings({ + host, + organization: sessionWithData.factors?.user?.organizationId, + }); const { valid } = isSessionValid(sessionWithData); diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 2bcfb5ec53..1f57d6c3be 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -42,7 +42,10 @@ export default async function Page(props: { const session = sessionId ? await loadSessionById(host, sessionId, organization) - : await loadMostRecentSession({ loginName, organization }); + : await loadMostRecentSession({ + host, + sessionParams: { loginName, organization }, + }); async function loadSessionById( host: string, 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 e64b4debe0..b341cc47ba 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -14,6 +14,7 @@ import { } from "@/lib/zitadel"; import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; import Link from "next/link"; import { redirect } from "next/navigation"; @@ -31,18 +32,27 @@ export default async function Page(props: { searchParams; const { method } = params; - const branding = await getBrandingSettings(organization); - const loginSettings = await getLoginSettings(organization); + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const branding = await getBrandingSettings({ host, organization }); + const loginSettings = await getLoginSettings({ host, organization }); const session = await loadMostRecentSession({ - loginName, - organization, + host, + sessionParams: { + loginName, + organization, + }, }); let totpResponse: RegisterTOTPResponse | undefined, error: Error | undefined; if (session && session.factors?.user?.id) { if (method === "time-based") { - await registerTOTP(session.factors.user.id) + await registerTOTP({ host, userId: session.factors.user.id }) .then((resp) => { if (resp) { totpResponse = resp; @@ -53,14 +63,18 @@ export default async function Page(props: { }); } else if (method === "sms") { // does not work - await addOTPSMS(session.factors.user.id).catch((error) => { - error = new Error("Could not add OTP via SMS"); - }); + await addOTPSMS({ host, userId: session.factors.user.id }).catch( + (error) => { + error = new Error("Could not add OTP via SMS"); + }, + ); } else if (method === "email") { // works - await addOTPEmail(session.factors.user.id).catch((error) => { - error = new Error("Could not add OTP via Email"); - }); + await addOTPEmail({ host, userId: session.factors.user.id }).catch( + (error) => { + error = new Error("Could not add OTP via Email"); + }, + ); } else { throw new Error("Invalid method"); } diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index 0804f3ce2e..f2d67fe9bf 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -10,6 +10,7 @@ import { getSession, } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -22,13 +23,27 @@ export default async function Page(props: { const { loginName, altPassword, authRequestId, organization, sessionId } = searchParams; - const sessionFactors = sessionId - ? await loadSessionById(sessionId, organization) - : await loadMostRecentSession({ loginName, organization }); + const host = (await headers()).get("host"); - async function loadSessionById(sessionId: string, organization?: string) { + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const sessionFactors = sessionId + ? await loadSessionById(host, sessionId, organization) + : await loadMostRecentSession({ + host, + sessionParams: { loginName, organization }, + }); + + async function loadSessionById( + host: string, + sessionId: string, + organization?: string, + ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ + host, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { @@ -38,9 +53,9 @@ export default async function Page(props: { }); } - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings({ host, organization }); - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings({ host, organization }); return ( diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 26a2de2428..5a768eef47 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -5,6 +5,7 @@ import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -17,12 +18,21 @@ export default async function Page(props: { const { loginName, prompt, organization, authRequestId, userId } = searchParams; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const session = await loadMostRecentSession({ - loginName, - organization, + host, + sessionParams: { + loginName, + organization, + }, }); - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings({ host, organization }); return ( diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index a2c10c3238..9ef9ec430d 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -11,6 +11,7 @@ import { import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -22,9 +23,15 @@ export default async function Page(props: { let { loginName, organization, authRequestId, alt } = searchParams; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg(); + const org: Organization | null = await getDefaultOrg({ host }); if (org) { defaultOrganization = org.id; @@ -35,20 +42,25 @@ export default async function Page(props: { let sessionFactors; try { sessionFactors = await loadMostRecentSession({ - loginName, - organization, + host, + sessionParams: { + loginName, + organization, + }, }); } catch (error) { // ignore error to continue to show the password form console.warn(error); } - const branding = await getBrandingSettings( - organization ?? defaultOrganization, - ); - const loginSettings = await getLoginSettings( - organization ?? defaultOrganization, - ); + const branding = await getBrandingSettings({ + host, + organization: organization ?? defaultOrganization, + }); + const loginSettings = await getLoginSettings({ + host, + organization: organization ?? defaultOrganization, + }); return ( diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 2cd1c35346..08943c3048 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -9,6 +9,7 @@ import { } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -20,20 +21,28 @@ export default async function Page(props: { let { firstname, lastname, email, organization, authRequestId } = searchParams; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + if (!organization) { - const org: Organization | null = await getDefaultOrg(); + const org: Organization | null = await getDefaultOrg({ host }); if (org) { organization = org.id; } } - const legal = await getLegalAndSupportSettings(organization); - const passwordComplexitySettings = - await getPasswordComplexitySettings(organization); + const legal = await getLegalAndSupportSettings({ host, organization }); + const passwordComplexitySettings = await getPasswordComplexitySettings({ + host, + organization, + }); - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings({ host, organization }); - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings({ host, organization }); if (!loginSettings?.allowRegister) { return ( diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx index 48e454c312..6fed1af8b2 100644 --- a/apps/login/src/app/(login)/register/password/page.tsx +++ b/apps/login/src/app/(login)/register/password/page.tsx @@ -9,6 +9,7 @@ import { } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -20,8 +21,14 @@ export default async function Page(props: { let { firstname, lastname, email, organization, authRequestId } = searchParams; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + if (!organization) { - const org: Organization | null = await getDefaultOrg(); + const org: Organization | null = await getDefaultOrg({ host }); if (org) { organization = org.id; } @@ -29,13 +36,15 @@ export default async function Page(props: { const missingData = !firstname || !lastname || !email; - const legal = await getLegalAndSupportSettings(organization); - const passwordComplexitySettings = - await getPasswordComplexitySettings(organization); + const legal = await getLegalAndSupportSettings({ host, organization }); + const passwordComplexitySettings = await getPasswordComplexitySettings({ + host, + organization, + }); - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings({ host, organization }); - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings({ host, organization }); return missingData ? ( diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index 4fc9ac8546..be9e643177 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -18,12 +18,17 @@ import { getLocale, getTranslations } from "next-intl/server"; import Link from "next/link"; import { redirect } from "next/navigation"; -async function loadSession(loginName: string, authRequestId?: string) { +async function loadSession( + host: string, + loginName: string, + authRequestId?: string, +) { const recent = await getMostRecentCookieWithLoginname({ loginName }); if (authRequestId) { - return createCallback( - create(CreateCallbackRequestSchema, { + return createCallback({ + host, + req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { case: "session", @@ -33,17 +38,19 @@ async function loadSession(loginName: string, authRequestId?: string) { }), }, }), - ).then(({ callbackUrl }) => { + }).then(({ callbackUrl }) => { return redirect(callbackUrl); }); } - return getSession({ sessionId: recent.id, sessionToken: recent.token }).then( - (response) => { - if (response?.session) { - return response.session; - } - }, - ); + return getSession({ + host, + sessionId: recent.id, + sessionToken: recent.token, + }).then((response) => { + if (response?.session) { + return response.session; + } + }); } export default async function Page(props: { searchParams: Promise }) { diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index e4dd2bd8d2..a9e468b572 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -6,6 +6,7 @@ import { getSessionCookieById } from "@/lib/cookies"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -17,15 +18,29 @@ export default async function Page(props: { const { loginName, authRequestId, sessionId, organization } = searchParams; - const branding = await getBrandingSettings(organization); + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const branding = await getBrandingSettings({ host, organization }); const sessionFactors = sessionId - ? await loadSessionById(sessionId, organization) - : await loadMostRecentSession({ loginName, organization }); + ? await loadSessionById(host, sessionId, organization) + : await loadMostRecentSession({ + host, + sessionParams: { loginName, organization }, + }); - async function loadSessionById(sessionId: string, organization?: string) { + async function loadSessionById( + host: string, + sessionId: string, + organization?: string, + ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ + host, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index f9f3daeaba..47564abd9d 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -5,6 +5,7 @@ import { UserAvatar } from "@/components/user-avatar"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -16,12 +17,21 @@ export default async function Page(props: { const { loginName, organization, authRequestId, checkAfter } = searchParams; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const sessionFactors = await loadMostRecentSession({ - loginName, - organization, + host, + sessionParams: { + loginName, + organization, + }, }); - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings({ host, organization }); return ( diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 128623963b..0eca62e561 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -13,6 +13,7 @@ import { import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise }) { const searchParams = await props.searchParams; @@ -23,7 +24,13 @@ export default async function Page(props: { searchParams: Promise }) { const { userId, loginName, code, organization, authRequestId, invite } = searchParams; - const branding = await getBrandingSettings(organization); + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const branding = await getBrandingSettings({ host, organization }); let sessionFactors; let user: User | undefined; @@ -34,12 +41,16 @@ export default async function Page(props: { searchParams: Promise }) { if ("loginName" in searchParams) { sessionFactors = await loadMostRecentSession({ - loginName, - organization, + host, + sessionParams: { + loginName, + organization, + }, }); if (doSend && sessionFactors?.factors?.user?.id) { await sendEmailCode({ + host, userId: sessionFactors?.factors?.user?.id, authRequestId, }).catch((error) => { @@ -50,6 +61,7 @@ export default async function Page(props: { searchParams: Promise }) { } else if ("userId" in searchParams && userId) { if (doSend) { await sendEmailCode({ + host, userId, authRequestId, }).catch((error) => { diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 2c0c78272a..37499c80e3 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -191,19 +191,32 @@ type resendVerifyEmailCommand = { export async function resendVerification(command: resendVerifyEmailCommand) { const host = (await headers()).get("host"); + if (!host) { + return { error: "No host found" }; + } + return command.isInvite - ? resendInviteCode(command.userId) - : resendEmailCode(command.userId, host, command.authRequestId); + ? resendInviteCode({ host, userId: command.userId }) + : resendEmailCode({ + userId: command.userId, + host, + authRequestId: command.authRequestId, + }); } type sendEmailCommand = { + host: string; userId: string; authRequestId?: string; }; export async function sendEmailCode(command: sendEmailCommand) { const host = (await headers()).get("host"); - return zitadelSendEmailCode(command.userId, host, command.authRequestId); + return zitadelSendEmailCode({ + userId: command.userId, + host: command.host, + authRequestId: command.authRequestId, + }); } export type SendVerificationRedirectWithoutCheckCommand = { diff --git a/apps/login/src/lib/session.ts b/apps/login/src/lib/session.ts index 29ceb3764b..3aee153246 100644 --- a/apps/login/src/lib/session.ts +++ b/apps/login/src/lib/session.ts @@ -1,17 +1,28 @@ import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { getMostRecentCookieWithLoginname } from "./cookies"; -import { sessionService } from "./zitadel"; +import { getSession } from "./zitadel"; -export async function loadMostRecentSession(sessionParams: { - loginName?: string; - organization?: string; -}): Promise { +type LoadMostRecentSessionParams = { + host: string; + sessionParams: { + loginName?: string; + organization?: string; + }; +}; + +export async function loadMostRecentSession({ + host, + sessionParams, +}: LoadMostRecentSessionParams): Promise { const recent = await getMostRecentCookieWithLoginname({ loginName: sessionParams.loginName, organization: sessionParams.organization, }); - return sessionService - .getSession({ sessionId: recent.id, sessionToken: recent.token }, {}) - .then((resp: GetSessionResponse) => resp.session); + + return getSession({ + host, + sessionId: recent.id, + sessionToken: recent.token, + }).then((resp: GetSessionResponse) => resp.session); } From 6afdb982992fdc5c03e66efdffccc9c3ee0cc785 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 15 Jan 2025 15:39:18 +0100 Subject: [PATCH 10/67] provide host --- .../app/(login)/authenticator/set/page.tsx | 7 ++- apps/login/src/lib/server/cookie.ts | 43 ++++++++++++--- apps/login/src/lib/server/idp.ts | 16 ++++-- apps/login/src/lib/server/invite.ts | 7 ++- apps/login/src/lib/server/loginname.ts | 52 +++++++++++++------ apps/login/src/lib/server/otp.ts | 12 ++++- apps/login/src/lib/server/passkeys.ts | 47 +++++++++++------ 7 files changed, 137 insertions(+), 47 deletions(-) diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 82f2cad72b..55a4d3c74e 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -67,8 +67,11 @@ export default async function Page(props: { organization?: string, ) { return loadMostRecentSession({ - loginName, - organization, + host, + sessionParams: { + loginName, + organization, + }, }).then((session) => { return getAuthMethodsAndUser(host, session); }); diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 91447174f6..65409db7d2 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -14,6 +14,7 @@ import { } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; 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"; type CustomCookieData = { id: string; @@ -30,12 +31,22 @@ export async function createSessionAndUpdateCookie( checks: Checks, challenges: RequestChallenges | undefined, authRequestId: string | undefined, - lifetime?: Duration, ): Promise { - const createdSession = await createSessionFromChecks(checks, challenges); + const host = (await headers()).get("host"); + + if (!host) { + throw new Error("Could not get domain"); + } + + const createdSession = await createSessionFromChecks({ + host, + checks, + challenges, + }); if (createdSession) { return getSession({ + host, sessionId: createdSession.sessionId, sessionToken: createdSession.sessionToken, }).then((response) => { @@ -85,17 +96,25 @@ export async function createSessionForIdpAndUpdateCookie( authRequestId: string | undefined, lifetime?: Duration, ): Promise { - const createdSession = await createSessionForUserIdAndIdpIntent( + const host = (await headers()).get("host"); + + if (!host) { + throw new Error("Could not get domain"); + } + + const createdSession = await createSessionForUserIdAndIdpIntent({ + host, userId, idpIntent, lifetime, - ); + }); if (!createdSession) { throw "Could not create session"; } const { session } = await getSession({ + host, sessionId: createdSession.sessionId, sessionToken: createdSession.sessionToken, }); @@ -142,13 +161,20 @@ export async function setSessionAndUpdateCookie( authRequestId?: string, lifetime?: Duration, ) { - return setSession( - recentCookie.id, - recentCookie.token, + const host = (await headers()).get("host"); + + if (!host) { + throw new Error("Could not get domain"); + } + + return setSession({ + host, + sessionId: recentCookie.id, + sessionToken: recentCookie.token, challenges, checks, lifetime, - ).then((updatedSession) => { + }).then((updatedSession) => { if (updatedSession) { const sessionCookie: CustomCookieData = { id: recentCookie.id, @@ -168,6 +194,7 @@ export async function setSessionAndUpdateCookie( } return getSession({ + host, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }).then((response) => { diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index b48f796160..b43cd4705d 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -24,6 +24,7 @@ export async function startIDPFlow(command: StartIDPFlowCommand) { } return startIdentityProviderFlow({ + host, idpId: command.idpId, urls: { successUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${command.successUrl}`, @@ -55,19 +56,26 @@ type CreateNewSessionCommand = { export async function createNewSessionFromIdpIntent( command: CreateNewSessionCommand, ) { + const host = (await headers()).get("host"); + + if (!host) { + return { error: "Could not get domain" }; + } + if (!command.userId || !command.idpIntent) { throw new Error("No userId or loginName provided"); } - const userResponse = await getUserByID(command.userId); + const userResponse = await getUserByID({ host, userId: command.userId }); if (!userResponse || !userResponse.user) { return { error: "User not found in the system" }; } - const loginSettings = await getLoginSettings( - userResponse.user.details?.resourceOwner, - ); + const loginSettings = await getLoginSettings({ + host, + organization: userResponse.user.details?.resourceOwner, + }); const session = await createSessionForIdpAndUpdateCookie( command.userId, diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 3c68587898..4bfb55d10b 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -22,7 +22,12 @@ export type RegisterUserResponse = { export async function inviteUser(command: InviteUserCommand) { const host = (await headers()).get("host"); + if (!host) { + return { error: "Could not get domain" }; + } + const human = await addHumanUser({ + host, email: command.email, firstName: command.firstName, lastName: command.lastName, @@ -34,7 +39,7 @@ export async function inviteUser(command: InviteUserCommand) { return { error: "Could not create user" }; } - const codeResponse = await createInviteCode(human.userId, host); + const codeResponse = await createInviteCode({ userId: human.userId, host }); if (!codeResponse || !human) { return { error: "Could not create invite code" }; diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 65acb80bf0..98c742ca42 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -32,13 +32,23 @@ export type SendLoginnameCommand = { const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; export async function sendLoginname(command: SendLoginnameCommand) { - const loginSettingsByContext = await getLoginSettings(command.organization); + const host = (await headers()).get("host"); + + if (!host) { + throw new Error("Could not get domain"); + } + + const loginSettingsByContext = await getLoginSettings({ + host, + organization: command.organization, + }); if (!loginSettingsByContext) { return { error: "Could not get login settings" }; } let searchUsersRequest: SearchUsersCommand = { + host, searchValue: command.loginName, organizationId: command.organization, loginSettings: loginSettingsByContext, @@ -58,9 +68,10 @@ export async function sendLoginname(command: SendLoginnameCommand) { const { result: potentialUsers } = searchResult; const redirectUserToSingleIDPIfAvailable = async () => { - const identityProviders = await getActiveIdentityProviders( - command.organization, - ).then((resp) => { + const identityProviders = await getActiveIdentityProviders({ + host, + orgId: command.organization, + }).then((resp) => { return resp.identityProviders; }); @@ -86,6 +97,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } const resp = await startIdentityProviderFlow({ + host, idpId: identityProviders[0].id, urls: { successUrl: @@ -104,9 +116,11 @@ export async function sendLoginname(command: SendLoginnameCommand) { }; const redirectUserToIDP = async (userId: string) => { - const identityProviders = await listIDPLinks(userId).then((resp) => { - return resp.result; - }); + const identityProviders = await listIDPLinks({ host, userId }).then( + (resp) => { + return resp.result; + }, + ); if (identityProviders.length === 1) { const host = (await headers()).get("host"); @@ -117,7 +131,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const identityProviderId = identityProviders[0].idpId; - const idp = await getIDPByID(identityProviderId); + const idp = await getIDPByID({ host, id: identityProviderId }); const idpType = idp?.type; @@ -139,6 +153,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } const resp = await startIdentityProviderFlow({ + host, idpId: idp.id, urls: { successUrl: @@ -162,9 +177,10 @@ export async function sendLoginname(command: SendLoginnameCommand) { const user = potentialUsers[0]; const userId = potentialUsers[0].userId; - const userLoginSettings = await getLoginSettings( - user.details?.resourceOwner, - ); + const userLoginSettings = await getLoginSettings({ + host, + organization: user.details?.resourceOwner, + }); // compare with the concatenated suffix when set const concatLoginname = command.suffix @@ -219,9 +235,10 @@ export async function sendLoginname(command: SendLoginnameCommand) { return { error: "Initial User not supported" }; } - const methods = await listAuthenticationMethodTypes( - session.factors?.user?.id, - ); + const methods = await listAuthenticationMethodTypes({ + host, + userId: session.factors?.user?.id, + }); // this can be expected to be an invite as users created in console have a password set. if (!methods.authMethodTypes || !methods.authMethodTypes.length) { @@ -376,11 +393,14 @@ export async function sendLoginname(command: SendLoginnameCommand) { const suffix = matched?.[1] ?? ""; // this just returns orgs where the suffix is set as primary domain - const orgs = await getOrgsByDomain(suffix); + const orgs = await getOrgsByDomain({ host, domain: suffix }); const orgToCheckForDiscovery = orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; - const orgLoginSettings = await getLoginSettings(orgToCheckForDiscovery); + const orgLoginSettings = await getLoginSettings({ + host, + organization: orgToCheckForDiscovery, + }); if (orgLoginSettings?.allowDomainDiscovery) { orgToRegisterOn = orgToCheckForDiscovery; } diff --git a/apps/login/src/lib/server/otp.ts b/apps/login/src/lib/server/otp.ts index b91d8eac7c..53e1ca3b16 100644 --- a/apps/login/src/lib/server/otp.ts +++ b/apps/login/src/lib/server/otp.ts @@ -7,6 +7,7 @@ import { ChecksSchema, CheckTOTPSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { headers } from "next/headers"; import { getMostRecentSessionCookie, getSessionCookieById, @@ -24,6 +25,12 @@ export type SetOTPCommand = { }; export async function setOTP(command: SetOTPCommand) { + const host = (await headers()).get("host"); + + if (!host) { + throw new Error("Could not get domain"); + } + const recentSession = command.sessionId ? await getSessionCookieById({ sessionId: command.sessionId }).catch( (error) => { @@ -57,7 +64,10 @@ export async function setOTP(command: SetOTPCommand) { }); } - const loginSettings = await getLoginSettings(command.organization); + const loginSettings = await getLoginSettings({ + host, + organization: command.organization, + }); return setSessionAndUpdateCookie( recentSession, diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index c21076265c..7a662fbd88 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -41,18 +41,19 @@ export async function registerPasskeyLink( ): Promise { const { sessionId } = command; - const sessionCookie = await getSessionCookieById({ sessionId }); - const session = await getSession({ - sessionId: sessionCookie.id, - sessionToken: sessionCookie.token, - }); - const host = (await headers()).get("host"); if (!host) { throw new Error("Could not get domain"); } + const sessionCookie = await getSessionCookieById({ sessionId }); + const session = await getSession({ + host, + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); + const [hostname, port] = host.split(":"); if (!hostname) { @@ -67,19 +68,30 @@ export async function registerPasskeyLink( // TODO: add org context // use session token to add the passkey - const registerLink = await createPasskeyRegistrationLink( + const registerLink = await createPasskeyRegistrationLink({ + host, userId, - // sessionCookie.token, - ); + }); if (!registerLink.code) { throw new Error("Missing code in response"); } - return registerPasskey(userId, registerLink.code, hostname); + return registerPasskey({ + host, + userId, + code: registerLink.code, + domain: hostname, + }); } export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { + const host = (await headers()).get("host"); + + if (!host) { + throw new Error("Could not get domain"); + } + // if no name is provided, try to generate one from the user agent let passkeyName = command.passkeyName; if (!!!passkeyName) { @@ -96,6 +108,7 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { sessionId: command.sessionId, }); const session = await getSession({ + host, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -105,14 +118,15 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { throw new Error("Could not get session"); } - return zitadelVerifyPasskeyRegistration( - create(VerifyPasskeyRegistrationRequestSchema, { + return zitadelVerifyPasskeyRegistration({ + host, + request: create(VerifyPasskeyRegistrationRequestSchema, { passkeyId: command.passkeyId, publicKeyCredential: command.publicKeyCredential, passkeyName, userId, }), - ); + }); } type SendPasskeyCommand = { @@ -144,7 +158,7 @@ export async function sendPasskey(command: SendPasskeyCommand) { return { error: "Could not get host" }; } - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings({ host, organization }); const lifetime = checks?.webAuthN ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey @@ -164,7 +178,10 @@ export async function sendPasskey(command: SendPasskeyCommand) { return { error: "Could not update session" }; } - const userResponse = await getUserByID(session?.factors?.user?.id); + const userResponse = await getUserByID({ + host, + userId: session?.factors?.user?.id, + }); if (!userResponse.user) { return { error: "User not found in the system" }; From c3ba56870fd5c75d6447045fc0f48467316f2b9d Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 15 Jan 2025 16:44:08 +0100 Subject: [PATCH 11/67] host server actions --- .../src/app/(login)/password/set/page.tsx | 27 +++++-- apps/login/src/lib/self.ts | 8 ++ apps/login/src/lib/server/password.ts | 78 ++++++++++++++----- apps/login/src/lib/server/register.ts | 18 ++++- 4 files changed, 103 insertions(+), 28 deletions(-) diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index d60ac7023c..61ced3b1e6 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -12,6 +12,7 @@ import { import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; @@ -24,27 +25,37 @@ export default async function Page(props: { const { userId, loginName, organization, authRequestId, code, initial } = searchParams; + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + // also allow no session to be found (ignoreUnkownUsername) let session: Session | undefined; if (loginName) { session = await loadMostRecentSession({ - loginName, - organization, + host, + sessionParams: { + loginName, + organization, + }, }); } - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings({ host, organization }); - const passwordComplexity = await getPasswordComplexitySettings( - session?.factors?.user?.organizationId, - ); + const passwordComplexity = await getPasswordComplexitySettings({ + host, + organization: session?.factors?.user?.organizationId, + }); - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings({ host, organization }); let user: User | undefined; let displayName: string | undefined; if (userId) { - const userResponse = await getUserByID(userId); + const userResponse = await getUserByID({ host, userId }); user = userResponse.user; if (user?.type.case === "human") { diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index 0328adfaff..5f02afcf5f 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -2,6 +2,7 @@ import { createServerTransport } from "@zitadel/client/node"; import { createUserServiceClient } from "@zitadel/client/v2"; +import { headers } from "next/headers"; import { getSessionCookieById } from "./cookies"; import { getSession } from "./zitadel"; @@ -21,9 +22,16 @@ export async function setMyPassword({ sessionId: string; password: string; }) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const sessionCookie = await getSessionCookieById({ sessionId }); const { session } = await getSession({ + host, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 3b7a24a718..c8fa186d90 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -45,7 +45,12 @@ type ResetPasswordCommand = { export async function resetPassword(command: ResetPasswordCommand) { const host = (await headers()).get("host"); + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const users = await listUsers({ + host, loginName: command.loginName, organizationId: command.organization, }); @@ -59,7 +64,7 @@ export async function resetPassword(command: ResetPasswordCommand) { } const userId = users.result[0].userId; - return passwordReset(userId, host, command.authRequestId); + return passwordReset({ userId, host, authRequestId: command.authRequestId }); } export type UpdateSessionCommand = { @@ -70,6 +75,12 @@ export type UpdateSessionCommand = { }; export async function sendPassword(command: UpdateSessionCommand) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + let sessionCookie = await getSessionCookieByLoginName({ loginName: command.loginName, organization: command.organization, @@ -83,6 +94,7 @@ export async function sendPassword(command: UpdateSessionCommand) { if (!sessionCookie) { const users = await listUsers({ + host, loginName: command.loginName, organizationId: command.organization, }); @@ -95,7 +107,10 @@ export async function sendPassword(command: UpdateSessionCommand) { password: { password: command.checks.password?.password }, }); - loginSettings = await getLoginSettings(command.organization); + loginSettings = await getLoginSettings({ + host, + organization: command.organization, + }); session = await createSessionAndUpdateCookie( checks, @@ -120,7 +135,10 @@ export async function sendPassword(command: UpdateSessionCommand) { return { error: "Could not create session for user" }; } - const userResponse = await getUserByID(session?.factors?.user?.id); + const userResponse = await getUserByID({ + host, + userId: session?.factors?.user?.id, + }); if (!userResponse.user) { return { error: "User not found in the system" }; @@ -130,9 +148,11 @@ export async function sendPassword(command: UpdateSessionCommand) { } if (!loginSettings) { - loginSettings = await getLoginSettings( - command.organization ?? session.factors?.user?.organizationId, - ); + loginSettings = await getLoginSettings({ + host, + organization: + command.organization ?? session.factors?.user?.organizationId, + }); } if (!session?.factors?.user?.id || !sessionCookie) { @@ -173,9 +193,10 @@ export async function sendPassword(command: UpdateSessionCommand) { // if password, check if user has MFA methods let authMethods; if (command.checks && command.checks.password && session.factors?.user?.id) { - const response = await listAuthenticationMethodTypes( - session.factors.user.id, - ); + const response = await listAuthenticationMethodTypes({ + host, + userId: session.factors.user.id, + }); if (response.authMethodTypes && response.authMethodTypes.length) { authMethods = response.authMethodTypes; } @@ -227,15 +248,27 @@ export async function changePassword(command: { userId: string; password: string; }) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + // check for init state - const { user } = await getUserByID(command.userId); + const { user } = await getUserByID({ host, userId: command.userId }); if (!user || user.userId !== command.userId) { return { error: "Could not send Password Reset Link" }; } const userId = user.userId; - return setUserPassword(userId, command.password, user, command.code); + return setUserPassword({ + host, + userId, + password: command.password, + user, + code: command.code, + }); } type CheckSessionAndSetPasswordCommand = { @@ -247,9 +280,16 @@ export async function checkSessionAndSetPassword({ sessionId, password, }: CheckSessionAndSetPasswordCommand) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const sessionCookie = await getSessionCookieById({ sessionId }); const { session } = await getSession({ + host, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -266,9 +306,10 @@ export async function checkSessionAndSetPassword({ }); // check if the user has no password set in order to set a password - const authmethods = await listAuthenticationMethodTypes( - session.factors.user.id, - ); + const authmethods = await listAuthenticationMethodTypes({ + host, + userId: session.factors.user.id, + }); if (!authmethods) { return { error: "Could not load auth methods" }; @@ -285,9 +326,10 @@ export async function checkSessionAndSetPassword({ (method) => !authmethods.authMethodTypes.includes(method), ); - const loginSettings = await getLoginSettings( - session.factors.user.organizationId, - ); + const loginSettings = await getLoginSettings({ + host, + organization: session.factors.user.organizationId, + }); const forceMfa = !!( loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly @@ -295,7 +337,7 @@ export async function checkSessionAndSetPassword({ // if the user has no MFA but MFA is enforced, we can set a password otherwise we use the token of the user if (forceMfa && hasNoMFAMethods) { - return setPassword(payload).catch((error) => { + return setPassword({ host, payload }).catch((error) => { // throw error if failed precondition (ex. User is not yet initialized) if (error.code === 9 && error.message) { return { error: "Failed precondition" }; diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 284689523a..38dc025b8a 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -8,6 +8,7 @@ import { ChecksJson, ChecksSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { headers } from "next/headers"; import { getNextUrl } from "../client"; import { checkEmailVerification } from "../verify-helper"; @@ -26,7 +27,14 @@ export type RegisterUserResponse = { factors: Factors | undefined; }; export async function registerUser(command: RegisterUserCommand) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const addResponse = await addHumanUser({ + host, email: command.email, firstName: command.firstName, lastName: command.lastName, @@ -38,7 +46,10 @@ export async function registerUser(command: RegisterUserCommand) { return { error: "Could not create user" }; } - const loginSettings = await getLoginSettings(command.organization); + const loginSettings = await getLoginSettings({ + host, + organization: command.organization, + }); let checkPayload: any = { user: { search: { case: "userId", value: addResponse.userId } }, @@ -76,7 +87,10 @@ export async function registerUser(command: RegisterUserCommand) { return { redirect: "/passkey/set?" + params }; } else { - const userResponse = await getUserByID(session?.factors?.user?.id); + const userResponse = await getUserByID({ + host, + userId: session?.factors?.user?.id, + }); if (!userResponse.user) { return { error: "User not found in the system" }; From 18dbffc02da9fc4b427af50eafa4d10a930fcc65 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 09:50:25 +0100 Subject: [PATCH 12/67] host context --- apps/login/src/lib/server/cookie.ts | 2 + apps/login/src/lib/server/session.ts | 49 +++++++++++++++++------ apps/login/src/lib/server/u2f.ts | 26 +++++++----- apps/login/src/lib/server/verify.ts | 60 +++++++++++++++++++++++----- 4 files changed, 106 insertions(+), 31 deletions(-) diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 65409db7d2..3e3347e750 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -31,6 +31,7 @@ export async function createSessionAndUpdateCookie( checks: Checks, challenges: RequestChallenges | undefined, authRequestId: string | undefined, + lifetime?: Duration, ): Promise { const host = (await headers()).get("host"); @@ -42,6 +43,7 @@ export async function createSessionAndUpdateCookie( host, checks, challenges, + lifetime, }); if (createdSession) { diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 70bc18f6d5..84f028db44 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -23,9 +23,16 @@ export async function continueWithSession({ authRequestId, ...session }: Session & { authRequestId?: string }) { - const loginSettings = await getLoginSettings( - session.factors?.user?.organizationId, - ); + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const loginSettings = await getLoginSettings({ + host, + organization: session.factors?.user?.organizationId, + }); const url = authRequestId && session.id && session.factors?.user @@ -99,7 +106,7 @@ export async function updateSession(options: UpdateSessionCommand) { challenges.webAuthN.domain = hostname; } - const loginSettings = await getLoginSettings(organization); + const loginSettings = await getLoginSettings({ host, organization }); const lifetime = checks?.webAuthN ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey @@ -122,9 +129,10 @@ export async function updateSession(options: UpdateSessionCommand) { // if password, check if user has MFA methods let authMethods; if (checks && checks.password && session.factors?.user?.id) { - const response = await listAuthenticationMethodTypes( - session.factors.user.id, - ); + const response = await listAuthenticationMethodTypes({ + host, + userId: session.factors.user.id, + }); if (response.authMethodTypes && response.authMethodTypes.length) { authMethods = response.authMethodTypes; } @@ -143,11 +151,21 @@ type ClearSessionOptions = { }; export async function clearSession(options: ClearSessionOptions) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const { sessionId } = options; const session = await getSessionCookieById({ sessionId }); - const deletedSession = await deleteSession(session.id, session.token); + const deletedSession = await deleteSession({ + host, + sessionId: session.id, + sessionToken: session.token, + }); if (deletedSession) { return removeSessionFromCookie(session); @@ -159,12 +177,19 @@ type CleanupSessionCommand = { }; export async function cleanupSession({ sessionId }: CleanupSessionCommand) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const sessionCookie = await getSessionCookieById({ sessionId }); - const deleteResponse = await deleteSession( - sessionCookie.id, - sessionCookie.token, - ); + const deleteResponse = await deleteSession({ + host, + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); if (!deleteResponse) { throw new Error("Could not delete session"); diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index 5cbd80611b..8620350656 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -19,6 +19,12 @@ type VerifyU2FCommand = { }; export async function addU2F(command: RegisterU2FCommand) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const sessionCookie = await getSessionCookieById({ sessionId: command.sessionId, }); @@ -28,16 +34,11 @@ export async function addU2F(command: RegisterU2FCommand) { } const session = await getSession({ + host, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); - const host = (await headers()).get("host"); - - if (!host) { - return { error: "Could not get domain" }; - } - const [hostname, port] = host.split(":"); if (!hostname) { @@ -50,10 +51,16 @@ export async function addU2F(command: RegisterU2FCommand) { return { error: "Could not get session" }; } - return registerU2F(userId, hostname); + return registerU2F({ host, userId, domain: hostname }); } export async function verifyU2F(command: VerifyU2FCommand) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + let passkeyName = command.passkeyName; if (!!!passkeyName) { const headersList = await headers(); @@ -69,6 +76,7 @@ export async function verifyU2F(command: VerifyU2FCommand) { }); const session = await getSession({ + host, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -79,12 +87,12 @@ export async function verifyU2F(command: VerifyU2FCommand) { return { error: "Could not get session" }; } - const req = create(VerifyU2FRegistrationRequestSchema, { + const request = create(VerifyU2FRegistrationRequestSchema, { u2fId: command.u2fId, publicKeyCredential: command.publicKeyCredential, tokenName: passkeyName, userId, }); - return verifyU2FRegistration(req); + return verifyU2FRegistration({ host, request }); } diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 37499c80e3..7b6f0feca7 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -31,11 +31,25 @@ type VerifyUserByEmailCommand = { }; export async function sendVerification(command: VerifyUserByEmailCommand) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const verifyResponse = command.isInvite - ? await verifyInviteCode(command.userId, command.code).catch(() => { + ? await verifyInviteCode({ + host, + userId: command.userId, + verificationCode: command.code, + }).catch(() => { return { error: "Could not verify invite" }; }) - : await verifyEmail(command.userId, command.code).catch(() => { + : await verifyEmail({ + host, + userId: command.userId, + verificationCode: command.code, + }).catch(() => { return { error: "Could not verify email" }; }); @@ -63,6 +77,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { } session = await getSession({ + host, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }).then((response) => { @@ -75,7 +90,10 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { return { error: "Could not create session for user" }; } - const userResponse = await getUserByID(session?.factors?.user?.id); + const userResponse = await getUserByID({ + host, + userId: session?.factors?.user?.id, + }); if (!userResponse?.user) { return { error: "Could not load user" }; @@ -83,7 +101,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { user = userResponse.user; } else { - const userResponse = await getUserByID(command.userId); + const userResponse = await getUserByID({ host, userId: command.userId }); if (!userResponse || !userResponse.user) { return { error: "Could not load user" }; @@ -119,9 +137,15 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { return { error: "Could not load user" }; } - const loginSettings = await getLoginSettings(user.details?.resourceOwner); + const loginSettings = await getLoginSettings({ + host, + organization: user.details?.resourceOwner, + }); - const authMethodResponse = await listAuthenticationMethodTypes(user.userId); + const authMethodResponse = await listAuthenticationMethodTypes({ + host, + userId: user.userId, + }); if (!authMethodResponse || !authMethodResponse.authMethodTypes) { return { error: "Could not load possible authenticators" }; @@ -230,6 +254,12 @@ export type SendVerificationRedirectWithoutCheckCommand = { export async function sendVerificationRedirectWithoutCheck( command: SendVerificationRedirectWithoutCheckCommand, ) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + if (!("loginName" in command || "userId" in command)) { return { error: "No userId, nor loginname provided" }; } @@ -250,6 +280,7 @@ export async function sendVerificationRedirectWithoutCheck( } session = await getSession({ + host, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }).then((response) => { @@ -262,7 +293,10 @@ export async function sendVerificationRedirectWithoutCheck( return { error: "Could not create session for user" }; } - const userResponse = await getUserByID(session?.factors?.user?.id); + const userResponse = await getUserByID({ + host, + userId: session?.factors?.user?.id, + }); if (!userResponse?.user) { return { error: "Could not load user" }; @@ -270,7 +304,7 @@ export async function sendVerificationRedirectWithoutCheck( user = userResponse.user; } else if ("userId" in command) { - const userResponse = await getUserByID(command.userId); + const userResponse = await getUserByID({ host, userId: command.userId }); if (!userResponse?.user) { return { error: "Could not load user" }; @@ -306,7 +340,10 @@ export async function sendVerificationRedirectWithoutCheck( return { error: "Could not load user" }; } - const authMethodResponse = await listAuthenticationMethodTypes(user.userId); + const authMethodResponse = await listAuthenticationMethodTypes({ + host, + userId: user.userId, + }); if (!authMethodResponse || !authMethodResponse.authMethodTypes) { return { error: "Could not load possible authenticators" }; @@ -328,7 +365,10 @@ export async function sendVerificationRedirectWithoutCheck( return { redirect: `/authenticator/set?${params}` }; } - const loginSettings = await getLoginSettings(user.details?.resourceOwner); + const loginSettings = await getLoginSettings({ + host, + organization: user.details?.resourceOwner, + }); // redirect to mfa factor if user has one, or redirect to set one up const mfaFactorCheck = checkMFAFactors( From 52548e35c5d7c2ba417e5906a3db90e199bf80a4 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 09:55:28 +0100 Subject: [PATCH 13/67] async system service --- .../src/app/(login)/password/change/page.tsx | 30 +++++++++++++------ apps/login/src/lib/zitadel.ts | 3 +- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index bbcaea4950..f242d364d4 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -9,10 +9,17 @@ import { getPasswordComplexitySettings, } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; }) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + const searchParams = await props.searchParams; const locale = getLocale(); const t = await getTranslations({ locale, namespace: "password" }); @@ -22,19 +29,24 @@ export default async function Page(props: { // also allow no session to be found (ignoreUnkownUsername) const sessionFactors = await loadMostRecentSession({ - loginName, - organization, + host, + sessionParams: { + loginName, + organization, + }, }); - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings({ host, organization }); - const passwordComplexity = await getPasswordComplexitySettings( - sessionFactors?.factors?.user?.organizationId, - ); + const passwordComplexity = await getPasswordComplexitySettings({ + host, + organization: sessionFactors?.factors?.user?.organizationId, + }); - const loginSettings = await getLoginSettings( - sessionFactors?.factors?.user?.organizationId, - ); + const loginSettings = await getLoginSettings({ + host, + organization: sessionFactors?.factors?.user?.organizationId, + }); return ( diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index a0cf47f450..e5a8915c5f 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -84,7 +84,8 @@ const systemService = async () => { }; export async function getInstanceByHost(host: string) { - return (await systemService()) + const system = await systemService(); + return system .listInstances( { queries: [ From b6a2f20dee333d78e64a3e6d0c3915eb163896dd Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 10:01:51 +0100 Subject: [PATCH 14/67] login route host context --- apps/login/src/app/login/route.ts | 94 ++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 33 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index f5741bb39c..de38d4bcb6 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -22,16 +22,18 @@ import { } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; export const dynamic = "force-dynamic"; export const revalidate = false; export const fetchCache = "default-no-store"; -async function loadSessions(ids: string[]): Promise { - const response = await listSessions( - ids.filter((id: string | undefined) => !!id), - ); +async function loadSessions(host: string, ids: string[]): Promise { + const response = await listSessions({ + host, + ids: ids.filter((id: string | undefined) => !!id), + }); return response?.sessions ?? []; } @@ -44,7 +46,10 @@ const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/; * mfa is required, session is not valid anymore (e.g. session expired, user logged out, etc.) * to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId); **/ -async function isSessionValid(session: Session): Promise { +async function isSessionValid( + host: string, + session: Session, +): Promise { // session can't be checked without user if (!session.factors?.user) { console.warn("Session has no user"); @@ -53,9 +58,10 @@ async function isSessionValid(session: Session): Promise { let mfaValid = true; - const authMethodTypes = await listAuthenticationMethodTypes( - session.factors.user.id, - ); + const authMethodTypes = await listAuthenticationMethodTypes({ + host, + userId: session.factors.user.id, + }); const authMethods = authMethodTypes.authMethodTypes; if (authMethods && authMethods.includes(AuthenticationMethodType.TOTP)) { @@ -101,9 +107,10 @@ async function isSessionValid(session: Session): Promise { } } else { // only check settings if no auth methods are available, as this would require a setup - const loginSettings = await getLoginSettings( - session.factors?.user?.organizationId, - ); + const loginSettings = await getLoginSettings({ + host, + organization: session.factors?.user?.organizationId, + }); if (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) { const otpEmail = session.factors.otpEmail?.verifiedAt; const otpSms = session.factors.otpSms?.verifiedAt; @@ -144,6 +151,7 @@ async function isSessionValid(session: Session): Promise { } async function findValidSession( + host: string, sessions: Session[], authRequest: AuthRequest, ): Promise { @@ -170,7 +178,7 @@ async function findValidSession( // return the first valid session according to settings for (const session of sessionsWithHint) { - if (await isSessionValid(session)) { + if (await isSessionValid(host, session)) { return session; } } @@ -183,6 +191,12 @@ export async function GET(request: NextRequest) { const authRequestId = searchParams.get("authRequest"); const sessionId = searchParams.get("sessionId"); + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + // TODO: find a better way to handle _rsc (react server components) requests and block them to avoid conflicts when creating oidc callback const _rsc = searchParams.get("_rsc"); if (_rsc) { @@ -193,7 +207,7 @@ export async function GET(request: NextRequest) { const ids = sessionCookies.map((s) => s.id); let sessions: Session[] = []; if (ids && ids.length) { - sessions = await loadSessions(ids); + sessions = await loadSessions(host, ids); } if (authRequestId && sessionId) { @@ -206,7 +220,7 @@ export async function GET(request: NextRequest) { if (selectedSession && selectedSession.id) { console.log(`Found session ${selectedSession.id}`); - const isValid = await isSessionValid(selectedSession); + const isValid = await isSessionValid(host, selectedSession); console.log("Session is valid:", isValid); @@ -239,15 +253,16 @@ export async function GET(request: NextRequest) { // works not with _rsc request try { - const { callbackUrl } = await createCallback( - create(CreateCallbackRequestSchema, { + const { callbackUrl } = await createCallback({ + host, + req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { case: "session", value: create(SessionSchema, session), }, }), - ); + }); if (callbackUrl) { return NextResponse.redirect(callbackUrl); } else { @@ -265,9 +280,10 @@ export async function GET(request: NextRequest) { "code" in error && error?.code === 9 ) { - const loginSettings = await getLoginSettings( - selectedSession.factors?.user?.organizationId, - ); + const loginSettings = await getLoginSettings({ + host, + organization: selectedSession.factors?.user?.organizationId, + }); if (loginSettings?.defaultRedirectUri) { return NextResponse.redirect(loginSettings.defaultRedirectUri); @@ -297,7 +313,7 @@ export async function GET(request: NextRequest) { } if (authRequestId) { - const { authRequest } = await getAuthRequest({ authRequestId }); + const { authRequest } = await getAuthRequest({ host, authRequestId }); let organization = ""; let suffix = ""; @@ -324,7 +340,7 @@ export async function GET(request: NextRequest) { const matched = ORG_DOMAIN_SCOPE_REGEX.exec(orgDomainScope); const orgDomain = matched?.[1] ?? ""; if (orgDomain) { - const orgs = await getOrgsByDomain(orgDomain); + const orgs = await getOrgsByDomain({ host, domain: orgDomain }); if (orgs.result && orgs.result.length === 1) { organization = orgs.result[0].id ?? ""; suffix = orgDomain; @@ -337,9 +353,10 @@ export async function GET(request: NextRequest) { const matched = IDP_SCOPE_REGEX.exec(idpScope); idpId = matched?.[1] ?? ""; - const identityProviders = await getActiveIdentityProviders( - organization ? organization : undefined, - ).then((resp) => { + const identityProviders = await getActiveIdentityProviders({ + host, + orgId: organization ? organization : undefined, + }).then((resp) => { return resp.identityProviders; }); @@ -362,6 +379,7 @@ export async function GET(request: NextRequest) { } return startIdentityProviderFlow({ + host, idpId, urls: { successUrl: @@ -460,7 +478,11 @@ 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 selectedSession = await findValidSession(sessions, authRequest); + const selectedSession = await findValidSession( + host, + sessions, + authRequest, + ); if (!selectedSession || !selectedSession.id) { return NextResponse.json( @@ -485,19 +507,24 @@ export async function GET(request: NextRequest) { sessionToken: cookie.token, }; - const { callbackUrl } = await createCallback( - create(CreateCallbackRequestSchema, { + const { callbackUrl } = await createCallback({ + host, + req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { case: "session", value: create(SessionSchema, session), }, }), - ); + }); return NextResponse.redirect(callbackUrl); } else { // check for loginHint, userId hint and valid sessions - let selectedSession = await findValidSession(sessions, authRequest); + let selectedSession = await findValidSession( + host, + sessions, + authRequest, + ); if (!selectedSession || !selectedSession.id) { return gotoAccounts(); @@ -517,15 +544,16 @@ export async function GET(request: NextRequest) { }; try { - const { callbackUrl } = await createCallback( - create(CreateCallbackRequestSchema, { + const { callbackUrl } = await createCallback({ + host, + req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { case: "session", value: create(SessionSchema, session), }, }), - ); + }); if (callbackUrl) { return NextResponse.redirect(callbackUrl); } else { From c6fa7dd3f01010b0003a7a6455b2daaae1509547 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 10:06:18 +0100 Subject: [PATCH 15/67] fix build --- apps/login/src/components/totp-register.tsx | 3 +- apps/login/src/lib/server-actions.ts | 21 -------------- apps/login/src/lib/server/verify.ts | 32 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 22 deletions(-) delete mode 100644 apps/login/src/lib/server-actions.ts diff --git a/apps/login/src/components/totp-register.tsx b/apps/login/src/components/totp-register.tsx index e3b5985aea..40aa94a165 100644 --- a/apps/login/src/components/totp-register.tsx +++ b/apps/login/src/components/totp-register.tsx @@ -1,6 +1,7 @@ "use client"; + import { getNextUrl } from "@/lib/client"; -import { verifyTOTP } from "@/lib/server-actions"; +import { verifyTOTP } from "@/lib/server/verify"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useTranslations } from "next-intl"; import Link from "next/link"; diff --git a/apps/login/src/lib/server-actions.ts b/apps/login/src/lib/server-actions.ts deleted file mode 100644 index ce0726075f..0000000000 --- a/apps/login/src/lib/server-actions.ts +++ /dev/null @@ -1,21 +0,0 @@ -"use server"; - -import { loadMostRecentSession } from "./session"; -import { verifyTOTPRegistration } from "./zitadel"; - -export async function verifyTOTP( - code: string, - loginName?: string, - organization?: string, -) { - return loadMostRecentSession({ - loginName, - organization, - }).then((session) => { - if (session?.factors?.user?.id) { - return verifyTOTPRegistration(code, session.factors.user.id); - } else { - throw Error("No user id found in session."); - } - }); -} diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 7b6f0feca7..0b70b41d1f 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -9,6 +9,7 @@ import { resendInviteCode, verifyEmail, verifyInviteCode, + verifyTOTPRegistration, sendEmailCode as zitadelSendEmailCode, } from "@/lib/zitadel"; import { create } from "@zitadel/client"; @@ -18,9 +19,40 @@ import { User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { headers } from "next/headers"; import { getNextUrl } from "../client"; import { getSessionCookieByLoginName } from "../cookies"; +import { loadMostRecentSession } from "../session"; import { checkMFAFactors } from "../verify-helper"; import { createSessionAndUpdateCookie } from "./cookie"; +export async function verifyTOTP( + code: string, + loginName?: string, + organization?: string, +) { + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + return loadMostRecentSession({ + host, + sessionParams: { + loginName, + organization, + }, + }).then((session) => { + if (session?.factors?.user?.id) { + return verifyTOTPRegistration({ + host, + code, + userId: session.factors.user.id, + }); + } else { + throw Error("No user id found in session."); + } + }); +} + type VerifyUserByEmailCommand = { userId: string; loginName?: string; // to determine already existing session From 59d2d6e0812bece2d09d3d35f96a799b547b36bb Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 10:19:14 +0100 Subject: [PATCH 16/67] token handling --- apps/login/src/lib/zitadel.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index e5a8915c5f..b2c8c90c55 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -56,28 +56,11 @@ async function cacheWrapper(callback: Promise) { return callback; } -// const idpService: Client = -// await createServiceForHost(IdentityProviderService, host); -// const orgService: Client = -// await createServiceForHost(OrganizationService, host); -// const sessionService: Client = -// await createServiceForHost(SessionService, host); -// const userService: Client = await createServiceForHost( -// UserService, -// host, -// ); -// const oidcService: Client = await createServiceForHost( -// OIDCService, -// host, -// ); -// const settingsService: Client = -// await createServiceForHost(SettingsService, host); - const systemService = async () => { const systemToken = await systemAPIToken(); const transport = createServerTransport(systemToken, { - baseUrl: process.env.ZITADEL_API_URL, + baseUrl: process.env.AUDIENCE, }); return createSystemServiceClient(transport); From e2cf6266052582606b1607e8074290c26baf3085 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 10:22:19 +0100 Subject: [PATCH 17/67] handle localhost --- apps/login/src/lib/api.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index b6df92f481..e5caa7de66 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -2,6 +2,13 @@ import { importPKCS8, SignJWT } from "jose"; import { getInstanceByHost } from "./zitadel"; export async function getInstanceUrl(host: string): Promise { + const [hostname, port] = host.split(":"); + + if (hostname === "localhost") { + console.log("fallback to ZITADEL_API_URL"); + return process.env.ZITADEL_API_URL || ""; + } + const instance = await getInstanceByHost(host); const generatedDomain = instance.domains.find( (domain) => domain.generated === true, From 89dcce193fc18ba3421fe484702580198fb5816f Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 10:28:06 +0100 Subject: [PATCH 18/67] dynamic middleware to get instanceurl from host --- apps/login/src/middleware.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 27ad70c448..fb08cc5393 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -1,4 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; +import { getInstanceUrl } from "./lib/api"; export const config = { matcher: [ @@ -19,8 +20,18 @@ export async function middleware(request: NextRequest) { // return NextResponse.next(); // } - const INSTANCE_URL = process.env.ZITADEL_API_URL; - const instanceHost = `${INSTANCE_URL}`.replace("https://", ""); + let instanceUrl; + try { + instanceUrl = await getInstanceUrl(request.nextUrl.host); + } catch (error) { + console.error( + "Could not get instance url, fallback to ZITADEL_API_URL", + error, + ); + instanceUrl = process.env.ZITADEL_API_URL; + } + + const instanceHost = `${instanceUrl}`.replace("https://", ""); const requestHeaders = new Headers(request.headers); requestHeaders.set("x-zitadel-login-client", process.env.ZITADEL_USER_ID); @@ -36,7 +47,7 @@ export async function middleware(request: NextRequest) { responseHeaders.set("Access-Control-Allow-Origin", "*"); responseHeaders.set("Access-Control-Allow-Headers", "*"); - request.nextUrl.href = `${INSTANCE_URL}${request.nextUrl.pathname}${request.nextUrl.search}`; + request.nextUrl.href = `${instanceUrl}${request.nextUrl.pathname}${request.nextUrl.search}`; return NextResponse.rewrite(request.nextUrl, { request: { headers: requestHeaders, From bb6c6375fe784843bb329d3928a01fd2abf329bc Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 10:42:15 +0100 Subject: [PATCH 19/67] log host on error --- apps/login/src/lib/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index 3cb4752385..4888f120ac 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -25,7 +25,7 @@ export async function createServiceForHost( instanceUrl = await getInstanceUrl(host); } catch (error) { console.error( - "Could not get instance url, fallback to ZITADEL_API_URL", + `Could not get instance url for ${host}, fallback to ZITADEL_API_URL`, error, ); instanceUrl = process.env.ZITADEL_API_URL; From 5a1853a040c0da35456b9d8b604b301edd1bdb9e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 10:44:34 +0100 Subject: [PATCH 20/67] cache get by host --- apps/login/src/lib/zitadel.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index b2c8c90c55..bfc9b1013e 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -68,7 +68,7 @@ const systemService = async () => { export async function getInstanceByHost(host: string) { const system = await systemService(); - return system + const callback = system .listInstances( { queries: [ @@ -91,6 +91,8 @@ export async function getInstanceByHost(host: string) { return resp.result[0]; }); + + return useCache ? cacheWrapper(callback) : callback; } export async function getBrandingSettings({ From 827f38a4d8e964222daccb238f1e335c30ed3299 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 10:48:26 +0100 Subject: [PATCH 21/67] use host header in middleware instead of nexturl.host --- apps/login/src/middleware.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index fb08cc5393..701ec93c9c 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -1,3 +1,4 @@ +import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; import { getInstanceUrl } from "./lib/api"; @@ -19,10 +20,16 @@ export async function middleware(request: NextRequest) { // ) { // return NextResponse.next(); // } + const _headers = await headers(); + const _host = _headers.get("host"); + + console.log("host", _host); + + const host = _host || request.nextUrl.host; let instanceUrl; try { - instanceUrl = await getInstanceUrl(request.nextUrl.host); + instanceUrl = await getInstanceUrl(host); } catch (error) { console.error( "Could not get instance url, fallback to ZITADEL_API_URL", From 596299b6ec9ce979c7c1982eed022554e471ce7c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 10:54:49 +0100 Subject: [PATCH 22/67] host context on signedin page --- apps/login/src/app/(login)/signedin/page.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index be9e643177..d8a4ebaece 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -15,6 +15,7 @@ import { SessionSchema, } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; import Link from "next/link"; import { redirect } from "next/navigation"; @@ -58,14 +59,20 @@ export default async function Page(props: { searchParams: Promise }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "signedin" }); - const { loginName, authRequestId, organization } = searchParams; - const sessionFactors = await loadSession(loginName, authRequestId); + const host = (await headers()).get("host"); - const branding = await getBrandingSettings(organization); + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const { loginName, authRequestId, organization } = searchParams; + const sessionFactors = await loadSession(host, loginName, authRequestId); + + const branding = await getBrandingSettings({ host, organization }); let loginSettings; if (!authRequestId) { - loginSettings = await getLoginSettings(organization); + loginSettings = await getLoginSettings({ host, organization }); } return ( From fc7045aeb225859ca0e11139bfeeb663ff067940 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 16 Jan 2025 11:08:12 +0100 Subject: [PATCH 23/67] error handling --- apps/login/src/lib/api.ts | 10 +++++++++- apps/login/src/middleware.ts | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index e5caa7de66..31301fdc77 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -9,7 +9,15 @@ export async function getInstanceUrl(host: string): Promise { return process.env.ZITADEL_API_URL || ""; } - const instance = await getInstanceByHost(host); + const instance = await getInstanceByHost(host).catch((error) => { + console.error(`Could not get instance by host ${host}`, error); + return null; + }); + + if (!instance) { + throw new Error("No instance found"); + } + const generatedDomain = instance.domains.find( (domain) => domain.generated === true, ); diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 701ec93c9c..04bc0e2b02 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -32,7 +32,7 @@ export async function middleware(request: NextRequest) { instanceUrl = await getInstanceUrl(host); } catch (error) { console.error( - "Could not get instance url, fallback to ZITADEL_API_URL", + `[Middleware]: Could not get instance url of ${host}, fallback to ZITADEL_API_URL ${process.env.ZITADEL_API_URL}`, error, ); instanceUrl = process.env.ZITADEL_API_URL; From d9ba427b6a2f98114d08f49576e14606f7d75b30 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 17 Jan 2025 10:21:11 +0100 Subject: [PATCH 24/67] change audience for self service services --- apps/login/src/lib/api.ts | 8 ++++---- apps/login/src/lib/self.ts | 28 +++++++++++++++++++------- apps/login/src/lib/server/password.ts | 29 ++++++++++++++++++++------- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index 31301fdc77..6ccee72c68 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -4,10 +4,10 @@ import { getInstanceByHost } from "./zitadel"; export async function getInstanceUrl(host: string): Promise { const [hostname, port] = host.split(":"); - if (hostname === "localhost") { - console.log("fallback to ZITADEL_API_URL"); - return process.env.ZITADEL_API_URL || ""; - } + // if (hostname === "localhost") { + // console.log("fallback to ZITADEL_API_URL"); + // return process.env.ZITADEL_API_URL || ""; + // } const instance = await getInstanceByHost(host).catch((error) => { console.error(`Could not get instance by host ${host}`, error); diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index 5f02afcf5f..4977967018 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -3,16 +3,30 @@ import { createServerTransport } from "@zitadel/client/node"; import { createUserServiceClient } from "@zitadel/client/v2"; import { headers } from "next/headers"; +import { getInstanceUrl } from "./api"; import { getSessionCookieById } from "./cookies"; import { getSession } from "./zitadel"; -const transport = (token: string) => - createServerTransport(token, { - baseUrl: process.env.ZITADEL_API_URL!, - }); +const transport = async (host: string, token: string) => { + let instanceUrl; + try { + instanceUrl = await getInstanceUrl(host); + } catch (error) { + console.error( + `Could not get instance url for ${host}, fallback to ZITADEL_API_URL`, + error, + ); + instanceUrl = process.env.ZITADEL_API_URL; + } -const myUserService = (sessionToken: string) => { - return createUserServiceClient(transport(sessionToken)); + return createServerTransport(token, { + baseUrl: instanceUrl, + }); +}; + +const myUserService = async (host: string, sessionToken: string) => { + const transportPromise = await transport(host, sessionToken); + return createUserServiceClient(transportPromise); }; export async function setMyPassword({ @@ -40,7 +54,7 @@ export async function setMyPassword({ return { error: "Could not load session" }; } - const service = await myUserService(`${sessionCookie.token}`); + const service = await myUserService(host, `${sessionCookie.token}`); if (!session?.factors?.user?.id) { return { error: "No user id found in session" }; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index c8fa186d90..9d20e10022 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -28,6 +28,7 @@ import { SetPasswordRequestSchema, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; +import { getInstanceUrl } from "../api"; import { getNextUrl } from "../client"; import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies"; import { @@ -346,15 +347,29 @@ export async function checkSessionAndSetPassword({ } }); } else { - const myUserService = (sessionToken: string) => { - return createUserServiceClient( - createServerTransport(sessionToken, { - baseUrl: process.env.ZITADEL_API_URL!, - }), - ); + const transport = async (host: string, token: string) => { + let instanceUrl; + try { + instanceUrl = await getInstanceUrl(host); + } catch (error) { + console.error( + `Could not get instance url for ${host}, fallback to ZITADEL_API_URL`, + error, + ); + instanceUrl = process.env.ZITADEL_API_URL; + } + + return createServerTransport(token, { + baseUrl: instanceUrl, + }); }; - const selfService = await myUserService(`${sessionCookie.token}`); + const myUserService = async (host: string, sessionToken: string) => { + const transportPromise = await transport(host, sessionToken); + return createUserServiceClient(transportPromise); + }; + + const selfService = await myUserService(host, `${sessionCookie.token}`); return selfService .setPassword( From 972909781114ba57d1525cb430e13607a96c033f Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 17 Jan 2025 13:17:18 +0100 Subject: [PATCH 25/67] readd localhost exception --- apps/login/src/lib/api.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index 6ccee72c68..31301fdc77 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -4,10 +4,10 @@ import { getInstanceByHost } from "./zitadel"; export async function getInstanceUrl(host: string): Promise { const [hostname, port] = host.split(":"); - // if (hostname === "localhost") { - // console.log("fallback to ZITADEL_API_URL"); - // return process.env.ZITADEL_API_URL || ""; - // } + if (hostname === "localhost") { + console.log("fallback to ZITADEL_API_URL"); + return process.env.ZITADEL_API_URL || ""; + } const instance = await getInstanceByHost(host).catch((error) => { console.error(`Could not get instance by host ${host}`, error); From e0a6c1223103f4ec775f48dba6fcee94cb4a2c86 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 20 Jan 2025 11:39:43 +0100 Subject: [PATCH 26/67] mock --- apps/login/src/lib/zitadel.ts | 59 +++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index bfc9b1013e..bb2da97d38 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -66,31 +66,42 @@ const systemService = async () => { return createSystemServiceClient(transport); }; -export async function getInstanceByHost(host: string) { - const system = await systemService(); - const callback = system - .listInstances( - { - queries: [ - { - query: { - case: "domainQuery", - value: { - domains: [host], - }, - }, - }, - ], - }, - {}, - ) - .then((resp) => { - if (resp.result.length !== 1) { - throw new Error("Could not find instance"); - } +export async function getInstanceByHost(host: string): Promise { + // const system = await systemService(); + // const callback = system + // .listInstances( + // { + // queries: [ + // { + // query: { + // case: "domainQuery", + // value: { + // domains: [host], + // }, + // }, + // }, + // ], + // }, + // {}, + // ) + // .then((resp) => { + // if (resp.result.length !== 1) { + // throw new Error("Could not find instance"); + // } - return resp.result[0]; - }); + // return resp.result[0]; + // }); + + const mockFcn = async (host: string) => { + switch (host) { + case "multitenancy-qa.vercel.app": + return "https://another-i8pcvz.zitadel.app"; + default: + return process.env.ZITADEL_API_URL; + } + }; + + const callback = mockFcn(host); return useCache ? cacheWrapper(callback) : callback; } From f535b6da4cca3bb81df6399487da8d41d8c6dab1 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 20 Jan 2025 11:43:57 +0100 Subject: [PATCH 27/67] instance domain callback --- apps/login/src/lib/api.ts | 18 +++++------------- apps/login/src/lib/zitadel.ts | 14 +++++++++++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index 31301fdc77..0f10c4addd 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -1,5 +1,5 @@ import { importPKCS8, SignJWT } from "jose"; -import { getInstanceByHost } from "./zitadel"; +import { getInstanceDomainByHost } from "./zitadel"; export async function getInstanceUrl(host: string): Promise { const [hostname, port] = host.split(":"); @@ -9,26 +9,18 @@ export async function getInstanceUrl(host: string): Promise { return process.env.ZITADEL_API_URL || ""; } - const instance = await getInstanceByHost(host).catch((error) => { + const instanceDomain = await getInstanceDomainByHost(host).catch((error) => { console.error(`Could not get instance by host ${host}`, error); return null; }); - if (!instance) { + if (!instanceDomain) { throw new Error("No instance found"); } - const generatedDomain = instance.domains.find( - (domain) => domain.generated === true, - ); + console.log(`host: ${host}, api: ${instanceDomain}`); - if (!generatedDomain?.domain) { - throw new Error("No generated domain found"); - } - - console.log(`host: ${host}, api: ${generatedDomain?.domain}`); - - return generatedDomain?.domain; + return instanceDomain; } export async function systemAPIToken() { diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index bb2da97d38..b69455e0f3 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -66,9 +66,9 @@ const systemService = async () => { return createSystemServiceClient(transport); }; -export async function getInstanceByHost(host: string): Promise { +export async function getInstanceDomainByHost(host: string): Promise { // const system = await systemService(); - // const callback = system + // const callbacks = system // .listInstances( // { // queries: [ @@ -89,7 +89,15 @@ export async function getInstanceByHost(host: string): Promise { // throw new Error("Could not find instance"); // } - // return resp.result[0]; + // const generatedDomain = resp.result[0].domains.find( + // (domain) => domain.generated === true, + // ); + + // if (!generatedDomain) { + // throw new Error("Could not find generated domain"); + // } + + // return generatedDomain.domain; // }); const mockFcn = async (host: string) => { From 86679f14a058f0f4dd2ec3ff8ba73538846ddb2c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 20 Jan 2025 11:55:31 +0100 Subject: [PATCH 28/67] listSessions --- apps/login/src/app/(login)/accounts/page.tsx | 13 +++++++------ apps/login/src/lib/zitadel.ts | 9 ++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index 9af2f12848..99e7acb408 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -12,13 +12,14 @@ import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; import Link from "next/link"; -async function loadSessions() { - const ids = await getAllSessionCookieIds(); +async function loadSessions(host: string) { + const ids: (string | undefined)[] = await getAllSessionCookieIds(); if (ids && ids.length) { - const response = await listSessions( - ids.filter((id: string | undefined) => !!id), - ); + const response = await listSessions({ + host, + ids: ids.filter((id) => !!id) as string[], + }); return response?.sessions ?? []; } else { console.info("No session cookie found."); @@ -50,7 +51,7 @@ export default async function Page(props: { } } - let sessions = await loadSessions(); + let sessions = await loadSessions(host); const branding = await getBrandingSettings({ host, diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index b69455e0f3..dfa0f65081 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -362,13 +362,12 @@ export async function deleteSession({ return sessionService.deleteSession({ sessionId, sessionToken }, {}); } -export async function listSessions({ - host, - ids, -}: { +type ListSessionsCommand = { host: string; ids: string[]; -}) { +}; + +export async function listSessions({ host, ids }: ListSessionsCommand) { const sessionService: Client = await createServiceForHost(SessionService, host); From 28dc956f403eb87ffe7402349c1c911ea018e39d Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 20 Jan 2025 15:22:14 +0100 Subject: [PATCH 29/67] token util --- .changeset/twenty-clouds-prove.md | 5 +++++ apps/login/src/lib/api.ts | 15 ++++++--------- packages/zitadel-client/src/node.ts | 22 ++++++++++++++++------ turbo.json | 3 ++- 4 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 .changeset/twenty-clouds-prove.md diff --git a/.changeset/twenty-clouds-prove.md b/.changeset/twenty-clouds-prove.md new file mode 100644 index 0000000000..58877517c6 --- /dev/null +++ b/.changeset/twenty-clouds-prove.md @@ -0,0 +1,5 @@ +--- +"@zitadel/client": patch +--- + +dynamic properties for system token utility diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index 0f10c4addd..6bde583f82 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -1,4 +1,4 @@ -import { importPKCS8, SignJWT } from "jose"; +import { newSystemToken } from "@zitadel/client/node"; import { getInstanceDomainByHost } from "./zitadel"; export async function getInstanceUrl(host: string): Promise { @@ -30,14 +30,11 @@ export async function systemAPIToken() { const decodedToken = Buffer.from(key, "base64").toString("utf-8"); - const token = new SignJWT({}) - .setProtectedHeader({ alg: "RS256" }) - .setIssuedAt() - .setExpirationTime("1h") - .setIssuer(userID) - .setSubject(userID) - .setAudience(audience) - .sign(await importPKCS8(decodedToken, "RS256")); + const token = newSystemToken({ + audience: audience, + subject: userID, + key: decodedToken, + }); return token; } diff --git a/packages/zitadel-client/src/node.ts b/packages/zitadel-client/src/node.ts index 8f70a4edf1..db7838ebc3 100644 --- a/packages/zitadel-client/src/node.ts +++ b/packages/zitadel-client/src/node.ts @@ -27,13 +27,23 @@ export function createClientTransport(token: string, opts: GrpcTransportOptions) }); } -export async function newSystemToken() { +export async function newSystemToken({ + audience, + subject, + key, + expirationTime, +}: { + audience: string; + subject: string; + key: string; + expirationTime?: number | string | Date; +}) { return await new SignJWT({}) .setProtectedHeader({ alg: "RS256" }) .setIssuedAt() - .setExpirationTime("1h") - .setIssuer(process.env.ZITADEL_SYSTEM_API_USERID ?? "") - .setSubject(process.env.ZITADEL_SYSTEM_API_USERID ?? "") - .setAudience(process.env.ZITADEL_ISSUER ?? "") - .sign(await importPKCS8(process.env.ZITADEL_SYSTEM_API_KEY ?? "", "RS256")); + .setExpirationTime(expirationTime ?? "1h") + .setIssuer(subject) + .setSubject(subject) + .setAudience(audience) + .sign(await importPKCS8(key, "RS256")); } diff --git a/turbo.json b/turbo.json index ca14a23035..65e4603081 100644 --- a/turbo.json +++ b/turbo.json @@ -11,7 +11,8 @@ "SYSTEM_USER_PRIVATE_KEY", "ZITADEL_API_URL", "ZITADEL_USER_ID", - "ZITADEL_USER_TOKEN" + "ZITADEL_USER_TOKEN", + "ZITADEL_SYSTEM_API_USERID" ], "tasks": { "generate": { From c9531d79df680748f111937e5a076aaa2bdeba6a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 22 Jan 2025 08:37:18 +0100 Subject: [PATCH 30/67] cleanup token --- apps/login/src/lib/service.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index 4888f120ac..e0382c03b7 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -20,20 +20,24 @@ export async function createServiceForHost( service: T, host: string, ) { - let instanceUrl; + let instanceUrl, token; try { instanceUrl = await getInstanceUrl(host); + token = await systemAPIToken(); } catch (error) { console.error( `Could not get instance url for ${host}, fallback to ZITADEL_API_URL`, error, ); instanceUrl = process.env.ZITADEL_API_URL; + token = process.env.ZITADEL_SERVICE_USER_TOKEN; } - const systemToken = await systemAPIToken(); + if (!instanceUrl || !token) { + throw new Error("No instance url or token found"); + } - const transport = createServerTransport(systemToken, { + const transport = createServerTransport(token, { baseUrl: instanceUrl, }); From 2cafb7ee59ae1c2f4405ddbb2fabbe7d5c9fb991 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 23 Jan 2025 10:41:17 +0100 Subject: [PATCH 31/67] cleanup turbo.json env config --- apps/login/next-env-vars.d.ts | 4 ++-- turbo.json | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/login/next-env-vars.d.ts b/apps/login/next-env-vars.d.ts index 773986a7c9..be266fd5a0 100644 --- a/apps/login/next-env-vars.d.ts +++ b/apps/login/next-env-vars.d.ts @@ -23,11 +23,11 @@ declare namespace NodeJS { /** * Self hosting: The service user id */ - ZITADEL_USER_ID: string; + ZITADEL_SERVICE_USER_ID: string; /** * Self hosting: The service user token */ - ZITADEL_USER_TOKEN: string; + ZITADEL_SERVICE_USER_TOKEN: string; /** * Optional: wheter a user must have verified email diff --git a/turbo.json b/turbo.json index 65e4603081..003d6c11cc 100644 --- a/turbo.json +++ b/turbo.json @@ -10,9 +10,8 @@ "SYSTEM_USER_ID", "SYSTEM_USER_PRIVATE_KEY", "ZITADEL_API_URL", - "ZITADEL_USER_ID", - "ZITADEL_USER_TOKEN", - "ZITADEL_SYSTEM_API_USERID" + "ZITADEL_SERVICE_USER_ID", + "ZITADEL_SERVICE_USER_TOKEN" ], "tasks": { "generate": { From 6f295bce1b688bf4b871ec270f3428daac73df21 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 23 Jan 2025 10:43:53 +0100 Subject: [PATCH 32/67] fix middleware --- apps/login/src/middleware.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 04bc0e2b02..e7bbfd7dba 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -41,7 +41,10 @@ export async function middleware(request: NextRequest) { const instanceHost = `${instanceUrl}`.replace("https://", ""); const requestHeaders = new Headers(request.headers); - requestHeaders.set("x-zitadel-login-client", process.env.ZITADEL_USER_ID); + requestHeaders.set( + "x-zitadel-login-client", + process.env.ZITADEL_SERVICE_USER_ID, + ); // this is a workaround for the next.js server not forwarding the host header // requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); From b93035eeb18a1a38d506f2337c5639e863511e7b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 27 Jan 2025 13:26:20 +0100 Subject: [PATCH 33/67] check for outdated password --- apps/login/src/lib/server/password.ts | 6 ++++++ apps/login/src/lib/verify-helper.ts | 16 +++++++++++++++- apps/login/src/lib/zitadel.ts | 8 ++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 3b7a24a718..6f1b79be62 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -6,6 +6,7 @@ import { } from "@/lib/server/cookie"; import { getLoginSettings, + getPasswordExpirySettings, getSession, getUserByID, listAuthenticationMethodTypes, @@ -141,8 +142,13 @@ export async function sendPassword(command: UpdateSessionCommand) { const humanUser = user.type.case === "human" ? user.type.value : undefined; + const expirySettings = await getPasswordExpirySettings( + command.organization ?? session.factors?.user?.organizationId, + ); + // check if the user has to change password first const passwordChangedCheck = checkPasswordChangeRequired( + expirySettings, session, humanUser, command.organization, diff --git a/apps/login/src/lib/verify-helper.ts b/apps/login/src/lib/verify-helper.ts index b37287a959..053d1cc71f 100644 --- a/apps/login/src/lib/verify-helper.ts +++ b/apps/login/src/lib/verify-helper.ts @@ -1,15 +1,29 @@ +import { timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { PasswordExpirySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { HumanUser } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import moment from "moment"; export function checkPasswordChangeRequired( + expirySettings: PasswordExpirySettings | undefined, session: Session, humanUser: HumanUser | undefined, organization?: string, authRequestId?: string, ) { - if (humanUser?.passwordChangeRequired) { + let isOutdated = false; + if (expirySettings?.maxAgeDays && humanUser?.passwordChanged) { + const maxAgeDays = Number(expirySettings.maxAgeDays); // Convert bigint to number + const passwordChangedDate = moment( + timestampDate(humanUser.passwordChanged), + ); + const outdatedPassword = passwordChangedDate.add(maxAgeDays, "days"); + isOutdated = moment().isAfter(outdatedPassword); + } + + if (humanUser?.passwordChangeRequired || isOutdated) { const params = new URLSearchParams({ loginName: session.factors?.user?.loginName as string, }); diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 835b16a46b..589b5a43a0 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -81,6 +81,14 @@ export async function getLoginSettings(orgId?: string) { return useCache ? cacheWrapper(callback) : callback; } +export async function getPasswordExpirySettings(orgId?: string) { + const callback = settingsService + .getPasswordExpirySettings({ ctx: makeReqCtx(orgId) }, {}) + .then((resp) => (resp.settings ? resp.settings : undefined)); + + return useCache ? cacheWrapper(callback) : callback; +} + export async function listIDPLinks(userId: string) { return userService.listIDPLinks( { From 63656e16fbf9cc8536469e59599c5472661891b4 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 27 Jan 2025 14:15:05 +0100 Subject: [PATCH 34/67] handle password attempts error --- apps/login/src/lib/server/cookie.ts | 128 +++++++++++++++----------- apps/login/src/lib/server/password.ts | 51 +++++++--- apps/login/src/lib/zitadel.ts | 8 ++ packages/zitadel-proto/package.json | 2 +- 4 files changed, 123 insertions(+), 66 deletions(-) diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 91447174f6..795a41cd8d 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -8,6 +8,10 @@ import { setSession, } from "@/lib/zitadel"; import { Duration, timestampMs } from "@zitadel/client"; +import { + CredentialsCheckError, + ErrorDetail, +} from "@zitadel/proto/zitadel/message_pb"; import { Challenges, RequestChallenges, @@ -89,7 +93,16 @@ export async function createSessionForIdpAndUpdateCookie( userId, idpIntent, lifetime, - ); + ).catch((error: ErrorDetail | CredentialsCheckError) => { + console.error("Could not set session", error); + if ("failedAttempts" in error && error.failedAttempts) { + throw { + error: `Failed to authenticate: You had ${error.failedAttempts} password attempts.`, + failedAttempts: error.failedAttempts, + }; + } + throw error; + }); if (!createdSession) { throw "Could not create session"; @@ -148,57 +161,68 @@ export async function setSessionAndUpdateCookie( challenges, checks, lifetime, - ).then((updatedSession) => { - if (updatedSession) { - const sessionCookie: CustomCookieData = { - id: recentCookie.id, - token: updatedSession.sessionToken, - creationTs: recentCookie.creationTs, - expirationTs: recentCookie.expirationTs, - // just overwrite the changeDate with the new one - changeTs: updatedSession.details?.changeDate - ? `${timestampMs(updatedSession.details.changeDate)}` - : "", - loginName: recentCookie.loginName, - organization: recentCookie.organization, - }; + ) + .then((updatedSession) => { + if (updatedSession) { + const sessionCookie: CustomCookieData = { + id: recentCookie.id, + token: updatedSession.sessionToken, + creationTs: recentCookie.creationTs, + expirationTs: recentCookie.expirationTs, + // just overwrite the changeDate with the new one + changeTs: updatedSession.details?.changeDate + ? `${timestampMs(updatedSession.details.changeDate)}` + : "", + loginName: recentCookie.loginName, + organization: recentCookie.organization, + }; - if (authRequestId) { - sessionCookie.authRequestId = authRequestId; - } - - return getSession({ - 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.authRequestId) { - newCookie.authRequestId = sessionCookie.authRequestId; - } - - return updateSessionCookie(sessionCookie.id, newCookie).then(() => { - return { challenges: updatedSession.challenges, ...session }; - }); - } else { - throw "could not get session or session does not have loginName"; + if (authRequestId) { + sessionCookie.authRequestId = authRequestId; } - }); - } else { - throw "Session not be set"; - } - }); + + return getSession({ + 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.authRequestId) { + newCookie.authRequestId = sessionCookie.authRequestId; + } + + return updateSessionCookie(sessionCookie.id, newCookie).then(() => { + return { challenges: updatedSession.challenges, ...session }; + }); + } else { + throw "could not get session or session does not have loginName"; + } + }); + } else { + throw "Session not be set"; + } + }) + .catch((error: ErrorDetail | CredentialsCheckError) => { + console.error("Could not set session", error); + if ("failedAttempts" in error && error.failedAttempts) { + throw { + error: `Failed to authenticate: You had ${error.failedAttempts} password attempts.`, + failedAttempts: error.failedAttempts, + }; + } + throw error; + }); } diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 6f1b79be62..d69074aca3 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -5,6 +5,7 @@ import { setSessionAndUpdateCookie, } from "@/lib/server/cookie"; import { + getLockoutSettings, getLoginSettings, getPasswordExpirySettings, getSession, @@ -98,24 +99,48 @@ export async function sendPassword(command: UpdateSessionCommand) { loginSettings = await getLoginSettings(command.organization); - session = await createSessionAndUpdateCookie( - checks, - undefined, - command.authRequestId, - loginSettings?.passwordCheckLifetime, - ); + try { + session = await createSessionAndUpdateCookie( + checks, + undefined, + command.authRequestId, + loginSettings?.passwordCheckLifetime, + ); + } catch (error: any) { + if ("failedAttempts" in error && error.failedAttempts) { + const lockoutSettings = await getLockoutSettings( + command.organization, + ); + + return { + error: `Failed to authenticate: You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.`, + }; + } + return { error: "Could not create session for user" }; + } } // this is a fake error message to hide that the user does not even exist return { error: "Could not verify password" }; } else { - session = await setSessionAndUpdateCookie( - sessionCookie, - command.checks, - undefined, - command.authRequestId, - loginSettings?.passwordCheckLifetime, - ); + try { + session = await setSessionAndUpdateCookie( + sessionCookie, + command.checks, + undefined, + command.authRequestId, + loginSettings?.passwordCheckLifetime, + ); + } catch (error: any) { + if ("failedAttempts" in error && error.failedAttempts) { + const lockoutSettings = await getLockoutSettings(command.organization); + + return { + error: `Failed to authenticate: You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.`, + }; + } + throw error; + } if (!session?.factors?.user?.id) { return { error: "Could not create session for user" }; diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 589b5a43a0..c7aa7c262c 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -81,6 +81,14 @@ export async function getLoginSettings(orgId?: string) { return useCache ? cacheWrapper(callback) : callback; } +export async function getLockoutSettings(orgId?: string) { + const callback = settingsService + .getLockoutSettings({ ctx: makeReqCtx(orgId) }, {}) + .then((resp) => (resp.settings ? resp.settings : undefined)); + + return useCache ? cacheWrapper(callback) : callback; +} + export async function getPasswordExpirySettings(orgId?: string) { const callback = settingsService .getPasswordExpirySettings({ ctx: makeReqCtx(orgId) }, {}) diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index 30c0932934..abd06bb05d 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -14,7 +14,7 @@ ], "sideEffects": false, "scripts": { - "generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel", + "generate": "buf generate https://github.com/zitadel/zitadel.git#branch=fix/9198-user_password_lockout_error_response --path ./proto/zitadel", "clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate" }, "dependencies": { From b9d4ca824f8d580830a372df63d6ce1a7500279d Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 27 Jan 2025 16:23:46 +0100 Subject: [PATCH 35/67] error handler --- apps/login/src/lib/server/cookie.ts | 32 +++++++++++++++++---------- apps/login/src/lib/server/password.ts | 14 ++++++++++-- packages/zitadel-client/src/index.ts | 1 + 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 795a41cd8d..32b39d6e9a 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -7,9 +7,10 @@ import { getSession, setSession, } from "@/lib/zitadel"; -import { Duration, timestampMs } from "@zitadel/client"; +import { ConnectError, Duration, timestampMs } from "@zitadel/client"; import { CredentialsCheckError, + CredentialsCheckErrorSchema, ErrorDetail, } from "@zitadel/proto/zitadel/message_pb"; import { @@ -30,13 +31,29 @@ type CustomCookieData = { authRequestId?: string; // if its linked to an OIDC flow }; +const passwordAttemptsHandler = (error: ConnectError) => { + const details = error.findDetails(CredentialsCheckErrorSchema); + + if (details[0] && "failedAttempts" in details[0]) { + const failedAttempts = details[0].failedAttempts; + throw { + error: `Failed to authenticate: You had ${failedAttempts} password attempts.`, + failedAttempts: failedAttempts, + }; + } + throw error; +}; + export async function createSessionAndUpdateCookie( checks: Checks, challenges: RequestChallenges | undefined, authRequestId: string | undefined, lifetime?: Duration, ): Promise { - const createdSession = await createSessionFromChecks(checks, challenges); + const createdSession = await createSessionFromChecks( + checks, + challenges, + ).catch(passwordAttemptsHandler); if (createdSession) { return getSession({ @@ -215,14 +232,5 @@ export async function setSessionAndUpdateCookie( throw "Session not be set"; } }) - .catch((error: ErrorDetail | CredentialsCheckError) => { - console.error("Could not set session", error); - if ("failedAttempts" in error && error.failedAttempts) { - throw { - error: `Failed to authenticate: You had ${error.failedAttempts} password attempts.`, - failedAttempts: error.failedAttempts, - }; - } - throw error; - }); + .catch(passwordAttemptsHandler); } diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index d69074aca3..2f8afd87a2 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -113,7 +113,12 @@ export async function sendPassword(command: UpdateSessionCommand) { ); return { - error: `Failed to authenticate: You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.`, + error: + `Failed to authenticate. You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.` + + (lockoutSettings?.maxPasswordAttempts && + error.failedAttempts >= lockoutSettings?.maxPasswordAttempts + ? "Contact your administrator to unlock your account" + : ""), }; } return { error: "Could not create session for user" }; @@ -136,7 +141,12 @@ export async function sendPassword(command: UpdateSessionCommand) { const lockoutSettings = await getLockoutSettings(command.organization); return { - error: `Failed to authenticate: You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.`, + error: + `Failed to authenticate. You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.` + + (lockoutSettings?.maxPasswordAttempts && + error.failedAttempts >= lockoutSettings?.maxPasswordAttempts + ? " Contact your administrator to unlock your account" + : ""), }; } throw error; diff --git a/packages/zitadel-client/src/index.ts b/packages/zitadel-client/src/index.ts index 64c3af5050..4d50003d7a 100644 --- a/packages/zitadel-client/src/index.ts +++ b/packages/zitadel-client/src/index.ts @@ -6,3 +6,4 @@ export { create, fromJson, toJson } from "@bufbuild/protobuf"; export type { JsonObject } from "@bufbuild/protobuf"; export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt"; export type { Duration, Timestamp } from "@bufbuild/protobuf/wkt"; +export type { Code, ConnectError } from "@connectrpc/connect"; From 44bb8588de432aca2f5af191753640d98cee15eb Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 09:41:03 +0100 Subject: [PATCH 36/67] use x-zitadel-forward-host header as target api, util --- apps/login/next.config.mjs | 1 + apps/login/src/lib/api.ts | 23 ---------------- apps/login/src/lib/self.ts | 24 +++++------------ apps/login/src/lib/server/password.ts | 31 +++++++++------------ apps/login/src/lib/service.ts | 39 ++++++++++++++++----------- apps/login/src/middleware.ts | 18 ++----------- 6 files changed, 46 insertions(+), 90 deletions(-) diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index a6e1d1a6d8..9b9d624fb1 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -35,6 +35,7 @@ const secureHeaders = [ ]; const nextConfig = { + basePath: "/login", reactStrictMode: true, // Recommended for the `pages` directory, default in `app`. experimental: { dynamicIO: true, diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index 6bde583f82..ba70a764f3 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -1,27 +1,4 @@ import { newSystemToken } from "@zitadel/client/node"; -import { getInstanceDomainByHost } from "./zitadel"; - -export async function getInstanceUrl(host: string): Promise { - const [hostname, port] = host.split(":"); - - if (hostname === "localhost") { - console.log("fallback to ZITADEL_API_URL"); - return process.env.ZITADEL_API_URL || ""; - } - - const instanceDomain = await getInstanceDomainByHost(host).catch((error) => { - console.error(`Could not get instance by host ${host}`, error); - return null; - }); - - if (!instanceDomain) { - throw new Error("No instance found"); - } - - console.log(`host: ${host}, api: ${instanceDomain}`); - - return instanceDomain; -} export async function systemAPIToken() { const audience = process.env.AUDIENCE; diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index 4977967018..b7aaea3ea5 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -3,24 +3,13 @@ import { createServerTransport } from "@zitadel/client/node"; import { createUserServiceClient } from "@zitadel/client/v2"; import { headers } from "next/headers"; -import { getInstanceUrl } from "./api"; import { getSessionCookieById } from "./cookies"; +import { getApiUrlOfHeaders } from "./service"; import { getSession } from "./zitadel"; const transport = async (host: string, token: string) => { - let instanceUrl; - try { - instanceUrl = await getInstanceUrl(host); - } catch (error) { - console.error( - `Could not get instance url for ${host}, fallback to ZITADEL_API_URL`, - error, - ); - instanceUrl = process.env.ZITADEL_API_URL; - } - return createServerTransport(token, { - baseUrl: instanceUrl, + baseUrl: host, }); }; @@ -36,16 +25,17 @@ export async function setMyPassword({ sessionId: string; password: string; }) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); - if (!host || typeof host !== "string") { + if (!instanceUrl) { throw new Error("No host found"); } const sessionCookie = await getSessionCookieById({ sessionId }); const { session } = await getSession({ - host, + host: instanceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -54,7 +44,7 @@ export async function setMyPassword({ return { error: "Could not load session" }; } - const service = await myUserService(host, `${sessionCookie.token}`); + const service = await myUserService(instanceUrl, `${sessionCookie.token}`); if (!session?.factors?.user?.id) { return { error: "No user id found in session" }; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 9d20e10022..7a417e803a 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -28,9 +28,9 @@ import { SetPasswordRequestSchema, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; -import { getInstanceUrl } from "../api"; import { getNextUrl } from "../client"; import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies"; +import { getApiUrlOfHeaders } from "../service"; import { checkEmailVerification, checkMFAFactors, @@ -281,16 +281,17 @@ export async function checkSessionAndSetPassword({ sessionId, password, }: CheckSessionAndSetPasswordCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); - if (!host || typeof host !== "string") { + if (!instanceUrl) { throw new Error("No host found"); } const sessionCookie = await getSessionCookieById({ sessionId }); const { session } = await getSession({ - host, + host: instanceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -308,7 +309,7 @@ export async function checkSessionAndSetPassword({ // check if the user has no password set in order to set a password const authmethods = await listAuthenticationMethodTypes({ - host, + host: instanceUrl, userId: session.factors.user.id, }); @@ -328,7 +329,7 @@ export async function checkSessionAndSetPassword({ ); const loginSettings = await getLoginSettings({ - host, + host: instanceUrl, organization: session.factors.user.organizationId, }); @@ -348,19 +349,8 @@ export async function checkSessionAndSetPassword({ }); } else { const transport = async (host: string, token: string) => { - let instanceUrl; - try { - instanceUrl = await getInstanceUrl(host); - } catch (error) { - console.error( - `Could not get instance url for ${host}, fallback to ZITADEL_API_URL`, - error, - ); - instanceUrl = process.env.ZITADEL_API_URL; - } - return createServerTransport(token, { - baseUrl: instanceUrl, + baseUrl: host, }); }; @@ -369,7 +359,10 @@ export async function checkSessionAndSetPassword({ return createUserServiceClient(transportPromise); }; - const selfService = await myUserService(host, `${sessionCookie.token}`); + const selfService = await myUserService( + instanceUrl, + `${sessionCookie.token}`, + ); return selfService .setPassword( diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index e0382c03b7..e6d55253e8 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -6,7 +6,8 @@ import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_p 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 { getInstanceUrl, systemAPIToken } from "./api"; +import { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers"; +import { systemAPIToken } from "./api"; type ServiceClass = | typeof IdentityProviderService @@ -20,26 +21,34 @@ export async function createServiceForHost( service: T, host: string, ) { - let instanceUrl, token; - try { - instanceUrl = await getInstanceUrl(host); - token = await systemAPIToken(); - } catch (error) { - console.error( - `Could not get instance url for ${host}, fallback to ZITADEL_API_URL`, - error, - ); - instanceUrl = process.env.ZITADEL_API_URL; - token = process.env.ZITADEL_SERVICE_USER_TOKEN; - } + const token = await systemAPIToken(); - if (!instanceUrl || !token) { + if (!host || !token) { throw new Error("No instance url or token found"); } const transport = createServerTransport(token, { - baseUrl: instanceUrl, + baseUrl: host, }); return createClientFor(service)(transport); } + +export function getApiUrlOfHeaders(headers: ReadonlyHeaders): string { + let instanceUrl: string = process.env.ZITADEL_API_URL; + + if (headers.get("x-zitadel-forward-host")) { + instanceUrl = headers.get("x-zitadel-forward-host") as string; + } else { + const host = headers.get("host"); + + if (host) { + const [hostname, port] = host.split(":"); + if (hostname !== "localhost") { + instanceUrl = host; + } + } + } + + return instanceUrl; +} diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index e7bbfd7dba..e059aa032f 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 { getInstanceUrl } from "./lib/api"; +import { getApiUrlOfHeaders } from "./lib/service"; export const config = { matcher: [ @@ -21,22 +21,8 @@ export async function middleware(request: NextRequest) { // return NextResponse.next(); // } const _headers = await headers(); - const _host = _headers.get("host"); - console.log("host", _host); - - const host = _host || request.nextUrl.host; - - let instanceUrl; - try { - instanceUrl = await getInstanceUrl(host); - } catch (error) { - console.error( - `[Middleware]: Could not get instance url of ${host}, fallback to ZITADEL_API_URL ${process.env.ZITADEL_API_URL}`, - error, - ); - instanceUrl = process.env.ZITADEL_API_URL; - } + const instanceUrl = getApiUrlOfHeaders(_headers); const instanceHost = `${instanceUrl}`.replace("https://", ""); From d378c6504ecfeb3b7c0db8baa1ef9ca531a55f60 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 09:47:35 +0100 Subject: [PATCH 37/67] host change everywhere --- apps/login/src/app/(login)/accounts/page.tsx | 5 ++++- .../src/app/(login)/authenticator/set/page.tsx | 5 ++++- .../(login)/idp/[provider]/failure/page.tsx | 5 ++++- .../(login)/idp/[provider]/success/page.tsx | 5 ++++- apps/login/src/app/(login)/idp/page.tsx | 5 ++++- apps/login/src/app/(login)/invite/page.tsx | 5 ++++- .../src/app/(login)/invite/success/page.tsx | 5 ++++- apps/login/src/app/(login)/loginname/page.tsx | 5 ++++- apps/login/src/app/(login)/mfa/page.tsx | 5 ++++- apps/login/src/app/(login)/mfa/set/page.tsx | 5 ++++- .../src/app/(login)/otp/[method]/page.tsx | 5 ++++- .../src/app/(login)/otp/[method]/set/page.tsx | 5 ++++- apps/login/src/app/(login)/passkey/page.tsx | 5 ++++- .../login/src/app/(login)/passkey/set/page.tsx | 5 ++++- .../src/app/(login)/password/change/page.tsx | 5 ++++- apps/login/src/app/(login)/password/page.tsx | 5 ++++- .../src/app/(login)/password/set/page.tsx | 5 ++++- apps/login/src/app/(login)/register/page.tsx | 5 ++++- .../src/app/(login)/register/password/page.tsx | 5 ++++- apps/login/src/app/(login)/signedin/page.tsx | 5 ++++- apps/login/src/app/(login)/u2f/page.tsx | 5 ++++- apps/login/src/app/(login)/u2f/set/page.tsx | 5 ++++- apps/login/src/app/(login)/verify/page.tsx | 5 ++++- apps/login/src/app/login/route.ts | 5 ++++- apps/login/src/lib/server/cookie.ts | 13 ++++++++++--- apps/login/src/lib/server/idp.ts | 9 +++++++-- apps/login/src/lib/server/invite.ts | 5 ++++- apps/login/src/lib/server/loginname.ts | 13 ++++++++++--- apps/login/src/lib/server/otp.ts | 5 ++++- apps/login/src/lib/server/passkeys.ts | 13 ++++++++++--- apps/login/src/lib/server/password.ts | 12 +++++++++--- apps/login/src/lib/server/register.ts | 5 ++++- apps/login/src/lib/server/session.ts | 17 +++++++++++++---- apps/login/src/lib/server/u2f.ts | 9 +++++++-- apps/login/src/lib/server/verify.ts | 18 +++++++++++++----- 35 files changed, 187 insertions(+), 52 deletions(-) diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index 99e7acb408..f17c799a93 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -1,6 +1,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SessionsList } from "@/components/sessions-list"; import { getAllSessionCookieIds } from "@/lib/cookies"; +import { getApiUrlOfHeaders } from "@/lib/service"; import { getBrandingSettings, getDefaultOrg, @@ -37,7 +38,9 @@ export default async function Page(props: { const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 55a4d3c74e..1c88014416 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -5,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getActiveIdentityProviders, @@ -28,7 +29,9 @@ export default async function Page(props: { const { loginName, authRequestId, organization, sessionId } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); 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 13bbb44e31..e368b8c070 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -1,4 +1,5 @@ import { DynamicTheme } from "@/components/dynamic-theme"; +import { getApiUrlOfHeaders } from "@/lib/service"; import { getBrandingSettings } from "@/lib/zitadel"; import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; @@ -23,7 +24,9 @@ export default async function Page(props: { const { organization } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); 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 0fdc44f6d5..bfd78ea3fc 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -5,6 +5,7 @@ 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, PROVIDER_MAPPING } from "@/lib/idp"; +import { getApiUrlOfHeaders } from "@/lib/service"; import { addHuman, addIDPLink, @@ -38,7 +39,9 @@ export default async function Page(props: { const { id, token, authRequestId, organization, link } = searchParams; const { provider } = params; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index 8b91d7c064..56897c28f3 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -1,5 +1,6 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; +import { getApiUrlOfHeaders } from "@/lib/service"; import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; @@ -14,7 +15,9 @@ export default async function Page(props: { const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx index 5d99d3e4a4..9b92be5381 100644 --- a/apps/login/src/app/(login)/invite/page.tsx +++ b/apps/login/src/app/(login)/invite/page.tsx @@ -1,6 +1,7 @@ import { Alert, AlertType } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { InviteForm } from "@/components/invite-form"; +import { getApiUrlOfHeaders } from "@/lib/service"; import { getBrandingSettings, getDefaultOrg, @@ -19,7 +20,9 @@ export default async function Page(props: { let { firstname, lastname, email, organization } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/invite/success/page.tsx b/apps/login/src/app/(login)/invite/success/page.tsx index 8bb7a68094..5ae7300ea0 100644 --- a/apps/login/src/app/(login)/invite/success/page.tsx +++ b/apps/login/src/app/(login)/invite/success/page.tsx @@ -2,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; 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"; @@ -17,7 +18,9 @@ export default async function Page(props: { let { userId, organization } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 1c513d74a1..21881d2253 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -1,6 +1,7 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { UsernameForm } from "@/components/username-form"; +import { getApiUrlOfHeaders } from "@/lib/service"; import { getActiveIdentityProviders, getBrandingSettings, @@ -24,7 +25,9 @@ export default async function Page(props: { const suffix = searchParams?.suffix; const submit: boolean = searchParams?.submit === "true"; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index 62625c7efa..f2df51847d 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -4,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -23,7 +24,9 @@ export default async function Page(props: { const { loginName, authRequestId, organization, sessionId } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index d92e1ce820..4adfa87fdb 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -4,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -50,7 +51,9 @@ export default async function Page(props: { sessionId, } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 1f57d6c3be..3e9b41f902 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -3,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -22,7 +23,9 @@ export default async function Page(props: { const t = await getTranslations({ locale, namespace: "otp" }); const tError = await getTranslations({ locale, namespace: "error" }); - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); 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 b341cc47ba..e9c3da8848 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -4,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { addOTPEmail, @@ -32,7 +33,9 @@ export default async function Page(props: { searchParams; const { method } = params; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index f2d67fe9bf..fad64ebac0 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -3,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -23,7 +24,9 @@ export default async function Page(props: { const { loginName, altPassword, authRequestId, organization, sessionId } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 5a768eef47..184eed8296 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -2,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; @@ -18,7 +19,9 @@ export default async function Page(props: { const { loginName, prompt, organization, authRequestId, userId } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index f242d364d4..304ad4ec7b 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -2,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -14,7 +15,9 @@ import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; }) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 9ef9ec430d..6adfb82754 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -2,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -23,7 +24,9 @@ export default async function Page(props: { let { loginName, organization, authRequestId, alt } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index 61ced3b1e6..f951e40a6a 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -2,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -25,7 +26,9 @@ export default async function Page(props: { const { userId, loginName, organization, authRequestId, code, initial } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 08943c3048..0a1deb6a23 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -1,5 +1,6 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { RegisterForm } from "@/components/register-form"; +import { getApiUrlOfHeaders } from "@/lib/service"; import { getBrandingSettings, getDefaultOrg, @@ -21,7 +22,9 @@ export default async function Page(props: { let { firstname, lastname, email, organization, authRequestId } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx index 6fed1af8b2..549b7a90ef 100644 --- a/apps/login/src/app/(login)/register/password/page.tsx +++ b/apps/login/src/app/(login)/register/password/page.tsx @@ -1,5 +1,6 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SetRegisterPasswordForm } from "@/components/set-register-password-form"; +import { getApiUrlOfHeaders } from "@/lib/service"; import { getBrandingSettings, getDefaultOrg, @@ -21,7 +22,9 @@ export default async function Page(props: { let { firstname, lastname, email, organization, authRequestId } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index d8a4ebaece..2658a915ca 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -3,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { createCallback, getBrandingSettings, @@ -59,7 +60,9 @@ export default async function Page(props: { searchParams: Promise }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "signedin" }); - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index a9e468b572..f588c7d62c 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -3,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; @@ -18,7 +19,9 @@ export default async function Page(props: { const { loginName, authRequestId, sessionId, organization } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index 47564abd9d..1ebe4d5edd 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -2,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; @@ -17,7 +18,9 @@ export default async function Page(props: { const { loginName, organization, authRequestId, checkAfter } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 0eca62e561..1000fcadeb 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -4,6 +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 { getApiUrlOfHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -24,7 +25,9 @@ export default async function Page(props: { searchParams: Promise }) { const { userId, loginName, code, organization, authRequestId, invite } = searchParams; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index de38d4bcb6..42599f00b3 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -1,6 +1,7 @@ import { getAllSessions } from "@/lib/cookies"; import { idpTypeToSlug } from "@/lib/idp"; import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname"; +import { getApiUrlOfHeaders } from "@/lib/service"; import { createCallback, getActiveIdentityProviders, @@ -191,7 +192,9 @@ export async function GET(request: NextRequest) { const authRequestId = searchParams.get("authRequest"); const sessionId = searchParams.get("sessionId"); - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 3e3347e750..e9cdd80493 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -15,6 +15,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 { getApiUrlOfHeaders } from "../service"; type CustomCookieData = { id: string; @@ -33,7 +34,9 @@ export async function createSessionAndUpdateCookie( authRequestId: string | undefined, lifetime?: Duration, ): Promise { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { throw new Error("Could not get domain"); @@ -98,7 +101,9 @@ export async function createSessionForIdpAndUpdateCookie( authRequestId: string | undefined, lifetime?: Duration, ): Promise { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { throw new Error("Could not get domain"); @@ -163,7 +168,9 @@ export async function setSessionAndUpdateCookie( authRequestId?: string, lifetime?: Duration, ) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { throw new Error("Could not get domain"); diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index b43cd4705d..560611b407 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -7,6 +7,7 @@ import { } from "@/lib/zitadel"; import { headers } from "next/headers"; import { getNextUrl } from "../client"; +import { getApiUrlOfHeaders } from "../service"; import { checkEmailVerification } from "../verify-helper"; import { createSessionForIdpAndUpdateCookie } from "./cookie"; @@ -17,7 +18,9 @@ export type StartIDPFlowCommand = { }; export async function startIDPFlow(command: StartIDPFlowCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { return { error: "Could not get host" }; @@ -56,7 +59,9 @@ type CreateNewSessionCommand = { export async function createNewSessionFromIdpIntent( command: CreateNewSessionCommand, ) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { return { error: "Could not get domain" }; diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 4bfb55d10b..332155ae3a 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -3,6 +3,7 @@ import { addHumanUser, createInviteCode } from "@/lib/zitadel"; import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { headers } from "next/headers"; +import { getApiUrlOfHeaders } from "../service"; type InviteUserCommand = { email: string; @@ -20,7 +21,9 @@ export type RegisterUserResponse = { }; export async function inviteUser(command: InviteUserCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { return { error: "Could not get domain" }; diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 98c742ca42..bf9664c29d 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -8,6 +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 { getApiUrlOfHeaders } from "../service"; import { checkInvite } from "../verify-helper"; import { getActiveIdentityProviders, @@ -32,7 +33,9 @@ export type SendLoginnameCommand = { const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; export async function sendLoginname(command: SendLoginnameCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { throw new Error("Could not get domain"); @@ -76,7 +79,9 @@ export async function sendLoginname(command: SendLoginnameCommand) { }); if (identityProviders.length === 1) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { return { error: "Could not get host" }; @@ -123,7 +128,9 @@ export async function sendLoginname(command: SendLoginnameCommand) { ); if (identityProviders.length === 1) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { return { error: "Could not get host" }; diff --git a/apps/login/src/lib/server/otp.ts b/apps/login/src/lib/server/otp.ts index 53e1ca3b16..d70fffaa2c 100644 --- a/apps/login/src/lib/server/otp.ts +++ b/apps/login/src/lib/server/otp.ts @@ -13,6 +13,7 @@ import { getSessionCookieById, getSessionCookieByLoginName, } from "../cookies"; +import { getApiUrlOfHeaders } from "../service"; import { getLoginSettings } from "../zitadel"; export type SetOTPCommand = { @@ -25,7 +26,9 @@ export type SetOTPCommand = { }; export async function setOTP(command: SetOTPCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { throw new Error("Could not get domain"); diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 7a662fbd88..2ffa594ff5 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -22,6 +22,7 @@ import { getSessionCookieById, getSessionCookieByLoginName, } from "../cookies"; +import { getApiUrlOfHeaders } from "../service"; import { checkEmailVerification } from "../verify-helper"; import { setSessionAndUpdateCookie } from "./cookie"; @@ -41,7 +42,9 @@ export async function registerPasskeyLink( ): Promise { const { sessionId } = command; - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { throw new Error("Could not get domain"); @@ -86,7 +89,9 @@ export async function registerPasskeyLink( } export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { throw new Error("Could not get domain"); @@ -152,7 +157,9 @@ export async function sendPasskey(command: SendPasskeyCommand) { }; } - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { return { error: "Could not get host" }; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 7a417e803a..6de1ca2bb4 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -44,7 +44,9 @@ type ResetPasswordCommand = { }; export async function resetPassword(command: ResetPasswordCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); @@ -76,7 +78,9 @@ export type UpdateSessionCommand = { }; export async function sendPassword(command: UpdateSessionCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); @@ -249,7 +253,9 @@ export async function changePassword(command: { userId: string; password: string; }) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 38dc025b8a..1aa2f2efdd 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -10,6 +10,7 @@ import { } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { headers } from "next/headers"; import { getNextUrl } from "../client"; +import { getApiUrlOfHeaders } from "../service"; import { checkEmailVerification } from "../verify-helper"; type RegisterUserCommand = { @@ -27,7 +28,9 @@ export type RegisterUserResponse = { factors: Factors | undefined; }; export async function registerUser(command: RegisterUserCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 84f028db44..9b95e77a6c 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -18,12 +18,15 @@ import { getSessionCookieByLoginName, removeSessionFromCookie, } from "../cookies"; +import { getApiUrlOfHeaders } from "../service"; export async function continueWithSession({ authRequestId, ...session }: Session & { authRequestId?: string }) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); @@ -89,7 +92,9 @@ export async function updateSession(options: UpdateSessionCommand) { }; } - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { return { error: "Could not get host" }; @@ -151,7 +156,9 @@ type ClearSessionOptions = { }; export async function clearSession(options: ClearSessionOptions) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); @@ -177,7 +184,9 @@ type CleanupSessionCommand = { }; export async function cleanupSession({ sessionId }: CleanupSessionCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index 8620350656..b544b8d75b 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -6,6 +6,7 @@ import { VerifyU2FRegistrationRequestSchema } from "@zitadel/proto/zitadel/user/ import { headers } from "next/headers"; import { userAgent } from "next/server"; import { getSessionCookieById } from "../cookies"; +import { getApiUrlOfHeaders } from "../service"; type RegisterU2FCommand = { sessionId: string; @@ -19,7 +20,9 @@ type VerifyU2FCommand = { }; export async function addU2F(command: RegisterU2FCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); @@ -55,7 +58,9 @@ export async function addU2F(command: RegisterU2FCommand) { } export async function verifyU2F(command: VerifyU2FCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 0b70b41d1f..21a8e03fcd 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -19,6 +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 { getApiUrlOfHeaders } from "../service"; import { loadMostRecentSession } from "../session"; import { checkMFAFactors } from "../verify-helper"; import { createSessionAndUpdateCookie } from "./cookie"; @@ -28,7 +29,9 @@ export async function verifyTOTP( loginName?: string, organization?: string, ) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); @@ -63,7 +66,9 @@ type VerifyUserByEmailCommand = { }; export async function sendVerification(command: VerifyUserByEmailCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); @@ -245,7 +250,9 @@ type resendVerifyEmailCommand = { }; export async function resendVerification(command: resendVerifyEmailCommand) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host) { return { error: "No host found" }; @@ -267,7 +274,6 @@ type sendEmailCommand = { }; export async function sendEmailCode(command: sendEmailCommand) { - const host = (await headers()).get("host"); return zitadelSendEmailCode({ userId: command.userId, host: command.host, @@ -286,7 +292,9 @@ export type SendVerificationRedirectWithoutCheckCommand = { export async function sendVerificationRedirectWithoutCheck( command: SendVerificationRedirectWithoutCheckCommand, ) { - const host = (await headers()).get("host"); + const _headers = await headers(); + const instanceUrl = getApiUrlOfHeaders(_headers); + const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); From 6234ba18ba926c0e08871b6ac9a74b3fe941bee5 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 09:56:06 +0100 Subject: [PATCH 38/67] fix password server action --- apps/login/src/lib/server/password.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 6de1ca2bb4..a07c4e6054 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -293,6 +293,7 @@ export async function checkSessionAndSetPassword({ if (!instanceUrl) { throw new Error("No host found"); } + const host = instanceUrl; const sessionCookie = await getSessionCookieById({ sessionId }); @@ -315,7 +316,7 @@ export async function checkSessionAndSetPassword({ // check if the user has no password set in order to set a password const authmethods = await listAuthenticationMethodTypes({ - host: instanceUrl, + host, userId: session.factors.user.id, }); From de21556f5d07e5a8c96e768f67068e4874078050 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 10:21:53 +0100 Subject: [PATCH 39/67] concat with protocol --- apps/login/src/lib/service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index e6d55253e8..0be711debe 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -39,6 +39,9 @@ export function getApiUrlOfHeaders(headers: ReadonlyHeaders): string { if (headers.get("x-zitadel-forward-host")) { instanceUrl = headers.get("x-zitadel-forward-host") as string; + instanceUrl = instanceUrl.startsWith("https://") + ? instanceUrl + : `https://${instanceUrl}`; } else { const host = headers.get("host"); From dd58fa8f7bdd7c0c43f2cf823e7ff9e5faa6d30e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 11:29:32 +0100 Subject: [PATCH 40/67] update readme --- apps/login/readme.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index fd6ba6f48c..4df81f9f9d 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -389,10 +389,6 @@ In future, self service options to jump to are shown below, like: ## Currently NOT Supported -Timebased features like the multifactor init prompt or password expiry, are not supported due to a current limitation in the API. Lockout settings which keeps track of the password retries, will also be implemented in a later stage. - -- Lockout Settings -- Password Expiry Settings - Login Settings: multifactor init prompt - forceMFA on login settings is not checked for IDPs From 059bbbca1c19cdb32b336f37e4b38432eb697d74 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 11:30:59 +0100 Subject: [PATCH 41/67] revert buf gen to main --- packages/zitadel-proto/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index abd06bb05d..30c0932934 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -14,7 +14,7 @@ ], "sideEffects": false, "scripts": { - "generate": "buf generate https://github.com/zitadel/zitadel.git#branch=fix/9198-user_password_lockout_error_response --path ./proto/zitadel", + "generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel", "clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate" }, "dependencies": { From 54a373baec1297c528cd4c5bcea13c19145751f7 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 13:20:45 +0100 Subject: [PATCH 42/67] headers endpoint --- apps/login/src/app/headers/route.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 apps/login/src/app/headers/route.ts diff --git a/apps/login/src/app/headers/route.ts b/apps/login/src/app/headers/route.ts new file mode 100644 index 0000000000..715013945a --- /dev/null +++ b/apps/login/src/app/headers/route.ts @@ -0,0 +1,16 @@ +import { NextRequest, NextResponse } from "next/server"; + +export const dynamic = "force-dynamic"; +export const revalidate = false; +export const fetchCache = "default-no-store"; + +export async function GET(request: NextRequest) { + const headers = request.headers; + + const headersObject: Record = {}; + headers.forEach((value, key) => { + headersObject[key] = value; + }); + + return NextResponse.json(headersObject); +} From 1a207a14f129cc522ff012fab9ed8bcd45e7cae3 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 14:34:14 +0100 Subject: [PATCH 43/67] log headers --- apps/login/src/lib/service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index 0be711debe..e34bbe9297 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -53,5 +53,8 @@ export function getApiUrlOfHeaders(headers: ReadonlyHeaders): string { } } + console.log("x-zitadel-forward-host" + headers.get("x-zitadel-forward-host")); + console.log("host" + headers.get("host")); + return instanceUrl; } From e97cfa9832f5c1eacbc46ec26327daf619d49023 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 14:44:22 +0100 Subject: [PATCH 44/67] ./ --- 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 9b9d624fb1..672e79c67f 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -35,7 +35,7 @@ const secureHeaders = [ ]; const nextConfig = { - basePath: "/login", + basePath: "./", reactStrictMode: true, // Recommended for the `pages` directory, default in `app`. experimental: { dynamicIO: true, From 0fdd4fa9110810d4733fdc43b27e7adb61fc2a37 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 14:45:38 +0100 Subject: [PATCH 45/67] / --- 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 672e79c67f..fff9efda0b 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -35,7 +35,7 @@ const secureHeaders = [ ]; const nextConfig = { - basePath: "./", + basePath: "/", reactStrictMode: true, // Recommended for the `pages` directory, default in `app`. experimental: { dynamicIO: true, From 5a801d6de4844433891501525bcbd71f1a751e62 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 14:47:48 +0100 Subject: [PATCH 46/67] basepath /new-login --- 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 fff9efda0b..c3771f9dd4 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -35,7 +35,7 @@ const secureHeaders = [ ]; const nextConfig = { - basePath: "/", + basePath: "/new-login", reactStrictMode: true, // Recommended for the `pages` directory, default in `app`. experimental: { dynamicIO: true, From ad0397af7c0c2a97b1b74ef90316ce13ab1a5ece Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 15:58:16 +0100 Subject: [PATCH 47/67] rm header logs --- apps/login/src/app/headers/route.ts | 16 ---------------- apps/login/src/lib/service.ts | 3 --- 2 files changed, 19 deletions(-) delete mode 100644 apps/login/src/app/headers/route.ts diff --git a/apps/login/src/app/headers/route.ts b/apps/login/src/app/headers/route.ts deleted file mode 100644 index 715013945a..0000000000 --- a/apps/login/src/app/headers/route.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -export const dynamic = "force-dynamic"; -export const revalidate = false; -export const fetchCache = "default-no-store"; - -export async function GET(request: NextRequest) { - const headers = request.headers; - - const headersObject: Record = {}; - headers.forEach((value, key) => { - headersObject[key] = value; - }); - - return NextResponse.json(headersObject); -} diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index e34bbe9297..0be711debe 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -53,8 +53,5 @@ export function getApiUrlOfHeaders(headers: ReadonlyHeaders): string { } } - console.log("x-zitadel-forward-host" + headers.get("x-zitadel-forward-host")); - console.log("host" + headers.get("host")); - return instanceUrl; } From e8900501b9a91fd2c77963c384c0eeeefb2ebe93 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 28 Jan 2025 16:56:31 +0100 Subject: [PATCH 48/67] service url context --- .../(login)/idp/[provider]/success/page.tsx | 6 +- .../src/app/(login)/otp/[method]/page.tsx | 14 +- apps/login/src/app/(login)/verify/page.tsx | 12 +- apps/login/src/lib/server/idp.ts | 3 +- apps/login/src/lib/server/invite.ts | 4 +- apps/login/src/lib/server/password.ts | 10 +- apps/login/src/lib/server/verify.ts | 19 +- apps/login/src/lib/zitadel.ts | 312 ++++++++---------- 8 files changed, 183 insertions(+), 197 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 bfd78ea3fc..88b4c6bdeb 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -53,7 +53,11 @@ export default async function Page(props: { return loginFailed(branding, "IDP context missing"); } - const intent = await retrieveIDPIntent({ host, id, token }); + const intent = await retrieveIDPIntent({ + serviceUrl: instanceUrl, + id, + token, + }); const { idpInformation, userId } = intent; diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 3e9b41f902..2fc21d764b 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -24,13 +24,17 @@ export default async function Page(props: { const tError = await getTranslations({ locale, namespace: "error" }); const _headers = await headers(); + const host = _headers.get("host"); const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; if (!host || typeof host !== "string") { throw new Error("No host found"); } + if (!instanceUrl) { + throw new Error("No instanceUrl found"); + } + const { loginName, // send from password page userId, // send from email link @@ -44,9 +48,9 @@ export default async function Page(props: { const { method } = params; const session = sessionId - ? await loadSessionById(host, sessionId, organization) + ? await loadSessionById(instanceUrl, sessionId, organization) : await loadMostRecentSession({ - host, + host: instanceUrl, sessionParams: { loginName, organization }, }); @@ -69,12 +73,12 @@ export default async function Page(props: { // email links do not come with organization, thus we need to use the session's organization const branding = await getBrandingSettings({ - host, + host: instanceUrl, organization: organization ?? session?.factors?.user?.organizationId, }); const loginSettings = await getLoginSettings({ - host, + host: instanceUrl, organization: organization ?? session?.factors?.user?.organizationId, }); diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 1000fcadeb..1dde49aabd 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -27,13 +27,16 @@ export default async function Page(props: { searchParams: Promise }) { const _headers = await headers(); const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const host = _headers.get("host"); if (!host || typeof host !== "string") { throw new Error("No host found"); } - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ + host: instanceUrl, + organization, + }); let sessionFactors; let user: User | undefined; @@ -44,7 +47,7 @@ export default async function Page(props: { searchParams: Promise }) { if ("loginName" in searchParams) { sessionFactors = await loadMostRecentSession({ - host, + host: instanceUrl, sessionParams: { loginName, organization, @@ -54,6 +57,9 @@ export default async function Page(props: { searchParams: Promise }) { if (doSend && sessionFactors?.factors?.user?.id) { await sendEmailCode({ host, + urlTemplate: + `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + + (authRequestId ? `&authRequestId=${authRequestId}` : ""), userId: sessionFactors?.factors?.user?.id, authRequestId, }).catch((error) => { diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 560611b407..4f0bd4d930 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -19,8 +19,7 @@ export type StartIDPFlowCommand = { export async function startIDPFlow(command: StartIDPFlowCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const host = _headers.get("host"); if (!host) { return { error: "Could not get host" }; diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 332155ae3a..5393e9547d 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -23,14 +23,14 @@ export type RegisterUserResponse = { export async function inviteUser(command: InviteUserCommand) { const _headers = await headers(); const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const host = _headers.get("host"); if (!host) { return { error: "Could not get domain" }; } const human = await addHumanUser({ - host, + host: instanceUrl, email: command.email, firstName: command.firstName, lastName: command.lastName, diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index a07c4e6054..4ec6be613d 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -53,7 +53,7 @@ export async function resetPassword(command: ResetPasswordCommand) { } const users = await listUsers({ - host, + serviceUrl: instanceUrl, loginName: command.loginName, organizationId: command.organization, }); @@ -67,7 +67,13 @@ export async function resetPassword(command: ResetPasswordCommand) { } const userId = users.result[0].userId; - return passwordReset({ userId, host, authRequestId: command.authRequestId }); + return passwordReset({ + serviceUrl: instanceUrl, + userId, + urlTemplate: + `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + + (command.authRequestId ? `&authRequestId=${command.authRequestId}` : ""), + }); } export type UpdateSessionCommand = { diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 21a8e03fcd..c1407fcd96 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -251,24 +251,30 @@ type resendVerifyEmailCommand = { export async function resendVerification(command: resendVerifyEmailCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + + const host = _headers.get("host"); + + if (!serviceUrl) { + return { error: "No host found" }; + } if (!host) { return { error: "No host found" }; } return command.isInvite - ? resendInviteCode({ host, userId: command.userId }) + ? resendInviteCode({ serviceUrl, host, userId: command.userId }) : resendEmailCode({ userId: command.userId, + serviceUrl, host, authRequestId: command.authRequestId, }); } type sendEmailCommand = { - host: string; + serviceUrl: string; userId: string; authRequestId?: string; }; @@ -276,8 +282,11 @@ type sendEmailCommand = { export async function sendEmailCode(command: sendEmailCommand) { return zitadelSendEmailCode({ userId: command.userId, - host: command.host, + serviceUrl: command.serviceUrl, authRequestId: command.authRequestId, + urlTemplate: + `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + + (authRequestId ? `&authRequestId=${authRequestId}` : ""), }); } diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index dfa0f65081..352b844e57 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -66,63 +66,15 @@ const systemService = async () => { return createSystemServiceClient(transport); }; -export async function getInstanceDomainByHost(host: string): Promise { - // const system = await systemService(); - // const callbacks = system - // .listInstances( - // { - // queries: [ - // { - // query: { - // case: "domainQuery", - // value: { - // domains: [host], - // }, - // }, - // }, - // ], - // }, - // {}, - // ) - // .then((resp) => { - // if (resp.result.length !== 1) { - // throw new Error("Could not find instance"); - // } - - // const generatedDomain = resp.result[0].domains.find( - // (domain) => domain.generated === true, - // ); - - // if (!generatedDomain) { - // throw new Error("Could not find generated domain"); - // } - - // return generatedDomain.domain; - // }); - - const mockFcn = async (host: string) => { - switch (host) { - case "multitenancy-qa.vercel.app": - return "https://another-i8pcvz.zitadel.app"; - default: - return process.env.ZITADEL_API_URL; - } - }; - - const callback = mockFcn(host); - - return useCache ? cacheWrapper(callback) : callback; -} - export async function getBrandingSettings({ - host, + serviceUrl, organization, }: { - host: string; + serviceUrl: string; organization?: string; }) { const settingsService: Client = - await createServiceForHost(SettingsService, host); + await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) @@ -132,10 +84,10 @@ export async function getBrandingSettings({ } export async function getLoginSettings({ - host, + serviceUrl, organization, }: { - host: string; + serviceurl: string; organization?: string; }) { const settingsService: Client = @@ -149,66 +101,66 @@ export async function getLoginSettings({ } export async function listIDPLinks({ - host, + serviceUrl, userId, }: { - host: string; + serviceurl: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.listIDPLinks({ userId }, {}); } export async function addOTPEmail({ - host, + serviceUrl, userId, }: { - host: string; + serviceurl: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.addOTPEmail({ userId }, {}); } export async function addOTPSMS({ - host, + serviceUrl, userId, }: { - host: string; + serviceurl: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.addOTPSMS({ userId }, {}); } export async function registerTOTP({ - host, + serviceUrl, userId, }: { - host: string; + serviceurl: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.registerTOTP({ userId }, {}); } -export async function getGeneralSettings({ host }: { host: string }) { +export async function getGeneralSettings({ host }: { serviceurl: string }) { const settingsService: Client = await createServiceForHost(SettingsService, host); @@ -220,10 +172,10 @@ export async function getGeneralSettings({ host }: { host: string }) { } export async function getLegalAndSupportSettings({ - host, + serviceUrl, organization, }: { - host: string; + serviceurl: string; organization?: string; }) { const settingsService: Client = @@ -237,10 +189,10 @@ export async function getLegalAndSupportSettings({ } export async function getPasswordComplexitySettings({ - host, + serviceUrl, organization, }: { - host: string; + serviceurl: string; organization?: string; }) { const settingsService: Client = @@ -254,12 +206,12 @@ export async function getPasswordComplexitySettings({ } export async function createSessionFromChecks({ - host, + serviceUrl, checks, challenges, lifetime, }: { - host: string; + serviceurl: string; checks: Checks; challenges: RequestChallenges | undefined; lifetime?: Duration; @@ -271,12 +223,12 @@ export async function createSessionFromChecks({ } export async function createSessionForUserIdAndIdpIntent({ - host, + serviceUrl, userId, idpIntent, lifetime, }: { - host: string; + serviceurl: string; userId: string; idpIntent: { idpIntentId?: string | undefined; @@ -302,14 +254,14 @@ export async function createSessionForUserIdAndIdpIntent({ } export async function setSession({ - host, + serviceUrl, sessionId, sessionToken, challenges, checks, lifetime, }: { - host: string; + serviceurl: string; sessionId: string; sessionToken: string; challenges: RequestChallenges | undefined; @@ -333,11 +285,11 @@ export async function setSession({ } export async function getSession({ - host, + serviceUrl, sessionId, sessionToken, }: { - host: string; + serviceurl: string; sessionId: string; sessionToken: string; }) { @@ -348,11 +300,11 @@ export async function getSession({ } export async function deleteSession({ - host, + serviceUrl, sessionId, sessionToken, }: { - host: string; + serviceurl: string; sessionId: string; sessionToken: string; }) { @@ -363,11 +315,11 @@ export async function deleteSession({ } type ListSessionsCommand = { - host: string; + serviceUrl: string; ids: string[]; }; -export async function listSessions({ host, ids }: ListSessionsCommand) { +export async function listSessions({ serviceUrl, ids }: ListSessionsCommand) { const sessionService: Client = await createServiceForHost(SessionService, host); @@ -387,7 +339,7 @@ export async function listSessions({ host, ids }: ListSessionsCommand) { } export type AddHumanUserData = { - host: string; + serviceUrl: string; firstName: string; lastName: string; email: string; @@ -396,7 +348,7 @@ export type AddHumanUserData = { }; export async function addHumanUser({ - host, + serviceUrl, email, firstName, lastName, @@ -405,7 +357,7 @@ export async function addHumanUser({ }: AddHumanUserData) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.addHumanUser({ @@ -428,90 +380,91 @@ export async function addHumanUser({ } export async function addHuman({ - host, + serviceUrl, request, }: { - host: string; + serviceUrl: string; request: AddHumanUserRequest; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.addHumanUser(request); } export async function verifyTOTPRegistration({ - host, + serviceUrl, code, userId, }: { - host: string; + serviceUrl: string; code: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.verifyTOTPRegistration({ code, userId }, {}); } export async function getUserByID({ - host, + serviceUrl, userId, }: { - host: string; + serviceUrl: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.getUserByID({ userId }, {}); } export async function verifyInviteCode({ - host, + serviceUrl, userId, verificationCode, }: { - host: string; + serviceUrl: string; userId: string; verificationCode: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.verifyInviteCode({ userId, verificationCode }, {}); } export async function resendInviteCode({ - host, + serviceUrl, userId, }: { - host: string; + serviceUrl: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.resendInviteCode({ userId }, {}); } export async function sendEmailCode({ - host, + serviceUrl, + urlTemplate userId, authRequestId, }: { - host: string; + serviceUrl: string; userId: string; authRequestId?: string; }) { @@ -523,9 +476,7 @@ export async function sendEmailCode({ verification: { case: "sendCode", value: create(SendEmailVerificationCodeSchema, { - urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + - (authRequestId ? `&authRequestId=${authRequestId}` : ""), + urlTemplate, }), }, }); @@ -533,17 +484,17 @@ export async function sendEmailCode({ const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.sendEmailCode(medium, {}); } export async function createInviteCode({ - host, + serviceUrl, userId, }: { - host: string; + serviceUrl: string; userId: string; }) { let medium = create(SendInviteCodeSchema, { @@ -559,7 +510,7 @@ export async function createInviteCode({ const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.createInviteCode( @@ -575,7 +526,7 @@ export async function createInviteCode({ } export type ListUsersCommand = { - host: string; + serviceUrl: string; loginName?: string; userName?: string; email?: string; @@ -584,7 +535,7 @@ export type ListUsersCommand = { }; export async function listUsers({ - host, + serviceUrl, loginName, userName, phone, @@ -675,14 +626,14 @@ export async function listUsers({ const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.listUsers({ queries }); } export type SearchUsersCommand = { - host: string; + serviceUrl: string; searchValue: string; loginSettings: LoginSettings; organizationId?: string; @@ -727,7 +678,7 @@ const EmailQuery = (searchValue: string) => * it searches users based on the loginName or userName and org suffix combination, and falls back to email and phone if no users are found * */ export async function searchUsers({ - host, + serviceUrl, searchValue, loginSettings, organizationId, @@ -760,7 +711,7 @@ export async function searchUsers({ const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); const loginNameResult = await userService.listUsers({ queries }); @@ -846,9 +797,9 @@ export async function searchUsers({ } export async function getDefaultOrg({ - host, + serviceUrl, }: { - host: string; + serviceUrl: string; }): Promise { const orgService: Client = await createServiceForHost(OrganizationService, host); @@ -871,10 +822,10 @@ export async function getDefaultOrg({ } export async function getOrgsByDomain({ - host, + serviceUrl, domain, }: { - host: string; + serviceUrl: string; domain: string; }) { const orgService: Client = @@ -896,17 +847,17 @@ export async function getOrgsByDomain({ } export async function startIdentityProviderFlow({ - host, + serviceUrl, idpId, urls, }: { - host: string; + serviceUrl: string; idpId: string; urls: RedirectURLsJson; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.startIdentityProviderIntent({ @@ -919,17 +870,17 @@ export async function startIdentityProviderFlow({ } export async function retrieveIdentityProviderInformation({ - host, + serviceUrl, idpIntentId, idpIntentToken, }: { - host: string; + serviceUrl: string; idpIntentId: string; idpIntentToken: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.retrieveIdentityProviderIntent({ @@ -939,10 +890,10 @@ export async function retrieveIdentityProviderInformation({ } export async function getAuthRequest({ - host, + serviceUrl, authRequestId, }: { - host: string; + serviceUrl: string; authRequestId: string; }) { const oidcService = await createServiceForHost(OIDCService, host); @@ -953,10 +904,10 @@ export async function getAuthRequest({ } export async function createCallback({ - host, + serviceUrl, req, }: { - host: string; + serviceUrl: string; req: CreateCallbackRequest; }) { const oidcService = await createServiceForHost(OIDCService, host); @@ -965,17 +916,17 @@ export async function createCallback({ } export async function verifyEmail({ - host, + serviceUrl, userId, verificationCode, }: { - host: string; + serviceUrl: string; userId: string; verificationCode: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.verifyEmail( @@ -988,10 +939,12 @@ export async function verifyEmail({ } export async function resendEmailCode({ + serviceUrl, host, userId, authRequestId, }: { + serviceUrl: string; host: string; userId: string; authRequestId?: string; @@ -1012,24 +965,24 @@ export async function resendEmailCode({ const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.resendEmailCode(request, {}); } export async function retrieveIDPIntent({ - host, + serviceUrl, id, token, }: { - host: string; + serviceUrl: string; id: string; token: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.retrieveIdentityProviderIntent( @@ -1038,7 +991,13 @@ export async function retrieveIDPIntent({ ); } -export async function getIDPByID({ host, id }: { host: string; id: string }) { +export async function getIDPByID({ + serviceUrl, + id, +}: { + serviceUrl: string; + id: string; +}) { const idpService: Client = await createServiceForHost(IdentityProviderService, host); @@ -1046,17 +1005,17 @@ export async function getIDPByID({ host, id }: { host: string; id: string }) { } export async function addIDPLink({ - host, + serviceUrl, idp, userId, }: { - host: string; + serviceUrl: string; idp: { id: string; userId: string; userName: string }; userId: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.addIDPLink( @@ -1073,30 +1032,26 @@ export async function addIDPLink({ } export async function passwordReset({ - host, + serviceUrl, + urlTemplate, userId, - authRequestId, }: { - host: string; + serviceUrl: string; + urlTemplate: string; userId: string; - authRequestId?: string; }) { let medium = create(SendPasswordResetLinkSchema, { notificationType: NotificationType.Email, }); - if (host) { medium = { ...medium, - urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + - (authRequestId ? `&authRequestId=${authRequestId}` : ""), + urlTemplate, }; - } const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.passwordReset( @@ -1112,13 +1067,13 @@ export async function passwordReset({ } export async function setUserPassword({ - host, + serviceUrl, userId, password, user, code, }: { - host: string; + serviceUrl: string; userId: string; password: string; user: User; @@ -1133,7 +1088,10 @@ export async function setUserPassword({ // check if the user has no password set in order to set a password if (!code) { - const authmethods = await listAuthenticationMethodTypes({ host, userId }); + const authmethods = await listAuthenticationMethodTypes({ + serviceUrl, + userId, + }); // if the user has no authmethods set, we can set a password otherwise we need a code if ( @@ -1156,7 +1114,7 @@ export async function setUserPassword({ const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.setPassword(payload, {}).catch((error) => { @@ -1170,15 +1128,15 @@ export async function setUserPassword({ } export async function setPassword({ - host, + serviceUrl, payload, }: { - host: string; + serviceUrl: string; payload: SetPasswordRequest; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.setPassword(payload, {}); @@ -1191,15 +1149,15 @@ export async function setPassword({ * @returns the newly set email */ export async function createPasskeyRegistrationLink({ - host, + serviceUrl, userId, }: { - host: string; + serviceUrl: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.createPasskeyRegistrationLink({ @@ -1219,17 +1177,17 @@ export async function createPasskeyRegistrationLink({ * @returns the newly set email */ export async function registerU2F({ - host, + serviceUrl, userId, domain, }: { - host: string; + serviceUrl: string; userId: string; domain: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.registerU2F({ @@ -1245,15 +1203,15 @@ export async function registerU2F({ * @returns the result of the verification */ export async function verifyU2FRegistration({ - host, + serviceUrl, request, }: { - host: string; + serviceUrl: string; request: VerifyU2FRegistrationRequest; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.verifyU2FRegistration(request, {}); @@ -1267,11 +1225,11 @@ export async function verifyU2FRegistration({ * @returns the active identity providers */ export async function getActiveIdentityProviders({ - host, + serviceUrl, orgId, linking_allowed, }: { - host: string; + serviceUrl: string; orgId?: string; linking_allowed?: boolean; }) { @@ -1292,15 +1250,15 @@ export async function getActiveIdentityProviders({ * @returns the result of the verification */ export async function verifyPasskeyRegistration({ - host, + serviceUrl, request, }: { - host: string; + serviceUrl: string; request: VerifyPasskeyRegistrationRequest; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.verifyPasskeyRegistration(request, {}); @@ -1315,19 +1273,19 @@ export async function verifyPasskeyRegistration({ * @returns the newly set email */ export async function registerPasskey({ - host, + serviceUrl, userId, code, domain, }: { - host: string; + serviceUrl: string; userId: string; code: { id: string; code: string }; domain: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.registerPasskey({ @@ -1344,15 +1302,15 @@ export async function registerPasskey({ * @returns the list of authentication method types */ export async function listAuthenticationMethodTypes({ - host, + serviceUrl, userId, }: { - host: string; + serviceUrl: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, - host, + serviceUrl, ); return userService.listAuthenticationMethodTypes({ From 04f9b47960f8a89b3d75f9c4f0f1296dcf525547 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 29 Jan 2025 10:34:33 +0100 Subject: [PATCH 49/67] cleanup serviceUrl, host, basepath configurable --- apps/login/next.config.mjs | 2 +- apps/login/src/app/(login)/accounts/page.tsx | 17 +-- .../app/(login)/authenticator/set/page.tsx | 55 ++++--- .../(login)/idp/[provider]/failure/page.tsx | 9 +- .../(login)/idp/[provider]/success/page.tsx | 31 ++-- apps/login/src/app/(login)/idp/page.tsx | 11 +- apps/login/src/app/(login)/invite/page.tsx | 15 +- .../src/app/(login)/invite/success/page.tsx | 13 +- apps/login/src/app/(login)/loginname/page.tsx | 20 ++- apps/login/src/app/(login)/mfa/page.tsx | 23 ++- apps/login/src/app/(login)/mfa/set/page.tsx | 53 ++++--- .../src/app/(login)/otp/[method]/page.tsx | 16 +- .../src/app/(login)/otp/[method]/set/page.tsx | 19 +-- apps/login/src/app/(login)/passkey/page.tsx | 19 +-- .../src/app/(login)/passkey/set/page.tsx | 11 +- .../src/app/(login)/password/change/page.tsx | 15 +- apps/login/src/app/(login)/password/page.tsx | 15 +- .../src/app/(login)/password/set/page.tsx | 17 +-- apps/login/src/app/(login)/register/page.tsx | 17 +-- .../app/(login)/register/password/page.tsx | 17 +-- apps/login/src/app/(login)/signedin/page.tsx | 23 ++- apps/login/src/app/(login)/u2f/page.tsx | 12 +- apps/login/src/app/(login)/u2f/set/page.tsx | 11 +- apps/login/src/app/(login)/verify/page.tsx | 17 ++- apps/login/src/app/login/route.ts | 54 ++++--- apps/login/src/components/login-otp.tsx | 4 +- apps/login/src/lib/self.ts | 18 +-- apps/login/src/lib/server/cookie.ts | 33 ++-- apps/login/src/lib/server/idp.ts | 14 +- apps/login/src/lib/server/invite.ts | 10 +- apps/login/src/lib/server/loginname.ts | 34 ++--- apps/login/src/lib/server/otp.ts | 9 +- apps/login/src/lib/server/passkeys.ts | 32 ++-- apps/login/src/lib/server/password.ts | 57 +++---- apps/login/src/lib/server/register.ts | 10 +- apps/login/src/lib/server/session.ts | 35 ++--- apps/login/src/lib/server/u2f.ts | 16 +- apps/login/src/lib/server/verify.ts | 76 ++++------ apps/login/src/lib/service.ts | 6 +- apps/login/src/lib/session.ts | 6 +- apps/login/src/lib/zitadel.ts | 142 ++++++++---------- apps/login/src/middleware.ts | 6 +- turbo.json | 3 +- 43 files changed, 426 insertions(+), 597 deletions(-) diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index c3771f9dd4..32209b11e7 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -35,7 +35,7 @@ const secureHeaders = [ ]; const nextConfig = { - basePath: "/new-login", + basePath: process.env.NEXT_PUBLIC_BASE_PATH, reactStrictMode: true, // Recommended for the `pages` directory, default in `app`. experimental: { dynamicIO: true, diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index f17c799a93..bd0b428aae 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -13,12 +13,12 @@ import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; import Link from "next/link"; -async function loadSessions(host: string) { +async function loadSessions({ serviceUrl }: { serviceUrl: string }) { const ids: (string | undefined)[] = await getAllSessionCookieIds(); if (ids && ids.length) { const response = await listSessions({ - host, + serviceUrl, ids: ids.filter((id) => !!id) as string[], }); return response?.sessions ?? []; @@ -39,25 +39,20 @@ export default async function Page(props: { const organization = searchParams?.organization; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg({ host }); + const org: Organization | null = await getDefaultOrg({ serviceUrl }); if (org) { defaultOrganization = org.id; } } - let sessions = await loadSessions(host); + let sessions = await loadSessions({ serviceUrl }); const branding = await getBrandingSettings({ - host, + serviceUrl, organization: organization ?? defaultOrganization, }); diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 1c88014416..dd99ad043f 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -30,16 +30,11 @@ export default async function Page(props: { const { loginName, authRequestId, organization, sessionId } = searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const sessionWithData = sessionId - ? await loadSessionById(host, sessionId, organization) - : await loadSessionByLoginname(host, loginName, organization); + ? await loadSessionById(serviceUrl, sessionId, organization) + : await loadSessionByLoginname(serviceUrl, loginName, organization); async function getAuthMethodsAndUser(host: string, session?: Session) { const userId = session?.factors?.user?.id; @@ -48,20 +43,24 @@ export default async function Page(props: { throw Error("Could not get user id from session"); } - return listAuthenticationMethodTypes({ host, userId }).then((methods) => { - return getUserByID({ host, userId }).then((user) => { - const humanUser = - user.user?.type.case === "human" ? user.user?.type.value : undefined; + return listAuthenticationMethodTypes({ serviceUrl, userId }).then( + (methods) => { + return getUserByID({ serviceUrl, userId }).then((user) => { + const humanUser = + user.user?.type.case === "human" + ? user.user?.type.value + : undefined; - return { - factors: session?.factors, - authMethods: methods.authMethodTypes ?? [], - phoneVerified: humanUser?.phone?.isVerified ?? false, - emailVerified: humanUser?.email?.isVerified ?? false, - expirationDate: session?.expirationDate, - }; - }); - }); + return { + factors: session?.factors, + authMethods: methods.authMethodTypes ?? [], + phoneVerified: humanUser?.phone?.isVerified ?? false, + emailVerified: humanUser?.email?.isVerified ?? false, + expirationDate: session?.expirationDate, + }; + }); + }, + ); } async function loadSessionByLoginname( @@ -70,13 +69,13 @@ export default async function Page(props: { organization?: string, ) { return loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization, }, }).then((session) => { - return getAuthMethodsAndUser(host, session); + return getAuthMethodsAndUser(serviceUrl, session); }); } @@ -87,11 +86,11 @@ export default async function Page(props: { ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ - host, + serviceUrl, sessionId: recent.id, sessionToken: recent.token, }).then((sessionResponse) => { - return getAuthMethodsAndUser(host, sessionResponse.session); + return getAuthMethodsAndUser(serviceUrl, sessionResponse.session); }); } @@ -100,17 +99,17 @@ export default async function Page(props: { } const branding = await getBrandingSettings({ - host, + serviceUrl, organization: sessionWithData.factors?.user?.organizationId, }); const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: sessionWithData.factors?.user?.organizationId, }); const identityProviders = await getActiveIdentityProviders({ - host, + serviceUrl, orgId: sessionWithData.factors?.user?.organizationId, linking_allowed: true, }).then((resp) => { 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 e368b8c070..ed7b63092b 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -25,14 +25,9 @@ export default async function Page(props: { const { organization } = searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } - - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); return ( 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 88b4c6bdeb..c39f2515ab 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -40,21 +40,16 @@ export default async function Page(props: { const { provider } = params; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } - - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); if (!provider || !id || !token) { return loginFailed(branding, "IDP context missing"); } const intent = await retrieveIDPIntent({ - serviceUrl: instanceUrl, + serviceUrl, id, token, }); @@ -77,7 +72,7 @@ export default async function Page(props: { return loginFailed(branding, "IDP information missing"); } - const idp = await getIDPByID({ host, id: idpInformation.idpId }); + const idp = await getIDPByID({ serviceUrl, id: idpInformation.idpId }); const options = idp?.config?.options; if (!idp) { @@ -95,7 +90,7 @@ export default async function Page(props: { let idpLink; try { idpLink = await addIDPLink({ - host, + serviceUrl, idp: { id: idpInformation.idpId, userId: idpInformation.userId, @@ -126,20 +121,20 @@ export default async function Page(props: { const email = PROVIDER_MAPPING[providerType](idpInformation).email?.email; if (options.autoLinking === AutoLinkingOption.EMAIL && email) { - foundUser = await listUsers({ host, email }).then((response) => { + foundUser = await listUsers({ serviceUrl, email }).then((response) => { return response.result ? response.result[0] : null; }); } else if (options.autoLinking === AutoLinkingOption.USERNAME) { foundUser = await listUsers( options.autoLinking === AutoLinkingOption.USERNAME - ? { host, userName: idpInformation.userName } - : { host, email }, + ? { serviceUrl, userName: idpInformation.userName } + : { serviceUrl, email }, ).then((response) => { return response.result ? response.result[0] : null; }); } else { foundUser = await listUsers({ - host, + serviceUrl, userName: idpInformation.userName, email, }).then((response) => { @@ -151,7 +146,7 @@ export default async function Page(props: { let idpLink; try { idpLink = await addIDPLink({ - host, + serviceUrl, idp: { id: idpInformation.idpId, userId: idpInformation.userId, @@ -192,12 +187,12 @@ export default async function Page(props: { const suffix = matched?.[1] ?? ""; // this just returns orgs where the suffix is set as primary domain - const orgs = await getOrgsByDomain({ host, domain: suffix }); + const orgs = await getOrgsByDomain({ serviceUrl, domain: suffix }); const orgToCheckForDiscovery = orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; const orgLoginSettings = await getLoginSettings({ - host, + serviceUrl, organization: orgToCheckForDiscovery, }); if (orgLoginSettings?.allowDomainDiscovery) { @@ -216,7 +211,7 @@ export default async function Page(props: { }); } - const newUser = await addHuman({ host, request: userData }); + const newUser = await addHuman({ serviceUrl, request: userData }); if (newUser) { return ( diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index 56897c28f3..da545f7a6e 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -16,21 +16,16 @@ export default async function Page(props: { const organization = searchParams?.organization; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const identityProviders = await getActiveIdentityProviders({ - host, + serviceUrl, orgId: organization, }).then((resp) => { return resp.identityProviders; }); - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); return ( diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx index 9b92be5381..4378a3e2d6 100644 --- a/apps/login/src/app/(login)/invite/page.tsx +++ b/apps/login/src/app/(login)/invite/page.tsx @@ -21,15 +21,10 @@ export default async function Page(props: { let { firstname, lastname, email, organization } = searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); if (!organization) { - const org = await getDefaultOrg({ host }); + const org = await getDefaultOrg({ serviceUrl }); if (!org) { throw new Error("No default organization found"); } @@ -37,14 +32,14 @@ export default async function Page(props: { organization = org.id; } - const loginSettings = await getLoginSettings({ host, organization }); + const loginSettings = await getLoginSettings({ serviceUrl, organization }); const passwordComplexitySettings = await getPasswordComplexitySettings({ - host, + serviceUrl, organization, }); - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); return ( diff --git a/apps/login/src/app/(login)/invite/success/page.tsx b/apps/login/src/app/(login)/invite/success/page.tsx index 5ae7300ea0..5ef81fc3cb 100644 --- a/apps/login/src/app/(login)/invite/success/page.tsx +++ b/apps/login/src/app/(login)/invite/success/page.tsx @@ -19,15 +19,10 @@ export default async function Page(props: { let { userId, organization } = searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); if (!organization) { - const org = await getDefaultOrg({ host }); + const org = await getDefaultOrg({ serviceUrl }); if (!org) { throw new Error("No default organization found"); } @@ -35,12 +30,12 @@ export default async function Page(props: { organization = org.id; } - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); let user: User | undefined; let human: HumanUser | undefined; if (userId) { - const userResponse = await getUserByID({ host, userId }); + const userResponse = await getUserByID({ serviceUrl, userId }); if (userResponse) { user = userResponse.user; if (user?.type.case === "human") { diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 21881d2253..b1c2d89570 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -26,37 +26,35 @@ export default async function Page(props: { const submit: boolean = searchParams?.submit === "true"; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg({ host }); + const org: Organization | null = await getDefaultOrg({ serviceUrl }); if (org) { defaultOrganization = org.id; } } const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: organization ?? defaultOrganization, }); - const contextLoginSettings = await getLoginSettings({ host, organization }); + const contextLoginSettings = await getLoginSettings({ + serviceUrl, + organization, + }); const identityProviders = await getActiveIdentityProviders({ - host, + serviceUrl, orgId: organization ?? defaultOrganization, }).then((resp) => { return resp.identityProviders; }); const branding = await getBrandingSettings({ - host, + serviceUrl, organization: organization ?? defaultOrganization, }); diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index f2df51847d..53a5324682 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -25,24 +25,19 @@ export default async function Page(props: { const { loginName, authRequestId, organization, sessionId } = searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const sessionFactors = sessionId - ? await loadSessionById(host, sessionId, organization) - : await loadSessionByLoginname(host, loginName, organization); + ? await loadSessionById(serviceUrl, sessionId, organization) + : await loadSessionByLoginname(serviceUrl, loginName, organization); async function loadSessionByLoginname( - host: string, + serviceUrl: string, loginName?: string, organization?: string, ) { return loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization, @@ -50,7 +45,7 @@ export default async function Page(props: { }).then((session) => { if (session && session.factors?.user?.id) { return listAuthenticationMethodTypes({ - host, + serviceUrl, userId: session.factors.user.id, }).then((methods) => { return { @@ -69,13 +64,13 @@ export default async function Page(props: { ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ - host, + serviceUrl, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { if (response?.session && response.session.factors?.user?.id) { return listAuthenticationMethodTypes({ - host, + serviceUrl, userId: response.session.factors.user.id, }).then((methods) => { return { @@ -87,7 +82,7 @@ export default async function Page(props: { }); } - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); return ( diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index 4adfa87fdb..e757f4139d 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -52,16 +52,11 @@ export default async function Page(props: { } = searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const sessionWithData = sessionId - ? await loadSessionById(host, sessionId, organization) - : await loadSessionByLoginname(host, loginName, organization); + ? await loadSessionById(serviceUrl, sessionId, organization) + : await loadSessionByLoginname(serviceUrl, loginName, organization); async function getAuthMethodsAndUser(host: string, session?: Session) { const userId = session?.factors?.user?.id; @@ -70,20 +65,24 @@ export default async function Page(props: { throw Error("Could not get user id from session"); } - return listAuthenticationMethodTypes({ host, userId }).then((methods) => { - return getUserByID({ host, userId }).then((user) => { - const humanUser = - user.user?.type.case === "human" ? user.user?.type.value : undefined; + return listAuthenticationMethodTypes({ serviceUrl, userId }).then( + (methods) => { + return getUserByID({ serviceUrl, userId }).then((user) => { + const humanUser = + user.user?.type.case === "human" + ? user.user?.type.value + : undefined; - return { - factors: session?.factors, - authMethods: methods.authMethodTypes ?? [], - phoneVerified: humanUser?.phone?.isVerified ?? false, - emailVerified: humanUser?.email?.isVerified ?? false, - expirationDate: session?.expirationDate, - }; - }); - }); + return { + factors: session?.factors, + authMethods: methods.authMethodTypes ?? [], + phoneVerified: humanUser?.phone?.isVerified ?? false, + emailVerified: humanUser?.email?.isVerified ?? false, + expirationDate: session?.expirationDate, + }; + }); + }, + ); } async function loadSessionByLoginname( @@ -92,13 +91,13 @@ export default async function Page(props: { organization?: string, ) { return loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization, }, }).then((session) => { - return getAuthMethodsAndUser(host, session); + return getAuthMethodsAndUser(serviceUrl, session); }); } @@ -109,17 +108,17 @@ export default async function Page(props: { ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ - host, + serviceUrl, sessionId: recent.id, sessionToken: recent.token, }).then((sessionResponse) => { - return getAuthMethodsAndUser(host, sessionResponse.session); + return getAuthMethodsAndUser(serviceUrl, sessionResponse.session); }); } - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: sessionWithData.factors?.user?.organizationId, }); diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 2fc21d764b..f66d0ed8b0 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -24,17 +24,13 @@ export default async function Page(props: { const tError = await getTranslations({ locale, namespace: "error" }); const _headers = await headers(); + const serviceUrl = getApiUrlOfHeaders(_headers); const host = _headers.get("host"); - const instanceUrl = getApiUrlOfHeaders(_headers); if (!host || typeof host !== "string") { throw new Error("No host found"); } - if (!instanceUrl) { - throw new Error("No instanceUrl found"); - } - const { loginName, // send from password page userId, // send from email link @@ -48,9 +44,9 @@ export default async function Page(props: { const { method } = params; const session = sessionId - ? await loadSessionById(instanceUrl, sessionId, organization) + ? await loadSessionById(serviceUrl, sessionId, organization) : await loadMostRecentSession({ - host: instanceUrl, + serviceUrl, sessionParams: { loginName, organization }, }); @@ -61,7 +57,7 @@ export default async function Page(props: { ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ - host, + serviceUrl, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { @@ -73,12 +69,12 @@ export default async function Page(props: { // email links do not come with organization, thus we need to use the session's organization const branding = await getBrandingSettings({ - host: instanceUrl, + serviceUrl, organization: organization ?? session?.factors?.user?.organizationId, }); const loginSettings = await getLoginSettings({ - host: instanceUrl, + serviceUrl, organization: organization ?? session?.factors?.user?.organizationId, }); 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 e9c3da8848..dc10eee436 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -34,18 +34,13 @@ export default async function Page(props: { const { method } = params; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } - - const branding = await getBrandingSettings({ host, organization }); - const loginSettings = await getLoginSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); + const loginSettings = await getLoginSettings({ serviceUrl, organization }); const session = await loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization, @@ -55,7 +50,7 @@ export default async function Page(props: { let totpResponse: RegisterTOTPResponse | undefined, error: Error | undefined; if (session && session.factors?.user?.id) { if (method === "time-based") { - await registerTOTP({ host, userId: session.factors.user.id }) + await registerTOTP({ serviceUrl, userId: session.factors.user.id }) .then((resp) => { if (resp) { totpResponse = resp; @@ -66,14 +61,14 @@ export default async function Page(props: { }); } else if (method === "sms") { // does not work - await addOTPSMS({ host, userId: session.factors.user.id }).catch( + await addOTPSMS({ serviceUrl, userId: session.factors.user.id }).catch( (error) => { error = new Error("Could not add OTP via SMS"); }, ); } else if (method === "email") { // works - await addOTPEmail({ host, userId: session.factors.user.id }).catch( + await addOTPEmail({ serviceUrl, userId: session.factors.user.id }).catch( (error) => { error = new Error("Could not add OTP via Email"); }, diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index fad64ebac0..e28426e0a9 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -25,28 +25,23 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const sessionFactors = sessionId - ? await loadSessionById(host, sessionId, organization) + ? await loadSessionById(serviceUrl, sessionId, organization) : await loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization }, }); async function loadSessionById( - host: string, + serviceUrl: string, sessionId: string, organization?: string, ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ - host, + serviceUrl, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { @@ -56,9 +51,9 @@ export default async function Page(props: { }); } - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); - const loginSettings = await getLoginSettings({ host, organization }); + const loginSettings = await getLoginSettings({ serviceUrl, organization }); return ( diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 184eed8296..20b2038ced 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -20,22 +20,17 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const session = await loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization, }, }); - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); return ( diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index 304ad4ec7b..1da5945f67 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -16,12 +16,7 @@ export default async function Page(props: { searchParams: Promise>; }) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const searchParams = await props.searchParams; const locale = getLocale(); @@ -32,22 +27,22 @@ export default async function Page(props: { // also allow no session to be found (ignoreUnkownUsername) const sessionFactors = await loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization, }, }); - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); const passwordComplexity = await getPasswordComplexitySettings({ - host, + serviceUrl, organization: sessionFactors?.factors?.user?.organizationId, }); const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: sessionFactors?.factors?.user?.organizationId, }); diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 6adfb82754..b2075ba463 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -25,16 +25,11 @@ export default async function Page(props: { let { loginName, organization, authRequestId, alt } = searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg({ host }); + const org: Organization | null = await getDefaultOrg({ serviceUrl }); if (org) { defaultOrganization = org.id; @@ -45,7 +40,7 @@ export default async function Page(props: { let sessionFactors; try { sessionFactors = await loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization, @@ -57,11 +52,11 @@ export default async function Page(props: { } const branding = await getBrandingSettings({ - host, + serviceUrl, organization: organization ?? defaultOrganization, }); const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: organization ?? defaultOrganization, }); diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index f951e40a6a..a289798c5d 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -27,18 +27,13 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); // also allow no session to be found (ignoreUnkownUsername) let session: Session | undefined; if (loginName) { session = await loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization, @@ -46,19 +41,19 @@ export default async function Page(props: { }); } - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); const passwordComplexity = await getPasswordComplexitySettings({ - host, + serviceUrl, organization: session?.factors?.user?.organizationId, }); - const loginSettings = await getLoginSettings({ host, organization }); + const loginSettings = await getLoginSettings({ serviceUrl, organization }); let user: User | undefined; let displayName: string | undefined; if (userId) { - const userResponse = await getUserByID({ host, userId }); + const userResponse = await getUserByID({ serviceUrl, userId }); user = userResponse.user; if (user?.type.case === "human") { diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 0a1deb6a23..e1bf29bff4 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -23,29 +23,24 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); if (!organization) { - const org: Organization | null = await getDefaultOrg({ host }); + const org: Organization | null = await getDefaultOrg({ serviceUrl }); if (org) { organization = org.id; } } - const legal = await getLegalAndSupportSettings({ host, organization }); + const legal = await getLegalAndSupportSettings({ serviceUrl, organization }); const passwordComplexitySettings = await getPasswordComplexitySettings({ - host, + serviceUrl, organization, }); - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); - const loginSettings = await getLoginSettings({ host, organization }); + const loginSettings = await getLoginSettings({ serviceUrl, organization }); if (!loginSettings?.allowRegister) { return ( diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx index 549b7a90ef..b5d6fd36fc 100644 --- a/apps/login/src/app/(login)/register/password/page.tsx +++ b/apps/login/src/app/(login)/register/password/page.tsx @@ -23,15 +23,10 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); if (!organization) { - const org: Organization | null = await getDefaultOrg({ host }); + const org: Organization | null = await getDefaultOrg({ serviceUrl }); if (org) { organization = org.id; } @@ -39,15 +34,15 @@ export default async function Page(props: { const missingData = !firstname || !lastname || !email; - const legal = await getLegalAndSupportSettings({ host, organization }); + const legal = await getLegalAndSupportSettings({ serviceUrl, organization }); const passwordComplexitySettings = await getPasswordComplexitySettings({ - host, + serviceUrl, organization, }); - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); - const loginSettings = await getLoginSettings({ host, organization }); + const loginSettings = await getLoginSettings({ serviceUrl, organization }); return missingData ? ( diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index 2658a915ca..a7e8da1254 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -21,7 +21,7 @@ import Link from "next/link"; import { redirect } from "next/navigation"; async function loadSession( - host: string, + serviceUrl: string, loginName: string, authRequestId?: string, ) { @@ -29,7 +29,7 @@ async function loadSession( if (authRequestId) { return createCallback({ - host, + serviceUrl, req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { @@ -45,7 +45,7 @@ async function loadSession( }); } return getSession({ - host, + serviceUrl, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { @@ -61,21 +61,20 @@ export default async function Page(props: { searchParams: Promise }) { const t = await getTranslations({ locale, namespace: "signedin" }); const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const { loginName, authRequestId, organization } = searchParams; - const sessionFactors = await loadSession(host, loginName, authRequestId); + const sessionFactors = await loadSession( + serviceUrl, + loginName, + authRequestId, + ); - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); let loginSettings; if (!authRequestId) { - loginSettings = await getLoginSettings({ host, organization }); + loginSettings = await getLoginSettings({ serviceUrl, organization }); } return ( diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index f588c7d62c..4961773ab9 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -20,19 +20,19 @@ export default async function Page(props: { const { loginName, authRequestId, sessionId, organization } = searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host || typeof host !== "string") { throw new Error("No host found"); } - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); const sessionFactors = sessionId - ? await loadSessionById(host, sessionId, organization) + ? await loadSessionById(serviceUrl, sessionId, organization) : await loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization }, }); @@ -43,7 +43,7 @@ export default async function Page(props: { ) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ - host, + serviceUrl, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index 1ebe4d5edd..de4a0ff6d4 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -19,22 +19,17 @@ export default async function Page(props: { const { loginName, organization, authRequestId, checkAfter } = searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const sessionFactors = await loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization, }, }); - const branding = await getBrandingSettings({ host, organization }); + const branding = await getBrandingSettings({ serviceUrl, organization }); return ( diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 1dde49aabd..21354cfe4f 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -26,7 +26,7 @@ export default async function Page(props: { searchParams: Promise }) { searchParams; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getApiUrlOfHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { @@ -34,7 +34,7 @@ export default async function Page(props: { searchParams: Promise }) { } const branding = await getBrandingSettings({ - host: instanceUrl, + serviceUrl, organization, }); @@ -47,7 +47,7 @@ export default async function Page(props: { searchParams: Promise }) { if ("loginName" in searchParams) { sessionFactors = await loadMostRecentSession({ - host: instanceUrl, + serviceUrl, sessionParams: { loginName, organization, @@ -56,12 +56,11 @@ export default async function Page(props: { searchParams: Promise }) { if (doSend && sessionFactors?.factors?.user?.id) { await sendEmailCode({ - host, + serviceUrl, + userId: sessionFactors?.factors?.user?.id, urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + (authRequestId ? `&authRequestId=${authRequestId}` : ""), - userId: sessionFactors?.factors?.user?.id, - authRequestId, }).catch((error) => { console.error("Could not resend verification email", error); throw Error("Failed to send verification email"); @@ -70,9 +69,11 @@ export default async function Page(props: { searchParams: Promise }) { } else if ("userId" in searchParams && userId) { if (doSend) { await sendEmailCode({ - host, + serviceUrl, userId, - authRequestId, + urlTemplate: + `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + + (authRequestId ? `&authRequestId=${authRequestId}` : ""), }).catch((error) => { console.error("Could not resend verification email", error); throw Error("Failed to send verification email"); diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 42599f00b3..7c4d0bbd25 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -30,9 +30,15 @@ export const dynamic = "force-dynamic"; export const revalidate = false; export const fetchCache = "default-no-store"; -async function loadSessions(host: string, ids: string[]): Promise { +async function loadSessions({ + serviceUrl, + ids, +}: { + serviceUrl: string; + ids: string[]; +}): Promise { const response = await listSessions({ - host, + serviceUrl, ids: ids.filter((id: string | undefined) => !!id), }); @@ -48,7 +54,7 @@ const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/; * to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId); **/ async function isSessionValid( - host: string, + serviceUrl: string, session: Session, ): Promise { // session can't be checked without user @@ -60,7 +66,7 @@ async function isSessionValid( let mfaValid = true; const authMethodTypes = await listAuthenticationMethodTypes({ - host, + serviceUrl, userId: session.factors.user.id, }); @@ -109,7 +115,7 @@ async function isSessionValid( } else { // only check settings if no auth methods are available, as this would require a setup const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: session.factors?.user?.organizationId, }); if (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) { @@ -152,7 +158,7 @@ async function isSessionValid( } async function findValidSession( - host: string, + serviceUrl: string, sessions: Session[], authRequest: AuthRequest, ): Promise { @@ -179,7 +185,7 @@ async function findValidSession( // return the first valid session according to settings for (const session of sessionsWithHint) { - if (await isSessionValid(host, session)) { + if (await isSessionValid(serviceUrl, session)) { return session; } } @@ -193,12 +199,7 @@ export async function GET(request: NextRequest) { const sessionId = searchParams.get("sessionId"); const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); // TODO: find a better way to handle _rsc (react server components) requests and block them to avoid conflicts when creating oidc callback const _rsc = searchParams.get("_rsc"); @@ -210,7 +211,7 @@ export async function GET(request: NextRequest) { const ids = sessionCookies.map((s) => s.id); let sessions: Session[] = []; if (ids && ids.length) { - sessions = await loadSessions(host, ids); + sessions = await loadSessions({ serviceUrl, ids }); } if (authRequestId && sessionId) { @@ -223,7 +224,7 @@ export async function GET(request: NextRequest) { if (selectedSession && selectedSession.id) { console.log(`Found session ${selectedSession.id}`); - const isValid = await isSessionValid(host, selectedSession); + const isValid = await isSessionValid(serviceUrl, selectedSession); console.log("Session is valid:", isValid); @@ -257,7 +258,7 @@ export async function GET(request: NextRequest) { // works not with _rsc request try { const { callbackUrl } = await createCallback({ - host, + serviceUrl, req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { @@ -284,7 +285,7 @@ export async function GET(request: NextRequest) { error?.code === 9 ) { const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: selectedSession.factors?.user?.organizationId, }); @@ -316,7 +317,7 @@ export async function GET(request: NextRequest) { } if (authRequestId) { - const { authRequest } = await getAuthRequest({ host, authRequestId }); + const { authRequest } = await getAuthRequest({ serviceUrl, authRequestId }); let organization = ""; let suffix = ""; @@ -343,7 +344,10 @@ export async function GET(request: NextRequest) { const matched = ORG_DOMAIN_SCOPE_REGEX.exec(orgDomainScope); const orgDomain = matched?.[1] ?? ""; if (orgDomain) { - const orgs = await getOrgsByDomain({ host, domain: orgDomain }); + const orgs = await getOrgsByDomain({ + serviceUrl, + domain: orgDomain, + }); if (orgs.result && orgs.result.length === 1) { organization = orgs.result[0].id ?? ""; suffix = orgDomain; @@ -357,7 +361,7 @@ export async function GET(request: NextRequest) { idpId = matched?.[1] ?? ""; const identityProviders = await getActiveIdentityProviders({ - host, + serviceUrl, orgId: organization ? organization : undefined, }).then((resp) => { return resp.identityProviders; @@ -382,7 +386,7 @@ export async function GET(request: NextRequest) { } return startIdentityProviderFlow({ - host, + serviceUrl, idpId, urls: { successUrl: @@ -482,7 +486,7 @@ export async function GET(request: NextRequest) { * Instead, the server attempts to silently authenticate the user using an existing session or other authentication mechanisms that do not require user interaction **/ const selectedSession = await findValidSession( - host, + serviceUrl, sessions, authRequest, ); @@ -511,7 +515,7 @@ export async function GET(request: NextRequest) { }; const { callbackUrl } = await createCallback({ - host, + serviceUrl, req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { @@ -524,7 +528,7 @@ export async function GET(request: NextRequest) { } else { // check for loginHint, userId hint and valid sessions let selectedSession = await findValidSession( - host, + serviceUrl, sessions, authRequest, ); @@ -548,7 +552,7 @@ export async function GET(request: NextRequest) { try { const { callbackUrl } = await createCallback({ - host, + serviceUrl, req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 21d895e370..c5be74d252 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -18,6 +18,7 @@ import { Spinner } from "./spinner"; // either loginName or sessionId must be provided type Props = { + host: string | null; loginName?: string; sessionId?: string; authRequestId?: string; @@ -25,7 +26,6 @@ type Props = { method: string; code?: string; loginSettings?: LoginSettings; - host: string | null; }; type Inputs = { @@ -33,6 +33,7 @@ type Inputs = { }; export function LoginOTP({ + host, loginName, sessionId, authRequestId, @@ -40,7 +41,6 @@ export function LoginOTP({ method, code, loginSettings, - host, }: Props) { const t = useTranslations("otp"); diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index b7aaea3ea5..e04caa6e38 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -7,14 +7,14 @@ import { getSessionCookieById } from "./cookies"; import { getApiUrlOfHeaders } from "./service"; import { getSession } from "./zitadel"; -const transport = async (host: string, token: string) => { +const transport = async (serviceUrl: string, token: string) => { return createServerTransport(token, { - baseUrl: host, + baseUrl: serviceUrl, }); }; -const myUserService = async (host: string, sessionToken: string) => { - const transportPromise = await transport(host, sessionToken); +const myUserService = async (serviceUrl: string, sessionToken: string) => { + const transportPromise = await transport(serviceUrl, sessionToken); return createUserServiceClient(transportPromise); }; @@ -26,16 +26,12 @@ export async function setMyPassword({ password: string; }) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - - if (!instanceUrl) { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const sessionCookie = await getSessionCookieById({ sessionId }); const { session } = await getSession({ - host: instanceUrl, + serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -44,7 +40,7 @@ export async function setMyPassword({ return { error: "Could not load session" }; } - const service = await myUserService(instanceUrl, `${sessionCookie.token}`); + const service = await myUserService(serviceUrl, `${sessionCookie.token}`); if (!session?.factors?.user?.id) { return { error: "No user id found in session" }; diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index e9cdd80493..8e9f83e7c0 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -35,15 +35,10 @@ export async function createSessionAndUpdateCookie( lifetime?: Duration, ): Promise { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host) { - throw new Error("Could not get domain"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const createdSession = await createSessionFromChecks({ - host, + serviceUrl, checks, challenges, lifetime, @@ -51,7 +46,7 @@ export async function createSessionAndUpdateCookie( if (createdSession) { return getSession({ - host, + serviceUrl, sessionId: createdSession.sessionId, sessionToken: createdSession.sessionToken, }).then((response) => { @@ -102,15 +97,10 @@ export async function createSessionForIdpAndUpdateCookie( lifetime?: Duration, ): Promise { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host) { - throw new Error("Could not get domain"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const createdSession = await createSessionForUserIdAndIdpIntent({ - host, + serviceUrl, userId, idpIntent, lifetime, @@ -121,7 +111,7 @@ export async function createSessionForIdpAndUpdateCookie( } const { session } = await getSession({ - host, + serviceUrl, sessionId: createdSession.sessionId, sessionToken: createdSession.sessionToken, }); @@ -169,15 +159,10 @@ export async function setSessionAndUpdateCookie( lifetime?: Duration, ) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host) { - throw new Error("Could not get domain"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); return setSession({ - host, + serviceUrl, sessionId: recentCookie.id, sessionToken: recentCookie.token, challenges, @@ -203,7 +188,7 @@ export async function setSessionAndUpdateCookie( } return getSession({ - host, + serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }).then((response) => { diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 4f0bd4d930..396bebd6b2 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -19,6 +19,7 @@ export type StartIDPFlowCommand = { export async function startIDPFlow(command: StartIDPFlowCommand) { const _headers = await headers(); + const serviceUrl = getApiUrlOfHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -26,7 +27,7 @@ export async function startIDPFlow(command: StartIDPFlowCommand) { } return startIdentityProviderFlow({ - host, + serviceUrl, idpId: command.idpId, urls: { successUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${command.successUrl}`, @@ -59,8 +60,8 @@ export async function createNewSessionFromIdpIntent( command: CreateNewSessionCommand, ) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host) { return { error: "Could not get domain" }; @@ -70,14 +71,17 @@ export async function createNewSessionFromIdpIntent( throw new Error("No userId or loginName provided"); } - const userResponse = await getUserByID({ host, userId: command.userId }); + const userResponse = await getUserByID({ + serviceUrl, + userId: command.userId, + }); if (!userResponse || !userResponse.user) { return { error: "User not found in the system" }; } const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: userResponse.user.details?.resourceOwner, }); diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 5393e9547d..4249cd8d77 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -22,7 +22,7 @@ export type RegisterUserResponse = { export async function inviteUser(command: InviteUserCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getApiUrlOfHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -30,7 +30,7 @@ export async function inviteUser(command: InviteUserCommand) { } const human = await addHumanUser({ - host: instanceUrl, + serviceUrl, email: command.email, firstName: command.firstName, lastName: command.lastName, @@ -42,7 +42,11 @@ export async function inviteUser(command: InviteUserCommand) { return { error: "Could not create user" }; } - const codeResponse = await createInviteCode({ userId: human.userId, host }); + const codeResponse = await createInviteCode({ + serviceUrl, + urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`, + userId: human.userId, + }); if (!codeResponse || !human) { return { error: "Could not create invite code" }; diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index bf9664c29d..1177d683e3 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -34,15 +34,15 @@ const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; export async function sendLoginname(command: SendLoginnameCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host) { throw new Error("Could not get domain"); } const loginSettingsByContext = await getLoginSettings({ - host, + serviceUrl, organization: command.organization, }); @@ -51,7 +51,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } let searchUsersRequest: SearchUsersCommand = { - host, + serviceUrl, searchValue: command.loginName, organizationId: command.organization, loginSettings: loginSettingsByContext, @@ -72,7 +72,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const redirectUserToSingleIDPIfAvailable = async () => { const identityProviders = await getActiveIdentityProviders({ - host, + serviceUrl, orgId: command.organization, }).then((resp) => { return resp.identityProviders; @@ -80,8 +80,8 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (identityProviders.length === 1) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host) { return { error: "Could not get host" }; @@ -102,7 +102,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } const resp = await startIdentityProviderFlow({ - host, + serviceUrl, idpId: identityProviders[0].id, urls: { successUrl: @@ -121,7 +121,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { }; const redirectUserToIDP = async (userId: string) => { - const identityProviders = await listIDPLinks({ host, userId }).then( + const identityProviders = await listIDPLinks({ serviceUrl, userId }).then( (resp) => { return resp.result; }, @@ -129,8 +129,8 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (identityProviders.length === 1) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host) { return { error: "Could not get host" }; @@ -138,7 +138,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const identityProviderId = identityProviders[0].idpId; - const idp = await getIDPByID({ host, id: identityProviderId }); + const idp = await getIDPByID({ serviceUrl, id: identityProviderId }); const idpType = idp?.type; @@ -160,7 +160,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } const resp = await startIdentityProviderFlow({ - host, + serviceUrl, idpId: idp.id, urls: { successUrl: @@ -185,7 +185,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const userId = potentialUsers[0].userId; const userLoginSettings = await getLoginSettings({ - host, + serviceUrl, organization: user.details?.resourceOwner, }); @@ -243,7 +243,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } const methods = await listAuthenticationMethodTypes({ - host, + serviceUrl, userId: session.factors?.user?.id, }); @@ -400,12 +400,12 @@ export async function sendLoginname(command: SendLoginnameCommand) { const suffix = matched?.[1] ?? ""; // this just returns orgs where the suffix is set as primary domain - const orgs = await getOrgsByDomain({ host, domain: suffix }); + const orgs = await getOrgsByDomain({ serviceUrl, domain: suffix }); const orgToCheckForDiscovery = orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; const orgLoginSettings = await getLoginSettings({ - host, + serviceUrl, organization: orgToCheckForDiscovery, }); if (orgLoginSettings?.allowDomainDiscovery) { diff --git a/apps/login/src/lib/server/otp.ts b/apps/login/src/lib/server/otp.ts index d70fffaa2c..3618b6e8c0 100644 --- a/apps/login/src/lib/server/otp.ts +++ b/apps/login/src/lib/server/otp.ts @@ -27,12 +27,7 @@ export type SetOTPCommand = { export async function setOTP(command: SetOTPCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host) { - throw new Error("Could not get domain"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const recentSession = command.sessionId ? await getSessionCookieById({ sessionId: command.sessionId }).catch( @@ -68,7 +63,7 @@ export async function setOTP(command: SetOTPCommand) { } const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: command.organization, }); diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 2ffa594ff5..98d89ab01e 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -43,8 +43,8 @@ export async function registerPasskeyLink( const { sessionId } = command; const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host) { throw new Error("Could not get domain"); @@ -52,7 +52,7 @@ export async function registerPasskeyLink( const sessionCookie = await getSessionCookieById({ sessionId }); const session = await getSession({ - host, + serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -72,7 +72,7 @@ export async function registerPasskeyLink( // use session token to add the passkey const registerLink = await createPasskeyRegistrationLink({ - host, + serviceUrl, userId, }); @@ -81,7 +81,7 @@ export async function registerPasskeyLink( } return registerPasskey({ - host, + serviceUrl, userId, code: registerLink.code, domain: hostname, @@ -90,12 +90,7 @@ export async function registerPasskeyLink( export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host) { - throw new Error("Could not get domain"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); // if no name is provided, try to generate one from the user agent let passkeyName = command.passkeyName; @@ -113,7 +108,7 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { sessionId: command.sessionId, }); const session = await getSession({ - host, + serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -124,7 +119,7 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { } return zitadelVerifyPasskeyRegistration({ - host, + serviceUrl, request: create(VerifyPasskeyRegistrationRequestSchema, { passkeyId: command.passkeyId, publicKeyCredential: command.publicKeyCredential, @@ -158,14 +153,9 @@ export async function sendPasskey(command: SendPasskeyCommand) { } const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); - if (!host) { - return { error: "Could not get host" }; - } - - const loginSettings = await getLoginSettings({ host, organization }); + const loginSettings = await getLoginSettings({ serviceUrl, organization }); const lifetime = checks?.webAuthN ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey @@ -186,7 +176,7 @@ export async function sendPasskey(command: SendPasskeyCommand) { } const userResponse = await getUserByID({ - host, + serviceUrl, userId: session?.factors?.user?.id, }); diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 4ec6be613d..91af9e1cb4 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -45,15 +45,15 @@ type ResetPasswordCommand = { export async function resetPassword(command: ResetPasswordCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host || typeof host !== "string") { throw new Error("No host found"); } const users = await listUsers({ - serviceUrl: instanceUrl, + serviceUrl, loginName: command.loginName, organizationId: command.organization, }); @@ -68,7 +68,7 @@ export async function resetPassword(command: ResetPasswordCommand) { const userId = users.result[0].userId; return passwordReset({ - serviceUrl: instanceUrl, + serviceUrl, userId, urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + @@ -85,12 +85,7 @@ export type UpdateSessionCommand = { export async function sendPassword(command: UpdateSessionCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); let sessionCookie = await getSessionCookieByLoginName({ loginName: command.loginName, @@ -105,7 +100,7 @@ export async function sendPassword(command: UpdateSessionCommand) { if (!sessionCookie) { const users = await listUsers({ - host, + serviceUrl, loginName: command.loginName, organizationId: command.organization, }); @@ -119,7 +114,7 @@ export async function sendPassword(command: UpdateSessionCommand) { }); loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: command.organization, }); @@ -147,7 +142,7 @@ export async function sendPassword(command: UpdateSessionCommand) { } const userResponse = await getUserByID({ - host, + serviceUrl, userId: session?.factors?.user?.id, }); @@ -160,7 +155,7 @@ export async function sendPassword(command: UpdateSessionCommand) { if (!loginSettings) { loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: command.organization ?? session.factors?.user?.organizationId, }); @@ -205,7 +200,7 @@ export async function sendPassword(command: UpdateSessionCommand) { let authMethods; if (command.checks && command.checks.password && session.factors?.user?.id) { const response = await listAuthenticationMethodTypes({ - host, + serviceUrl, userId: session.factors.user.id, }); if (response.authMethodTypes && response.authMethodTypes.length) { @@ -260,15 +255,10 @@ export async function changePassword(command: { password: string; }) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); // check for init state - const { user } = await getUserByID({ host, userId: command.userId }); + const { user } = await getUserByID({ serviceUrl, userId: command.userId }); if (!user || user.userId !== command.userId) { return { error: "Could not send Password Reset Link" }; @@ -276,7 +266,7 @@ export async function changePassword(command: { const userId = user.userId; return setUserPassword({ - host, + serviceUrl, userId, password: command.password, user, @@ -294,17 +284,12 @@ export async function checkSessionAndSetPassword({ password, }: CheckSessionAndSetPasswordCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - - if (!instanceUrl) { - throw new Error("No host found"); - } - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); const sessionCookie = await getSessionCookieById({ sessionId }); const { session } = await getSession({ - host: instanceUrl, + serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -322,7 +307,7 @@ export async function checkSessionAndSetPassword({ // check if the user has no password set in order to set a password const authmethods = await listAuthenticationMethodTypes({ - host, + serviceUrl, userId: session.factors.user.id, }); @@ -342,7 +327,7 @@ export async function checkSessionAndSetPassword({ ); const loginSettings = await getLoginSettings({ - host: instanceUrl, + serviceUrl, organization: session.factors.user.organizationId, }); @@ -352,7 +337,7 @@ export async function checkSessionAndSetPassword({ // if the user has no MFA but MFA is enforced, we can set a password otherwise we use the token of the user if (forceMfa && hasNoMFAMethods) { - return setPassword({ host, payload }).catch((error) => { + return setPassword({ serviceUrl, payload }).catch((error) => { // throw error if failed precondition (ex. User is not yet initialized) if (error.code === 9 && error.message) { return { error: "Failed precondition" }; @@ -363,17 +348,17 @@ export async function checkSessionAndSetPassword({ } else { const transport = async (host: string, token: string) => { return createServerTransport(token, { - baseUrl: host, + baseUrl: serviceUrl, }); }; const myUserService = async (host: string, sessionToken: string) => { - const transportPromise = await transport(host, sessionToken); + const transportPromise = await transport(serviceUrl, sessionToken); return createUserServiceClient(transportPromise); }; const selfService = await myUserService( - instanceUrl, + serviceUrl, `${sessionCookie.token}`, ); diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 1aa2f2efdd..071b008a87 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -29,15 +29,15 @@ export type RegisterUserResponse = { }; export async function registerUser(command: RegisterUserCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host || typeof host !== "string") { throw new Error("No host found"); } const addResponse = await addHumanUser({ - host, + serviceUrl, email: command.email, firstName: command.firstName, lastName: command.lastName, @@ -50,7 +50,7 @@ export async function registerUser(command: RegisterUserCommand) { } const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: command.organization, }); @@ -91,7 +91,7 @@ export async function registerUser(command: RegisterUserCommand) { return { redirect: "/passkey/set?" + params }; } else { const userResponse = await getUserByID({ - host, + serviceUrl, userId: session?.factors?.user?.id, }); diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 9b95e77a6c..9387dfea70 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -25,15 +25,10 @@ export async function continueWithSession({ ...session }: Session & { authRequestId?: string }) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: session.factors?.user?.organizationId, }); @@ -93,8 +88,8 @@ export async function updateSession(options: UpdateSessionCommand) { } const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host) { return { error: "Could not get host" }; @@ -111,7 +106,7 @@ export async function updateSession(options: UpdateSessionCommand) { challenges.webAuthN.domain = hostname; } - const loginSettings = await getLoginSettings({ host, organization }); + const loginSettings = await getLoginSettings({ serviceUrl, organization }); const lifetime = checks?.webAuthN ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey @@ -135,7 +130,7 @@ export async function updateSession(options: UpdateSessionCommand) { let authMethods; if (checks && checks.password && session.factors?.user?.id) { const response = await listAuthenticationMethodTypes({ - host, + serviceUrl, userId: session.factors.user.id, }); if (response.authMethodTypes && response.authMethodTypes.length) { @@ -157,19 +152,14 @@ type ClearSessionOptions = { export async function clearSession(options: ClearSessionOptions) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const { sessionId } = options; const session = await getSessionCookieById({ sessionId }); const deletedSession = await deleteSession({ - host, + serviceUrl, sessionId: session.id, sessionToken: session.token, }); @@ -185,17 +175,12 @@ type CleanupSessionCommand = { export async function cleanupSession({ sessionId }: CleanupSessionCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const sessionCookie = await getSessionCookieById({ sessionId }); const deleteResponse = await deleteSession({ - host, + serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index b544b8d75b..c621eaab0d 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -21,8 +21,8 @@ type VerifyU2FCommand = { export async function addU2F(command: RegisterU2FCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host || typeof host !== "string") { throw new Error("No host found"); @@ -37,7 +37,7 @@ export async function addU2F(command: RegisterU2FCommand) { } const session = await getSession({ - host, + serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -54,13 +54,13 @@ export async function addU2F(command: RegisterU2FCommand) { return { error: "Could not get session" }; } - return registerU2F({ host, userId, domain: hostname }); + return registerU2F({ serviceUrl, userId, domain: hostname }); } export async function verifyU2F(command: VerifyU2FCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; + const serviceUrl = getApiUrlOfHeaders(_headers); + const host = _headers.get("host"); if (!host || typeof host !== "string") { throw new Error("No host found"); @@ -81,7 +81,7 @@ export async function verifyU2F(command: VerifyU2FCommand) { }); const session = await getSession({ - host, + serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -99,5 +99,5 @@ export async function verifyU2F(command: VerifyU2FCommand) { userId, }); - return verifyU2FRegistration({ host, request }); + return verifyU2FRegistration({ serviceUrl, request }); } diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index c1407fcd96..4ce3031eee 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -30,15 +30,10 @@ export async function verifyTOTP( organization?: string, ) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); return loadMostRecentSession({ - host, + serviceUrl, sessionParams: { loginName, organization, @@ -46,7 +41,7 @@ export async function verifyTOTP( }).then((session) => { if (session?.factors?.user?.id) { return verifyTOTPRegistration({ - host, + serviceUrl, code, userId: session.factors.user.id, }); @@ -67,23 +62,18 @@ type VerifyUserByEmailCommand = { export async function sendVerification(command: VerifyUserByEmailCommand) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); const verifyResponse = command.isInvite ? await verifyInviteCode({ - host, + serviceUrl, userId: command.userId, verificationCode: command.code, }).catch(() => { return { error: "Could not verify invite" }; }) : await verifyEmail({ - host, + serviceUrl, userId: command.userId, verificationCode: command.code, }).catch(() => { @@ -114,7 +104,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { } session = await getSession({ - host, + serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }).then((response) => { @@ -128,7 +118,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { } const userResponse = await getUserByID({ - host, + serviceUrl, userId: session?.factors?.user?.id, }); @@ -138,7 +128,10 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { user = userResponse.user; } else { - const userResponse = await getUserByID({ host, userId: command.userId }); + const userResponse = await getUserByID({ + serviceUrl, + userId: command.userId, + }); if (!userResponse || !userResponse.user) { return { error: "Could not load user" }; @@ -175,12 +168,12 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { } const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: user.details?.resourceOwner, }); const authMethodResponse = await listAuthenticationMethodTypes({ - host, + serviceUrl, userId: user.userId, }); @@ -252,41 +245,36 @@ type resendVerifyEmailCommand = { export async function resendVerification(command: resendVerifyEmailCommand) { const _headers = await headers(); const serviceUrl = getApiUrlOfHeaders(_headers); - const host = _headers.get("host"); - if (!serviceUrl) { - return { error: "No host found" }; - } - if (!host) { return { error: "No host found" }; } return command.isInvite - ? resendInviteCode({ serviceUrl, host, userId: command.userId }) + ? resendInviteCode({ serviceUrl, userId: command.userId }) : resendEmailCode({ userId: command.userId, serviceUrl, - host, - authRequestId: command.authRequestId, + urlTemplate: + `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + + (command.authRequestId + ? `&authRequestId=${command.authRequestId}` + : ""), }); } type sendEmailCommand = { serviceUrl: string; userId: string; - authRequestId?: string; + urlTemplate: string; }; export async function sendEmailCode(command: sendEmailCommand) { return zitadelSendEmailCode({ userId: command.userId, serviceUrl: command.serviceUrl, - authRequestId: command.authRequestId, - urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + - (authRequestId ? `&authRequestId=${authRequestId}` : ""), + urlTemplate: command.urlTemplate, }); } @@ -302,12 +290,7 @@ export async function sendVerificationRedirectWithoutCheck( command: SendVerificationRedirectWithoutCheckCommand, ) { const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); - const host = instanceUrl; - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const serviceUrl = getApiUrlOfHeaders(_headers); if (!("loginName" in command || "userId" in command)) { return { error: "No userId, nor loginname provided" }; @@ -329,7 +312,7 @@ export async function sendVerificationRedirectWithoutCheck( } session = await getSession({ - host, + serviceUrl, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }).then((response) => { @@ -343,7 +326,7 @@ export async function sendVerificationRedirectWithoutCheck( } const userResponse = await getUserByID({ - host, + serviceUrl, userId: session?.factors?.user?.id, }); @@ -353,7 +336,10 @@ export async function sendVerificationRedirectWithoutCheck( user = userResponse.user; } else if ("userId" in command) { - const userResponse = await getUserByID({ host, userId: command.userId }); + const userResponse = await getUserByID({ + serviceUrl, + userId: command.userId, + }); if (!userResponse?.user) { return { error: "Could not load user" }; @@ -390,7 +376,7 @@ export async function sendVerificationRedirectWithoutCheck( } const authMethodResponse = await listAuthenticationMethodTypes({ - host, + serviceUrl, userId: user.userId, }); @@ -415,7 +401,7 @@ export async function sendVerificationRedirectWithoutCheck( } const loginSettings = await getLoginSettings({ - host, + serviceUrl, organization: user.details?.resourceOwner, }); diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index 0be711debe..e3e6ca1b0c 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -19,16 +19,16 @@ type ServiceClass = export async function createServiceForHost( service: T, - host: string, + serviceUrl: string, ) { const token = await systemAPIToken(); - if (!host || !token) { + if (!serviceUrl || !token) { throw new Error("No instance url or token found"); } const transport = createServerTransport(token, { - baseUrl: host, + baseUrl: serviceUrl, }); return createClientFor(service)(transport); diff --git a/apps/login/src/lib/session.ts b/apps/login/src/lib/session.ts index 3aee153246..758594845c 100644 --- a/apps/login/src/lib/session.ts +++ b/apps/login/src/lib/session.ts @@ -4,7 +4,7 @@ import { getMostRecentCookieWithLoginname } from "./cookies"; import { getSession } from "./zitadel"; type LoadMostRecentSessionParams = { - host: string; + serviceUrl: string; sessionParams: { loginName?: string; organization?: string; @@ -12,7 +12,7 @@ type LoadMostRecentSessionParams = { }; export async function loadMostRecentSession({ - host, + serviceUrl, sessionParams, }: LoadMostRecentSessionParams): Promise { const recent = await getMostRecentCookieWithLoginname({ @@ -21,7 +21,7 @@ export async function loadMostRecentSession({ }); return getSession({ - host, + serviceUrl, sessionId: recent.id, sessionToken: recent.token, }).then((resp: GetSessionResponse) => resp.session); diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 352b844e57..5a03e5ece0 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -1,6 +1,4 @@ import { Client, create, Duration } from "@zitadel/client"; -import { createServerTransport } from "@zitadel/client/node"; -import { createSystemServiceClient } from "@zitadel/client/v1"; import { makeReqCtx } from "@zitadel/client/v2"; import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; @@ -44,7 +42,6 @@ import { VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { unstable_cacheLife as cacheLife } from "next/cache"; -import { systemAPIToken } from "./api"; import { createServiceForHost } from "./service"; const useCache = process.env.DEBUG !== "true"; @@ -56,16 +53,6 @@ async function cacheWrapper(callback: Promise) { return callback; } -const systemService = async () => { - const systemToken = await systemAPIToken(); - - const transport = createServerTransport(systemToken, { - baseUrl: process.env.AUDIENCE, - }); - - return createSystemServiceClient(transport); -}; - export async function getBrandingSettings({ serviceUrl, organization, @@ -87,11 +74,11 @@ export async function getLoginSettings({ serviceUrl, organization, }: { - serviceurl: string; + serviceUrl: string; organization?: string; }) { const settingsService: Client = - await createServiceForHost(SettingsService, host); + await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getLoginSettings({ ctx: makeReqCtx(organization) }, {}) @@ -104,7 +91,7 @@ export async function listIDPLinks({ serviceUrl, userId, }: { - serviceurl: string; + serviceUrl: string; userId: string; }) { const userService: Client = await createServiceForHost( @@ -119,7 +106,7 @@ export async function addOTPEmail({ serviceUrl, userId, }: { - serviceurl: string; + serviceUrl: string; userId: string; }) { const userService: Client = await createServiceForHost( @@ -134,7 +121,7 @@ export async function addOTPSMS({ serviceUrl, userId, }: { - serviceurl: string; + serviceUrl: string; userId: string; }) { const userService: Client = await createServiceForHost( @@ -149,7 +136,7 @@ export async function registerTOTP({ serviceUrl, userId, }: { - serviceurl: string; + serviceUrl: string; userId: string; }) { const userService: Client = await createServiceForHost( @@ -160,9 +147,13 @@ export async function registerTOTP({ return userService.registerTOTP({ userId }, {}); } -export async function getGeneralSettings({ host }: { serviceurl: string }) { +export async function getGeneralSettings({ + serviceUrl, +}: { + serviceUrl: string; +}) { const settingsService: Client = - await createServiceForHost(SettingsService, host); + await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getGeneralSettings({}, {}) @@ -175,11 +166,11 @@ export async function getLegalAndSupportSettings({ serviceUrl, organization, }: { - serviceurl: string; + serviceUrl: string; organization?: string; }) { const settingsService: Client = - await createServiceForHost(SettingsService, host); + await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) @@ -192,11 +183,11 @@ export async function getPasswordComplexitySettings({ serviceUrl, organization, }: { - serviceurl: string; + serviceUrl: string; organization?: string; }) { const settingsService: Client = - await createServiceForHost(SettingsService, host); + await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) @@ -211,13 +202,13 @@ export async function createSessionFromChecks({ challenges, lifetime, }: { - serviceurl: string; + serviceUrl: string; checks: Checks; challenges: RequestChallenges | undefined; lifetime?: Duration; }) { const sessionService: Client = - await createServiceForHost(SessionService, host); + await createServiceForHost(SessionService, serviceUrl); return sessionService.createSession({ checks, challenges, lifetime }, {}); } @@ -228,7 +219,7 @@ export async function createSessionForUserIdAndIdpIntent({ idpIntent, lifetime, }: { - serviceurl: string; + serviceUrl: string; userId: string; idpIntent: { idpIntentId?: string | undefined; @@ -237,7 +228,7 @@ export async function createSessionForUserIdAndIdpIntent({ lifetime?: Duration; }) { const sessionService: Client = - await createServiceForHost(SessionService, host); + await createServiceForHost(SessionService, serviceUrl); return sessionService.createSession({ checks: { @@ -261,7 +252,7 @@ export async function setSession({ checks, lifetime, }: { - serviceurl: string; + serviceUrl: string; sessionId: string; sessionToken: string; challenges: RequestChallenges | undefined; @@ -269,7 +260,7 @@ export async function setSession({ lifetime?: Duration; }) { const sessionService: Client = - await createServiceForHost(SessionService, host); + await createServiceForHost(SessionService, serviceUrl); return sessionService.setSession( { @@ -289,12 +280,12 @@ export async function getSession({ sessionId, sessionToken, }: { - serviceurl: string; + serviceUrl: string; sessionId: string; sessionToken: string; }) { const sessionService: Client = - await createServiceForHost(SessionService, host); + await createServiceForHost(SessionService, serviceUrl); return sessionService.getSession({ sessionId, sessionToken }, {}); } @@ -304,12 +295,12 @@ export async function deleteSession({ sessionId, sessionToken, }: { - serviceurl: string; + serviceUrl: string; sessionId: string; sessionToken: string; }) { const sessionService: Client = - await createServiceForHost(SessionService, host); + await createServiceForHost(SessionService, serviceUrl); return sessionService.deleteSession({ sessionId, sessionToken }, {}); } @@ -321,7 +312,7 @@ type ListSessionsCommand = { export async function listSessions({ serviceUrl, ids }: ListSessionsCommand) { const sessionService: Client = - await createServiceForHost(SessionService, host); + await createServiceForHost(SessionService, serviceUrl); return sessionService.listSessions( { @@ -460,27 +451,24 @@ export async function resendInviteCode({ export async function sendEmailCode({ serviceUrl, - urlTemplate userId, - authRequestId, + urlTemplate, }: { serviceUrl: string; userId: string; - authRequestId?: string; + urlTemplate: string; }) { let medium = create(SendEmailCodeRequestSchema, { userId }); - if (host) { - medium = create(SendEmailCodeRequestSchema, { - ...medium, - verification: { - case: "sendCode", - value: create(SendEmailVerificationCodeSchema, { - urlTemplate, - }), - }, - }); - } + medium = create(SendEmailCodeRequestSchema, { + ...medium, + verification: { + case: "sendCode", + value: create(SendEmailVerificationCodeSchema, { + urlTemplate, + }), + }, + }); const userService: Client = await createServiceForHost( UserService, @@ -492,21 +480,21 @@ export async function sendEmailCode({ export async function createInviteCode({ serviceUrl, + urlTemplate, userId, }: { serviceUrl: string; + urlTemplate: string; userId: string; }) { let medium = create(SendInviteCodeSchema, { applicationName: "Typescript Login", }); - if (host) { - medium = { - ...medium, - urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`, - }; - } + medium = { + ...medium, + urlTemplate, + }; const userService: Client = await createServiceForHost( UserService, @@ -802,7 +790,7 @@ export async function getDefaultOrg({ serviceUrl: string; }): Promise { const orgService: Client = - await createServiceForHost(OrganizationService, host); + await createServiceForHost(OrganizationService, serviceUrl); return orgService .listOrganizations( @@ -829,7 +817,7 @@ export async function getOrgsByDomain({ domain: string; }) { const orgService: Client = - await createServiceForHost(OrganizationService, host); + await createServiceForHost(OrganizationService, serviceUrl); return orgService.listOrganizations( { @@ -896,7 +884,7 @@ export async function getAuthRequest({ serviceUrl: string; authRequestId: string; }) { - const oidcService = await createServiceForHost(OIDCService, host); + const oidcService = await createServiceForHost(OIDCService, serviceUrl); return oidcService.getAuthRequest({ authRequestId, @@ -910,7 +898,7 @@ export async function createCallback({ serviceUrl: string; req: CreateCallbackRequest; }) { - const oidcService = await createServiceForHost(OIDCService, host); + const oidcService = await createServiceForHost(OIDCService, serviceUrl); return oidcService.createCallback(req); } @@ -940,28 +928,22 @@ export async function verifyEmail({ export async function resendEmailCode({ serviceUrl, - host, userId, - authRequestId, + urlTemplate, }: { serviceUrl: string; - host: string; userId: string; - authRequestId?: string; + urlTemplate: string; }) { let request: ResendEmailCodeRequest = create(ResendEmailCodeRequestSchema, { userId, }); - if (host) { - const medium = create(SendEmailVerificationCodeSchema, { - urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + - (authRequestId ? `&authRequestId=${authRequestId}` : ""), - }); + const medium = create(SendEmailVerificationCodeSchema, { + urlTemplate, + }); - request = { ...request, verification: { case: "sendCode", value: medium } }; - } + request = { ...request, verification: { case: "sendCode", value: medium } }; const userService: Client = await createServiceForHost( UserService, @@ -999,7 +981,7 @@ export async function getIDPByID({ id: string; }) { const idpService: Client = - await createServiceForHost(IdentityProviderService, host); + await createServiceForHost(IdentityProviderService, serviceUrl); return idpService.getIDPByID({ id }, {}).then((resp) => resp.idp); } @@ -1033,21 +1015,21 @@ export async function addIDPLink({ export async function passwordReset({ serviceUrl, - urlTemplate, userId, + urlTemplate, }: { serviceUrl: string; - urlTemplate: string; userId: string; + urlTemplate?: string; }) { let medium = create(SendPasswordResetLinkSchema, { notificationType: NotificationType.Email, }); - medium = { - ...medium, - urlTemplate, - }; + medium = { + ...medium, + urlTemplate, + }; const userService: Client = await createServiceForHost( UserService, @@ -1238,7 +1220,7 @@ export async function getActiveIdentityProviders({ props.linkingAllowed = linking_allowed; } const settingsService: Client = - await createServiceForHost(SettingsService, host); + await createServiceForHost(SettingsService, serviceUrl); return settingsService.getActiveIdentityProviders(props, {}); } diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index e059aa032f..816ea0f066 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -22,9 +22,9 @@ export async function middleware(request: NextRequest) { // } const _headers = await headers(); - const instanceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getApiUrlOfHeaders(_headers); - const instanceHost = `${instanceUrl}`.replace("https://", ""); + const instanceHost = `${serviceUrl}`.replace("https://", ""); const requestHeaders = new Headers(request.headers); requestHeaders.set( @@ -43,7 +43,7 @@ export async function middleware(request: NextRequest) { responseHeaders.set("Access-Control-Allow-Origin", "*"); responseHeaders.set("Access-Control-Allow-Headers", "*"); - request.nextUrl.href = `${instanceUrl}${request.nextUrl.pathname}${request.nextUrl.search}`; + request.nextUrl.href = `${serviceUrl}${request.nextUrl.pathname}${request.nextUrl.search}`; return NextResponse.rewrite(request.nextUrl, { request: { headers: requestHeaders, diff --git a/turbo.json b/turbo.json index 003d6c11cc..1db73e60c9 100644 --- a/turbo.json +++ b/turbo.json @@ -11,7 +11,8 @@ "SYSTEM_USER_PRIVATE_KEY", "ZITADEL_API_URL", "ZITADEL_SERVICE_USER_ID", - "ZITADEL_SERVICE_USER_TOKEN" + "ZITADEL_SERVICE_USER_TOKEN", + "NEXT_PUBLIC_BASE_PATH" ], "tasks": { "generate": { From 68515bda7e69c29011681bec5dca31ff5929ae44 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 29 Jan 2025 10:44:26 +0100 Subject: [PATCH 50/67] escape proxy for multitenant scenario, rename fcn --- apps/login/src/app/(login)/accounts/page.tsx | 4 ++-- .../app/(login)/authenticator/set/page.tsx | 4 ++-- .../(login)/idp/[provider]/failure/page.tsx | 4 ++-- .../(login)/idp/[provider]/success/page.tsx | 4 ++-- apps/login/src/app/(login)/idp/page.tsx | 4 ++-- apps/login/src/app/(login)/invite/page.tsx | 4 ++-- .../src/app/(login)/invite/success/page.tsx | 4 ++-- apps/login/src/app/(login)/loginname/page.tsx | 4 ++-- apps/login/src/app/(login)/mfa/page.tsx | 4 ++-- apps/login/src/app/(login)/mfa/set/page.tsx | 4 ++-- .../src/app/(login)/otp/[method]/page.tsx | 4 ++-- .../src/app/(login)/otp/[method]/set/page.tsx | 4 ++-- apps/login/src/app/(login)/passkey/page.tsx | 4 ++-- .../src/app/(login)/passkey/set/page.tsx | 4 ++-- .../src/app/(login)/password/change/page.tsx | 4 ++-- apps/login/src/app/(login)/password/page.tsx | 4 ++-- .../src/app/(login)/password/set/page.tsx | 4 ++-- apps/login/src/app/(login)/register/page.tsx | 4 ++-- .../app/(login)/register/password/page.tsx | 4 ++-- apps/login/src/app/(login)/signedin/page.tsx | 4 ++-- apps/login/src/app/(login)/u2f/page.tsx | 4 ++-- apps/login/src/app/(login)/u2f/set/page.tsx | 4 ++-- apps/login/src/app/(login)/verify/page.tsx | 4 ++-- apps/login/src/app/login/route.ts | 4 ++-- apps/login/src/lib/self.ts | 4 ++-- apps/login/src/lib/server/cookie.ts | 8 ++++---- apps/login/src/lib/server/idp.ts | 6 +++--- apps/login/src/lib/server/invite.ts | 4 ++-- apps/login/src/lib/server/loginname.ts | 8 ++++---- apps/login/src/lib/server/otp.ts | 4 ++-- apps/login/src/lib/server/passkeys.ts | 8 ++++---- apps/login/src/lib/server/password.ts | 10 +++++----- apps/login/src/lib/server/register.ts | 4 ++-- apps/login/src/lib/server/session.ts | 10 +++++----- apps/login/src/lib/server/u2f.ts | 6 +++--- apps/login/src/lib/server/verify.ts | 10 +++++----- apps/login/src/lib/service.ts | 3 ++- apps/login/src/middleware.ts | 19 ++++++++++--------- 38 files changed, 101 insertions(+), 99 deletions(-) diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index bd0b428aae..e9518dbfb5 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { getBrandingSettings, getDefaultOrg, @@ -39,7 +39,7 @@ export default async function Page(props: { const organization = searchParams?.organization; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); let defaultOrganization; if (!organization) { diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index dd99ad043f..28bc0b7077 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getActiveIdentityProviders, @@ -30,7 +30,7 @@ export default async function Page(props: { const { loginName, authRequestId, organization, sessionId } = searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const sessionWithData = sessionId ? await loadSessionById(serviceUrl, sessionId, organization) 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 ed7b63092b..bb1272f332 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -1,5 +1,5 @@ import { DynamicTheme } from "@/components/dynamic-theme"; -import { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { getBrandingSettings } from "@/lib/zitadel"; import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { getLocale, getTranslations } from "next-intl/server"; @@ -25,7 +25,7 @@ export default async function Page(props: { const { organization } = searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const branding = await getBrandingSettings({ serviceUrl, organization }); 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 c39f2515ab..0d26606a3e 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -5,7 +5,7 @@ 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, PROVIDER_MAPPING } from "@/lib/idp"; -import { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { addHuman, addIDPLink, @@ -40,7 +40,7 @@ export default async function Page(props: { const { provider } = params; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const branding = await getBrandingSettings({ serviceUrl, organization }); diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index da545f7a6e..bda16051af 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; @@ -16,7 +16,7 @@ export default async function Page(props: { const organization = searchParams?.organization; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const identityProviders = await getActiveIdentityProviders({ serviceUrl, diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx index 4378a3e2d6..cc06281eec 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { getBrandingSettings, getDefaultOrg, @@ -21,7 +21,7 @@ export default async function Page(props: { let { firstname, lastname, email, organization } = searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); if (!organization) { const org = await getDefaultOrg({ serviceUrl }); diff --git a/apps/login/src/app/(login)/invite/success/page.tsx b/apps/login/src/app/(login)/invite/success/page.tsx index 5ef81fc3cb..b8692d5c5b 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; 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"; @@ -19,7 +19,7 @@ export default async function Page(props: { let { userId, organization } = searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); if (!organization) { const org = await getDefaultOrg({ serviceUrl }); diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index b1c2d89570..6e1a97b2b6 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { getActiveIdentityProviders, getBrandingSettings, @@ -26,7 +26,7 @@ export default async function Page(props: { const submit: boolean = searchParams?.submit === "true"; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); let defaultOrganization; if (!organization) { diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index 53a5324682..ce658129c3 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -25,7 +25,7 @@ export default async function Page(props: { const { loginName, authRequestId, organization, sessionId } = searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const sessionFactors = sessionId ? await loadSessionById(serviceUrl, sessionId, organization) diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index e757f4139d..a2a3c5d933 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -52,7 +52,7 @@ export default async function Page(props: { } = searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const sessionWithData = sessionId ? await loadSessionById(serviceUrl, sessionId, organization) diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index f66d0ed8b0..4d29777f6b 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -24,7 +24,7 @@ export default async function Page(props: { const tError = await getTranslations({ locale, namespace: "error" }); const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { 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 dc10eee436..1e80d847a2 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { addOTPEmail, @@ -34,7 +34,7 @@ export default async function Page(props: { const { method } = params; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const branding = await getBrandingSettings({ serviceUrl, organization }); const loginSettings = await getLoginSettings({ serviceUrl, organization }); diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index e28426e0a9..7dbede8871 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -25,7 +25,7 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const sessionFactors = sessionId ? await loadSessionById(serviceUrl, sessionId, organization) diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 20b2038ced..1035f4b55e 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; @@ -20,7 +20,7 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const session = await loadMostRecentSession({ serviceUrl, diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index 1da5945f67..4ea0808395 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -16,7 +16,7 @@ export default async function Page(props: { searchParams: Promise>; }) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const searchParams = await props.searchParams; const locale = getLocale(); diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index b2075ba463..e794f7bbbc 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -25,7 +25,7 @@ export default async function Page(props: { let { loginName, organization, authRequestId, alt } = searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); let defaultOrganization; if (!organization) { diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index a289798c5d..2f29a66630 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -27,7 +27,7 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); // also allow no session to be found (ignoreUnkownUsername) let session: Session | undefined; diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index e1bf29bff4..adb1f28310 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { getBrandingSettings, getDefaultOrg, @@ -23,7 +23,7 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); if (!organization) { const org: Organization | null = await getDefaultOrg({ serviceUrl }); diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx index b5d6fd36fc..2eac65536d 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { getBrandingSettings, getDefaultOrg, @@ -23,7 +23,7 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); if (!organization) { const org: Organization | null = await getDefaultOrg({ serviceUrl }); diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index a7e8da1254..f387925500 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { createCallback, getBrandingSettings, @@ -61,7 +61,7 @@ export default async function Page(props: { searchParams: Promise }) { const t = await getTranslations({ locale, namespace: "signedin" }); const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const { loginName, authRequestId, organization } = searchParams; const sessionFactors = await loadSession( diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index 4961773ab9..c5db30a21c 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; @@ -20,7 +20,7 @@ export default async function Page(props: { const { loginName, authRequestId, sessionId, organization } = searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index de4a0ff6d4..79f5d3ab50 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; @@ -19,7 +19,7 @@ export default async function Page(props: { const { loginName, organization, authRequestId, checkAfter } = searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const sessionFactors = await loadMostRecentSession({ serviceUrl, diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 21354cfe4f..09e4eaba19 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 { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, @@ -26,7 +26,7 @@ export default async function Page(props: { searchParams: Promise }) { searchParams; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 7c4d0bbd25..f366342570 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -1,7 +1,7 @@ import { getAllSessions } from "@/lib/cookies"; import { idpTypeToSlug } from "@/lib/idp"; import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname"; -import { getApiUrlOfHeaders } from "@/lib/service"; +import { getServiceUrlFromHeaders } from "@/lib/service"; import { createCallback, getActiveIdentityProviders, @@ -199,7 +199,7 @@ export async function GET(request: NextRequest) { const sessionId = searchParams.get("sessionId"); const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); // TODO: find a better way to handle _rsc (react server components) requests and block them to avoid conflicts when creating oidc callback const _rsc = searchParams.get("_rsc"); diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index e04caa6e38..0553aef21b 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 { getApiUrlOfHeaders } from "./service"; +import { getServiceUrlFromHeaders } from "./service"; import { getSession } from "./zitadel"; const transport = async (serviceUrl: string, token: string) => { @@ -26,7 +26,7 @@ export async function setMyPassword({ password: string; }) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const sessionCookie = await getSessionCookieById({ sessionId }); diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 8e9f83e7c0..9f20c1a00d 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -15,7 +15,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 { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; type CustomCookieData = { id: string; @@ -35,7 +35,7 @@ export async function createSessionAndUpdateCookie( lifetime?: Duration, ): Promise { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const createdSession = await createSessionFromChecks({ serviceUrl, @@ -97,7 +97,7 @@ export async function createSessionForIdpAndUpdateCookie( lifetime?: Duration, ): Promise { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const createdSession = await createSessionForUserIdAndIdpIntent({ serviceUrl, @@ -159,7 +159,7 @@ export async function setSessionAndUpdateCookie( lifetime?: Duration, ) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); return setSession({ serviceUrl, diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 396bebd6b2..3e597c5041 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -7,7 +7,7 @@ import { } from "@/lib/zitadel"; import { headers } from "next/headers"; import { getNextUrl } from "../client"; -import { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; import { checkEmailVerification } from "../verify-helper"; import { createSessionForIdpAndUpdateCookie } from "./cookie"; @@ -19,7 +19,7 @@ export type StartIDPFlowCommand = { export async function startIDPFlow(command: StartIDPFlowCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -60,7 +60,7 @@ export async function createNewSessionFromIdpIntent( command: CreateNewSessionCommand, ) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 4249cd8d77..56986930f9 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 { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; type InviteUserCommand = { email: string; @@ -22,7 +22,7 @@ export type RegisterUserResponse = { export async function inviteUser(command: InviteUserCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 1177d683e3..13bf7f0ed0 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 { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; import { checkInvite } from "../verify-helper"; import { getActiveIdentityProviders, @@ -34,7 +34,7 @@ const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; export async function sendLoginname(command: SendLoginnameCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -80,7 +80,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (identityProviders.length === 1) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -129,7 +129,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (identityProviders.length === 1) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { diff --git a/apps/login/src/lib/server/otp.ts b/apps/login/src/lib/server/otp.ts index 3618b6e8c0..72c663e9fe 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 { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; import { getLoginSettings } from "../zitadel"; export type SetOTPCommand = { @@ -27,7 +27,7 @@ export type SetOTPCommand = { export async function setOTP(command: SetOTPCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const recentSession = command.sessionId ? await getSessionCookieById({ sessionId: command.sessionId }).catch( diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 98d89ab01e..97255ead86 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 { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; import { checkEmailVerification } from "../verify-helper"; import { setSessionAndUpdateCookie } from "./cookie"; @@ -43,7 +43,7 @@ export async function registerPasskeyLink( const { sessionId } = command; const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -90,7 +90,7 @@ export async function registerPasskeyLink( export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); // if no name is provided, try to generate one from the user agent let passkeyName = command.passkeyName; @@ -153,7 +153,7 @@ export async function sendPasskey(command: SendPasskeyCommand) { } const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const loginSettings = await getLoginSettings({ serviceUrl, organization }); diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 91af9e1cb4..74518efabe 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -30,7 +30,7 @@ import { import { headers } from "next/headers"; import { getNextUrl } from "../client"; import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies"; -import { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; import { checkEmailVerification, checkMFAFactors, @@ -45,7 +45,7 @@ type ResetPasswordCommand = { export async function resetPassword(command: ResetPasswordCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { @@ -85,7 +85,7 @@ export type UpdateSessionCommand = { export async function sendPassword(command: UpdateSessionCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); let sessionCookie = await getSessionCookieByLoginName({ loginName: command.loginName, @@ -255,7 +255,7 @@ export async function changePassword(command: { password: string; }) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); // check for init state const { user } = await getUserByID({ serviceUrl, userId: command.userId }); @@ -284,7 +284,7 @@ export async function checkSessionAndSetPassword({ password, }: CheckSessionAndSetPasswordCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const sessionCookie = await getSessionCookieById({ sessionId }); diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 071b008a87..67021f21f6 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 { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; import { checkEmailVerification } from "../verify-helper"; type RegisterUserCommand = { @@ -29,7 +29,7 @@ export type RegisterUserResponse = { }; export async function registerUser(command: RegisterUserCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 9387dfea70..db165dfabd 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -18,14 +18,14 @@ import { getSessionCookieByLoginName, removeSessionFromCookie, } from "../cookies"; -import { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; export async function continueWithSession({ authRequestId, ...session }: Session & { authRequestId?: string }) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const loginSettings = await getLoginSettings({ serviceUrl, @@ -88,7 +88,7 @@ export async function updateSession(options: UpdateSessionCommand) { } const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -152,7 +152,7 @@ type ClearSessionOptions = { export async function clearSession(options: ClearSessionOptions) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const { sessionId } = options; @@ -175,7 +175,7 @@ type CleanupSessionCommand = { export async function cleanupSession({ sessionId }: CleanupSessionCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const sessionCookie = await getSessionCookieById({ sessionId }); diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index c621eaab0d..5eaabce532 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 { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; type RegisterU2FCommand = { sessionId: string; @@ -21,7 +21,7 @@ type VerifyU2FCommand = { export async function addU2F(command: RegisterU2FCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { @@ -59,7 +59,7 @@ export async function addU2F(command: RegisterU2FCommand) { export async function verifyU2F(command: VerifyU2FCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 4ce3031eee..6c07197b87 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 { getApiUrlOfHeaders } from "../service"; +import { getServiceUrlFromHeaders } from "../service"; import { loadMostRecentSession } from "../session"; import { checkMFAFactors } from "../verify-helper"; import { createSessionAndUpdateCookie } from "./cookie"; @@ -30,7 +30,7 @@ export async function verifyTOTP( organization?: string, ) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); return loadMostRecentSession({ serviceUrl, @@ -62,7 +62,7 @@ type VerifyUserByEmailCommand = { export async function sendVerification(command: VerifyUserByEmailCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const verifyResponse = command.isInvite ? await verifyInviteCode({ @@ -244,7 +244,7 @@ type resendVerifyEmailCommand = { export async function resendVerification(command: resendVerifyEmailCommand) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -290,7 +290,7 @@ export async function sendVerificationRedirectWithoutCheck( command: SendVerificationRedirectWithoutCheckCommand, ) { const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); if (!("loginName" in command || "userId" in command)) { return { error: "No userId, nor loginname provided" }; diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index e3e6ca1b0c..3665e6f185 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -34,9 +34,10 @@ export async function createServiceForHost( return createClientFor(service)(transport); } -export function getApiUrlOfHeaders(headers: ReadonlyHeaders): string { +export function getServiceUrlFromHeaders(headers: ReadonlyHeaders): string { let instanceUrl: string = process.env.ZITADEL_API_URL; + // use the forwarded host if available (multitenant), otherwise fall back to the host of the deployment itself if (headers.get("x-zitadel-forward-host")) { instanceUrl = headers.get("x-zitadel-forward-host") as string; instanceUrl = instanceUrl.startsWith("https://") diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 816ea0f066..cbf7c12542 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 { getApiUrlOfHeaders } from "./lib/service"; +import { getServiceUrlFromHeaders } from "./lib/service"; export const config = { matcher: [ @@ -13,16 +13,17 @@ export const config = { export async function middleware(request: NextRequest) { // escape proxy if the environment is setup for multitenancy - // if ( - // !process.env.ZITADEL_API_URL || - // !process.env.ZITADEL_USER_ID || - // !process.env.ZITADEL_USER_TOKEN - // ) { - // return NextResponse.next(); - // } + if ( + !process.env.ZITADEL_API_URL || + !process.env.ZITADEL_USER_ID || + !process.env.ZITADEL_USER_TOKEN + ) { + return NextResponse.next(); + } + const _headers = await headers(); - const serviceUrl = getApiUrlOfHeaders(_headers); + const serviceUrl = getServiceUrlFromHeaders(_headers); const instanceHost = `${serviceUrl}`.replace("https://", ""); From af5aa2e4ba0fe2b2e6a30def2deb16ee7dac958c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 29 Jan 2025 11:03:48 +0100 Subject: [PATCH 51/67] either system or other token --- apps/login/src/lib/service.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index 3665e6f185..dd261e0b22 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -21,7 +21,18 @@ export async function createServiceForHost( service: T, serviceUrl: string, ) { - const token = await systemAPIToken(); + let token; + + // if we are running in a multitenancy context, use the system user token + if ( + process.env.AUDIENCE && + process.env.SYSTEM_USER_ID && + process.env.SYSTEM_USER_PRIVATE_KEY + ) { + token = await systemAPIToken(); + } else if (process.env.ZITADEL_SERVICE_USER_TOKEN) { + token = process.env.ZITADEL_SERVICE_USER_TOKEN; + } if (!serviceUrl || !token) { throw new Error("No instance url or token found"); From 44b4746ed635e82ada7f2d16bb8a2b6af6d456bf Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 29 Jan 2025 11:24:49 +0100 Subject: [PATCH 52/67] env --- apps/login/src/middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index cbf7c12542..cb42234cac 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -15,8 +15,8 @@ export async function middleware(request: NextRequest) { // escape proxy if the environment is setup for multitenancy if ( !process.env.ZITADEL_API_URL || - !process.env.ZITADEL_USER_ID || - !process.env.ZITADEL_USER_TOKEN + !process.env.ZITADEL_SERVICE_USER_ID || + !process.env.ZITADEL_SERVICE_USER_TOKEN ) { return NextResponse.next(); } From f117044c53146da7b31d07a053d5f5b6a588f52e Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 29 Jan 2025 11:41:57 +0100 Subject: [PATCH 53/67] base path to root for tests --- apps/login/.env.integration | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/login/.env.integration b/apps/login/.env.integration index 90adb84eee..8c07ac37d3 100644 --- a/apps/login/.env.integration +++ b/apps/login/.env.integration @@ -1,3 +1,4 @@ ZITADEL_API_URL=http://localhost:22222 EMAIL_VERIFICATION=true -DEBUG=true \ No newline at end of file +DEBUG=true +NEXT_PUBLIC_BASE_PATH="/" \ No newline at end of file From 4687db084f624817be4b30284f8ea88200d00daa Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 29 Jan 2025 13:25:45 +0100 Subject: [PATCH 54/67] fix fcn request --- apps/login/.env.integration | 3 +-- apps/login/src/app/(login)/verify/page.tsx | 2 +- apps/login/src/lib/service.ts | 13 +++++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/login/.env.integration b/apps/login/.env.integration index 8c07ac37d3..90adb84eee 100644 --- a/apps/login/.env.integration +++ b/apps/login/.env.integration @@ -1,4 +1,3 @@ ZITADEL_API_URL=http://localhost:22222 EMAIL_VERIFICATION=true -DEBUG=true -NEXT_PUBLIC_BASE_PATH="/" \ No newline at end of file +DEBUG=true \ No newline at end of file diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 09e4eaba19..833ed663df 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -80,7 +80,7 @@ export default async function Page(props: { searchParams: Promise }) { }); } - const userResponse = await getUserByID(userId); + const userResponse = await getUserByID({ serviceUrl, userId }); if (userResponse) { user = userResponse.user; if (user?.type.case === "human") { diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index dd261e0b22..f6c70c66d3 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -34,8 +34,12 @@ export async function createServiceForHost( token = process.env.ZITADEL_SERVICE_USER_TOKEN; } - if (!serviceUrl || !token) { - throw new Error("No instance url or token found"); + if (!serviceUrl) { + throw new Error("No instance url found"); + } + + if (!token) { + throw new Error("No token found"); } const transport = createServerTransport(token, { @@ -48,9 +52,10 @@ export async function createServiceForHost( export function getServiceUrlFromHeaders(headers: ReadonlyHeaders): string { let instanceUrl: string = process.env.ZITADEL_API_URL; + 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 (headers.get("x-zitadel-forward-host")) { - instanceUrl = headers.get("x-zitadel-forward-host") as string; + if (forwardedHost) { + instanceUrl = forwardedHost; instanceUrl = instanceUrl.startsWith("https://") ? instanceUrl : `https://${instanceUrl}`; From 5af60568815754539e6597784738cc0b633f5d92 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 29 Jan 2025 13:51:24 +0100 Subject: [PATCH 55/67] yolo --- apps/login/.env.integration | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/login/.env.integration b/apps/login/.env.integration index 90adb84eee..705d7d0733 100644 --- a/apps/login/.env.integration +++ b/apps/login/.env.integration @@ -1,3 +1,6 @@ ZITADEL_API_URL=http://localhost:22222 +ZITADEL_SERVICE_USER_ID="yolo" +ZITADEL_SERVICE_USER_TOKEN="yolo" EMAIL_VERIFICATION=true -DEBUG=true \ No newline at end of file +DEBUG=true +NEXT_PUBLIC_BASE_PATH="" \ No newline at end of file From dedbed014a1c25faa2870b6553796c3e25de8d79 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 29 Jan 2025 13:54:27 +0100 Subject: [PATCH 56/67] passkey cleanup --- apps/login/src/app/(login)/passkey/page.tsx | 1 - apps/login/src/components/login-passkey.tsx | 4 ---- 2 files changed, 5 deletions(-) diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index 7dbede8871..2ec8b0d627 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -79,7 +79,6 @@ export default async function Page(props: { authRequestId={authRequestId} altPassword={altPassword === "true"} organization={organization} - loginSettings={loginSettings} /> )} diff --git a/apps/login/src/components/login-passkey.tsx b/apps/login/src/components/login-passkey.tsx index 5e05cdb6a8..a5beae7396 100644 --- a/apps/login/src/components/login-passkey.tsx +++ b/apps/login/src/components/login-passkey.tsx @@ -9,7 +9,6 @@ import { UserVerificationRequirement, } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; @@ -26,7 +25,6 @@ type Props = { altPassword: boolean; login?: boolean; organization?: string; - loginSettings?: LoginSettings; }; export function LoginPasskey({ @@ -36,7 +34,6 @@ export function LoginPasskey({ altPassword, organization, login = true, - loginSettings, }: Props) { const t = useTranslations("passkey"); @@ -47,7 +44,6 @@ export function LoginPasskey({ const initialized = useRef(false); - // TODO: move this to server side useEffect(() => { if (!initialized.current) { initialized.current = true; From 41bafc0d41df046d5c023bbee6aa1979c6ac1a5c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 29 Jan 2025 15:47:31 +0100 Subject: [PATCH 57/67] zitadel.ts adaptations --- apps/login/next-env-vars.d.ts | 21 ++- 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/login/route.ts | 2 +- apps/login/src/lib/api.ts | 55 +++++- apps/login/src/lib/self.ts | 2 +- apps/login/src/lib/server/cookie.ts | 6 +- apps/login/src/lib/server/idp.ts | 4 +- apps/login/src/lib/server/invite.ts | 2 +- apps/login/src/lib/server/loginname.ts | 6 +- apps/login/src/lib/server/otp.ts | 2 +- apps/login/src/lib/server/passkeys.ts | 6 +- apps/login/src/lib/server/password.ts | 8 +- apps/login/src/lib/server/register.ts | 2 +- apps/login/src/lib/server/session.ts | 8 +- apps/login/src/lib/server/u2f.ts | 4 +- apps/login/src/lib/server/verify.ts | 8 +- apps/login/src/lib/service.ts | 19 +- apps/login/src/lib/zitadel.ts | 172 ++++++++++++++++-- apps/login/src/middleware.ts | 2 +- turbo.json | 9 +- 42 files changed, 290 insertions(+), 94 deletions(-) diff --git a/apps/login/next-env-vars.d.ts b/apps/login/next-env-vars.d.ts index be266fd5a0..61bf0c5b06 100644 --- a/apps/login/next-env-vars.d.ts +++ b/apps/login/next-env-vars.d.ts @@ -3,17 +3,32 @@ declare namespace NodeJS { /** * Multitenancy: The system api url */ - AUDIENCE: string; + QA_AUDIENCE: string; /** * Multitenancy: The service user id */ - SYSTEM_USER_ID: string; + QA_SYSTEM_USER_ID: string; /** * Multitenancy: The service user private key */ - SYSTEM_USER_PRIVATE_KEY: string; + QA_SYSTEM_USER_PRIVATE_KEY: string; + + /** + * Multitenancy: The system api url for prod environment + */ + PROD_AUDIENCE: string; + + /** + * Multitenancy: The service user id for prod environment + */ + PROD_SYSTEM_USER_ID: string; + + /** + * Multitenancy: The service user private key for prod environment + */ + PROD_SYSTEM_USER_PRIVATE_KEY: string; /** * Self hosting: The instance url diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index e9518dbfb5..998cb7f641 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -39,7 +39,7 @@ export default async function Page(props: { const organization = searchParams?.organization; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); let defaultOrganization; if (!organization) { diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 28bc0b7077..dddde2628a 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -30,7 +30,7 @@ export default async function Page(props: { const { loginName, authRequestId, organization, sessionId } = searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const sessionWithData = sessionId ? await loadSessionById(serviceUrl, sessionId, organization) 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 bb1272f332..6a62e2515e 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -25,7 +25,7 @@ export default async function Page(props: { const { organization } = searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const branding = await getBrandingSettings({ serviceUrl, organization }); 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 0d26606a3e..8cdb6cec83 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -40,7 +40,7 @@ export default async function Page(props: { const { provider } = params; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const branding = await getBrandingSettings({ serviceUrl, organization }); diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index bda16051af..53a77910d3 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -16,7 +16,7 @@ export default async function Page(props: { const organization = searchParams?.organization; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const identityProviders = await getActiveIdentityProviders({ serviceUrl, diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx index cc06281eec..1b1ac33470 100644 --- a/apps/login/src/app/(login)/invite/page.tsx +++ b/apps/login/src/app/(login)/invite/page.tsx @@ -21,7 +21,7 @@ export default async function Page(props: { let { firstname, lastname, email, organization } = searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); if (!organization) { const org = await getDefaultOrg({ serviceUrl }); diff --git a/apps/login/src/app/(login)/invite/success/page.tsx b/apps/login/src/app/(login)/invite/success/page.tsx index b8692d5c5b..e21b8ac4da 100644 --- a/apps/login/src/app/(login)/invite/success/page.tsx +++ b/apps/login/src/app/(login)/invite/success/page.tsx @@ -19,7 +19,7 @@ export default async function Page(props: { let { userId, organization } = searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); if (!organization) { const org = await getDefaultOrg({ serviceUrl }); diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 6e1a97b2b6..65dc02d046 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -26,7 +26,7 @@ export default async function Page(props: { const submit: boolean = searchParams?.submit === "true"; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); let defaultOrganization; if (!organization) { diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index ce658129c3..94f93ac1ad 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -25,7 +25,7 @@ export default async function Page(props: { const { loginName, authRequestId, organization, sessionId } = searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const sessionFactors = sessionId ? await loadSessionById(serviceUrl, sessionId, organization) diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index a2a3c5d933..a5c3eb3fc8 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -52,7 +52,7 @@ export default async function Page(props: { } = searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const sessionWithData = sessionId ? await loadSessionById(serviceUrl, sessionId, organization) diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 4d29777f6b..77c465826c 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -24,7 +24,7 @@ export default async function Page(props: { const tError = await getTranslations({ locale, namespace: "error" }); const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { 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 1e80d847a2..ebb65fe699 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -34,7 +34,7 @@ export default async function Page(props: { const { method } = params; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const branding = await getBrandingSettings({ serviceUrl, organization }); const loginSettings = await getLoginSettings({ serviceUrl, organization }); diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index 2ec8b0d627..41610d8930 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -25,7 +25,7 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const sessionFactors = sessionId ? await loadSessionById(serviceUrl, sessionId, organization) diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 1035f4b55e..6b99076b50 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -20,7 +20,7 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const session = await loadMostRecentSession({ serviceUrl, diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index 4ea0808395..ef6390d88c 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -16,7 +16,7 @@ export default async function Page(props: { searchParams: Promise>; }) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const searchParams = await props.searchParams; const locale = getLocale(); diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index e794f7bbbc..dfb07c934d 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -25,7 +25,7 @@ export default async function Page(props: { let { loginName, organization, authRequestId, alt } = searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); let defaultOrganization; if (!organization) { diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index 2f29a66630..55d21658f4 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -27,7 +27,7 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); // also allow no session to be found (ignoreUnkownUsername) let session: Session | undefined; diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index adb1f28310..a218852ec7 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -23,7 +23,7 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); if (!organization) { const org: Organization | null = await getDefaultOrg({ serviceUrl }); diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx index 2eac65536d..7ebe79e869 100644 --- a/apps/login/src/app/(login)/register/password/page.tsx +++ b/apps/login/src/app/(login)/register/password/page.tsx @@ -23,7 +23,7 @@ export default async function Page(props: { searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); if (!organization) { const org: Organization | null = await getDefaultOrg({ serviceUrl }); diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index f387925500..6e5deda345 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -61,7 +61,7 @@ export default async function Page(props: { searchParams: Promise }) { const t = await getTranslations({ locale, namespace: "signedin" }); const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const { loginName, authRequestId, organization } = searchParams; const sessionFactors = await loadSession( diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index c5db30a21c..93632d2f28 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -20,7 +20,7 @@ export default async function Page(props: { const { loginName, authRequestId, sessionId, organization } = searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index 79f5d3ab50..2d1e6b8507 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -19,7 +19,7 @@ export default async function Page(props: { const { loginName, organization, authRequestId, checkAfter } = searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const sessionFactors = await loadMostRecentSession({ serviceUrl, diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 833ed663df..673d78af93 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -26,7 +26,7 @@ export default async function Page(props: { searchParams: Promise }) { searchParams; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index f366342570..04548c95a7 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -199,7 +199,7 @@ export async function GET(request: NextRequest) { const sessionId = searchParams.get("sessionId"); const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); // TODO: find a better way to handle _rsc (react server components) requests and block them to avoid conflicts when creating oidc callback const _rsc = searchParams.get("_rsc"); diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index ba70a764f3..11acd701e0 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -1,17 +1,52 @@ import { newSystemToken } from "@zitadel/client/node"; -export async function systemAPIToken() { - const audience = process.env.AUDIENCE; - const userID = process.env.SYSTEM_USER_ID; - const key = process.env.SYSTEM_USER_PRIVATE_KEY; +export async function systemAPIToken({ + serviceRegion, +}: { + serviceRegion: string; +}) { + const QA = { + audience: process.env.QA_AUDIENCE, + userID: process.env.QA_SYSTEM_USER_ID, + token: Buffer.from( + process.env.QA_SYSTEM_USER_PRIVATE_KEY, + "base64", + ).toString("utf-8"), + }; - const decodedToken = Buffer.from(key, "base64").toString("utf-8"); + const PROD = { + audience: process.env.QA_AUDIENCE, + userID: process.env.QA_SYSTEM_USER_ID, + token: Buffer.from( + process.env.PROD_SYSTEM_USER_PRIVATE_KEY, + "base64", + ).toString("utf-8"), + }; - const token = newSystemToken({ - audience: audience, - subject: userID, - key: decodedToken, - }); + let token; + + switch (serviceRegion) { + case "eu1": + token = newSystemToken({ + audience: QA.audience, + subject: QA.userID, + key: QA.token, + }); + break; + case "us1": + token = newSystemToken({ + audience: PROD.audience, + subject: PROD.userID, + key: PROD.token, + }); + break; + default: + token = newSystemToken({ + audience: QA.audience, + subject: QA.userID, + key: QA.token, + }); + } return token; } diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index 0553aef21b..3c7482f43e 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -26,7 +26,7 @@ export async function setMyPassword({ password: string; }) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const sessionCookie = await getSessionCookieById({ sessionId }); diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 9f20c1a00d..e199bfd896 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -35,7 +35,7 @@ export async function createSessionAndUpdateCookie( lifetime?: Duration, ): Promise { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const createdSession = await createSessionFromChecks({ serviceUrl, @@ -97,7 +97,7 @@ export async function createSessionForIdpAndUpdateCookie( lifetime?: Duration, ): Promise { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const createdSession = await createSessionForUserIdAndIdpIntent({ serviceUrl, @@ -159,7 +159,7 @@ export async function setSessionAndUpdateCookie( lifetime?: Duration, ) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); return setSession({ serviceUrl, diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 3e597c5041..daef0aaeb6 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -19,7 +19,7 @@ export type StartIDPFlowCommand = { export async function startIDPFlow(command: StartIDPFlowCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -60,7 +60,7 @@ export async function createNewSessionFromIdpIntent( command: CreateNewSessionCommand, ) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 56986930f9..4e9c52ce9e 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -22,7 +22,7 @@ export type RegisterUserResponse = { export async function inviteUser(command: InviteUserCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 13bf7f0ed0..e894dcdd3f 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -34,7 +34,7 @@ const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; export async function sendLoginname(command: SendLoginnameCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -80,7 +80,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (identityProviders.length === 1) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -129,7 +129,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (identityProviders.length === 1) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { diff --git a/apps/login/src/lib/server/otp.ts b/apps/login/src/lib/server/otp.ts index 72c663e9fe..868fee03a5 100644 --- a/apps/login/src/lib/server/otp.ts +++ b/apps/login/src/lib/server/otp.ts @@ -27,7 +27,7 @@ export type SetOTPCommand = { export async function setOTP(command: SetOTPCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const recentSession = command.sessionId ? await getSessionCookieById({ sessionId: command.sessionId }).catch( diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 97255ead86..c2b2856a92 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -43,7 +43,7 @@ export async function registerPasskeyLink( const { sessionId } = command; const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -90,7 +90,7 @@ export async function registerPasskeyLink( export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); // if no name is provided, try to generate one from the user agent let passkeyName = command.passkeyName; @@ -153,7 +153,7 @@ export async function sendPasskey(command: SendPasskeyCommand) { } const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const loginSettings = await getLoginSettings({ serviceUrl, organization }); diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 74518efabe..ec7d61b7d1 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -45,7 +45,7 @@ type ResetPasswordCommand = { export async function resetPassword(command: ResetPasswordCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { @@ -85,7 +85,7 @@ export type UpdateSessionCommand = { export async function sendPassword(command: UpdateSessionCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); let sessionCookie = await getSessionCookieByLoginName({ loginName: command.loginName, @@ -255,7 +255,7 @@ export async function changePassword(command: { password: string; }) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); // check for init state const { user } = await getUserByID({ serviceUrl, userId: command.userId }); @@ -284,7 +284,7 @@ export async function checkSessionAndSetPassword({ password, }: CheckSessionAndSetPasswordCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const sessionCookie = await getSessionCookieById({ sessionId }); diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 67021f21f6..dd8d6d2031 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -29,7 +29,7 @@ export type RegisterUserResponse = { }; export async function registerUser(command: RegisterUserCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index db165dfabd..658ba41fe8 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -25,7 +25,7 @@ export async function continueWithSession({ ...session }: Session & { authRequestId?: string }) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const loginSettings = await getLoginSettings({ serviceUrl, @@ -88,7 +88,7 @@ export async function updateSession(options: UpdateSessionCommand) { } const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -152,7 +152,7 @@ type ClearSessionOptions = { export async function clearSession(options: ClearSessionOptions) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const { sessionId } = options; @@ -175,7 +175,7 @@ type CleanupSessionCommand = { export async function cleanupSession({ sessionId }: CleanupSessionCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const sessionCookie = await getSessionCookieById({ sessionId }); diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index 5eaabce532..223a5fa78f 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -21,7 +21,7 @@ type VerifyU2FCommand = { export async function addU2F(command: RegisterU2FCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { @@ -59,7 +59,7 @@ export async function addU2F(command: RegisterU2FCommand) { export async function verifyU2F(command: VerifyU2FCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host || typeof host !== "string") { diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 6c07197b87..befad512ef 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -30,7 +30,7 @@ export async function verifyTOTP( organization?: string, ) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); return loadMostRecentSession({ serviceUrl, @@ -62,7 +62,7 @@ type VerifyUserByEmailCommand = { export async function sendVerification(command: VerifyUserByEmailCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const verifyResponse = command.isInvite ? await verifyInviteCode({ @@ -244,7 +244,7 @@ type resendVerifyEmailCommand = { export async function resendVerification(command: resendVerifyEmailCommand) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const host = _headers.get("host"); if (!host) { @@ -290,7 +290,7 @@ export async function sendVerificationRedirectWithoutCheck( command: SendVerificationRedirectWithoutCheckCommand, ) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); if (!("loginName" in command || "userId" in command)) { return { error: "No userId, nor loginname provided" }; diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index f6c70c66d3..b1042a4e2e 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -20,16 +20,17 @@ type ServiceClass = export async function createServiceForHost( service: T, serviceUrl: string, + serviceRegion: string, ) { let token; // if we are running in a multitenancy context, use the system user token if ( - process.env.AUDIENCE && - process.env.SYSTEM_USER_ID && - process.env.SYSTEM_USER_PRIVATE_KEY + process.env.QA_AUDIENCE && + process.env.QA_SYSTEM_USER_ID && + process.env.QA_SYSTEM_USER_PRIVATE_KEY ) { - token = await systemAPIToken(); + token = await systemAPIToken(serviceRegion); } else if (process.env.ZITADEL_SERVICE_USER_TOKEN) { token = process.env.ZITADEL_SERVICE_USER_TOKEN; } @@ -49,7 +50,10 @@ export async function createServiceForHost( return createClientFor(service)(transport); } -export function getServiceUrlFromHeaders(headers: ReadonlyHeaders): string { +export function getServiceUrlFromHeaders(headers: ReadonlyHeaders): { + serviceUrl: string; + serviceRegion: string; +} { let instanceUrl: string = process.env.ZITADEL_API_URL; const forwardedHost = headers.get("x-zitadel-forward-host"); @@ -70,5 +74,8 @@ export function getServiceUrlFromHeaders(headers: ReadonlyHeaders): string { } } - return instanceUrl; + return { + serviceUrl: instanceUrl, + serviceRegion: headers.get("x-zitadel-region") || "", + }; } diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 5a03e5ece0..f24e5f73df 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -55,13 +55,15 @@ async function cacheWrapper(callback: Promise) { export async function getBrandingSettings({ serviceUrl, + serviceRegion, organization, }: { serviceUrl: string; + serviceRegion: string; organization?: string; }) { const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); + await createServiceForHost(SettingsService, serviceUrl, serviceRegion); const callback = settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) @@ -72,13 +74,15 @@ export async function getBrandingSettings({ export async function getLoginSettings({ serviceUrl, + serviceRegion, organization, }: { serviceUrl: string; + serviceRegion: string; organization?: string; }) { const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); + await createServiceForHost(SettingsService, serviceUrl, serviceRegion); const callback = settingsService .getLoginSettings({ ctx: makeReqCtx(organization) }, {}) @@ -89,14 +93,17 @@ export async function getLoginSettings({ export async function listIDPLinks({ serviceUrl, + serviceRegion, userId, }: { serviceUrl: string; + serviceRegion: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.listIDPLinks({ userId }, {}); @@ -104,14 +111,17 @@ export async function listIDPLinks({ export async function addOTPEmail({ serviceUrl, + serviceRegion, userId, }: { serviceUrl: string; + serviceRegion: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.addOTPEmail({ userId }, {}); @@ -119,14 +129,17 @@ export async function addOTPEmail({ export async function addOTPSMS({ serviceUrl, + serviceRegion, userId, }: { serviceUrl: string; + serviceRegion: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.addOTPSMS({ userId }, {}); @@ -134,14 +147,17 @@ export async function addOTPSMS({ export async function registerTOTP({ serviceUrl, + serviceRegion, userId, }: { serviceUrl: string; + serviceRegion: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.registerTOTP({ userId }, {}); @@ -149,11 +165,13 @@ export async function registerTOTP({ export async function getGeneralSettings({ serviceUrl, + serviceRegion, }: { serviceUrl: string; + serviceRegion: string; }) { const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); + await createServiceForHost(SettingsService, serviceUrl, serviceRegion); const callback = settingsService .getGeneralSettings({}, {}) @@ -164,13 +182,15 @@ export async function getGeneralSettings({ export async function getLegalAndSupportSettings({ serviceUrl, + serviceRegion, organization, }: { serviceUrl: string; + serviceRegion: string; organization?: string; }) { const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); + await createServiceForHost(SettingsService, serviceUrl, serviceRegion); const callback = settingsService .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) @@ -181,13 +201,15 @@ export async function getLegalAndSupportSettings({ export async function getPasswordComplexitySettings({ serviceUrl, + serviceRegion, organization, }: { serviceUrl: string; + serviceRegion: string; organization?: string; }) { const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); + await createServiceForHost(SettingsService, serviceUrl, serviceRegion); const callback = settingsService .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) @@ -198,28 +220,32 @@ export async function getPasswordComplexitySettings({ export async function createSessionFromChecks({ serviceUrl, + serviceRegion, checks, challenges, lifetime, }: { serviceUrl: string; + serviceRegion: string; checks: Checks; challenges: RequestChallenges | undefined; lifetime?: Duration; }) { const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + await createServiceForHost(SessionService, serviceUrl, serviceRegion); return sessionService.createSession({ checks, challenges, lifetime }, {}); } export async function createSessionForUserIdAndIdpIntent({ serviceUrl, + serviceRegion, userId, idpIntent, lifetime, }: { serviceUrl: string; + serviceRegion: string; userId: string; idpIntent: { idpIntentId?: string | undefined; @@ -228,7 +254,7 @@ export async function createSessionForUserIdAndIdpIntent({ lifetime?: Duration; }) { const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + await createServiceForHost(SessionService, serviceUrl, serviceRegion); return sessionService.createSession({ checks: { @@ -246,6 +272,7 @@ export async function createSessionForUserIdAndIdpIntent({ export async function setSession({ serviceUrl, + serviceRegion, sessionId, sessionToken, challenges, @@ -253,6 +280,7 @@ export async function setSession({ lifetime, }: { serviceUrl: string; + serviceRegion: string; sessionId: string; sessionToken: string; challenges: RequestChallenges | undefined; @@ -260,7 +288,7 @@ export async function setSession({ lifetime?: Duration; }) { const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + await createServiceForHost(SessionService, serviceUrl, serviceRegion); return sessionService.setSession( { @@ -277,42 +305,51 @@ export async function setSession({ export async function getSession({ serviceUrl, + serviceRegion, sessionId, sessionToken, }: { serviceUrl: string; + serviceRegion: string; sessionId: string; sessionToken: string; }) { const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + await createServiceForHost(SessionService, serviceUrl, serviceRegion); return sessionService.getSession({ sessionId, sessionToken }, {}); } export async function deleteSession({ serviceUrl, + serviceRegion, sessionId, sessionToken, }: { serviceUrl: string; + serviceRegion: string; sessionId: string; sessionToken: string; }) { const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + await createServiceForHost(SessionService, serviceUrl, serviceRegion); return sessionService.deleteSession({ sessionId, sessionToken }, {}); } type ListSessionsCommand = { serviceUrl: string; + serviceRegion: string; ids: string[]; }; -export async function listSessions({ serviceUrl, ids }: ListSessionsCommand) { +export async function listSessions({ + serviceUrl, + serviceRegion, + ids, +}: ListSessionsCommand) { const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + await createServiceForHost(SessionService, serviceUrl, serviceRegion); return sessionService.listSessions( { @@ -331,6 +368,7 @@ export async function listSessions({ serviceUrl, ids }: ListSessionsCommand) { export type AddHumanUserData = { serviceUrl: string; + serviceRegion: string; firstName: string; lastName: string; email: string; @@ -340,6 +378,7 @@ export type AddHumanUserData = { export async function addHumanUser({ serviceUrl, + serviceRegion, email, firstName, lastName, @@ -349,6 +388,7 @@ export async function addHumanUser({ const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.addHumanUser({ @@ -372,14 +412,17 @@ export async function addHumanUser({ export async function addHuman({ serviceUrl, + serviceRegion, request, }: { serviceUrl: string; + serviceRegion: string; request: AddHumanUserRequest; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.addHumanUser(request); @@ -387,16 +430,19 @@ export async function addHuman({ export async function verifyTOTPRegistration({ serviceUrl, + serviceRegion, code, userId, }: { serviceUrl: string; + serviceRegion: string; code: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.verifyTOTPRegistration({ code, userId }, {}); @@ -404,14 +450,17 @@ export async function verifyTOTPRegistration({ export async function getUserByID({ serviceUrl, + serviceRegion, userId, }: { serviceUrl: string; + serviceRegion: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.getUserByID({ userId }, {}); @@ -419,16 +468,19 @@ export async function getUserByID({ export async function verifyInviteCode({ serviceUrl, + serviceRegion, userId, verificationCode, }: { serviceUrl: string; + serviceRegion: string; userId: string; verificationCode: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.verifyInviteCode({ userId, verificationCode }, {}); @@ -436,14 +488,17 @@ export async function verifyInviteCode({ export async function resendInviteCode({ serviceUrl, + serviceRegion, userId, }: { serviceUrl: string; + serviceRegion: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.resendInviteCode({ userId }, {}); @@ -451,10 +506,12 @@ export async function resendInviteCode({ export async function sendEmailCode({ serviceUrl, + serviceRegion, userId, urlTemplate, }: { serviceUrl: string; + serviceRegion: string; userId: string; urlTemplate: string; }) { @@ -473,6 +530,7 @@ export async function sendEmailCode({ const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.sendEmailCode(medium, {}); @@ -480,10 +538,12 @@ export async function sendEmailCode({ export async function createInviteCode({ serviceUrl, + serviceRegion, urlTemplate, userId, }: { serviceUrl: string; + serviceRegion: string; urlTemplate: string; userId: string; }) { @@ -499,6 +559,7 @@ export async function createInviteCode({ const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.createInviteCode( @@ -515,6 +576,7 @@ export async function createInviteCode({ export type ListUsersCommand = { serviceUrl: string; + serviceRegion: string; loginName?: string; userName?: string; email?: string; @@ -524,6 +586,7 @@ export type ListUsersCommand = { export async function listUsers({ serviceUrl, + serviceRegion, loginName, userName, phone, @@ -615,6 +678,7 @@ export async function listUsers({ const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.listUsers({ queries }); @@ -622,6 +686,7 @@ export async function listUsers({ export type SearchUsersCommand = { serviceUrl: string; + serviceRegion: string; searchValue: string; loginSettings: LoginSettings; organizationId?: string; @@ -667,6 +732,7 @@ const EmailQuery = (searchValue: string) => * */ export async function searchUsers({ serviceUrl, + serviceRegion, searchValue, loginSettings, organizationId, @@ -700,6 +766,7 @@ export async function searchUsers({ const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); const loginNameResult = await userService.listUsers({ queries }); @@ -786,11 +853,13 @@ export async function searchUsers({ export async function getDefaultOrg({ serviceUrl, + serviceRegion, }: { serviceUrl: string; + serviceRegion: string; }): Promise { const orgService: Client = - await createServiceForHost(OrganizationService, serviceUrl); + await createServiceForHost(OrganizationService, serviceUrl, serviceRegion); return orgService .listOrganizations( @@ -811,13 +880,15 @@ export async function getDefaultOrg({ export async function getOrgsByDomain({ serviceUrl, + serviceRegion, domain, }: { serviceUrl: string; + serviceRegion: string; domain: string; }) { const orgService: Client = - await createServiceForHost(OrganizationService, serviceUrl); + await createServiceForHost(OrganizationService, serviceUrl, serviceRegion); return orgService.listOrganizations( { @@ -836,16 +907,19 @@ export async function getOrgsByDomain({ export async function startIdentityProviderFlow({ serviceUrl, + serviceRegion, idpId, urls, }: { serviceUrl: string; + serviceRegion: string; idpId: string; urls: RedirectURLsJson; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.startIdentityProviderIntent({ @@ -859,16 +933,19 @@ export async function startIdentityProviderFlow({ export async function retrieveIdentityProviderInformation({ serviceUrl, + serviceRegion, idpIntentId, idpIntentToken, }: { serviceUrl: string; + serviceRegion: string; idpIntentId: string; idpIntentToken: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.retrieveIdentityProviderIntent({ @@ -879,12 +956,18 @@ export async function retrieveIdentityProviderInformation({ export async function getAuthRequest({ serviceUrl, + serviceRegion, authRequestId, }: { serviceUrl: string; + serviceRegion: string; authRequestId: string; }) { - const oidcService = await createServiceForHost(OIDCService, serviceUrl); + const oidcService = await createServiceForHost( + OIDCService, + serviceUrl, + serviceRegion, + ); return oidcService.getAuthRequest({ authRequestId, @@ -893,28 +976,37 @@ export async function getAuthRequest({ export async function createCallback({ serviceUrl, + serviceRegion, req, }: { serviceUrl: string; + serviceRegion: string; req: CreateCallbackRequest; }) { - const oidcService = await createServiceForHost(OIDCService, serviceUrl); + const oidcService = await createServiceForHost( + OIDCService, + serviceUrl, + serviceRegion, + ); return oidcService.createCallback(req); } export async function verifyEmail({ serviceUrl, + serviceRegion, userId, verificationCode, }: { serviceUrl: string; + serviceRegion: string; userId: string; verificationCode: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.verifyEmail( @@ -928,10 +1020,12 @@ export async function verifyEmail({ export async function resendEmailCode({ serviceUrl, + serviceRegion, userId, urlTemplate, }: { serviceUrl: string; + serviceRegion: string; userId: string; urlTemplate: string; }) { @@ -948,6 +1042,7 @@ export async function resendEmailCode({ const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.resendEmailCode(request, {}); @@ -955,16 +1050,19 @@ export async function resendEmailCode({ export async function retrieveIDPIntent({ serviceUrl, + serviceRegion, id, token, }: { serviceUrl: string; + serviceRegion: string; id: string; token: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.retrieveIdentityProviderIntent( @@ -975,29 +1073,38 @@ export async function retrieveIDPIntent({ export async function getIDPByID({ serviceUrl, + serviceRegion, id, }: { serviceUrl: string; + serviceRegion: string; id: string; }) { const idpService: Client = - await createServiceForHost(IdentityProviderService, serviceUrl); + await createServiceForHost( + IdentityProviderService, + serviceUrl, + serviceRegion, + ); return idpService.getIDPByID({ id }, {}).then((resp) => resp.idp); } export async function addIDPLink({ serviceUrl, + serviceRegion, idp, userId, }: { serviceUrl: string; + serviceRegion: string; idp: { id: string; userId: string; userName: string }; userId: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.addIDPLink( @@ -1015,10 +1122,12 @@ export async function addIDPLink({ export async function passwordReset({ serviceUrl, + serviceRegion, userId, urlTemplate, }: { serviceUrl: string; + serviceRegion: string; userId: string; urlTemplate?: string; }) { @@ -1034,6 +1143,7 @@ export async function passwordReset({ const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.passwordReset( @@ -1050,12 +1160,14 @@ export async function passwordReset({ export async function setUserPassword({ serviceUrl, + serviceRegion, userId, password, user, code, }: { serviceUrl: string; + serviceRegion: string; userId: string; password: string; user: User; @@ -1097,6 +1209,7 @@ export async function setUserPassword({ const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.setPassword(payload, {}).catch((error) => { @@ -1111,14 +1224,17 @@ export async function setUserPassword({ export async function setPassword({ serviceUrl, + serviceRegion, payload, }: { serviceUrl: string; + serviceRegion: string; payload: SetPasswordRequest; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.setPassword(payload, {}); @@ -1132,14 +1248,17 @@ export async function setPassword({ */ export async function createPasskeyRegistrationLink({ serviceUrl, + serviceRegion, userId, }: { serviceUrl: string; + serviceRegion: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.createPasskeyRegistrationLink({ @@ -1160,16 +1279,19 @@ export async function createPasskeyRegistrationLink({ */ export async function registerU2F({ serviceUrl, + serviceRegion, userId, domain, }: { serviceUrl: string; + serviceRegion: string; userId: string; domain: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.registerU2F({ @@ -1186,14 +1308,17 @@ export async function registerU2F({ */ export async function verifyU2FRegistration({ serviceUrl, + serviceRegion, request, }: { serviceUrl: string; + serviceRegion: string; request: VerifyU2FRegistrationRequest; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.verifyU2FRegistration(request, {}); @@ -1208,10 +1333,12 @@ export async function verifyU2FRegistration({ */ export async function getActiveIdentityProviders({ serviceUrl, + serviceRegion, orgId, linking_allowed, }: { serviceUrl: string; + serviceRegion: string; orgId?: string; linking_allowed?: boolean; }) { @@ -1220,7 +1347,7 @@ export async function getActiveIdentityProviders({ props.linkingAllowed = linking_allowed; } const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); + await createServiceForHost(SettingsService, serviceUrl, serviceRegion); return settingsService.getActiveIdentityProviders(props, {}); } @@ -1233,14 +1360,17 @@ export async function getActiveIdentityProviders({ */ export async function verifyPasskeyRegistration({ serviceUrl, + serviceRegion, request, }: { serviceUrl: string; + serviceRegion: string; request: VerifyPasskeyRegistrationRequest; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.verifyPasskeyRegistration(request, {}); @@ -1256,11 +1386,13 @@ export async function verifyPasskeyRegistration({ */ export async function registerPasskey({ serviceUrl, + serviceRegion, userId, code, domain, }: { serviceUrl: string; + serviceRegion: string; userId: string; code: { id: string; code: string }; domain: string; @@ -1268,6 +1400,7 @@ export async function registerPasskey({ const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.registerPasskey({ @@ -1285,14 +1418,17 @@ export async function registerPasskey({ */ export async function listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId, }: { serviceUrl: string; + serviceRegion: string; userId: string; }) { const userService: Client = await createServiceForHost( UserService, serviceUrl, + serviceRegion, ); return userService.listAuthenticationMethodTypes({ diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index cb42234cac..8ffe8e5830 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -23,7 +23,7 @@ export async function middleware(request: NextRequest) { const _headers = await headers(); - const serviceUrl = getServiceUrlFromHeaders(_headers); + const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); const instanceHost = `${serviceUrl}`.replace("https://", ""); diff --git a/turbo.json b/turbo.json index 1db73e60c9..b342c3f52a 100644 --- a/turbo.json +++ b/turbo.json @@ -6,9 +6,12 @@ "DEBUG", "VERCEL_URL", "EMAIL_VERIFICATION", - "AUDIENCE", - "SYSTEM_USER_ID", - "SYSTEM_USER_PRIVATE_KEY", + "QA_AUDIENCE", + "QA_SYSTEM_USER_ID", + "QA_SYSTEM_USER_PRIVATE_KEY", + "PROD_AUDIENCE", + "PROD_SYSTEM_USER_ID", + "PROD_SYSTEM_USER_PRIVATE_KEY", "ZITADEL_API_URL", "ZITADEL_SERVICE_USER_ID", "ZITADEL_SERVICE_USER_TOKEN", From 323a22370697d57b1a37afcbba61d082f2988855 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 29 Jan 2025 16:00:21 +0100 Subject: [PATCH 58/67] service Region context everywhere --- apps/login/src/app/(login)/accounts/page.tsx | 17 ++++-- .../app/(login)/authenticator/set/page.tsx | 53 ++++++++++++------- .../(login)/idp/[provider]/success/page.tsx | 41 ++++++++++---- apps/login/src/app/(login)/idp/page.tsx | 7 ++- apps/login/src/app/(login)/invite/page.tsx | 19 +++++-- .../src/app/(login)/invite/success/page.tsx | 14 +++-- apps/login/src/app/(login)/loginname/page.tsx | 9 +++- apps/login/src/app/(login)/mfa/page.tsx | 10 +++- apps/login/src/app/(login)/mfa/set/page.tsx | 43 ++++++++------- .../src/app/(login)/otp/[method]/page.tsx | 4 ++ .../src/app/(login)/otp/[method]/set/page.tsx | 43 ++++++++++----- apps/login/src/app/(login)/passkey/page.tsx | 16 +++--- .../src/app/(login)/passkey/set/page.tsx | 7 ++- .../src/app/(login)/password/change/page.tsx | 9 +++- apps/login/src/app/(login)/password/page.tsx | 8 ++- .../src/app/(login)/password/set/page.tsx | 20 +++++-- apps/login/src/app/(login)/register/page.tsx | 24 +++++++-- .../app/(login)/register/password/page.tsx | 24 +++++++-- apps/login/src/app/(login)/signedin/page.tsx | 16 +++++- apps/login/src/app/(login)/u2f/page.tsx | 8 ++- apps/login/src/app/(login)/u2f/set/page.tsx | 7 ++- apps/login/src/app/(login)/verify/page.tsx | 10 +++- apps/login/src/app/login/route.ts | 32 +++++++++-- apps/login/src/lib/self.ts | 1 + apps/login/src/lib/server/cookie.ts | 6 +++ apps/login/src/lib/server/idp.ts | 3 ++ apps/login/src/lib/server/invite.ts | 2 + apps/login/src/lib/server/loginname.ts | 8 +++ apps/login/src/lib/server/otp.ts | 1 + apps/login/src/lib/server/passkeys.ts | 6 +++ apps/login/src/lib/server/password.ts | 12 +++++ apps/login/src/lib/server/register.ts | 3 ++ apps/login/src/lib/server/session.ts | 4 ++ apps/login/src/lib/server/u2f.ts | 2 + apps/login/src/lib/server/verify.ts | 21 +++++++- apps/login/src/lib/service.ts | 2 +- apps/login/src/lib/session.ts | 3 ++ apps/login/src/lib/zitadel.ts | 1 + 38 files changed, 408 insertions(+), 108 deletions(-) diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index 998cb7f641..bc63d990c9 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -13,12 +13,19 @@ import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; import Link from "next/link"; -async function loadSessions({ serviceUrl }: { serviceUrl: string }) { +async function loadSessions({ + serviceUrl, + serviceRegion, +}: { + serviceUrl: string; + serviceRegion: string; +}) { const ids: (string | undefined)[] = await getAllSessionCookieIds(); if (ids && ids.length) { const response = await listSessions({ serviceUrl, + serviceRegion, ids: ids.filter((id) => !!id) as string[], }); return response?.sessions ?? []; @@ -43,16 +50,20 @@ export default async function Page(props: { let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg({ serviceUrl }); + const org: Organization | null = await getDefaultOrg({ + serviceUrl, + serviceRegion, + }); if (org) { defaultOrganization = org.id; } } - let sessions = await loadSessions({ serviceUrl }); + let sessions = await loadSessions({ serviceUrl, serviceRegion }); const branding = await getBrandingSettings({ serviceUrl, + serviceRegion, organization: organization ?? defaultOrganization, }); diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index dddde2628a..634b116815 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -36,31 +36,35 @@ export default async function Page(props: { ? await loadSessionById(serviceUrl, sessionId, organization) : await loadSessionByLoginname(serviceUrl, loginName, organization); - async function getAuthMethodsAndUser(host: string, session?: Session) { + async function getAuthMethodsAndUser( + serviceUrl: string, + serviceRegion: string, + session?: Session, + ) { const userId = session?.factors?.user?.id; if (!userId) { throw Error("Could not get user id from session"); } - return listAuthenticationMethodTypes({ serviceUrl, userId }).then( - (methods) => { - return getUserByID({ serviceUrl, userId }).then((user) => { - const humanUser = - user.user?.type.case === "human" - ? user.user?.type.value - : undefined; + return listAuthenticationMethodTypes({ + serviceUrl, + serviceRegion, + userId, + }).then((methods) => { + return getUserByID({ serviceUrl, serviceRegion, userId }).then((user) => { + const humanUser = + user.user?.type.case === "human" ? user.user?.type.value : undefined; - return { - factors: session?.factors, - authMethods: methods.authMethodTypes ?? [], - phoneVerified: humanUser?.phone?.isVerified ?? false, - emailVerified: humanUser?.email?.isVerified ?? false, - expirationDate: session?.expirationDate, - }; - }); - }, - ); + return { + factors: session?.factors, + authMethods: methods.authMethodTypes ?? [], + phoneVerified: humanUser?.phone?.isVerified ?? false, + emailVerified: humanUser?.email?.isVerified ?? false, + expirationDate: session?.expirationDate, + }; + }); + }); } async function loadSessionByLoginname( @@ -70,12 +74,13 @@ export default async function Page(props: { ) { return loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, }, }).then((session) => { - return getAuthMethodsAndUser(serviceUrl, session); + return getAuthMethodsAndUser(serviceUrl, serviceRegion, session); }); } @@ -87,10 +92,15 @@ export default async function Page(props: { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ serviceUrl, + serviceRegion, sessionId: recent.id, sessionToken: recent.token, }).then((sessionResponse) => { - return getAuthMethodsAndUser(serviceUrl, sessionResponse.session); + return getAuthMethodsAndUser( + serviceUrl, + serviceRegion, + sessionResponse.session, + ); }); } @@ -100,16 +110,19 @@ export default async function Page(props: { const branding = await getBrandingSettings({ serviceUrl, + serviceRegion, organization: sessionWithData.factors?.user?.organizationId, }); const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: sessionWithData.factors?.user?.organizationId, }); const identityProviders = await getActiveIdentityProviders({ serviceUrl, + serviceRegion, orgId: sessionWithData.factors?.user?.organizationId, linking_allowed: true, }).then((resp) => { 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 8cdb6cec83..425e9f0caf 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -42,7 +42,11 @@ export default async function Page(props: { const _headers = await headers(); const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); if (!provider || !id || !token) { return loginFailed(branding, "IDP context missing"); @@ -50,6 +54,7 @@ export default async function Page(props: { const intent = await retrieveIDPIntent({ serviceUrl, + serviceRegion, id, token, }); @@ -72,7 +77,11 @@ export default async function Page(props: { return loginFailed(branding, "IDP information missing"); } - const idp = await getIDPByID({ serviceUrl, id: idpInformation.idpId }); + const idp = await getIDPByID({ + serviceUrl, + serviceRegion, + id: idpInformation.idpId, + }); const options = idp?.config?.options; if (!idp) { @@ -91,6 +100,7 @@ export default async function Page(props: { try { idpLink = await addIDPLink({ serviceUrl, + serviceRegion, idp: { id: idpInformation.idpId, userId: idpInformation.userId, @@ -121,20 +131,23 @@ export default async function Page(props: { const email = PROVIDER_MAPPING[providerType](idpInformation).email?.email; if (options.autoLinking === AutoLinkingOption.EMAIL && email) { - foundUser = await listUsers({ serviceUrl, email }).then((response) => { - return response.result ? response.result[0] : null; - }); + foundUser = await listUsers({ serviceUrl, serviceRegion, email }).then( + (response) => { + return response.result ? response.result[0] : null; + }, + ); } else if (options.autoLinking === AutoLinkingOption.USERNAME) { foundUser = await listUsers( options.autoLinking === AutoLinkingOption.USERNAME - ? { serviceUrl, userName: idpInformation.userName } - : { serviceUrl, email }, + ? { serviceUrl, serviceRegion, userName: idpInformation.userName } + : { serviceUrl, serviceRegion, email }, ).then((response) => { return response.result ? response.result[0] : null; }); } else { foundUser = await listUsers({ serviceUrl, + serviceRegion, userName: idpInformation.userName, email, }).then((response) => { @@ -147,6 +160,7 @@ export default async function Page(props: { try { idpLink = await addIDPLink({ serviceUrl, + serviceRegion, idp: { id: idpInformation.idpId, userId: idpInformation.userId, @@ -187,12 +201,17 @@ export default async function Page(props: { const suffix = matched?.[1] ?? ""; // this just returns orgs where the suffix is set as primary domain - const orgs = await getOrgsByDomain({ serviceUrl, domain: suffix }); + const orgs = await getOrgsByDomain({ + serviceUrl, + serviceRegion, + domain: suffix, + }); const orgToCheckForDiscovery = orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; const orgLoginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: orgToCheckForDiscovery, }); if (orgLoginSettings?.allowDomainDiscovery) { @@ -211,7 +230,11 @@ export default async function Page(props: { }); } - const newUser = await addHuman({ serviceUrl, request: userData }); + const newUser = await addHuman({ + serviceUrl, + serviceRegion, + request: userData, + }); if (newUser) { return ( diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/idp/page.tsx index 53a77910d3..80829557ec 100644 --- a/apps/login/src/app/(login)/idp/page.tsx +++ b/apps/login/src/app/(login)/idp/page.tsx @@ -20,12 +20,17 @@ export default async function Page(props: { const identityProviders = await getActiveIdentityProviders({ serviceUrl, + serviceRegion, orgId: organization, }).then((resp) => { return resp.identityProviders; }); - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); return ( diff --git a/apps/login/src/app/(login)/invite/page.tsx b/apps/login/src/app/(login)/invite/page.tsx index 1b1ac33470..f9c8405855 100644 --- a/apps/login/src/app/(login)/invite/page.tsx +++ b/apps/login/src/app/(login)/invite/page.tsx @@ -24,7 +24,7 @@ export default async function Page(props: { const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); if (!organization) { - const org = await getDefaultOrg({ serviceUrl }); + const org = await getDefaultOrg({ serviceUrl, serviceRegion }); if (!org) { throw new Error("No default organization found"); } @@ -32,14 +32,23 @@ export default async function Page(props: { organization = org.id; } - const loginSettings = await getLoginSettings({ serviceUrl, organization }); - - const passwordComplexitySettings = await getPasswordComplexitySettings({ + const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization, }); - const branding = await getBrandingSettings({ serviceUrl, organization }); + const passwordComplexitySettings = await getPasswordComplexitySettings({ + serviceUrl, + serviceRegion, + organization, + }); + + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); return ( diff --git a/apps/login/src/app/(login)/invite/success/page.tsx b/apps/login/src/app/(login)/invite/success/page.tsx index e21b8ac4da..155849ebb9 100644 --- a/apps/login/src/app/(login)/invite/success/page.tsx +++ b/apps/login/src/app/(login)/invite/success/page.tsx @@ -22,7 +22,7 @@ export default async function Page(props: { const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); if (!organization) { - const org = await getDefaultOrg({ serviceUrl }); + const org = await getDefaultOrg({ serviceUrl, serviceRegion }); if (!org) { throw new Error("No default organization found"); } @@ -30,12 +30,20 @@ export default async function Page(props: { organization = org.id; } - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); let user: User | undefined; let human: HumanUser | undefined; if (userId) { - const userResponse = await getUserByID({ serviceUrl, userId }); + const userResponse = await getUserByID({ + serviceUrl, + serviceRegion, + userId, + }); if (userResponse) { user = userResponse.user; if (user?.type.case === "human") { diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 65dc02d046..7f8cb92812 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -30,7 +30,10 @@ export default async function Page(props: { let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg({ serviceUrl }); + const org: Organization | null = await getDefaultOrg({ + serviceUrl, + serviceRegion, + }); if (org) { defaultOrganization = org.id; } @@ -38,16 +41,19 @@ export default async function Page(props: { const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: organization ?? defaultOrganization, }); const contextLoginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization, }); const identityProviders = await getActiveIdentityProviders({ serviceUrl, + serviceRegion, orgId: organization ?? defaultOrganization, }).then((resp) => { return resp.identityProviders; @@ -55,6 +61,7 @@ export default async function Page(props: { const branding = await getBrandingSettings({ serviceUrl, + serviceRegion, organization: organization ?? defaultOrganization, }); diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index 94f93ac1ad..53fc650788 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -38,6 +38,7 @@ export default async function Page(props: { ) { return loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, @@ -46,6 +47,7 @@ export default async function Page(props: { if (session && session.factors?.user?.id) { return listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId: session.factors.user.id, }).then((methods) => { return { @@ -65,12 +67,14 @@ export default async function Page(props: { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ serviceUrl, + serviceRegion, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { if (response?.session && response.session.factors?.user?.id) { return listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId: response.session.factors.user.id, }).then((methods) => { return { @@ -82,7 +86,11 @@ export default async function Page(props: { }); } - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); return ( diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index a5c3eb3fc8..64e9cd7605 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -65,24 +65,24 @@ export default async function Page(props: { throw Error("Could not get user id from session"); } - return listAuthenticationMethodTypes({ serviceUrl, userId }).then( - (methods) => { - return getUserByID({ serviceUrl, userId }).then((user) => { - const humanUser = - user.user?.type.case === "human" - ? user.user?.type.value - : undefined; + return listAuthenticationMethodTypes({ + serviceUrl, + serviceRegion, + userId, + }).then((methods) => { + return getUserByID({ serviceUrl, serviceRegion, userId }).then((user) => { + const humanUser = + user.user?.type.case === "human" ? user.user?.type.value : undefined; - return { - factors: session?.factors, - authMethods: methods.authMethodTypes ?? [], - phoneVerified: humanUser?.phone?.isVerified ?? false, - emailVerified: humanUser?.email?.isVerified ?? false, - expirationDate: session?.expirationDate, - }; - }); - }, - ); + return { + factors: session?.factors, + authMethods: methods.authMethodTypes ?? [], + phoneVerified: humanUser?.phone?.isVerified ?? false, + emailVerified: humanUser?.email?.isVerified ?? false, + expirationDate: session?.expirationDate, + }; + }); + }); } async function loadSessionByLoginname( @@ -92,6 +92,7 @@ export default async function Page(props: { ) { return loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, @@ -109,6 +110,7 @@ export default async function Page(props: { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ serviceUrl, + serviceRegion, sessionId: recent.id, sessionToken: recent.token, }).then((sessionResponse) => { @@ -116,9 +118,14 @@ export default async function Page(props: { }); } - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: sessionWithData.factors?.user?.organizationId, }); diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index 77c465826c..3f3072dd07 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -47,6 +47,7 @@ export default async function Page(props: { ? await loadSessionById(serviceUrl, sessionId, organization) : await loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization }, }); @@ -58,6 +59,7 @@ export default async function Page(props: { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ serviceUrl, + serviceRegion, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { @@ -70,11 +72,13 @@ export default async function Page(props: { // email links do not come with organization, thus we need to use the session's organization const branding = await getBrandingSettings({ serviceUrl, + serviceRegion, organization: organization ?? session?.factors?.user?.organizationId, }); const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: organization ?? session?.factors?.user?.organizationId, }); 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 ebb65fe699..3de97510fd 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -36,11 +36,20 @@ export default async function Page(props: { const _headers = await headers(); const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); - const branding = await getBrandingSettings({ serviceUrl, organization }); - const loginSettings = await getLoginSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); + const loginSettings = await getLoginSettings({ + serviceUrl, + serviceRegion, + organization, + }); const session = await loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, @@ -50,7 +59,11 @@ export default async function Page(props: { let totpResponse: RegisterTOTPResponse | undefined, error: Error | undefined; if (session && session.factors?.user?.id) { if (method === "time-based") { - await registerTOTP({ serviceUrl, userId: session.factors.user.id }) + await registerTOTP({ + serviceUrl, + serviceRegion, + userId: session.factors.user.id, + }) .then((resp) => { if (resp) { totpResponse = resp; @@ -61,18 +74,22 @@ export default async function Page(props: { }); } else if (method === "sms") { // does not work - await addOTPSMS({ serviceUrl, userId: session.factors.user.id }).catch( - (error) => { - error = new Error("Could not add OTP via SMS"); - }, - ); + await addOTPSMS({ + serviceUrl, + serviceRegion, + userId: session.factors.user.id, + }).catch((error) => { + error = new Error("Could not add OTP via SMS"); + }); } else if (method === "email") { // works - await addOTPEmail({ serviceUrl, userId: session.factors.user.id }).catch( - (error) => { - error = new Error("Could not add OTP via Email"); - }, - ); + await addOTPEmail({ + serviceUrl, + serviceRegion, + userId: session.factors.user.id, + }).catch((error) => { + error = new Error("Could not add OTP via Email"); + }); } else { throw new Error("Invalid method"); } diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index 41610d8930..312eacc571 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -5,11 +5,7 @@ import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service"; import { loadMostRecentSession } from "@/lib/session"; -import { - getBrandingSettings, - getLoginSettings, - getSession, -} from "@/lib/zitadel"; +import { getBrandingSettings, getSession } from "@/lib/zitadel"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; @@ -31,6 +27,7 @@ export default async function Page(props: { ? await loadSessionById(serviceUrl, sessionId, organization) : await loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization }, }); @@ -42,6 +39,7 @@ export default async function Page(props: { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ serviceUrl, + serviceRegion, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { @@ -51,9 +49,11 @@ export default async function Page(props: { }); } - const branding = await getBrandingSettings({ serviceUrl, organization }); - - const loginSettings = await getLoginSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); return ( diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx index 6b99076b50..e2f34ae830 100644 --- a/apps/login/src/app/(login)/passkey/set/page.tsx +++ b/apps/login/src/app/(login)/passkey/set/page.tsx @@ -24,13 +24,18 @@ export default async function Page(props: { const session = await loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, }, }); - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); return ( diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index ef6390d88c..28f77a4b6d 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -28,21 +28,28 @@ export default async function Page(props: { // also allow no session to be found (ignoreUnkownUsername) const sessionFactors = await loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, }, }); - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); const passwordComplexity = await getPasswordComplexitySettings({ serviceUrl, + serviceRegion, organization: sessionFactors?.factors?.user?.organizationId, }); const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: sessionFactors?.factors?.user?.organizationId, }); diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index dfb07c934d..b9b1756813 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -29,7 +29,10 @@ export default async function Page(props: { let defaultOrganization; if (!organization) { - const org: Organization | null = await getDefaultOrg({ serviceUrl }); + const org: Organization | null = await getDefaultOrg({ + serviceUrl, + serviceRegion, + }); if (org) { defaultOrganization = org.id; @@ -41,6 +44,7 @@ export default async function Page(props: { try { sessionFactors = await loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, @@ -53,10 +57,12 @@ export default async function Page(props: { const branding = await getBrandingSettings({ serviceUrl, + serviceRegion, organization: organization ?? defaultOrganization, }); const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: organization ?? defaultOrganization, }); diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index 55d21658f4..a6537b3513 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -34,6 +34,7 @@ export default async function Page(props: { if (loginName) { session = await loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, @@ -41,19 +42,32 @@ export default async function Page(props: { }); } - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); const passwordComplexity = await getPasswordComplexitySettings({ serviceUrl, + serviceRegion, organization: session?.factors?.user?.organizationId, }); - const loginSettings = await getLoginSettings({ serviceUrl, organization }); + const loginSettings = await getLoginSettings({ + serviceUrl, + serviceRegion, + organization, + }); let user: User | undefined; let displayName: string | undefined; if (userId) { - const userResponse = await getUserByID({ serviceUrl, userId }); + const userResponse = await getUserByID({ + serviceUrl, + serviceRegion, + userId, + }); user = userResponse.user; if (user?.type.case === "human") { diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index a218852ec7..9fe2f3393c 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -26,21 +26,37 @@ export default async function Page(props: { const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); if (!organization) { - const org: Organization | null = await getDefaultOrg({ serviceUrl }); + const org: Organization | null = await getDefaultOrg({ + serviceUrl, + serviceRegion, + }); if (org) { organization = org.id; } } - const legal = await getLegalAndSupportSettings({ serviceUrl, organization }); + const legal = await getLegalAndSupportSettings({ + serviceUrl, + serviceRegion, + organization, + }); const passwordComplexitySettings = await getPasswordComplexitySettings({ serviceUrl, + serviceRegion, organization, }); - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); - const loginSettings = await getLoginSettings({ serviceUrl, organization }); + const loginSettings = await getLoginSettings({ + serviceUrl, + serviceRegion, + organization, + }); if (!loginSettings?.allowRegister) { return ( diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx index 7ebe79e869..aeda4d56f6 100644 --- a/apps/login/src/app/(login)/register/password/page.tsx +++ b/apps/login/src/app/(login)/register/password/page.tsx @@ -26,7 +26,10 @@ export default async function Page(props: { const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); if (!organization) { - const org: Organization | null = await getDefaultOrg({ serviceUrl }); + const org: Organization | null = await getDefaultOrg({ + serviceUrl, + serviceRegion, + }); if (org) { organization = org.id; } @@ -34,15 +37,28 @@ export default async function Page(props: { const missingData = !firstname || !lastname || !email; - const legal = await getLegalAndSupportSettings({ serviceUrl, organization }); + const legal = await getLegalAndSupportSettings({ + serviceUrl, + serviceRegion, + organization, + }); const passwordComplexitySettings = await getPasswordComplexitySettings({ serviceUrl, + serviceRegion, organization, }); - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); - const loginSettings = await getLoginSettings({ serviceUrl, organization }); + const loginSettings = await getLoginSettings({ + serviceUrl, + serviceRegion, + organization, + }); return missingData ? ( diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index 6e5deda345..f689713479 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -22,6 +22,7 @@ import { redirect } from "next/navigation"; async function loadSession( serviceUrl: string, + serviceRegion: string, loginName: string, authRequestId?: string, ) { @@ -30,6 +31,7 @@ async function loadSession( if (authRequestId) { return createCallback({ serviceUrl, + serviceRegion, req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { @@ -46,6 +48,7 @@ async function loadSession( } return getSession({ serviceUrl, + serviceRegion, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { @@ -66,15 +69,24 @@ export default async function Page(props: { searchParams: Promise }) { const { loginName, authRequestId, organization } = searchParams; const sessionFactors = await loadSession( serviceUrl, + serviceRegion, loginName, authRequestId, ); - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); let loginSettings; if (!authRequestId) { - loginSettings = await getLoginSettings({ serviceUrl, organization }); + loginSettings = await getLoginSettings({ + serviceUrl, + serviceRegion, + organization, + }); } return ( diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index 93632d2f28..e0a21103a8 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -27,12 +27,17 @@ export default async function Page(props: { throw new Error("No host found"); } - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); const sessionFactors = sessionId ? await loadSessionById(serviceUrl, sessionId, organization) : await loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization }, }); @@ -44,6 +49,7 @@ export default async function Page(props: { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ serviceUrl, + serviceRegion, sessionId: recent.id, sessionToken: recent.token, }).then((response) => { diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/u2f/set/page.tsx index 2d1e6b8507..850f2652d0 100644 --- a/apps/login/src/app/(login)/u2f/set/page.tsx +++ b/apps/login/src/app/(login)/u2f/set/page.tsx @@ -23,13 +23,18 @@ export default async function Page(props: { const sessionFactors = await loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, }, }); - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); return ( diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 673d78af93..628d07f36f 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -35,6 +35,7 @@ export default async function Page(props: { searchParams: Promise }) { const branding = await getBrandingSettings({ serviceUrl, + serviceRegion, organization, }); @@ -48,6 +49,7 @@ export default async function Page(props: { searchParams: Promise }) { if ("loginName" in searchParams) { sessionFactors = await loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, @@ -57,6 +59,7 @@ export default async function Page(props: { searchParams: Promise }) { if (doSend && sessionFactors?.factors?.user?.id) { await sendEmailCode({ serviceUrl, + serviceRegion, userId: sessionFactors?.factors?.user?.id, urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + @@ -70,6 +73,7 @@ export default async function Page(props: { searchParams: Promise }) { if (doSend) { await sendEmailCode({ serviceUrl, + serviceRegion, userId, urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + @@ -80,7 +84,11 @@ export default async function Page(props: { searchParams: Promise }) { }); } - const userResponse = await getUserByID({ serviceUrl, userId }); + const userResponse = await getUserByID({ + serviceUrl, + serviceRegion, + userId, + }); if (userResponse) { user = userResponse.user; if (user?.type.case === "human") { diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 04548c95a7..2cea4b3b0b 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -32,13 +32,16 @@ export const fetchCache = "default-no-store"; async function loadSessions({ serviceUrl, + serviceRegion, ids, }: { serviceUrl: string; + serviceRegion: string; ids: string[]; }): Promise { const response = await listSessions({ serviceUrl, + serviceRegion, ids: ids.filter((id: string | undefined) => !!id), }); @@ -55,6 +58,7 @@ const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/; **/ async function isSessionValid( serviceUrl: string, + serviceRegion: string, session: Session, ): Promise { // session can't be checked without user @@ -67,6 +71,7 @@ async function isSessionValid( const authMethodTypes = await listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId: session.factors.user.id, }); @@ -116,6 +121,7 @@ async function isSessionValid( // only check settings if no auth methods are available, as this would require a setup const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: session.factors?.user?.organizationId, }); if (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) { @@ -159,6 +165,7 @@ async function isSessionValid( async function findValidSession( serviceUrl: string, + serviceRegion: string, sessions: Session[], authRequest: AuthRequest, ): Promise { @@ -185,7 +192,7 @@ async function findValidSession( // return the first valid session according to settings for (const session of sessionsWithHint) { - if (await isSessionValid(serviceUrl, session)) { + if (await isSessionValid(serviceUrl, serviceRegion, session)) { return session; } } @@ -211,7 +218,7 @@ export async function GET(request: NextRequest) { const ids = sessionCookies.map((s) => s.id); let sessions: Session[] = []; if (ids && ids.length) { - sessions = await loadSessions({ serviceUrl, ids }); + sessions = await loadSessions({ serviceUrl, serviceRegion, ids }); } if (authRequestId && sessionId) { @@ -224,7 +231,11 @@ export async function GET(request: NextRequest) { if (selectedSession && selectedSession.id) { console.log(`Found session ${selectedSession.id}`); - const isValid = await isSessionValid(serviceUrl, selectedSession); + const isValid = await isSessionValid( + serviceUrl, + serviceRegion, + selectedSession, + ); console.log("Session is valid:", isValid); @@ -259,6 +270,7 @@ export async function GET(request: NextRequest) { try { const { callbackUrl } = await createCallback({ serviceUrl, + serviceRegion, req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { @@ -286,6 +298,7 @@ export async function GET(request: NextRequest) { ) { const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: selectedSession.factors?.user?.organizationId, }); @@ -317,7 +330,11 @@ export async function GET(request: NextRequest) { } if (authRequestId) { - const { authRequest } = await getAuthRequest({ serviceUrl, authRequestId }); + const { authRequest } = await getAuthRequest({ + serviceUrl, + serviceRegion, + authRequestId, + }); let organization = ""; let suffix = ""; @@ -346,6 +363,7 @@ export async function GET(request: NextRequest) { if (orgDomain) { const orgs = await getOrgsByDomain({ serviceUrl, + serviceRegion, domain: orgDomain, }); if (orgs.result && orgs.result.length === 1) { @@ -362,6 +380,7 @@ export async function GET(request: NextRequest) { const identityProviders = await getActiveIdentityProviders({ serviceUrl, + serviceRegion, orgId: organization ? organization : undefined, }).then((resp) => { return resp.identityProviders; @@ -387,6 +406,7 @@ export async function GET(request: NextRequest) { return startIdentityProviderFlow({ serviceUrl, + serviceRegion, idpId, urls: { successUrl: @@ -487,6 +507,7 @@ export async function GET(request: NextRequest) { **/ const selectedSession = await findValidSession( serviceUrl, + serviceRegion, sessions, authRequest, ); @@ -516,6 +537,7 @@ export async function GET(request: NextRequest) { const { callbackUrl } = await createCallback({ serviceUrl, + serviceRegion, req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { @@ -529,6 +551,7 @@ export async function GET(request: NextRequest) { // check for loginHint, userId hint and valid sessions let selectedSession = await findValidSession( serviceUrl, + serviceRegion, sessions, authRequest, ); @@ -553,6 +576,7 @@ export async function GET(request: NextRequest) { try { const { callbackUrl } = await createCallback({ serviceUrl, + serviceRegion, req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { diff --git a/apps/login/src/lib/self.ts b/apps/login/src/lib/self.ts index 3c7482f43e..178583fb40 100644 --- a/apps/login/src/lib/self.ts +++ b/apps/login/src/lib/self.ts @@ -32,6 +32,7 @@ export async function setMyPassword({ const { session } = await getSession({ serviceUrl, + serviceRegion, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index e199bfd896..587c7b4c76 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -39,6 +39,7 @@ export async function createSessionAndUpdateCookie( const createdSession = await createSessionFromChecks({ serviceUrl, + serviceRegion, checks, challenges, lifetime, @@ -47,6 +48,7 @@ export async function createSessionAndUpdateCookie( if (createdSession) { return getSession({ serviceUrl, + serviceRegion, sessionId: createdSession.sessionId, sessionToken: createdSession.sessionToken, }).then((response) => { @@ -101,6 +103,7 @@ export async function createSessionForIdpAndUpdateCookie( const createdSession = await createSessionForUserIdAndIdpIntent({ serviceUrl, + serviceRegion, userId, idpIntent, lifetime, @@ -112,6 +115,7 @@ export async function createSessionForIdpAndUpdateCookie( const { session } = await getSession({ serviceUrl, + serviceRegion, sessionId: createdSession.sessionId, sessionToken: createdSession.sessionToken, }); @@ -163,6 +167,7 @@ export async function setSessionAndUpdateCookie( return setSession({ serviceUrl, + serviceRegion, sessionId: recentCookie.id, sessionToken: recentCookie.token, challenges, @@ -189,6 +194,7 @@ export async function setSessionAndUpdateCookie( return getSession({ serviceUrl, + serviceRegion, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }).then((response) => { diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index daef0aaeb6..c12f518fd3 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -28,6 +28,7 @@ export async function startIDPFlow(command: StartIDPFlowCommand) { return startIdentityProviderFlow({ serviceUrl, + serviceRegion, idpId: command.idpId, urls: { successUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${command.successUrl}`, @@ -73,6 +74,7 @@ export async function createNewSessionFromIdpIntent( const userResponse = await getUserByID({ serviceUrl, + serviceRegion, userId: command.userId, }); @@ -82,6 +84,7 @@ export async function createNewSessionFromIdpIntent( const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: userResponse.user.details?.resourceOwner, }); diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts index 4e9c52ce9e..864c91540e 100644 --- a/apps/login/src/lib/server/invite.ts +++ b/apps/login/src/lib/server/invite.ts @@ -31,6 +31,7 @@ export async function inviteUser(command: InviteUserCommand) { const human = await addHumanUser({ serviceUrl, + serviceRegion, email: command.email, firstName: command.firstName, lastName: command.lastName, @@ -44,6 +45,7 @@ export async function inviteUser(command: InviteUserCommand) { const codeResponse = await createInviteCode({ serviceUrl, + serviceRegion, urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`, userId: human.userId, }); diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index e894dcdd3f..d296ff1cc1 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -43,6 +43,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const loginSettingsByContext = await getLoginSettings({ serviceUrl, + serviceRegion, organization: command.organization, }); @@ -52,6 +53,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { let searchUsersRequest: SearchUsersCommand = { serviceUrl, + serviceRegion, searchValue: command.loginName, organizationId: command.organization, loginSettings: loginSettingsByContext, @@ -73,6 +75,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const redirectUserToSingleIDPIfAvailable = async () => { const identityProviders = await getActiveIdentityProviders({ serviceUrl, + serviceRegion, orgId: command.organization, }).then((resp) => { return resp.identityProviders; @@ -103,6 +106,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const resp = await startIdentityProviderFlow({ serviceUrl, + serviceRegion, idpId: identityProviders[0].id, urls: { successUrl: @@ -161,6 +165,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const resp = await startIdentityProviderFlow({ serviceUrl, + serviceRegion, idpId: idp.id, urls: { successUrl: @@ -186,6 +191,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const userLoginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: user.details?.resourceOwner, }); @@ -244,6 +250,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const methods = await listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId: session.factors?.user?.id, }); @@ -406,6 +413,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const orgLoginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: orgToCheckForDiscovery, }); if (orgLoginSettings?.allowDomainDiscovery) { diff --git a/apps/login/src/lib/server/otp.ts b/apps/login/src/lib/server/otp.ts index 868fee03a5..6d56d0c538 100644 --- a/apps/login/src/lib/server/otp.ts +++ b/apps/login/src/lib/server/otp.ts @@ -64,6 +64,7 @@ export async function setOTP(command: SetOTPCommand) { const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: command.organization, }); diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index c2b2856a92..05a1812f35 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -53,6 +53,7 @@ export async function registerPasskeyLink( const sessionCookie = await getSessionCookieById({ sessionId }); const session = await getSession({ serviceUrl, + serviceRegion, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -73,6 +74,7 @@ export async function registerPasskeyLink( // use session token to add the passkey const registerLink = await createPasskeyRegistrationLink({ serviceUrl, + serviceRegion, userId, }); @@ -82,6 +84,7 @@ export async function registerPasskeyLink( return registerPasskey({ serviceUrl, + serviceRegion, userId, code: registerLink.code, domain: hostname, @@ -109,6 +112,7 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { }); const session = await getSession({ serviceUrl, + serviceRegion, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -120,6 +124,7 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { return zitadelVerifyPasskeyRegistration({ serviceUrl, + serviceRegion, request: create(VerifyPasskeyRegistrationRequestSchema, { passkeyId: command.passkeyId, publicKeyCredential: command.publicKeyCredential, @@ -177,6 +182,7 @@ export async function sendPasskey(command: SendPasskeyCommand) { const userResponse = await getUserByID({ serviceUrl, + serviceRegion, userId: session?.factors?.user?.id, }); diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index ec7d61b7d1..df69c7a29e 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -54,6 +54,7 @@ export async function resetPassword(command: ResetPasswordCommand) { const users = await listUsers({ serviceUrl, + serviceRegion, loginName: command.loginName, organizationId: command.organization, }); @@ -69,6 +70,7 @@ export async function resetPassword(command: ResetPasswordCommand) { return passwordReset({ serviceUrl, + serviceRegion, userId, urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + @@ -101,6 +103,7 @@ export async function sendPassword(command: UpdateSessionCommand) { if (!sessionCookie) { const users = await listUsers({ serviceUrl, + serviceRegion, loginName: command.loginName, organizationId: command.organization, }); @@ -115,6 +118,7 @@ export async function sendPassword(command: UpdateSessionCommand) { loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: command.organization, }); @@ -143,6 +147,7 @@ export async function sendPassword(command: UpdateSessionCommand) { const userResponse = await getUserByID({ serviceUrl, + serviceRegion, userId: session?.factors?.user?.id, }); @@ -156,6 +161,7 @@ export async function sendPassword(command: UpdateSessionCommand) { if (!loginSettings) { loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: command.organization ?? session.factors?.user?.organizationId, }); @@ -201,6 +207,7 @@ export async function sendPassword(command: UpdateSessionCommand) { if (command.checks && command.checks.password && session.factors?.user?.id) { const response = await listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId: session.factors.user.id, }); if (response.authMethodTypes && response.authMethodTypes.length) { @@ -267,6 +274,7 @@ export async function changePassword(command: { return setUserPassword({ serviceUrl, + serviceRegion, userId, password: command.password, user, @@ -290,6 +298,7 @@ export async function checkSessionAndSetPassword({ const { session } = await getSession({ serviceUrl, + serviceRegion, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -308,6 +317,7 @@ export async function checkSessionAndSetPassword({ // check if the user has no password set in order to set a password const authmethods = await listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId: session.factors.user.id, }); @@ -328,6 +338,7 @@ export async function checkSessionAndSetPassword({ const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: session.factors.user.organizationId, }); @@ -359,6 +370,7 @@ export async function checkSessionAndSetPassword({ const selfService = await myUserService( serviceUrl, + serviceRegion, `${sessionCookie.token}`, ); diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index dd8d6d2031..2a23af3073 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -38,6 +38,7 @@ export async function registerUser(command: RegisterUserCommand) { const addResponse = await addHumanUser({ serviceUrl, + serviceRegion, email: command.email, firstName: command.firstName, lastName: command.lastName, @@ -51,6 +52,7 @@ export async function registerUser(command: RegisterUserCommand) { const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: command.organization, }); @@ -92,6 +94,7 @@ export async function registerUser(command: RegisterUserCommand) { } else { const userResponse = await getUserByID({ serviceUrl, + serviceRegion, userId: session?.factors?.user?.id, }); diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 658ba41fe8..e088062f08 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -29,6 +29,7 @@ export async function continueWithSession({ const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: session.factors?.user?.organizationId, }); @@ -131,6 +132,7 @@ export async function updateSession(options: UpdateSessionCommand) { if (checks && checks.password && session.factors?.user?.id) { const response = await listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId: session.factors.user.id, }); if (response.authMethodTypes && response.authMethodTypes.length) { @@ -160,6 +162,7 @@ export async function clearSession(options: ClearSessionOptions) { const deletedSession = await deleteSession({ serviceUrl, + serviceRegion, sessionId: session.id, sessionToken: session.token, }); @@ -181,6 +184,7 @@ export async function cleanupSession({ sessionId }: CleanupSessionCommand) { const deleteResponse = await deleteSession({ serviceUrl, + serviceRegion, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index 223a5fa78f..0b5e188bd9 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -38,6 +38,7 @@ export async function addU2F(command: RegisterU2FCommand) { const session = await getSession({ serviceUrl, + serviceRegion, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); @@ -82,6 +83,7 @@ export async function verifyU2F(command: VerifyU2FCommand) { const session = await getSession({ serviceUrl, + serviceRegion, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }); diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index befad512ef..2ab94a9252 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -34,6 +34,7 @@ export async function verifyTOTP( return loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams: { loginName, organization, @@ -42,6 +43,7 @@ export async function verifyTOTP( if (session?.factors?.user?.id) { return verifyTOTPRegistration({ serviceUrl, + serviceRegion, code, userId: session.factors.user.id, }); @@ -67,6 +69,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { const verifyResponse = command.isInvite ? await verifyInviteCode({ serviceUrl, + serviceRegion, userId: command.userId, verificationCode: command.code, }).catch(() => { @@ -74,6 +77,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { }) : await verifyEmail({ serviceUrl, + serviceRegion, userId: command.userId, verificationCode: command.code, }).catch(() => { @@ -105,6 +109,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { session = await getSession({ serviceUrl, + serviceRegion, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }).then((response) => { @@ -119,6 +124,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { const userResponse = await getUserByID({ serviceUrl, + serviceRegion, userId: session?.factors?.user?.id, }); @@ -130,6 +136,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { } else { const userResponse = await getUserByID({ serviceUrl, + serviceRegion, userId: command.userId, }); @@ -169,11 +176,13 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: user.details?.resourceOwner, }); const authMethodResponse = await listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId: user.userId, }); @@ -252,10 +261,11 @@ export async function resendVerification(command: resendVerifyEmailCommand) { } return command.isInvite - ? resendInviteCode({ serviceUrl, userId: command.userId }) + ? resendInviteCode({ serviceUrl, serviceRegion, userId: command.userId }) : resendEmailCode({ userId: command.userId, serviceUrl, + serviceRegion, urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + (command.authRequestId @@ -266,14 +276,16 @@ export async function resendVerification(command: resendVerifyEmailCommand) { type sendEmailCommand = { serviceUrl: string; + serviceRegion: string; userId: string; urlTemplate: string; }; export async function sendEmailCode(command: sendEmailCommand) { return zitadelSendEmailCode({ - userId: command.userId, serviceUrl: command.serviceUrl, + serviceRegion: command.serviceRegion, + userId: command.userId, urlTemplate: command.urlTemplate, }); } @@ -313,6 +325,7 @@ export async function sendVerificationRedirectWithoutCheck( session = await getSession({ serviceUrl, + serviceRegion, sessionId: sessionCookie.id, sessionToken: sessionCookie.token, }).then((response) => { @@ -327,6 +340,7 @@ export async function sendVerificationRedirectWithoutCheck( const userResponse = await getUserByID({ serviceUrl, + serviceRegion, userId: session?.factors?.user?.id, }); @@ -338,6 +352,7 @@ export async function sendVerificationRedirectWithoutCheck( } else if ("userId" in command) { const userResponse = await getUserByID({ serviceUrl, + serviceRegion, userId: command.userId, }); @@ -377,6 +392,7 @@ export async function sendVerificationRedirectWithoutCheck( const authMethodResponse = await listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId: user.userId, }); @@ -402,6 +418,7 @@ export async function sendVerificationRedirectWithoutCheck( const loginSettings = await getLoginSettings({ serviceUrl, + serviceRegion, organization: user.details?.resourceOwner, }); diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index b1042a4e2e..0f1dd0256e 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -30,7 +30,7 @@ export async function createServiceForHost( process.env.QA_SYSTEM_USER_ID && process.env.QA_SYSTEM_USER_PRIVATE_KEY ) { - token = await systemAPIToken(serviceRegion); + token = await systemAPIToken({ serviceRegion }); } else if (process.env.ZITADEL_SERVICE_USER_TOKEN) { token = process.env.ZITADEL_SERVICE_USER_TOKEN; } diff --git a/apps/login/src/lib/session.ts b/apps/login/src/lib/session.ts index 758594845c..58fe1d20df 100644 --- a/apps/login/src/lib/session.ts +++ b/apps/login/src/lib/session.ts @@ -5,6 +5,7 @@ import { getSession } from "./zitadel"; type LoadMostRecentSessionParams = { serviceUrl: string; + serviceRegion: string; sessionParams: { loginName?: string; organization?: string; @@ -13,6 +14,7 @@ type LoadMostRecentSessionParams = { export async function loadMostRecentSession({ serviceUrl, + serviceRegion, sessionParams, }: LoadMostRecentSessionParams): Promise { const recent = await getMostRecentCookieWithLoginname({ @@ -22,6 +24,7 @@ export async function loadMostRecentSession({ return getSession({ serviceUrl, + serviceRegion, sessionId: recent.id, sessionToken: recent.token, }).then((resp: GetSessionResponse) => resp.session); diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index f24e5f73df..ba96481e38 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -1184,6 +1184,7 @@ export async function setUserPassword({ if (!code) { const authmethods = await listAuthenticationMethodTypes({ serviceUrl, + serviceRegion, userId, }); From dbeb1f7936be139c9ee6f07c794239b8cb9f67c4 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 30 Jan 2025 08:27:52 +0100 Subject: [PATCH 59/67] dynamic env for region --- apps/login/next-env-vars.d.ts | 33 +++---------------- apps/login/src/lib/api.ts | 61 +++++++++++++---------------------- apps/login/src/lib/service.ts | 6 ++-- turbo.json | 12 +++---- 4 files changed, 35 insertions(+), 77 deletions(-) diff --git a/apps/login/next-env-vars.d.ts b/apps/login/next-env-vars.d.ts index 61bf0c5b06..e3ce2b4d71 100644 --- a/apps/login/next-env-vars.d.ts +++ b/apps/login/next-env-vars.d.ts @@ -1,34 +1,9 @@ declare namespace NodeJS { interface ProcessEnv { - /** - * Multitenancy: The system api url - */ - QA_AUDIENCE: string; - - /** - * Multitenancy: The service user id - */ - QA_SYSTEM_USER_ID: string; - - /** - * Multitenancy: The service user private key - */ - QA_SYSTEM_USER_PRIVATE_KEY: string; - - /** - * Multitenancy: The system api url for prod environment - */ - PROD_AUDIENCE: string; - - /** - * Multitenancy: The service user id for prod environment - */ - PROD_SYSTEM_USER_ID: string; - - /** - * Multitenancy: The service user private key for prod environment - */ - PROD_SYSTEM_USER_PRIVATE_KEY: string; + // Allow any environment variable that matches the pattern + [key: `${string}_AUDIENCE`]: string; // The system api url + [key: `${string}_AUDIENCE`]: string; // The service user id + [key: `${string}_AUDIENCE`]: string; // The service user private key /** * Self hosting: The instance url diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index 11acd701e0..d865cf19bc 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -5,48 +5,31 @@ export async function systemAPIToken({ }: { serviceRegion: string; }) { - const QA = { - audience: process.env.QA_AUDIENCE, - userID: process.env.QA_SYSTEM_USER_ID, - token: Buffer.from( - process.env.QA_SYSTEM_USER_PRIVATE_KEY, - "base64", - ).toString("utf-8"), - }; + const REGIONS = ["eu1", "us1"].map((region) => { + return { + id: region, + audience: process.env[region + "_AUDIENCE"], + userID: process.env[region + "_SYSTEM_USER_ID"], + token: Buffer.from( + process.env[ + region.toUpperCase() + "_SYSTEM_USER_PRIVATE_KEY" + ] as string, + "base64", + ).toString("utf-8"), + }; + }); - const PROD = { - audience: process.env.QA_AUDIENCE, - userID: process.env.QA_SYSTEM_USER_ID, - token: Buffer.from( - process.env.PROD_SYSTEM_USER_PRIVATE_KEY, - "base64", - ).toString("utf-8"), - }; + const region = REGIONS.find((region) => region.id === serviceRegion); - let token; - - switch (serviceRegion) { - case "eu1": - token = newSystemToken({ - audience: QA.audience, - subject: QA.userID, - key: QA.token, - }); - break; - case "us1": - token = newSystemToken({ - audience: PROD.audience, - subject: PROD.userID, - key: PROD.token, - }); - break; - default: - token = newSystemToken({ - audience: QA.audience, - subject: QA.userID, - key: QA.token, - }); + if (!region || !region.audience || !region.userID || !region.token) { + throw new Error("Invalid region"); } + const token = newSystemToken({ + audience: region.audience, + subject: region.userID, + key: region.token, + }); + return token; } diff --git a/apps/login/src/lib/service.ts b/apps/login/src/lib/service.ts index 0f1dd0256e..e543f35467 100644 --- a/apps/login/src/lib/service.ts +++ b/apps/login/src/lib/service.ts @@ -26,9 +26,9 @@ export async function createServiceForHost( // if we are running in a multitenancy context, use the system user token if ( - process.env.QA_AUDIENCE && - process.env.QA_SYSTEM_USER_ID && - process.env.QA_SYSTEM_USER_PRIVATE_KEY + process.env[serviceRegion + "_AUDIENCE"] && + process.env[serviceRegion + "_SYSTEM_USER_ID"] && + process.env[serviceRegion + "_SYSTEM_USER_PRIVATE_KEY"] ) { token = await systemAPIToken({ serviceRegion }); } else if (process.env.ZITADEL_SERVICE_USER_TOKEN) { diff --git a/turbo.json b/turbo.json index b342c3f52a..ff78533f40 100644 --- a/turbo.json +++ b/turbo.json @@ -6,12 +6,12 @@ "DEBUG", "VERCEL_URL", "EMAIL_VERIFICATION", - "QA_AUDIENCE", - "QA_SYSTEM_USER_ID", - "QA_SYSTEM_USER_PRIVATE_KEY", - "PROD_AUDIENCE", - "PROD_SYSTEM_USER_ID", - "PROD_SYSTEM_USER_PRIVATE_KEY", + "EU1_AUDIENCE", + "EU1_SYSTEM_USER_ID", + "EU1_SYSTEM_USER_PRIVATE_KEY", + "US1_AUDIENCE", + "US1_SYSTEM_USER_ID", + "US1_SYSTEM_USER_PRIVATE_KEY", "ZITADEL_API_URL", "ZITADEL_SERVICE_USER_ID", "ZITADEL_SERVICE_USER_TOKEN", From 90a222b12ade7ae6f0a0e1048c3dac03eed7afd9 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 30 Jan 2025 08:35:45 +0100 Subject: [PATCH 60/67] dynamic vars --- apps/login/next-env-vars.d.ts | 8 +++++-- apps/login/src/lib/api.ts | 43 +++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/apps/login/next-env-vars.d.ts b/apps/login/next-env-vars.d.ts index e3ce2b4d71..35fcb10919 100644 --- a/apps/login/next-env-vars.d.ts +++ b/apps/login/next-env-vars.d.ts @@ -2,8 +2,12 @@ declare namespace NodeJS { interface ProcessEnv { // Allow any environment variable that matches the pattern [key: `${string}_AUDIENCE`]: string; // The system api url - [key: `${string}_AUDIENCE`]: string; // The service user id - [key: `${string}_AUDIENCE`]: string; // The service user private key + [key: `${string}_SYSTEM_USER_ID`]: string; // The service user id + [key: `${string}_SYSTEM_USER_PRIVATE_KEY`]: string; // The service user private key + + AUDIENCE: string; // The fallback system api url + SYSTEM_USER_ID: string; // The fallback service user id + SYSTEM_USER_PRIVATE_KEY: string; // The fallback service user private key /** * Self hosting: The instance url diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index d865cf19bc..15e85fd2b6 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -5,31 +5,36 @@ export async function systemAPIToken({ }: { serviceRegion: string; }) { - const REGIONS = ["eu1", "us1"].map((region) => { - return { - id: region, - audience: process.env[region + "_AUDIENCE"], - userID: process.env[region + "_SYSTEM_USER_ID"], + const prefix = serviceRegion.toUpperCase(); + const token = { + audience: process.env[prefix + "_AUDIENCE"], + userID: process.env[prefix + "_SYSTEM_USER_ID"], + token: Buffer.from( + process.env[prefix.toUpperCase() + "_SYSTEM_USER_PRIVATE_KEY"] as string, + "base64", + ).toString("utf-8"), + }; + + if (!token.audience || !token.userID || !token.token) { + const fallbackToken = { + audience: process.env.AUDIENCE, + userID: process.env.SYSTEM_USER_ID, token: Buffer.from( - process.env[ - region.toUpperCase() + "_SYSTEM_USER_PRIVATE_KEY" - ] as string, + process.env.SYSTEM_USER_PRIVATE_KEY, "base64", ).toString("utf-8"), }; - }); - const region = REGIONS.find((region) => region.id === serviceRegion); - - if (!region || !region.audience || !region.userID || !region.token) { - throw new Error("Invalid region"); + return newSystemToken({ + audience: fallbackToken.audience, + subject: fallbackToken.userID, + key: fallbackToken.token, + }); } - const token = newSystemToken({ - audience: region.audience, - subject: region.userID, - key: region.token, + return newSystemToken({ + audience: token.audience, + subject: token.userID, + key: token.token, }); - - return token; } From 3fe1cef651b0cabbd6b5336091dbe446a1130cd1 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 30 Jan 2025 09:15:32 +0100 Subject: [PATCH 61/67] add fallback to turbo.json --- turbo.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/turbo.json b/turbo.json index ff78533f40..bf02da7ea0 100644 --- a/turbo.json +++ b/turbo.json @@ -12,6 +12,9 @@ "US1_AUDIENCE", "US1_SYSTEM_USER_ID", "US1_SYSTEM_USER_PRIVATE_KEY", + "AUDIENCE", + "SYSTEM_USER_ID", + "SYSTEM_USER_PRIVATE_KEY", "ZITADEL_API_URL", "ZITADEL_SERVICE_USER_ID", "ZITADEL_SERVICE_USER_TOKEN", From d5c3bf2d4de5db25bf71e1fad708a482898a0ebc Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 30 Jan 2025 09:47:25 +0100 Subject: [PATCH 62/67] fix build --- .../(login)/idp/[provider]/failure/page.tsx | 6 +++- apps/login/src/lib/server/loginname.ts | 24 ++++++++++----- apps/login/src/lib/server/passkeys.ts | 6 +++- apps/login/src/lib/server/password.ts | 29 +++++++++++-------- apps/login/src/lib/server/session.ts | 6 +++- apps/login/src/lib/server/u2f.ts | 4 +-- 6 files changed, 51 insertions(+), 24 deletions(-) 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 6a62e2515e..c1e4de1b26 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -27,7 +27,11 @@ export default async function Page(props: { const _headers = await headers(); const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); - const branding = await getBrandingSettings({ serviceUrl, organization }); + const branding = await getBrandingSettings({ + serviceUrl, + serviceRegion, + organization, + }); return ( diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index d296ff1cc1..18070ab76c 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -125,11 +125,13 @@ export async function sendLoginname(command: SendLoginnameCommand) { }; const redirectUserToIDP = async (userId: string) => { - const identityProviders = await listIDPLinks({ serviceUrl, userId }).then( - (resp) => { - return resp.result; - }, - ); + const identityProviders = await listIDPLinks({ + serviceUrl, + serviceRegion, + userId, + }).then((resp) => { + return resp.result; + }); if (identityProviders.length === 1) { const _headers = await headers(); @@ -142,7 +144,11 @@ export async function sendLoginname(command: SendLoginnameCommand) { const identityProviderId = identityProviders[0].idpId; - const idp = await getIDPByID({ serviceUrl, id: identityProviderId }); + const idp = await getIDPByID({ + serviceUrl, + serviceRegion, + id: identityProviderId, + }); const idpType = idp?.type; @@ -407,7 +413,11 @@ export async function sendLoginname(command: SendLoginnameCommand) { const suffix = matched?.[1] ?? ""; // this just returns orgs where the suffix is set as primary domain - const orgs = await getOrgsByDomain({ serviceUrl, domain: suffix }); + const orgs = await getOrgsByDomain({ + serviceUrl, + serviceRegion, + domain: suffix, + }); const orgToCheckForDiscovery = orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index 05a1812f35..819f319bd4 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -160,7 +160,11 @@ export async function sendPasskey(command: SendPasskeyCommand) { const _headers = await headers(); const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); - const loginSettings = await getLoginSettings({ serviceUrl, organization }); + const loginSettings = await getLoginSettings({ + serviceUrl, + serviceRegion, + organization, + }); const lifetime = checks?.webAuthN ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index df69c7a29e..15459fa734 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -265,7 +265,11 @@ export async function changePassword(command: { const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers); // check for init state - const { user } = await getUserByID({ serviceUrl, userId: command.userId }); + const { user } = await getUserByID({ + serviceUrl, + serviceRegion, + userId: command.userId, + }); if (!user || user.userId !== command.userId) { return { error: "Could not send Password Reset Link" }; @@ -348,29 +352,30 @@ export async function checkSessionAndSetPassword({ // if the user has no MFA but MFA is enforced, we can set a password otherwise we use the token of the user if (forceMfa && hasNoMFAMethods) { - return setPassword({ serviceUrl, payload }).catch((error) => { - // throw error if failed precondition (ex. User is not yet initialized) - if (error.code === 9 && error.message) { - return { error: "Failed precondition" }; - } else { - throw error; - } - }); + return setPassword({ serviceUrl, serviceRegion, payload }).catch( + (error) => { + // throw error if failed precondition (ex. User is not yet initialized) + if (error.code === 9 && error.message) { + return { error: "Failed precondition" }; + } else { + throw error; + } + }, + ); } else { - const transport = async (host: string, token: string) => { + const transport = async (serviceUrl: string, token: string) => { return createServerTransport(token, { baseUrl: serviceUrl, }); }; - const myUserService = async (host: string, sessionToken: string) => { + const myUserService = async (serviceUrl: string, sessionToken: string) => { const transportPromise = await transport(serviceUrl, sessionToken); return createUserServiceClient(transportPromise); }; const selfService = await myUserService( serviceUrl, - serviceRegion, `${sessionCookie.token}`, ); diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index e088062f08..7f71ec8f14 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -107,7 +107,11 @@ export async function updateSession(options: UpdateSessionCommand) { challenges.webAuthN.domain = hostname; } - const loginSettings = await getLoginSettings({ serviceUrl, organization }); + const loginSettings = await getLoginSettings({ + serviceUrl, + serviceRegion, + organization, + }); const lifetime = checks?.webAuthN ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey diff --git a/apps/login/src/lib/server/u2f.ts b/apps/login/src/lib/server/u2f.ts index 0b5e188bd9..60f1c12b66 100644 --- a/apps/login/src/lib/server/u2f.ts +++ b/apps/login/src/lib/server/u2f.ts @@ -55,7 +55,7 @@ export async function addU2F(command: RegisterU2FCommand) { return { error: "Could not get session" }; } - return registerU2F({ serviceUrl, userId, domain: hostname }); + return registerU2F({ serviceUrl, serviceRegion, userId, domain: hostname }); } export async function verifyU2F(command: VerifyU2FCommand) { @@ -101,5 +101,5 @@ export async function verifyU2F(command: VerifyU2FCommand) { userId, }); - return verifyU2FRegistration({ serviceUrl, request }); + return verifyU2FRegistration({ serviceUrl, serviceRegion, request }); } From 905fe300e05ccc56e6082eb9a23a733c4ee3272e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Jan 2025 10:15:05 +0000 Subject: [PATCH 63/67] Version Packages --- .changeset/twenty-clouds-prove.md | 5 ----- packages/zitadel-client/CHANGELOG.md | 6 ++++++ packages/zitadel-client/package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/twenty-clouds-prove.md diff --git a/.changeset/twenty-clouds-prove.md b/.changeset/twenty-clouds-prove.md deleted file mode 100644 index 58877517c6..0000000000 --- a/.changeset/twenty-clouds-prove.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@zitadel/client": patch ---- - -dynamic properties for system token utility diff --git a/packages/zitadel-client/CHANGELOG.md b/packages/zitadel-client/CHANGELOG.md index 8e4b17643a..0144cbc0dd 100644 --- a/packages/zitadel-client/CHANGELOG.md +++ b/packages/zitadel-client/CHANGELOG.md @@ -1,5 +1,11 @@ # @zitadel/client +## 1.0.4 + +### Patch Changes + +- 28dc956: dynamic properties for system token utility + ## 1.0.3 ### Patch Changes diff --git a/packages/zitadel-client/package.json b/packages/zitadel-client/package.json index e6b5323f2f..f3e1cbbce9 100644 --- a/packages/zitadel-client/package.json +++ b/packages/zitadel-client/package.json @@ -1,6 +1,6 @@ { "name": "@zitadel/client", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "publishConfig": { "access": "public" From 2ef9c238ac12ed0d4f226a3f17a2771db9fd3c3a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 30 Jan 2025 11:24:09 +0100 Subject: [PATCH 64/67] trigger From 79dcef37859a0872a91eae1741831750de3d342b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 30 Jan 2025 11:48:17 +0100 Subject: [PATCH 65/67] fix build --- apps/login/src/lib/server/password.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index b1d123fb50..f9a0931b6b 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -133,9 +133,11 @@ export async function sendPassword(command: UpdateSessionCommand) { ); } catch (error: any) { if ("failedAttempts" in error && error.failedAttempts) { - const lockoutSettings = await getLockoutSettings( - command.organization, - ); + const lockoutSettings = await getLockoutSettings({ + serviceUrl, + serviceRegion, + orgId: command.organization, + }); return { error: @@ -163,7 +165,11 @@ export async function sendPassword(command: UpdateSessionCommand) { ); } catch (error: any) { if ("failedAttempts" in error && error.failedAttempts) { - const lockoutSettings = await getLockoutSettings(command.organization); + const lockoutSettings = await getLockoutSettings({ + serviceUrl, + serviceRegion, + orgId: command.organization, + }); return { error: From 7c9c0e9e6934f8edb8e222db09af4f828a5fe7d0 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 31 Jan 2025 09:21:44 +0100 Subject: [PATCH 66/67] chore: update next canary --- apps/login/package.json | 2 +- pnpm-lock.yaml | 115 +++++++++++++++++++++------------------- 2 files changed, 62 insertions(+), 55 deletions(-) diff --git a/apps/login/package.json b/apps/login/package.json index 0f913c2553..6b71bca77d 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -46,7 +46,7 @@ "deepmerge": "^4.3.1", "jose": "^5.3.0", "moment": "^2.29.4", - "next": "15.0.4-canary.23", + "next": "15.2.0-canary.33", "next-intl": "^3.25.1", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b471e9e8ee..007c6d6003 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,7 +85,7 @@ importers: version: 0.5.7(tailwindcss@3.4.14) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0) + version: 1.3.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -108,14 +108,14 @@ importers: specifier: ^2.29.4 version: 2.30.1 next: - specifier: 15.0.4-canary.23 - version: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + specifier: 15.2.0-canary.33 + version: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) next-intl: specifier: ^3.25.1 - version: 3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0) + version: 3.25.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 0.2.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -1133,56 +1133,56 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@next/env@15.0.4-canary.23': - resolution: {integrity: sha512-NfBMRPa10yaEzQ693kGEsgHL58Y27jSbGCDbyXy14dx3z6UeQZQfEVRAwJ4iG1V6gND9+CzzugtiXvJZfSlC9A==} + '@next/env@15.2.0-canary.33': + resolution: {integrity: sha512-y3EPM+JYKU8t2K+i6bc0QrotEZVGpqu9eVjprj4cfS8QZyZcL54s+W9aGB0TBuGavU9tQdZ50W186+toeMV+hw==} '@next/eslint-plugin-next@14.2.18': resolution: {integrity: sha512-KyYTbZ3GQwWOjX3Vi1YcQbekyGP0gdammb7pbmmi25HBUCINzDReyrzCMOJIeZisK1Q3U6DT5Rlc4nm2/pQeXA==} - '@next/swc-darwin-arm64@15.0.4-canary.23': - resolution: {integrity: sha512-sX3MaDUiFiMT14KSx5mJz6B+IH9k7+buNniNrDP7iz4YG28jssm9e8uHbiWXsbn9jnkQUJu8PHoUOLhgjZgtsQ==} + '@next/swc-darwin-arm64@15.2.0-canary.33': + resolution: {integrity: sha512-+fCdK2KmR6lWoCTk1fSd5pvbiLZHfZF+D/Xdz3xrXw+pbnBtXWLKQrPT0bCtDseMxD31qcOywq5mAApvI3EGpA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.0.4-canary.23': - resolution: {integrity: sha512-KJRSDVvEPuvjRKe9IY3YMAv9KMOmB/U5+7g0c3OTT/50x1KL0XOlgnc+Af2GdZKIrkKiAdTFG54AHaSD584yHg==} + '@next/swc-darwin-x64@15.2.0-canary.33': + resolution: {integrity: sha512-GrrU+tSmeBRow+7bnn7i5M96g3tc28hPH5t5Y65qUXGmmrZwGZN1e1d+8QbXPdAGkvjEPcOkUNQuQVpp1qpYPA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.0.4-canary.23': - resolution: {integrity: sha512-0EqeqGdlG0MPDYGE/cPtTvBLtBiWDAd7fSRgRhIga6CkuaRVFKuTeRrsjTa0v+51C2OawjQp2N3ww1zBLuBhcg==} + '@next/swc-linux-arm64-gnu@15.2.0-canary.33': + resolution: {integrity: sha512-8RnGxnUpASHoUf6aHUifmZom5b4Ow5nTdCib/CNYXZ6VLuL5ocvmr+DXs/SKzi9h8OHR7JkLwKXHCcF8WyscSg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.0.4-canary.23': - resolution: {integrity: sha512-O06Gw8HU0z9f1b4TiGb0u1o87hgLa0yEW1odyLPE1d3+JKwhkh4L1Ug9uLpeqEUnxCoIrwVomEUyQBPGNQtq0Q==} + '@next/swc-linux-arm64-musl@15.2.0-canary.33': + resolution: {integrity: sha512-COyE0LzMuLBZSR+Z/TOGilyJPdwSU588Vt0+o8GoECkoDEnjyuO2s2nHa2kDAcEfUEPkhlo0tErU3mF+8AVOTQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.0.4-canary.23': - resolution: {integrity: sha512-BvERc3hri6eyUHnasZgwcRCdR8WpfCdKKe/M12Q+ZAkTeJeVkLXNakznaZbBWdlCc77F/NeHz/OWoQWUTpKm3g==} + '@next/swc-linux-x64-gnu@15.2.0-canary.33': + resolution: {integrity: sha512-3Y9lqJs+ftU9jgbLdCtvAvF8MNJsJYGMH7icb8QMs1+yOyHHbmwkZoElKdjwfUWzQ2sX28ywp73GWq4HbrsoUg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.0.4-canary.23': - resolution: {integrity: sha512-FF5LNTdra/tHxdHjRR3lb+UxFgRVT+v3EMruueQg6BpOqpciodyCkkYQFrx2DitpADojQ6bBBFBDs6KIb8jB5w==} + '@next/swc-linux-x64-musl@15.2.0-canary.33': + resolution: {integrity: sha512-FS9iA+RkZlhdWGQEKtsplVBXIYZJUn5nsRB+1UY46b3uaL6dDypu13ODaSwYuAwXGgkrZBVF9AFO3y4biBnPlA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.0.4-canary.23': - resolution: {integrity: sha512-XnHD7fqQYZR1XCCuAf8+yAdkMpzAFz2pWmny2K6g5C7BalrwNuxWLsM5LycW1PTMzSqkzLJeXCG6AZu099u7/w==} + '@next/swc-win32-arm64-msvc@15.2.0-canary.33': + resolution: {integrity: sha512-Ji9CtBbUx06qvvN/rPohJN2FEFGsUv26F50f2nMRYRwrq3POXDjloGOiRocrjU0ty/cUzCz71qTUfKdmv/ajmg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.0.4-canary.23': - resolution: {integrity: sha512-HGoW8LjYxbUhkND+vJ/21dWQ7sdv4SIUQDv2r/FpcdHFMzb5M/jgQVqcMFkqg2ibH65ZAcVBM0ICcUnTLlX7PQ==} + '@next/swc-win32-x64-msvc@15.2.0-canary.33': + resolution: {integrity: sha512-hjdbGnkwIZ8zN2vlS6lNsEJO37HRtcEGimzfkruBMsi/DwJBqkJvZbNC/XCJy3HFcU58igncqV52p1IPjmAJAw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1479,6 +1479,9 @@ packages: '@swc/helpers@0.5.13': resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} @@ -3468,16 +3471,16 @@ packages: react: '*' react-dom: '*' - next@15.0.4-canary.23: - resolution: {integrity: sha512-xCjjBx4csWdG4MP9tKV/C25OIDbN0o+zovMC5zd4yvE4lrd43Y5tt+w171IGUueb6VbPLTSlDaXvqOtrxKJXzQ==} + next@15.2.0-canary.33: + resolution: {integrity: sha512-WF8QLeYkakuYwksdWY/F+Bi8tNJfIbiSYk9hCmldn9sNp1lU3lqI1hrW1ynbcMSaXC+qQEr7yol2OdvVZ4nZYQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 '@playwright/test': ^1.41.2 babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-380f5d67-20241113 - react-dom: ^18.2.0 || 19.0.0-rc-380f5d67-20241113 + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': @@ -5250,7 +5253,7 @@ snapshots: '@emnapi/runtime@1.3.1': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 optional: true '@esbuild/aix-ppc64@0.21.5': @@ -5657,34 +5660,34 @@ snapshots: - encoding - supports-color - '@next/env@15.0.4-canary.23': {} + '@next/env@15.2.0-canary.33': {} '@next/eslint-plugin-next@14.2.18': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@15.0.4-canary.23': + '@next/swc-darwin-arm64@15.2.0-canary.33': optional: true - '@next/swc-darwin-x64@15.0.4-canary.23': + '@next/swc-darwin-x64@15.2.0-canary.33': optional: true - '@next/swc-linux-arm64-gnu@15.0.4-canary.23': + '@next/swc-linux-arm64-gnu@15.2.0-canary.33': optional: true - '@next/swc-linux-arm64-musl@15.0.4-canary.23': + '@next/swc-linux-arm64-musl@15.2.0-canary.33': optional: true - '@next/swc-linux-x64-gnu@15.0.4-canary.23': + '@next/swc-linux-x64-gnu@15.2.0-canary.33': optional: true - '@next/swc-linux-x64-musl@15.0.4-canary.23': + '@next/swc-linux-x64-musl@15.2.0-canary.33': optional: true - '@next/swc-win32-arm64-msvc@15.0.4-canary.23': + '@next/swc-win32-arm64-msvc@15.2.0-canary.33': optional: true - '@next/swc-win32-x64-msvc@15.0.4-canary.23': + '@next/swc-win32-x64-msvc@15.2.0-canary.33': optional: true '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': @@ -5919,6 +5922,10 @@ snapshots: dependencies: tslib: 2.7.0 + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 @@ -6140,11 +6147,11 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)': + '@vercel/analytics@1.3.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) react: 19.0.0 '@vercel/git-hooks@1.0.0': {} @@ -8210,25 +8217,25 @@ snapshots: negotiator@1.0.0: {} - next-intl@3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0): + next-intl@3.25.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 1.0.0 - next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) react: 19.0.0 use-intl: 3.25.1(react@19.0.0) - next-themes@0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next-themes@0.2.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7): + next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7): dependencies: - '@next/env': 15.0.4-canary.23 + '@next/env': 15.2.0-canary.33 '@swc/counter': 0.1.3 - '@swc/helpers': 0.5.13 + '@swc/helpers': 0.5.15 busboy: 1.6.0 caniuse-lite: 1.0.30001680 postcss: 8.4.31 @@ -8236,14 +8243,14 @@ snapshots: react-dom: 19.0.0(react@19.0.0) styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.0.4-canary.23 - '@next/swc-darwin-x64': 15.0.4-canary.23 - '@next/swc-linux-arm64-gnu': 15.0.4-canary.23 - '@next/swc-linux-arm64-musl': 15.0.4-canary.23 - '@next/swc-linux-x64-gnu': 15.0.4-canary.23 - '@next/swc-linux-x64-musl': 15.0.4-canary.23 - '@next/swc-win32-arm64-msvc': 15.0.4-canary.23 - '@next/swc-win32-x64-msvc': 15.0.4-canary.23 + '@next/swc-darwin-arm64': 15.2.0-canary.33 + '@next/swc-darwin-x64': 15.2.0-canary.33 + '@next/swc-linux-arm64-gnu': 15.2.0-canary.33 + '@next/swc-linux-arm64-musl': 15.2.0-canary.33 + '@next/swc-linux-x64-gnu': 15.2.0-canary.33 + '@next/swc-linux-x64-musl': 15.2.0-canary.33 + '@next/swc-win32-arm64-msvc': 15.2.0-canary.33 + '@next/swc-win32-x64-msvc': 15.2.0-canary.33 '@playwright/test': 1.48.2 sass: 1.80.7 sharp: 0.33.5 From 3021332ba120863a111d80e5ace789a1b3264ba9 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 31 Jan 2025 15:26:03 +0100 Subject: [PATCH 67/67] fix build --- apps/login/src/lib/server/password.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index f9a0931b6b..9a464e22d8 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -215,9 +215,11 @@ export async function sendPassword(command: UpdateSessionCommand) { const humanUser = user.type.case === "human" ? user.type.value : undefined; - const expirySettings = await getPasswordExpirySettings( - command.organization ?? session.factors?.user?.organizationId, - ); + const expirySettings = await getPasswordExpirySettings({ + serviceUrl, + serviceRegion, + orgId: command.organization ?? session.factors?.user?.organizationId, + }); // check if the user has to change password first const passwordChangedCheck = checkPasswordChangeRequired(