cleanup util
This commit is contained in:
parent
21fa8eb87a
commit
aa76970fe7
15
data/idp-public.key
Normal file
15
data/idp-public.key
Normal file
@ -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-----
|
||||||
88
package-lock.json
generated
88
package-lock.json
generated
@ -6,6 +6,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "fake",
|
"name": "fake",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/xml-crypto": "^1.4.2",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"next": "12.1.0",
|
"next": "12.1.0",
|
||||||
"node-fetch": "^3.2.0",
|
"node-fetch": "^3.2.0",
|
||||||
@ -13,6 +14,7 @@
|
|||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"webpack-filter-warnings-plugin": "^1.2.1",
|
"webpack-filter-warnings-plugin": "^1.2.1",
|
||||||
|
"xml-crypto": "^2.1.3",
|
||||||
"xml2js": "^0.4.23",
|
"xml2js": "^0.4.23",
|
||||||
"xmlbuilder": "^15.1.1"
|
"xmlbuilder": "^15.1.1"
|
||||||
},
|
},
|
||||||
@ -427,8 +429,7 @@
|
|||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "17.0.8",
|
"version": "17.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz",
|
||||||
"integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==",
|
"integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/parse-json": {
|
"node_modules/@types/parse-json": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@ -459,6 +460,23 @@
|
|||||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
|
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/@types/xml2js": {
|
||||||
"version": "0.4.9",
|
"version": "0.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz",
|
||||||
@ -805,6 +823,14 @@
|
|||||||
"@xtuc/long": "4.2.2"
|
"@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": {
|
"node_modules/@xtuc/ieee754": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
"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": {
|
"node_modules/xml2js": {
|
||||||
"version": "0.4.23",
|
"version": "0.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||||
@ -7962,6 +8000,14 @@
|
|||||||
"node": ">=8.0"
|
"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": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
@ -8242,8 +8288,7 @@
|
|||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "17.0.8",
|
"version": "17.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz",
|
||||||
"integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==",
|
"integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"@types/parse-json": {
|
"@types/parse-json": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@ -8274,6 +8319,22 @@
|
|||||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
|
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
|
||||||
"dev": true
|
"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": {
|
"@types/xml2js": {
|
||||||
"version": "0.4.9",
|
"version": "0.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz",
|
||||||
@ -8554,6 +8615,11 @@
|
|||||||
"@xtuc/long": "4.2.2"
|
"@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": {
|
"@xtuc/ieee754": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
"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": {
|
"xml2js": {
|
||||||
"version": "0.4.23",
|
"version": "0.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
||||||
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="
|
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="
|
||||||
},
|
},
|
||||||
|
"xpath": {
|
||||||
|
"version": "0.0.32",
|
||||||
|
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
|
||||||
|
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw=="
|
||||||
|
},
|
||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/xml-crypto": "^1.4.2",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"next": "12.1.0",
|
"next": "12.1.0",
|
||||||
"node-fetch": "^3.2.0",
|
"node-fetch": "^3.2.0",
|
||||||
@ -15,6 +16,7 @@
|
|||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"webpack-filter-warnings-plugin": "^1.2.1",
|
"webpack-filter-warnings-plugin": "^1.2.1",
|
||||||
|
"xml-crypto": "^2.1.3",
|
||||||
"xml2js": "^0.4.23",
|
"xml2js": "^0.4.23",
|
||||||
"xmlbuilder": "^15.1.1"
|
"xmlbuilder": "^15.1.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { createCertificate, createIdPMetadataXML } from '../../../../utils';
|
import { fetchPublicKey, createIdPMetadataXML } from '../../../../utils';
|
||||||
import { IdPMetadata } from '../../../../types';
|
import { IdPMetadata } from '../../../../types';
|
||||||
import stream from 'stream';
|
import stream from 'stream';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
@ -24,7 +24,7 @@ export default async function handler(
|
|||||||
const xml = await createIdPMetadataXML({
|
const xml = await createIdPMetadataXML({
|
||||||
idpEntityId: config.entityId,
|
idpEntityId: config.entityId,
|
||||||
idpSsoUrl: config.ssoUrl,
|
idpSsoUrl: config.ssoUrl,
|
||||||
certificate: await createCertificate(),
|
certificate: await fetchPublicKey(),
|
||||||
});
|
});
|
||||||
|
|
||||||
res.setHeader('Content-type', 'text/xml');
|
res.setHeader('Content-type', 'text/xml');
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
|||||||
import { createResponseForm, createSAMLResponseXML } from 'utils';
|
import { createResponseForm, createSAMLResponseXML } from 'utils';
|
||||||
import { User } from 'types';
|
import { User } from 'types';
|
||||||
import config from '../../../lib/env'
|
import config from '../../../lib/env'
|
||||||
|
import { signResponseXML } from 'utils/response';
|
||||||
|
import { fetchPrivateKey, fetchPublicKey } from 'utils/certificate';
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
@ -37,9 +39,11 @@ export default async function handler(
|
|||||||
user: user,
|
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);
|
const html = createResponseForm(relayState, encodedSamlResponse, acsUrl);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { GetStaticProps } from "next";
|
import { GetStaticProps } from "next";
|
||||||
import { IdPMetadata } from "../types";
|
import { IdPMetadata } from "../types";
|
||||||
import config from "../lib/env";
|
import config from "../lib/env";
|
||||||
import { createCertificate } from "../utils";
|
import { fetchPublicKey } from "../utils";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
@ -10,7 +10,7 @@ export const getStaticProps: GetStaticProps = async () => {
|
|||||||
const metadata: IdPMetadata = {
|
const metadata: IdPMetadata = {
|
||||||
ssoUrl: config.ssoUrl,
|
ssoUrl: config.ssoUrl,
|
||||||
entityId: config.entityId,
|
entityId: config.entityId,
|
||||||
certificate: await createCertificate(),
|
certificate: await fetchPublicKey(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -32,7 +32,7 @@ const Home: React.FC<{ metadata: IdPMetadata }> = ({ metadata }) => {
|
|||||||
<p className="font-extrabold">Entity ID</p>
|
<p className="font-extrabold">Entity ID</p>
|
||||||
<p className="col-span-2">{metadata.entityId}</p>
|
<p className="col-span-2">{metadata.entityId}</p>
|
||||||
<p className="font-extrabold">Certificate</p>
|
<p className="font-extrabold">Certificate</p>
|
||||||
<p className="min-w-0 overflow-auto text-sm col-span-2">
|
<p className="min-w-0 col-span-2 overflow-auto text-sm">
|
||||||
{metadata.certificate}
|
{metadata.certificate}
|
||||||
</p>
|
</p>
|
||||||
<br></br>
|
<br></br>
|
||||||
|
|||||||
23
utils/certificate.ts
Normal file
23
utils/certificate.ts
Normal file
@ -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,
|
||||||
|
}
|
||||||
222
utils/index.ts
222
utils/index.ts
@ -1,219 +1,3 @@
|
|||||||
import { promises as fs } from 'fs';
|
export { fetchPrivateKey, fetchPublicKey } from './certificate'
|
||||||
import path from 'path';
|
export {extractSAMLRequestAttributes, createIdPMetadataXML} from './request'
|
||||||
import xml2js from 'xml2js';
|
export { createSAMLResponseXML, createResponseForm, signResponseXML } from './response'
|
||||||
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<Record<string, any>> => {
|
|
||||||
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<string> => {
|
|
||||||
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<string> => {
|
|
||||||
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 = [
|
|
||||||
'<!DOCTYPE html>',
|
|
||||||
'<html>',
|
|
||||||
'<head>',
|
|
||||||
'<meta charset="utf-8">',
|
|
||||||
'<meta http-equiv="x-ua-compatible" content="ie=edge">',
|
|
||||||
'</head>',
|
|
||||||
'<body onload="document.forms[0].submit()">',
|
|
||||||
'<noscript>',
|
|
||||||
'<p>Note: Since your browser does not support JavaScript, you must press the Continue button once to proceed.</p>',
|
|
||||||
'</noscript>',
|
|
||||||
'<form method="post" action="' + encodeURI(acsUrl) + '">',
|
|
||||||
'<input type="hidden" name="RelayState" value="' + relayState + '"/>',
|
|
||||||
'<input type="hidden" name="SAMLResponse" value="' + encodedSamlResponse + '"/>',
|
|
||||||
'<input type="submit" value="Continue" />',
|
|
||||||
'</form>',
|
|
||||||
'<script>document.forms[0].style.display="none";</script>',
|
|
||||||
'</body>',
|
|
||||||
'</html>',
|
|
||||||
];
|
|
||||||
|
|
||||||
return formElements.join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
parseXML,
|
|
||||||
extractSAMLRequestAttributes,
|
|
||||||
createIdPMetadataXML,
|
|
||||||
createSAMLResponseXML,
|
|
||||||
createCertificate,
|
|
||||||
extractCert,
|
|
||||||
};
|
|
||||||
57
utils/request.ts
Normal file
57
utils/request.ts
Normal file
@ -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<Record<string, any>> => {
|
||||||
|
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<string> => {
|
||||||
|
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,
|
||||||
|
}
|
||||||
165
utils/response.ts
Normal file
165
utils/response.ts
Normal file
@ -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<string> => {
|
||||||
|
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 = [
|
||||||
|
'<!DOCTYPE html>',
|
||||||
|
'<html>',
|
||||||
|
'<head>',
|
||||||
|
'<meta charset="utf-8">',
|
||||||
|
'<meta http-equiv="x-ua-compatible" content="ie=edge">',
|
||||||
|
'</head>',
|
||||||
|
'<body onload="document.forms[0].submit()">',
|
||||||
|
'<noscript>',
|
||||||
|
'<p>Note: Since your browser does not support JavaScript, you must press the Continue button once to proceed.</p>',
|
||||||
|
'</noscript>',
|
||||||
|
'<form method="post" action="' + encodeURI(acsUrl) + '">',
|
||||||
|
'<input type="hidden" name="RelayState" value="' + relayState + '"/>',
|
||||||
|
'<input type="hidden" name="SAMLResponse" value="' + encodedSamlResponse + '"/>',
|
||||||
|
'<input type="submit" value="Continue" />',
|
||||||
|
'</form>',
|
||||||
|
'<script>document.forms[0].style.display="none";</script>',
|
||||||
|
'</body>',
|
||||||
|
'</html>',
|
||||||
|
];
|
||||||
|
|
||||||
|
return formElements.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const signResponseXML = async (xml: string, signingKey: any, publicKey: any): Promise<string> => {
|
||||||
|
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
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user