From ba3c3d596dcbad7c5fc5083a31d86e11493011fa Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 14 Feb 2025 16:38:03 +0100 Subject: [PATCH] form post --- apps/login/src/app/login/route.ts | 140 ++++++++++++++++++++++++------ apps/login/src/lib/session.ts | 14 ++- 2 files changed, 124 insertions(+), 30 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 4ae2be17b3..973e2142d6 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -7,6 +7,7 @@ import { getServiceUrlFromHeaders } from "@/lib/service"; import { findValidSession } from "@/lib/session"; import { createCallback, + createResponse, getActiveIdentityProviders, getAuthRequest, getOrgsByDomain, @@ -15,14 +16,12 @@ import { startIdentityProviderFlow, } from "@/lib/zitadel"; import { create } from "@zitadel/client"; -import { - AuthRequest, - Prompt, -} from "@zitadel/proto/zitadel/oidc/v2/authorization_pb"; +import { Prompt } from "@zitadel/proto/zitadel/oidc/v2/authorization_pb"; import { CreateCallbackRequestSchema, SessionSchema, } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; +import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; @@ -33,19 +32,17 @@ export const fetchCache = "default-no-store"; const gotoAccounts = ({ request, - authRequest, + requestId, organization, - idPrefix, }: { request: NextRequest; - authRequest: AuthRequest; - organization: string; - idPrefix: string; + requestId: string; + organization?: string; }): NextResponse => { const accountsUrl = new URL("/accounts", request.url); - if (authRequest?.id) { - accountsUrl.searchParams.set("requestId", `${idPrefix}${authRequest.id}`); + if (requestId) { + accountsUrl.searchParams.set("requestId", requestId); } if (organization) { accountsUrl.searchParams.set("organization", organization); @@ -59,7 +56,6 @@ async function loadSessions({ ids, }: { serviceUrl: string; - ids: string[]; }): Promise { const response = await listSessions({ @@ -177,7 +173,6 @@ export async function GET(request: NextRequest) { const identityProviders = await getActiveIdentityProviders({ serviceUrl, - orgId: organization ? organization : undefined, }).then((resp) => { return resp.identityProviders; @@ -203,7 +198,6 @@ export async function GET(request: NextRequest) { return startIdentityProviderFlow({ serviceUrl, - idpId, urls: { successUrl: @@ -243,9 +237,8 @@ export async function GET(request: NextRequest) { if (authRequest.prompt.includes(Prompt.SELECT_ACCOUNT)) { return gotoAccounts({ request, - authRequest, + requestId: `oidc_${authRequest.id}`, organization, - idPrefix: "oidc_", }); } else if (authRequest.prompt.includes(Prompt.LOGIN)) { /** @@ -300,7 +293,6 @@ export async function GET(request: NextRequest) { **/ const selectedSession = await findValidSession({ serviceUrl, - sessions, authRequest, }); @@ -330,7 +322,6 @@ export async function GET(request: NextRequest) { const { callbackUrl } = await createCallback({ serviceUrl, - req: create(CreateCallbackRequestSchema, { authRequestId: requestId.replace("oidc_", ""), callbackKind: { @@ -351,9 +342,8 @@ export async function GET(request: NextRequest) { if (!selectedSession || !selectedSession.id) { return gotoAccounts({ request, - authRequest, + requestId: `oidc_${authRequest.id}`, organization, - idPrefix: "oidc_", }); } @@ -364,9 +354,8 @@ export async function GET(request: NextRequest) { if (!cookie || !cookie.id || !cookie.token) { return gotoAccounts({ request, - authRequest, + requestId: `oidc_${authRequest.id}`, organization, - idPrefix: "oidc_", }); } @@ -395,18 +384,16 @@ export async function GET(request: NextRequest) { ); return gotoAccounts({ request, - authRequest, organization, - idPrefix: "oidc_", + requestId: `oidc_${authRequest.id}`, }); } } catch (error) { console.error(error); return gotoAccounts({ request, - authRequest, + requestId: `oidc_${authRequest.id}`, organization, - idPrefix: "oidc_", }); } } @@ -432,6 +419,107 @@ export async function GET(request: NextRequest) { serviceUrl, samlRequestId: requestId.replace("saml_", ""), }); + + if (!samlRequest) { + return NextResponse.json( + { error: "No samlRequest found" }, + { status: 400 }, + ); + } + + let selectedSession = await findValidSession({ + serviceUrl, + sessions, + samlRequest, + }); + + if (!selectedSession || !selectedSession.id) { + return gotoAccounts({ + request, + requestId: `saml_${samlRequest.id}`, + }); + } + + const cookie = sessionCookies.find( + (cookie) => cookie.id === selectedSession.id, + ); + + if (!cookie || !cookie.id || !cookie.token) { + return gotoAccounts({ + request, + requestId: `saml_${samlRequest.id}`, + // organization, + }); + } + + const session = { + sessionId: cookie.id, + sessionToken: cookie.token, + }; + + try { + const { url, binding } = await createResponse({ + serviceUrl, + req: create(CreateResponseRequestSchema, { + samlRequestId: requestId.replace("saml_", ""), + responseKind: { + case: "session", + value: session, + }, + }), + }); + if (url && binding.case === "redirect") { + return NextResponse.redirect(url); + } else if (url && binding.case === "post") { + const formData = { + key1: "value1", + key2: "value2", + }; + + // Convert form data to URL-encoded string + const formBody = Object.entries(formData) + .map( + ([key, value]) => + encodeURIComponent(key) + "=" + encodeURIComponent(value), + ) + .join("&"); + + // Make a POST request to the external URL with the form data + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: formBody, + }); + + // Handle the response from the external URL + if (response.ok) { + return NextResponse.json({ + message: "SAML request completed successfully", + }); + } else { + return NextResponse.json( + { error: "Failed to complete SAML request" }, + { status: response.status }, + ); + } + } else { + console.log( + "could not create response, redirect user to choose other account", + ); + return gotoAccounts({ + request, + requestId: `saml_${samlRequest.id}`, + }); + } + } catch (error) { + console.error(error); + return gotoAccounts({ + request, + requestId: `saml_${samlRequest.id}`, + }); + } } else { return NextResponse.json( { error: "No authRequest nor samlRequest provided" }, diff --git a/apps/login/src/lib/session.ts b/apps/login/src/lib/session.ts index d3d5c303c1..f9eb0ceeb2 100644 --- a/apps/login/src/lib/session.ts +++ b/apps/login/src/lib/session.ts @@ -1,5 +1,6 @@ import { timestampDate } from "@zitadel/client"; import { AuthRequest } from "@zitadel/proto/zitadel/oidc/v2/authorization_pb"; +import { SAMLRequest } from "@zitadel/proto/zitadel/saml/v2/authorization_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; @@ -150,21 +151,26 @@ export async function isSessionValid({ export async function findValidSession({ serviceUrl, - sessions, authRequest, + samlRequest, }: { serviceUrl: string; sessions: Session[]; - authRequest: AuthRequest; + authRequest?: AuthRequest; + samlRequest?: SAMLRequest; }): Promise { const sessionsWithHint = sessions.filter((s) => { - if (authRequest.hintUserId) { + if (authRequest && authRequest.hintUserId) { return s.factors?.user?.id === authRequest.hintUserId; } - if (authRequest.loginHint) { + if (authRequest && authRequest.loginHint) { return s.factors?.user?.loginName === authRequest.loginHint; } + if (samlRequest) { + // TODO: do whatever + return true; + } return true; });