// @ts-ignore import { promises as fs } from 'fs'; import path from 'path'; import xml2js from 'xml2js'; import { User } from '../types'; import {promisify} from 'util'; import zlib from 'zlib'; import xmlbuilder from 'xmlbuilder'; const inflateRawSync = promisify(zlib.inflateRawSync) // Parse XML const parseXML = (xml: string): Promise> => { return new Promise((resolve, reject) => { xml2js.parseString(xml, (err: Error, result: any) => { if(err) { reject(err); } resolve(result); }); }); }; // Parse SAMLRequest attributes const extractSAMLRequestAttributes = async (samlRequest: string) => { // const request = await inflateRawSync(Buffer.from(samlRequest, 'base64')).toString(); // const result = await parseXML(request); // const attributes = result['samlp:AuthnRequest']['$']; return { id: '123', acsUrl: 'https://hookb.in/NOrYqkDLnXse8mNNlDXx', providerName: 'BoxyHQ', }; }; const createIdPMetadataXML = async ({ idpEntityId, idpSsoUrl, certificate, }: { idpEntityId: string; idpSsoUrl: string; certificate: string; }): Promise => { const xmlPath = path.join('data', 'idp-metadata.xml'); const xml = await fs.readFile(xmlPath, 'utf8'); return xml .replace('idp_entity_id', idpEntityId) .replace('idp_certificate', extractCert(certificate)) .replace(/idp_sso_url/g, idpSsoUrl); }; const createCertificate = async () => { const certificateFilePath = path.join('data', 'x509cert.txt'); return await fs.readFile(certificateFilePath, 'utf8'); }; const extractCert = (certificate: string) => { return certificate .replace('-----BEGIN CERTIFICATE-----', '') .replace('-----END CERTIFICATE-----', '') .trim(); }; const createSAMLResponseXML = async (params: { idpIdentityId: string, audience: string, acsUrl: string, user: User }): Promise => { const {idpIdentityId, audience, acsUrl, user} = params; const authTimestamp = const nodes = { 'samlp:Response':{ '@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol', '@ID': '_dde944f3d9cb96238b0c', 'saml:Issuer': { '@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion', '#text': idpIdentityId, }, 'samlp:Status': { 'samlp:StatusCode': { '@Value': 'urn:oasis:names:tc:SAML:2.0:status:Success' } }, 'saml:Assertion': { '@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion', '@Version': '2.0', '@ID': '_bsyl9FgHslMWbBp2tFgM0FBJqWNTd3xd', '@IssueInstant': '2022-02-18T06:24:29.856Z', 'saml:Issuer': { '#text': idpIdentityId, }, 'saml:Subject': { 'saml:NameID': { '@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', '#text': 'google-oauth2|108149256146623609101', }, 'saml:SubjectConfirmation': { '@Method': 'urn:oasis:names:tc:SAML:2.0:cm:bearer', 'saml:SubjectConfirmationData': { '@NotOnOrAfter': '2022-02-18T07:24:29.856Z', '@Recipient': acsUrl, '@InResponseTo': '_e427c05d2462c8c2550e' } } }, 'saml:Conditions': { '@NotBefore': '2022-02-18T06:24:29.856Z', '@NotOnOrAfter': '2022-02-18T07:24:29.856Z', 'saml:AudienceRestriction': { 'saml:Audience': { '#text': audience, } } }, 'saml:AuthnStatement': { '@AuthnInstant': '2022-02-18T06:24:29.856Z', '@SessionIndex': '_YIlFoNFzLMDYxdwf-T_BuimfkGa5qhKg', 'saml:AuthnContext': { 'saml:AuthnContextClassRef': { '#text': 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified' } } }, 'saml:AttributeStatement': { '@xmlns:xs': 'http://www.w3.org/2001/XMLSchema', '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'saml:Attribute': [ { '@Name': 'id', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '#text': user.id, } }, { '@Name': 'email', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '#text': user.email, } }, { '@Name': 'firstName', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '#text': user.firstName, } }, { '@Name': 'lastName', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '#text': user.lastName, } }, ] } } } } return xmlbuilder.create(nodes).end({ pretty: true}); }; // Create the HTML form to submit the response export const createResponseForm = (relayState: string, encodedSamlResponse: string, acsUrl: string) => { const formElements = [ '', '', '', '', '', '', '', '', '
', '', '', '', '
', '', '', '', ]; return formElements.join(''); }; export { parseXML, extractSAMLRequestAttributes, createIdPMetadataXML, createSAMLResponseXML, createCertificate, extractCert, };