From 7ea2103b57d6e31c6737d0afd592cff89f9af25c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 28 May 2025 10:56:46 +0200 Subject: [PATCH] ldap page, start idp flow --- .../app/(login)/authenticator/set/page.tsx | 2 +- apps/login/src/app/(login)/idp/ldap/page.tsx | 55 ++++++++++++ .../src/components/username-password-form.tsx | 24 ++--- apps/login/src/lib/server/idp.ts | 90 +++++++++++++++---- apps/login/src/lib/zitadel.ts | 28 ++++++ 5 files changed, 167 insertions(+), 32 deletions(-) create mode 100644 apps/login/src/app/(login)/idp/ldap/page.tsx diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 5a8dfe810d..82b494f200 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -184,7 +184,7 @@ export default async function Page(props: { > )} - {loginSettings?.allowExternalIdp && identityProviders && ( + {loginSettings?.allowExternalIdp && !!identityProviders.length && ( <> {identityProviders.length && (
diff --git a/apps/login/src/app/(login)/idp/ldap/page.tsx b/apps/login/src/app/(login)/idp/ldap/page.tsx new file mode 100644 index 0000000000..6a54c2d648 --- /dev/null +++ b/apps/login/src/app/(login)/idp/ldap/page.tsx @@ -0,0 +1,55 @@ +import { DynamicTheme } from "@/components/dynamic-theme"; +import { UsernamePasswordForm } from "@/components/username-password-form"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; +import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel"; +import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; +import { getLocale, getTranslations } from "next-intl/server"; +import { headers } from "next/headers"; + +export default async function Page(props: { + searchParams: Promise>; + params: Promise<{ provider: string }>; +}) { + const searchParams = await props.searchParams; + const locale = getLocale(); + const t = await getTranslations({ locale, namespace: "ldap" }); + const { idpId, requestId, organization, link } = searchParams; + + if (!idpId) { + throw new Error("No idpId provided in searchParams"); + } + + const _headers = await headers(); + const { serviceUrl } = getServiceUrlFromHeaders(_headers); + + let defaultOrganization; + if (!organization) { + const org: Organization | null = await getDefaultOrg({ + serviceUrl, + }); + if (org) { + defaultOrganization = org.id; + } + } + + const branding = await getBrandingSettings({ + serviceUrl, + organization: organization ?? defaultOrganization, + }); + + // return login failed if no linking or creation is allowed and no user was found + return ( + +
+

{t("title")}

+

{t("description")}

+ + +
+
+ ); +} diff --git a/apps/login/src/components/username-password-form.tsx b/apps/login/src/components/username-password-form.tsx index fb6a98d8c3..4c5543866d 100644 --- a/apps/login/src/components/username-password-form.tsx +++ b/apps/login/src/components/username-password-form.tsx @@ -1,9 +1,6 @@ "use client"; -import { sendPassword } from "@/lib/server/password"; -import { create } from "@zitadel/client"; -import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { createNewSessionForLDAP } from "@/lib/server/idp"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -20,17 +17,15 @@ type Inputs = { }; type Props = { - loginSettings: LoginSettings | undefined; - loginName: string; organization?: string; requestId?: string; + idpId: string; }; export function UsernamePasswordForm({ - loginSettings, - loginName, organization, requestId, + idpId, }: Props) { const t = useTranslations("password"); @@ -48,13 +43,10 @@ export function UsernamePasswordForm({ setError(""); setLoading(true); - const response = await sendPassword({ - loginName, - organization, - checks: create(ChecksSchema, { - password: { password: values.password }, - }), - requestId, + const response = await createNewSessionForLDAP({ + idpId: idpId, + username: values.loginName, + password: values.password, }) .catch(() => { setError("Could not verify password"); @@ -75,7 +67,7 @@ export function UsernamePasswordForm({ } return ( -
+ { + const _headers = await headers(); + const { serviceUrl } = getServiceUrlFromHeaders(_headers); + const host = _headers.get("host"); + if (!host) { + return { error: "Could not get host" }; + } + const params = new URLSearchParams(); const linkOnly = formData.get("linkOnly") === "true"; @@ -26,52 +34,54 @@ export async function redirectToIdp( const idpId = formData.get("id") as string; const provider = formData.get("provider") as string; + // const username = formData.get("username") as string; + // const password = formData.get("password") as string; + if (linkOnly) params.set("link", "true"); if (requestId) params.set("requestId", requestId); if (organization) params.set("organization", organization); + // redirect to LDAP page where username and password is requested if (provider === "ldap") { - redirect("/idp/ldap?linkOnly=" + linkOnly + "&" + params.toString()); + redirect(`/idp/ldap?` + params.toString()); } const response = await startIDPFlow({ + serviceUrl, + host, idpId, successUrl: `/idp/${provider}/success?` + params.toString(), failureUrl: `/idp/${provider}/failure?` + params.toString(), }); - if (response && "error" in response && response?.error) { - return { error: response.error }; + if (!response) { + return { error: "Could not start IDP flow" }; } if (response && "redirect" in response && response?.redirect) { redirect(response.redirect); } + + return { error: "Unexpected response from IDP flow" }; } export type StartIDPFlowCommand = { + serviceUrl: string; + host: string; idpId: string; successUrl: string; failureUrl: string; }; -export async function startIDPFlow(command: StartIDPFlowCommand) { - const _headers = await headers(); - const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host) { - return { error: "Could not get host" }; - } - +async function startIDPFlow(command: StartIDPFlowCommand) { const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; return startIdentityProviderFlow({ - serviceUrl, + serviceUrl: command.serviceUrl, idpId: command.idpId, urls: { - successUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}${command.successUrl}`, - failureUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}${command.failureUrl}`, + successUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.successUrl}`, + failureUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.failureUrl}`, }, }).then((response) => { if ( @@ -178,3 +188,53 @@ export async function createNewSessionFromIdpIntent( return { redirect: url }; } } + +type createNewSessionForLDAPCommand = { + username: string; + password: string; + idpId: string; +}; + +export async function createNewSessionForLDAP( + command: createNewSessionForLDAPCommand, +) { + const _headers = await headers(); + + const { serviceUrl } = getServiceUrlFromHeaders(_headers); + const host = _headers.get("host"); + + if (!host) { + return { error: "Could not get domain" }; + } + + if (!command.username || !command.password) { + return { error: "No username or password provided" }; + } + + const response = await startLDAPIdentityProviderFlow({ + serviceUrl, + idpId: command.idpId, + username: command.username, + password: command.password, + }); + + if ( + !response || + response.nextStep.case !== "idpIntent" || + !response.nextStep.value + ) { + return { error: "Could not start LDAP identity provider flow" }; + } + + const { userId, idpIntentId, idpIntentToken } = response.nextStep.value; + + return { + redirect: + `/idp/ldap/success?` + + new URLSearchParams({ + userId, + id: idpIntentId, + token: idpIntentToken, + }).toString(), + }; +} diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index bdf46a5905..5cc4181e1f 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -910,6 +910,34 @@ export async function startIdentityProviderFlow({ }); } +export async function startLDAPIdentityProviderFlow({ + serviceUrl, + idpId, + username, + password, +}: { + serviceUrl: string; + idpId: string; + username: string; + password: string; +}) { + const userService: Client = await createServiceForHost( + UserService, + serviceUrl, + ); + + return userService.startIdentityProviderIntent({ + idpId, + content: { + case: "ldap", + value: { + username, + password, + }, + }, + }); +} + export async function getAuthRequest({ serviceUrl, authRequestId,