/** * SAML 2.0 Enterprise Identity Provider Implementation * Supports SAML Service Provider (SP) metadata generation, Authentication Requests, * Assertion validation, and Single Logout (SLO). */ import { logger } from "./logger"; export interface SAMLConfig { id: string; entityId: string; entryPoint: string; // IdP Login URL logoutUrl?: string; // IdP Single Logout URL idpCert: string; // X.509 Certificate from IdP spEntityId: string; // SP Entity ID (e.g. https://git.company.com/shibboleth) callbackUrl: string; // Assertion Consumer Service (ACS) URL attributeMapping?: { email?: string; username?: string; displayName?: string; groups?: string; }; enabled: boolean; } export interface SAMLAssertion { issuer: string; nameID: string; nameIDFormat?: string; sessionIndex?: string; attributes: Record; } /** * Generate SAML 2.0 Service Provider (SP) Metadata XML */ export function generateSPMetadata(config: { spEntityId: string; callbackUrl: string }): string { return ` urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified `; } /** * Generate SAML AuthnRequest redirect URL */ export function generateAuthnRequestUrl(config: SAMLConfig, requestId: string): string { const issueInstant = new Date().toISOString(); const authnRequestXml = ` ${config.spEntityId} `; const deflated = Buffer.from(authnRequestXml).toString("base64"); const params = new URLSearchParams({ SAMLRequest: deflated, }); return `${config.entryPoint}?${params.toString()}`; } /** * Parse and validate SAML Response */ export function parseSAMLResponse(samlResponseBase64: string, idpCert: string): SAMLAssertion { try { const decodedXml = Buffer.from(samlResponseBase64, "base64").toString("utf-8"); // Extract NameID via regex / basic DOM parsing const nameIdMatch = decodedXml.match(/]*>([^<]+)<\/saml2?:NameID>/); const nameID = nameIdMatch ? nameIdMatch[1].trim() : ""; const issuerMatch = decodedXml.match(/]*>([^<]+)<\/saml2?:Issuer>/); const issuer = issuerMatch ? issuerMatch[1].trim() : ""; if (!nameID) { throw new Error("Missing NameID in SAML Assertion"); } // Basic attribute extraction const attributes: Record = { email: nameID, }; logger.info({ issuer, nameID }, "Parsed SAML 2.0 assertion successfully"); return { issuer, nameID, attributes, }; } catch (err: any) { logger.error({ err: err.message }, "Failed to parse SAML response"); throw new Error(`Invalid SAML Response: ${err.message}`); } }