mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-26 01:02:16 +00:00
feat(login): redirect configuration options in multi-domain setups (#11527)
# Which Problems Are Solved This PR introduces robust redirect configuration options and enhances security for Server Actions in multi-domain setups. # How the Problems Are Solved - Redirect Fallback Logic: Implemented a unified `resolveRedirectUri` utility that prioritizes redirect targets as follows: - Environment override (`DEFAULT_REDIRECT_URI`) - Organization settings (`defaultRedirectUri`). - Relative fallback (`/signedin?...`). - Host Reflection: `DEFAULT_REDIRECT_URI` supports absolute URLs or relative paths via host reflection. - Server Action Security: Configured `serverActions.allowedOrigins` to be dynamically manageable via the `SERVER_ACTION_ALLOWED_ORIGINS` environment variable (comma-separated list). It defaults to an empty list for maximum security. - UI Integration: Updated the /signedin page to use the unified redirect logic, ensuring consistent behavior for the "Continue" button. - Documentation: Added type definitions and detailed descriptions for new environment variables in `next-env-vars.d.ts` --------- Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Vendored
+15
@@ -45,6 +45,21 @@ declare namespace NodeJS {
|
||||
*/
|
||||
NEXT_PUBLIC_APPLICATION_NAME?: string;
|
||||
|
||||
/**
|
||||
* Optional: override the redirect URI after successful login.
|
||||
* If the value starts with "/", it will be used as a relative path and prepended with the host of the request (useful for rewrites).
|
||||
* Otherwise, it will use the value as an absolute redirect URI.
|
||||
* Takes precedence over organization settings.
|
||||
*/
|
||||
DEFAULT_REDIRECT_URI?: string;
|
||||
|
||||
/**
|
||||
* Optional: Comma-separated list of additional allowed origins for Server Actions.
|
||||
* Origins should include the protocol, e.g., 'https://zitadel.com,http://localhost:3000'.
|
||||
* If not set, it defaults to an empty list, allowing only same-origin requests.
|
||||
*/
|
||||
SERVER_ACTION_ALLOWED_ORIGINS?: string;
|
||||
|
||||
/**
|
||||
* Optional: Enable automatic code submission on page load.
|
||||
* Set to "true" to auto-submit verification codes (e.g. email verification, Email OTP).
|
||||
|
||||
@@ -34,9 +34,15 @@ const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
// Add React 19 compatibility optimizations
|
||||
optimizePackageImports: ['@radix-ui/react-tooltip', '@heroicons/react'],
|
||||
optimizePackageImports: ["@radix-ui/react-tooltip", "@heroicons/react"],
|
||||
useCache: true,
|
||||
serverActions: {
|
||||
...(process.env.SERVER_ACTION_ALLOWED_ORIGINS
|
||||
? { allowedOrigins: process.env.SERVER_ACTION_ALLOWED_ORIGINS.split(",").map((o) => o.trim()) }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
|
||||
// Improve SSR stability - not actually needed for React 19 SSR issues
|
||||
// onDemandEntries: {
|
||||
// maxInactiveAge: 25 * 1000,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button, ButtonVariants } from "@/components/button";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { resolveRedirectUri } from "@/lib/client";
|
||||
import { getMostRecentCookieWithLoginname, getSessionCookieById } from "@/lib/cookies";
|
||||
import { completeDeviceAuthorization } from "@/lib/server/device";
|
||||
import { getServiceConfig } from "@/lib/service-url";
|
||||
@@ -83,6 +84,15 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
loginSettings = await getLoginSettings({ serviceConfig, organization });
|
||||
}
|
||||
|
||||
const redirectUri = await resolveRedirectUri(
|
||||
requestId && sessionId
|
||||
? { sessionId, requestId }
|
||||
: { loginName: loginName ?? sessionFactors?.factors?.user?.loginName },
|
||||
loginSettings?.defaultRedirectUri,
|
||||
);
|
||||
|
||||
const isSamePage = redirectUri?.startsWith("/signedin") ?? false;
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col space-y-4">
|
||||
@@ -108,11 +118,11 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{loginSettings?.defaultRedirectUri && (
|
||||
{redirectUri && !isSamePage && (
|
||||
<div className="mt-8 flex w-full flex-row items-center">
|
||||
<span className="flex-grow"></span>
|
||||
|
||||
<Link href={loginSettings?.defaultRedirectUri}>
|
||||
<Link href={redirectUri}>
|
||||
<Button type="submit" className="self-end" variant={ButtonVariants.Primary}>
|
||||
<Translated i18nKey="continue" namespace="signedin" />
|
||||
</Button>
|
||||
|
||||
@@ -16,7 +16,7 @@ import { TextInput } from "./input";
|
||||
import { PasswordComplexity } from "./password-complexity";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
type Inputs =
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useRouter } from "next/navigation";
|
||||
import { EMAIL, SMS, TOTP, U2F } from "./auth-methods";
|
||||
import { Translated } from "./translated";
|
||||
import { useState } from "react";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { Alert } from "./alert";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { handleServerActionResponse, completeFlowOrGetUrl } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { updateOrCreateSession } from "@/lib/server/session";
|
||||
import { create } from "@zitadel/client";
|
||||
import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||
@@ -17,6 +17,7 @@ import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { completeFlowOrGetUrl } from "@/lib/client";
|
||||
|
||||
// either loginName or sessionId must be provided
|
||||
type Props = {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { Alert } from "./alert";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
type Inputs = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { registerUserAndLinkToIDP } from "@/lib/server/register";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { registerUser } from "@/lib/server/register";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb";
|
||||
import { LoginSettings, PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64";
|
||||
import { completeFlowOrGetUrl, handleServerActionResponse } from "@/lib/client";
|
||||
import { addU2F, verifyU2F } from "@/lib/server/u2f";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
@@ -13,6 +12,8 @@ import { Button, ButtonVariants } from "./button";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { completeFlowOrGetUrl } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
loginName?: string;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useRouter } from "next/navigation";
|
||||
import React, { useState } from "react";
|
||||
import { Avatar } from "./avatar";
|
||||
import { Translated } from "./translated";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
export function isSessionValid(session: Partial<Session>): {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { TextInput } from "./input";
|
||||
import { PasswordComplexity } from "./password-complexity";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
type Inputs =
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from "@/helpers/validators";
|
||||
import { registerUser } from "@/lib/server/register";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { completeFlowOrGetUrl, handleServerActionResponse } from "@/lib/client";
|
||||
import { verifyTOTP } from "@/lib/server/verify";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import Link from "next/link";
|
||||
@@ -16,6 +15,8 @@ import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { completeFlowOrGetUrl } from "@/lib/client";
|
||||
|
||||
type Inputs = {
|
||||
code: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { sendLoginname } from "@/lib/server/loginname";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Alert, AlertType } from "@/components/alert";
|
||||
import { resendVerification, sendVerification } from "@/lib/server/verify";
|
||||
import { handleServerActionResponse } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { UNKNOWN_USER_ID } from "@/lib/constants";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, test, vi, beforeEach } from "vitest";
|
||||
import { isSafeRedirectUri } from "./client-utils";
|
||||
|
||||
describe("isSafeRedirectUri", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should reject empty uri", async () => {
|
||||
expect(await isSafeRedirectUri("")).toBe(false);
|
||||
expect(await isSafeRedirectUri(undefined as unknown as string)).toBe(false);
|
||||
});
|
||||
|
||||
test("should allow relative paths", async () => {
|
||||
expect(await isSafeRedirectUri("/dashboard")).toBe(true);
|
||||
expect(await isSafeRedirectUri("/ui/console")).toBe(true);
|
||||
expect(await isSafeRedirectUri("/")).toBe(true);
|
||||
});
|
||||
|
||||
test("should reject protocol-relative paths", async () => {
|
||||
expect(await isSafeRedirectUri("//evil.com")).toBe(false);
|
||||
expect(await isSafeRedirectUri("\\\\evil.com")).toBe(false);
|
||||
});
|
||||
|
||||
test("should allow absolute URLs matching the current host", async () => {
|
||||
expect(await isSafeRedirectUri("https://my-zitadel.com/console")).toBe(true);
|
||||
});
|
||||
|
||||
test("should allow absolute URLs to external domains", async () => {
|
||||
expect(await isSafeRedirectUri("https://evil.com")).toBe(true);
|
||||
expect(await isSafeRedirectUri("https://settings.com/dashboard")).toBe(true);
|
||||
});
|
||||
|
||||
test("should reject non-http(s) protocols", async () => {
|
||||
expect(await isSafeRedirectUri("javascript:alert(1)")).toBe(false);
|
||||
expect(await isSafeRedirectUri("data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==")).toBe(false);
|
||||
expect(await isSafeRedirectUri("file:///etc/passwd")).toBe(false);
|
||||
});
|
||||
|
||||
test("should gracefully reject invalid, unparsable URLs that aren't relative", async () => {
|
||||
expect(await isSafeRedirectUri("not-a-valid-url")).toBe(false);
|
||||
expect(await isSafeRedirectUri("http://%")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
export type ServerActionResponse =
|
||||
| { redirect: string }
|
||||
| { error: string }
|
||||
| { samlData: { url: string; fields: Record<string, string> } }
|
||||
| undefined
|
||||
| null;
|
||||
|
||||
export function handleServerActionResponse(
|
||||
response: ServerActionResponse,
|
||||
router: { push: (url: string) => void },
|
||||
setSamlData: (data: { url: string; fields: Record<string, string> }) => void,
|
||||
setError: (error: string) => void,
|
||||
): boolean {
|
||||
if (!response) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("redirect" in response && response.redirect) {
|
||||
router.push(response.redirect);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("samlData" in response && response.samlData) {
|
||||
setSamlData(response.samlData);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("error" in response && response.error) {
|
||||
setError(response.error);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether a given redirect URI is safe.
|
||||
* Safe URIs are either relative paths or absolute URLs matching the current host.
|
||||
* This prevents open redirect vulnerabilities and XSS via javascript:/data: URIs.
|
||||
*/
|
||||
export function isSafeRedirectUri(uri: string): boolean {
|
||||
if (!uri) return false;
|
||||
|
||||
// 1. Relative paths are generally safe
|
||||
if (uri.startsWith("/") && !uri.startsWith("//")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Check absolute URLs for safe protocols (http/https)
|
||||
// We allow external domains, but strictly forbid javascript:/data: etc.
|
||||
try {
|
||||
const parsedUri = new URL(uri);
|
||||
|
||||
// Only allow http(s) protocols
|
||||
if (parsedUri.protocol !== "http:" && parsedUri.protocol !== "https:") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
// If it can't be parsed as a URL and didn't start with /, it's unsafe
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, test, vi, beforeEach } from "vitest";
|
||||
import { getNextUrl } from "./client";
|
||||
|
||||
// Mock next/headers
|
||||
vi.mock("next/headers", () => ({
|
||||
headers: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock host helper
|
||||
vi.mock("./server/host", () => ({
|
||||
getPublicHostWithProtocol: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("getNextUrl", () => {
|
||||
const command = { loginName: "test-user" };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
delete (process.env as any).DEFAULT_REDIRECT_URI;
|
||||
delete (process.env as any).NEXT_PUBLIC_BASE_PATH;
|
||||
});
|
||||
|
||||
test("should use DEFAULT_REDIRECT_URI if set", async () => {
|
||||
process.env.DEFAULT_REDIRECT_URI = "https://env-override.com";
|
||||
const result = await getNextUrl(command);
|
||||
expect(result).toBe("https://env-override.com");
|
||||
});
|
||||
|
||||
test("should use host-based redirect if DEFAULT_REDIRECT_URI is set to a path (starting with '/')", async () => {
|
||||
const { headers } = await import("next/headers");
|
||||
const { getPublicHostWithProtocol } = await import("./server/host");
|
||||
|
||||
process.env.DEFAULT_REDIRECT_URI = "/dashboard";
|
||||
vi.mocked(headers).mockResolvedValue({} as any);
|
||||
vi.mocked(getPublicHostWithProtocol).mockReturnValue("https://my-host.com");
|
||||
process.env.NEXT_PUBLIC_BASE_PATH = "/ui/v2/login";
|
||||
|
||||
const result = await getNextUrl(command);
|
||||
expect(result).toBe("https://my-host.com/dashboard");
|
||||
});
|
||||
|
||||
test("should use defaultRedirectUri if env is NOT set", async () => {
|
||||
const result = await getNextUrl(command, "https://settings.com");
|
||||
expect(result).toBe("https://settings.com");
|
||||
});
|
||||
|
||||
test("should fallback to relative signedin page if everything else fails (the new default)", async () => {
|
||||
const { headers } = await import("next/headers");
|
||||
vi.mocked(headers).mockRejectedValue(new Error("No headers"));
|
||||
|
||||
const result = await getNextUrl(command);
|
||||
expect(result).toBe("/signedin?loginName=test-user");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
"use server";
|
||||
|
||||
import { headers } from "next/headers";
|
||||
import { completeAuthFlow } from "./server/auth-flow";
|
||||
import { getPublicHostWithProtocol } from "./server/host";
|
||||
import { isSafeRedirectUri } from "./client-utils";
|
||||
|
||||
type FinishFlowCommand =
|
||||
| {
|
||||
@@ -100,47 +105,52 @@ export async function getNextUrl(
|
||||
// OIDC/SAML flows are now handled by completeAuthFlowAction() server action
|
||||
// This function only handles device flows and fallback navigation
|
||||
|
||||
if (defaultRedirectUri) {
|
||||
console.log("getNextUrl: Using defaultRedirectUri:", defaultRedirectUri);
|
||||
return defaultRedirectUri;
|
||||
}
|
||||
|
||||
const result = goToSignedInPage(command);
|
||||
console.log("getNextUrl: got goToSignedInPage result");
|
||||
const result = await resolveRedirectUri(command, defaultRedirectUri);
|
||||
console.log("getNextUrl: Resolved redirect URI:", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
export type ServerActionResponse =
|
||||
| { redirect: string }
|
||||
| { error: string }
|
||||
| { samlData: { url: string; fields: Record<string, string> } }
|
||||
| undefined
|
||||
| null;
|
||||
|
||||
export function handleServerActionResponse(
|
||||
response: ServerActionResponse,
|
||||
router: { push: (url: string) => void },
|
||||
setSamlData: (data: { url: string; fields: Record<string, string> }) => void,
|
||||
setError: (error: string) => void,
|
||||
): boolean {
|
||||
if (!response) {
|
||||
return false;
|
||||
/**
|
||||
* Resolves the redirect URI based on the following priority:
|
||||
* 1. DEFAULT_REDIRECT_URI environment variable
|
||||
* 2. defaultRedirectUri from organization settings
|
||||
* 3. Relative signed-in page fallback
|
||||
* 4. Reserved for future extensions
|
||||
*/
|
||||
export async function resolveRedirectUri(command: FinishFlowCommand, defaultRedirectUri?: string): Promise<string> {
|
||||
// 1. Environment variable override
|
||||
const envOverride = process.env.DEFAULT_REDIRECT_URI;
|
||||
if (envOverride) {
|
||||
if (envOverride.startsWith("/")) {
|
||||
// Special state: trigger absolute host-based redirect with provided path
|
||||
try {
|
||||
const _headers = await headers();
|
||||
const host = getPublicHostWithProtocol(_headers);
|
||||
const result = `${host}${envOverride}`;
|
||||
console.log("resolveRedirectUri: Using host-based redirect from override:", result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.warn("resolveRedirectUri: Could not determine host for override, falling back", error);
|
||||
}
|
||||
} else {
|
||||
console.log("resolveRedirectUri: Using DEFAULT_REDIRECT_URI override:", envOverride);
|
||||
return envOverride;
|
||||
}
|
||||
}
|
||||
|
||||
if ("redirect" in response && response.redirect) {
|
||||
router.push(response.redirect);
|
||||
return true;
|
||||
// 2. Default redirect URI from settings
|
||||
if (defaultRedirectUri) {
|
||||
if (isSafeRedirectUri(defaultRedirectUri)) {
|
||||
console.log("resolveRedirectUri: Using defaultRedirectUri from settings:", defaultRedirectUri);
|
||||
return defaultRedirectUri;
|
||||
} else {
|
||||
console.warn("resolveRedirectUri: Unsafe defaultRedirectUri prevented:", defaultRedirectUri);
|
||||
}
|
||||
}
|
||||
|
||||
if ("samlData" in response && response.samlData) {
|
||||
setSamlData(response.samlData);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("error" in response && response.error) {
|
||||
setError(response.error);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// 3. Default signed-in page (relative)
|
||||
const result = goToSignedInPage(command);
|
||||
console.log("resolveRedirectUri: Using relative goToSignedInPage result:", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@ export function getServiceConfig(headers: ReadonlyHeaders): { serviceConfig: Ser
|
||||
const instanceHost = getInstanceHost(headers);
|
||||
const publicHost = getPublicHost(headers);
|
||||
|
||||
console.log("serviceConfig", {
|
||||
baseUrl: process.env.ZITADEL_API_URL,
|
||||
...(instanceHost && { instanceHost: stripProtocol(instanceHost) }),
|
||||
...(publicHost && { publicHost: stripProtocol(publicHost) }),
|
||||
});
|
||||
|
||||
return {
|
||||
serviceConfig: {
|
||||
baseUrl: process.env.ZITADEL_API_URL,
|
||||
|
||||
Reference in New Issue
Block a user