host everywhere

This commit is contained in:
Max Peintner
2025-01-15 09:19:54 +01:00
parent 94e14fdbda
commit c0f6676171
7 changed files with 132 additions and 66 deletions

View File

@@ -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();

View File

@@ -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<Record<string | number | symbol, string | undefined>>;
@@ -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 <Alert>{tError("unknownContext")}</Alert>;
}
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;
});

View File

@@ -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 (
<DynamicTheme branding={branding}>

View File

@@ -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 (

View File

@@ -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<Record<string | number | symbol, string | undefined>>;
@@ -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 (
<DynamicTheme branding={branding}>

View File

@@ -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 (
<DynamicTheme branding={branding}>

View File

@@ -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<T>(callback: Promise<T>) {
// const settingsService: Client<typeof SettingsService> =
// 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<typeof SettingsService> =
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;