diff --git a/data/idp-private-key.txt b/data/idp-private.key similarity index 100% rename from data/idp-private-key.txt rename to data/idp-private.key diff --git a/data/idp-public-key.txt b/data/idp-public-key.txt deleted file mode 100644 index e69de29..0000000 diff --git a/data/idp-public.key b/data/idp-public.key new file mode 100644 index 0000000..74f2978 --- /dev/null +++ b/data/idp-public.key @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSjCCAbOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBCMQswCQYDVQQGEwJ1czEN +MAsGA1UECAwERGVtbzEPMA0GA1UECgwGQm94eUhRMRMwEQYDVQQDDApib3h5aHEu +Y29tMB4XDTIyMDExMzE3NTQ1NVoXDTIzMDExMzE3NTQ1NVowQjELMAkGA1UEBhMC +dXMxDTALBgNVBAgMBERlbW8xDzANBgNVBAoMBkJveHlIUTETMBEGA1UEAwwKYm94 +eWhxLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4lbyAVpXmp1bGPGn +PfauUzTvPil0gDJaGBTYQ50A7lDLrD0rh/SbsRY5e8VA2JnYaKT7k53FL4n9ogjx +HQRT4b7s9ZjUUC7BHYPd4CzATjf6Iy48bbout2VphuZdWjwbY1uEfolaZR2QU4IR +4RYfa4L4fGZufA8ayunCWXTackMCAwEAAaNQME4wHQYDVR0OBBYEFKk0NXw5l0fq +MQ3GW4mNzazrZeEQMB8GA1UdIwQYMBaAFKk0NXw5l0fqMQ3GW4mNzazrZeEQMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQENBQADgYEABIL+uv5KbnqLnvbeyglcuDSf +MVlPqMlvvliPLZa2TGluutL3t+jFfJNi6Vavd4BNyVsCYRe/ab8+/nok1Lu/IqKF +vifu1QGHsF1vKyafmVC8cMX/lxsvjedsOs++59yOAHAgXn+0IuBwupinKF4Tuqd7 +n5gl9V4czyfFtrJUCQc= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6a09597..07b94c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "fake", "dependencies": { + "@types/xml-crypto": "^1.4.2", "axios": "^0.24.0", "next": "12.1.0", "node-fetch": "^3.2.0", @@ -13,6 +14,7 @@ "react": "17.0.2", "react-dom": "17.0.2", "webpack-filter-warnings-plugin": "^1.2.1", + "xml-crypto": "^2.1.3", "xml2js": "^0.4.23", "xmlbuilder": "^15.1.1" }, @@ -427,8 +429,7 @@ "node_modules/@types/node": { "version": "17.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", - "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", - "dev": true + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -459,6 +460,23 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, + "node_modules/@types/xml-crypto": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.2.tgz", + "integrity": "sha512-1kT+3gVkeBDg7Ih8NefxGYfCApwZViMIs5IEs5AXF6Fpsrnf9CLAEIRh0DYb1mIcRcvysVbe27cHsJD6rJi36w==", + "dependencies": { + "@types/node": "*", + "xpath": "0.0.27" + } + }, + "node_modules/@types/xml-crypto/node_modules/xpath": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", + "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==", + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/@types/xml2js": { "version": "0.4.9", "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz", @@ -805,6 +823,14 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz", + "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -7934,6 +7960,18 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "node_modules/xml-crypto": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-2.1.3.tgz", + "integrity": "sha512-MpXZwnn9JK0mNPZ5mnFIbNnQa+8lMGK4NtnX2FlJMfMWR60sJdFO9X72yO6ji068pxixzk53O7x0/iSKh6IhyQ==", + "dependencies": { + "@xmldom/xmldom": "^0.7.0", + "xpath": "0.0.32" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", @@ -7962,6 +8000,14 @@ "node": ">=8.0" } }, + "node_modules/xpath": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", + "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -8242,8 +8288,7 @@ "@types/node": { "version": "17.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", - "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", - "dev": true + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" }, "@types/parse-json": { "version": "4.0.0", @@ -8274,6 +8319,22 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, + "@types/xml-crypto": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.2.tgz", + "integrity": "sha512-1kT+3gVkeBDg7Ih8NefxGYfCApwZViMIs5IEs5AXF6Fpsrnf9CLAEIRh0DYb1mIcRcvysVbe27cHsJD6rJi36w==", + "requires": { + "@types/node": "*", + "xpath": "0.0.27" + }, + "dependencies": { + "xpath": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", + "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" + } + } + }, "@types/xml2js": { "version": "0.4.9", "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz", @@ -8554,6 +8615,11 @@ "@xtuc/long": "4.2.2" } }, + "@xmldom/xmldom": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz", + "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==" + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -14219,6 +14285,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "xml-crypto": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-2.1.3.tgz", + "integrity": "sha512-MpXZwnn9JK0mNPZ5mnFIbNnQa+8lMGK4NtnX2FlJMfMWR60sJdFO9X72yO6ji068pxixzk53O7x0/iSKh6IhyQ==", + "requires": { + "@xmldom/xmldom": "^0.7.0", + "xpath": "0.0.32" + } + }, "xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", @@ -14240,6 +14315,11 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==" }, + "xpath": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", + "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 06f6b49..bed0a4f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "lint": "next lint" }, "dependencies": { + "@types/xml-crypto": "^1.4.2", "axios": "^0.24.0", "next": "12.1.0", "node-fetch": "^3.2.0", @@ -15,6 +16,7 @@ "react": "17.0.2", "react-dom": "17.0.2", "webpack-filter-warnings-plugin": "^1.2.1", + "xml-crypto": "^2.1.3", "xml2js": "^0.4.23", "xmlbuilder": "^15.1.1" }, diff --git a/pages/api/saml/metadata/download.ts b/pages/api/saml/metadata/download.ts index 3fe59a9..beee0f3 100644 --- a/pages/api/saml/metadata/download.ts +++ b/pages/api/saml/metadata/download.ts @@ -1,5 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { createCertificate, createIdPMetadataXML } from '../../../../utils'; +import { fetchPublicKey, createIdPMetadataXML } from '../../../../utils'; import { IdPMetadata } from '../../../../types'; import stream from 'stream'; import { promisify } from 'util'; @@ -24,7 +24,7 @@ export default async function handler( const xml = await createIdPMetadataXML({ idpEntityId: config.entityId, idpSsoUrl: config.ssoUrl, - certificate: await createCertificate(), + certificate: await fetchPublicKey(), }); res.setHeader('Content-type', 'text/xml'); diff --git a/pages/api/saml/sso.ts b/pages/api/saml/sso.ts index eace963..4d151a4 100644 --- a/pages/api/saml/sso.ts +++ b/pages/api/saml/sso.ts @@ -2,6 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { createResponseForm, createSAMLResponseXML } from 'utils'; import { User } from 'types'; import config from '../../../lib/env' +import { signResponseXML } from 'utils/response'; +import { fetchPrivateKey, fetchPublicKey } from 'utils/certificate'; export default async function handler( req: NextApiRequest, @@ -37,9 +39,11 @@ export default async function handler( user: user, }); - console.log(xml) + const signingKey = await fetchPrivateKey(); + const publicKey = await fetchPublicKey(); + const xmlSigned = await signResponseXML(xml, signingKey, publicKey); - const encodedSamlResponse = Buffer.from(xml).toString('base64'); + const encodedSamlResponse = Buffer.from(xmlSigned).toString('base64'); const html = createResponseForm(relayState, encodedSamlResponse, acsUrl); diff --git a/pages/index.tsx b/pages/index.tsx index 201b8fb..7c3431c 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,7 +1,7 @@ import { GetStaticProps } from "next"; import { IdPMetadata } from "../types"; import config from "../lib/env"; -import { createCertificate } from "../utils"; +import { fetchPublicKey } from "../utils"; import React from "react"; import Link from "next/link"; import Head from "next/head"; @@ -10,7 +10,7 @@ export const getStaticProps: GetStaticProps = async () => { const metadata: IdPMetadata = { ssoUrl: config.ssoUrl, entityId: config.entityId, - certificate: await createCertificate(), + certificate: await fetchPublicKey(), }; return { @@ -32,7 +32,7 @@ const Home: React.FC<{ metadata: IdPMetadata }> = ({ metadata }) => {

Entity ID

{metadata.entityId}

Certificate

-

+

{metadata.certificate}



diff --git a/utils/certificate.ts b/utils/certificate.ts new file mode 100644 index 0000000..ea9565a --- /dev/null +++ b/utils/certificate.ts @@ -0,0 +1,23 @@ +import { promises as fs } from 'fs'; +import path from 'path'; + +const fetchPublicKey = async () => { + return await fs.readFile(path.join('data', 'idp-public.key'), 'utf8'); +}; + +const fetchPrivateKey = async () => { + return await fs.readFile(path.join('data', 'idp-private.key'), 'utf8'); +} + +const extractCert = (certificate: string) => { + return certificate + .replace('-----BEGIN CERTIFICATE-----', '') + .replace('-----END CERTIFICATE-----', '') + .trim(); +}; + +export { + fetchPublicKey, + fetchPrivateKey, + extractCert, +} \ No newline at end of file diff --git a/utils/index.ts b/utils/index.ts index 4d891f3..ffbf4df 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -1,219 +1,3 @@ -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'; -import crypto from 'crypto'; - -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', 'idp-public-key.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 authDate = new Date(); - const authTimestamp = authDate.toISOString(); - - authDate.setMinutes(authDate.getMinutes() - 5); - const notBefore = authDate.toISOString(); - - authDate.setMinutes(authDate.getMinutes() + 10); - const notAfter = authDate.toISOString(); - - const inResponseTo = '_1234' - const responseId = crypto.randomBytes(10).toString('hex'); - - const 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, - } - }, - ] - } - - const nodes = { - 'samlp:Response':{ - '@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol', - '@Version': '2.0', - '@ID': responseId, - '@Destination': acsUrl, - '@InResponseTo': inResponseTo, - '@IssueInstant': authTimestamp, - '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': responseId, - '@IssueInstant': authTimestamp, - 'saml:Issuer': { - '#text': idpIdentityId, - }, - 'saml:Subject': { - 'saml:NameID': { - '@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', - '#text': user.email, - } - }, - 'saml:Conditions': { - '@NotBefore': notBefore, - '@NotOnOrAfter': notAfter, - 'saml:AudienceRestriction': { - 'saml:Audience': { - '#text': audience, - } - } - }, - 'saml:AuthnStatement': { - '@AuthnInstant': authTimestamp, - '@SessionIndex': '_YIlFoNFzLMDYxdwf-T_BuimfkGa5qhKg', - 'saml:AuthnContext': { - 'saml:AuthnContextClassRef': { - '#text': 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified' - } - } - }, - 'saml:AttributeStatement': attributeStatement, - }, - } - } - - return xmlbuilder.create(nodes).end({ pretty: true}); -}; - -const signResponseXML = (xml: string, signingKey: any, publicKey: any): string => { - return xml; -} - -// 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, -}; \ No newline at end of file +export { fetchPrivateKey, fetchPublicKey } from './certificate' +export {extractSAMLRequestAttributes, createIdPMetadataXML} from './request' +export { createSAMLResponseXML, createResponseForm, signResponseXML } from './response' \ No newline at end of file diff --git a/utils/request.ts b/utils/request.ts new file mode 100644 index 0000000..b40485c --- /dev/null +++ b/utils/request.ts @@ -0,0 +1,57 @@ +import { promises as fs } from 'fs'; +import path from 'path'; +import xml2js from 'xml2js'; +import {promisify} from 'util'; +import zlib from 'zlib'; + +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', certificate) + .replace(/idp_sso_url/g, idpSsoUrl); +}; + +export { + extractSAMLRequestAttributes, + createIdPMetadataXML, +} \ No newline at end of file diff --git a/utils/response.ts b/utils/response.ts new file mode 100644 index 0000000..25f68c1 --- /dev/null +++ b/utils/response.ts @@ -0,0 +1,165 @@ +import { User } from '../types'; +import xmlbuilder from 'xmlbuilder'; +import crypto from 'crypto'; +import {SignedXml, FileKeyInfo} from 'xml-crypto'; + +const createSAMLResponseXML = async (params: { + idpIdentityId: string, + audience: string, + acsUrl: string, + user: User +}): Promise => { + const {idpIdentityId, audience, acsUrl, user} = params; + + const authDate = new Date(); + const authTimestamp = authDate.toISOString(); + + authDate.setMinutes(authDate.getMinutes() - 5); + const notBefore = authDate.toISOString(); + + authDate.setMinutes(authDate.getMinutes() + 10); + const notAfter = authDate.toISOString(); + + const inResponseTo = '_1234' + const responseId = crypto.randomBytes(10).toString('hex'); + + const 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, + } + }, + ] + } + + const nodes = { + 'samlp:Response':{ + '@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol', + '@Version': '2.0', + '@ID': responseId, + '@Destination': acsUrl, + '@InResponseTo': inResponseTo, + '@IssueInstant': authTimestamp, + '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': responseId, + '@IssueInstant': authTimestamp, + 'saml:Issuer': { + '#text': idpIdentityId, + }, + 'saml:Subject': { + 'saml:NameID': { + '@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', + '#text': user.email, + } + }, + 'saml:Conditions': { + '@NotBefore': notBefore, + '@NotOnOrAfter': notAfter, + 'saml:AudienceRestriction': { + 'saml:Audience': { + '#text': audience, + } + } + }, + 'saml:AuthnStatement': { + '@AuthnInstant': authTimestamp, + '@SessionIndex': '_YIlFoNFzLMDYxdwf-T_BuimfkGa5qhKg', + 'saml:AuthnContext': { + 'saml:AuthnContextClassRef': { + '#text': 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified' + } + } + }, + 'saml:AttributeStatement': attributeStatement, + }, + } + } + + return xmlbuilder.create(nodes).end({ pretty: true}); +}; + +// Create the HTML form to submit the response +const createResponseForm = (relayState: string, encodedSamlResponse: string, acsUrl: string) => { + const formElements = [ + '', + '', + '', + '', + '', + '', + '', + '', + '
', + '', + '', + '', + '
', + '', + '', + '', + ]; + + return formElements.join(''); +}; + +const signResponseXML = async (xml: string, signingKey: any, publicKey: any): Promise => { + return ''; + + // const sig = new SignedXml(); + + // sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; + // sig.keyInfoProvider = new PubKeyInfo(publicKey); + + // sig.signingKey = signingKey; + // sig.addReference(authnXPath, ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'], 'http://www.w3.org/2001/04/xmlenc#sha256'); + // sig.computeSignature(xml, { + // location: { reference: authnXPath + issuerXPath, action: 'after' }, + // }); + + // return sig.getSignedXml(); +} + +export { + createSAMLResponseXML, + createResponseForm, + signResponseXML +} \ No newline at end of file