Merge pull request #499 from zitadel/saml-idp

fix(saml-idp): post binding
This commit is contained in:
Max Peintner
2025-07-01 14:25:32 +02:00
committed by GitHub
5 changed files with 64 additions and 34 deletions

View File

@@ -217,7 +217,7 @@ export async function GET(request: NextRequest) {
params.set("organization", organization); params.set("organization", organization);
} }
return startIdentityProviderFlow({ let url: string | null = await startIdentityProviderFlow({
serviceUrl, serviceUrl,
idpId, idpId,
urls: { urls: {
@@ -228,14 +228,21 @@ export async function GET(request: NextRequest) {
`${origin}/idp/${provider}/failure?` + `${origin}/idp/${provider}/failure?` +
new URLSearchParams(params), new URLSearchParams(params),
}, },
}).then((resp) => {
if (
resp.nextStep.value &&
typeof resp.nextStep.value === "string"
) {
return NextResponse.redirect(resp.nextStep.value);
}
}); });
if (!url) {
return NextResponse.json(
{ error: "Could not start IDP flow" },
{ status: 500 },
);
}
if (url.startsWith("/")) {
// if the url is a relative path, construct the absolute url
url = constructUrl(request, url).toString();
}
return NextResponse.redirect(url);
} }
} }
} }

View File

@@ -16,7 +16,7 @@ export default getRequestConfig(async () => {
const { serviceUrl } = getServiceUrlFromHeaders(_headers); const { serviceUrl } = getServiceUrlFromHeaders(_headers);
const i18nOrganization = _headers.get("x-zitadel-i18n-organization") || ""; // You may need to set this header in middleware const i18nOrganization = _headers.get("x-zitadel-i18n-organization") || ""; // You may need to set this header in middleware
console.log("i18nOrganization:", i18nOrganization);
let translations: JsonObject | {} = {}; let translations: JsonObject | {} = {};
try { try {
const i18nJSON = await getHostedLoginTranslation({ const i18nJSON = await getHostedLoginTranslation({

View File

@@ -74,22 +74,20 @@ export type StartIDPFlowCommand = {
async function startIDPFlow(command: StartIDPFlowCommand) { async function startIDPFlow(command: StartIDPFlowCommand) {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
return startIdentityProviderFlow({ const url = await startIdentityProviderFlow({
serviceUrl: command.serviceUrl, serviceUrl: command.serviceUrl,
idpId: command.idpId, idpId: command.idpId,
urls: { urls: {
successUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.successUrl}`, successUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.successUrl}`,
failureUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.failureUrl}`, failureUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.failureUrl}`,
}, },
}).then((response) => {
if (
response &&
response.nextStep.case === "authUrl" &&
response?.nextStep.value
) {
return { redirect: response.nextStep.value };
}
}); });
if (!url) {
return { error: "Could not start IDP flow" };
}
return { redirect: url };
} }
type CreateNewSessionCommand = { type CreateNewSessionCommand = {

View File

@@ -102,7 +102,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
const resp = await startIdentityProviderFlow({ const url = await startIdentityProviderFlow({
serviceUrl, serviceUrl,
idpId: identityProviders[0].id, idpId: identityProviders[0].id,
urls: { urls: {
@@ -115,9 +115,11 @@ export async function sendLoginname(command: SendLoginnameCommand) {
}, },
}); });
if (resp?.nextStep.case === "authUrl") { if (!url) {
return { redirect: resp.nextStep.value }; return { error: "Could not start IDP flow" };
} }
return { redirect: url };
} }
}; };
@@ -166,7 +168,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
const resp = await startIdentityProviderFlow({ const url = await startIdentityProviderFlow({
serviceUrl, serviceUrl,
idpId: idp.id, idpId: idp.id,
urls: { urls: {
@@ -179,9 +181,11 @@ export async function sendLoginname(command: SendLoginnameCommand) {
}, },
}); });
if (resp?.nextStep.case === "authUrl") { if (!url) {
return { redirect: resp.nextStep.value }; return { error: "Could not start IDP flow" };
} }
return { redirect: url };
} }
}; };

View File

@@ -23,7 +23,10 @@ import {
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb"; import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb"; import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb";
import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import type {
FormData,
RedirectURLsJson,
} from "@zitadel/proto/zitadel/user/v2/idp_pb";
import { import {
NotificationType, NotificationType,
SendPasswordResetLinkSchema, SendPasswordResetLinkSchema,
@@ -88,7 +91,6 @@ export async function getHostedLoginTranslation({
{}, {},
) )
.then((resp) => { .then((resp) => {
console.log(resp);
return resp.translations ? resp.translations : undefined; return resp.translations ? resp.translations : undefined;
}); });
@@ -964,19 +966,38 @@ export async function startIdentityProviderFlow({
serviceUrl: string; serviceUrl: string;
idpId: string; idpId: string;
urls: RedirectURLsJson; urls: RedirectURLsJson;
}) { }): Promise<string | null> {
const userService: Client<typeof UserService> = await createServiceForHost( const userService: Client<typeof UserService> = await createServiceForHost(
UserService, UserService,
serviceUrl, serviceUrl,
); );
return userService.startIdentityProviderIntent({ return userService
idpId, .startIdentityProviderIntent({
content: { idpId,
case: "urls", content: {
value: urls, case: "urls",
}, value: urls,
}); },
})
.then((resp) => {
if (resp.nextStep.case === "authUrl" && resp.nextStep.value) {
return resp.nextStep.value;
} else if (resp.nextStep.case === "formData" && resp.nextStep.value) {
const formData: FormData = resp.nextStep.value;
const redirectUrl = "/saml-post";
const params = new URLSearchParams({ url: formData.url });
Object.entries(formData.fields).forEach(([k, v]) => {
params.append(k, v);
});
return `${redirectUrl}?${params.toString()}`;
} else {
return null;
}
});
} }
export async function startLDAPIdentityProviderFlow({ export async function startLDAPIdentityProviderFlow({