Files
zitadel/apps/login/src/lib/server/idp.ts
T
RamonandGitHub 057fb8dff2 fix(login): prettier setup (#11798)
# Which Problems Are Solved
Invalid formatting in the login codebase due to invalid prettier
configuration

# How the Problems Are Solved
Fxed .prettierignore wildcards for login source files

# Additional Changes
Removed .prettierrc file because there is already a prettier.config.mjs
file

# Additional Context
2026-03-10 13:30:20 +00:00

267 lines
8.0 KiB
TypeScript

"use server";
import {
getLoginSettings,
getUserByID,
listAuthenticationMethodTypes,
ServiceConfig,
startIdentityProviderFlow,
startLDAPIdentityProviderFlow,
} from "@/lib/zitadel";
import crypto from "crypto";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { completeFlowOrGetUrl } from "../client";
import { getSessionCookieById } from "../cookies";
import { getOrSetFingerprintId } from "../fingerprint";
import { getServiceConfig } from "../service-url";
import { checkEmailVerification, checkMFAFactors } from "../verify-helper";
import { createSessionForIdpAndUpdateCookie } from "./cookie";
import { getPublicHost } from "./host";
export type RedirectToIdpState =
| { error?: string | null; samlData?: { url: string; fields: Record<string, string> } }
| undefined;
export async function redirectToIdp(prevState: RedirectToIdpState, formData: FormData): Promise<RedirectToIdpState> {
const _headers = await headers();
const { serviceConfig } = getServiceConfig(_headers);
const host = getPublicHost(_headers);
const params = new URLSearchParams();
const sessionId = formData.get("sessionId") as string;
const requestId = formData.get("requestId") as string;
const organization = formData.get("organization") as string;
const idpId = formData.get("id") as string;
const provider = formData.get("provider") as string;
const postErrorRedirectUrl = formData.get("postErrorRedirectUrl") as string;
if (sessionId) {
try {
// Validate that the requestor owns the session they are trying to link
await getSessionCookieById({ sessionId });
// Get fingerprint (ensure it exists)
const fingerprintId = await getOrSetFingerprintId();
// Create hash to verify intent upon return
const linkFingerprint = crypto
.createHash("sha256")
.update(sessionId + fingerprintId)
.digest("hex");
params.set("linkToSessionId", sessionId);
params.set("linkFingerprint", linkFingerprint);
} catch {
return { error: "Invalid session for linking" };
}
}
if (requestId) params.set("requestId", requestId);
if (organization) params.set("organization", organization);
if (postErrorRedirectUrl) params.set("postErrorRedirectUrl", postErrorRedirectUrl);
// redirect to LDAP page where username and password is requested
if (provider === "ldap") {
params.set("idpId", idpId);
redirect(`/idp/ldap?` + params.toString());
}
const response = await startIDPFlow({
serviceConfig,
host,
idpId,
successUrl: `/idp/${provider}/process?` + params.toString(),
failureUrl: `/idp/${provider}/failure?` + params.toString(),
});
if (!response) {
return { error: "Could not start IDP flow" };
}
if (response && "error" in response && response?.error) {
return { error: response.error };
}
if (response && "redirect" in response && response?.redirect) {
redirect(response.redirect);
}
if (response && "samlData" in response && response?.samlData) {
return { samlData: response.samlData };
}
return { error: "Unexpected response from IDP flow" };
}
export type StartIDPFlowCommand = {
serviceConfig: ServiceConfig;
host: string;
idpId: string;
successUrl: string;
failureUrl: string;
};
async function startIDPFlow(command: StartIDPFlowCommand) {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
const response = await startIdentityProviderFlow({
serviceConfig: command.serviceConfig,
idpId: command.idpId,
urls: {
successUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.successUrl}`,
failureUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.failureUrl}`,
},
});
if (!response || !response.url) {
return { error: "Could not start IDP flow" };
}
if (response.fields) {
return { samlData: { url: response.url, fields: response.fields } };
}
return { redirect: response.url };
}
export type CreateNewSessionCommand = {
userId: string;
idpIntent: {
idpIntentId: string;
idpIntentToken: string;
};
loginName?: string;
password?: string;
organization?: string;
requestId?: string;
};
export async function createNewSessionFromIdpIntent(command: CreateNewSessionCommand) {
const _headers = await headers();
const { serviceConfig } = getServiceConfig(_headers);
if (!command.userId || !command.idpIntent) {
throw new Error("No userId or loginName provided");
}
const userResponse = await getUserByID({ serviceConfig, userId: command.userId });
if (!userResponse || !userResponse.user) {
return { error: "User not found in the system" };
}
const loginSettings = await getLoginSettings({ serviceConfig, organization: userResponse.user.details?.resourceOwner });
const session = await createSessionForIdpAndUpdateCookie({
userId: command.userId,
idpIntent: command.idpIntent,
requestId: command.requestId,
lifetime: loginSettings?.externalLoginCheckLifetime,
});
if (!session || !session.factors?.user) {
return { error: "Could not create session" };
}
const humanUser = userResponse.user.type.case === "human" ? userResponse.user.type.value : undefined;
// check to see if user was verified
const emailVerificationCheck = checkEmailVerification(session, humanUser, command.organization, command.requestId);
if (emailVerificationCheck?.redirect) {
return emailVerificationCheck;
}
// check if user has MFA methods
let authMethods;
if (session.factors?.user?.id) {
const response = await listAuthenticationMethodTypes({ serviceConfig, userId: session.factors.user.id });
if (response.authMethodTypes && response.authMethodTypes.length) {
authMethods = response.authMethodTypes;
}
}
const mfaFactorCheck = await checkMFAFactors(
serviceConfig,
session,
loginSettings,
authMethods || [], // Pass empty array if no auth methods
command.organization,
command.requestId,
);
if (mfaFactorCheck?.redirect) {
return mfaFactorCheck;
}
return completeFlowOrGetUrl(
command.requestId && session.id
? {
sessionId: session.id,
requestId: command.requestId,
organization: session.factors.user.organizationId,
}
: {
loginName: session.factors.user.loginName,
organization: session.factors.user.organizationId,
},
loginSettings?.defaultRedirectUri,
);
}
type createNewSessionForLDAPCommand = {
username: string;
password: string;
idpId: string;
link: boolean;
requestId?: string;
organization?: string;
postErrorRedirectUrl?: string;
linkToSessionId?: string;
linkFingerprint?: string;
};
export async function createNewSessionForLDAP(command: createNewSessionForLDAPCommand) {
const _headers = await headers();
const { serviceConfig } = getServiceConfig(_headers);
if (!command.username || !command.password) {
return { error: "No username or password provided" };
}
const response = await startLDAPIdentityProviderFlow({
serviceConfig,
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;
const params = new URLSearchParams({
userId,
id: idpIntentId,
token: idpIntentToken,
});
if (command.link) {
params.set("link", "true");
}
if (command.requestId) params.set("requestId", command.requestId);
if (command.organization) params.set("organization", command.organization);
if (command.postErrorRedirectUrl) params.set("postErrorRedirectUrl", command.postErrorRedirectUrl);
if (command.linkToSessionId) params.set("linkToSessionId", command.linkToSessionId);
if (command.linkFingerprint) params.set("linkFingerprint", command.linkFingerprint);
return {
redirect: `/idp/ldap/process?` + params.toString(),
};
}