2022-02-21 17:02:03 +00:00
|
|
|
import crypto from 'crypto';
|
2022-02-23 13:48:20 +00:00
|
|
|
import { SignedXml } from 'xml-crypto';
|
|
|
|
|
import xmlbuilder from 'xmlbuilder';
|
|
|
|
|
import { User } from '../types';
|
|
|
|
|
import { GetKeyInfo } from './certificate';
|
2022-02-21 14:31:47 +00:00
|
|
|
|
2022-02-21 15:36:25 +00:00
|
|
|
const createResponseXML = async (params: {
|
2022-02-22 06:14:12 +00:00
|
|
|
idpIdentityId: string;
|
|
|
|
|
audience: string;
|
|
|
|
|
acsUrl: string;
|
2022-02-23 12:35:58 +00:00
|
|
|
samlReqId: string;
|
2022-02-22 06:14:12 +00:00
|
|
|
user: User;
|
2022-02-21 14:31:47 +00:00
|
|
|
}): Promise<string> => {
|
2022-02-23 12:35:58 +00:00
|
|
|
const { idpIdentityId, audience, acsUrl, user, samlReqId } = params;
|
2022-02-21 14:31:47 +00:00
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
2022-02-23 12:35:58 +00:00
|
|
|
const inResponseTo = samlReqId;
|
2022-02-23 08:20:49 +00:00
|
|
|
// const responseId = crypto.randomBytes(10).toString('hex');
|
2022-02-21 14:31:47 +00:00
|
|
|
|
|
|
|
|
const attributeStatement = {
|
|
|
|
|
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
|
|
|
|
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
2022-02-22 06:14:12 +00:00
|
|
|
'saml:Attribute': [
|
2022-02-21 14:31:47 +00:00
|
|
|
{
|
|
|
|
|
'@Name': 'id',
|
|
|
|
|
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
|
|
|
|
|
'saml:AttributeValue': {
|
2022-02-23 08:20:49 +00:00
|
|
|
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
|
|
|
|
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
|
|
|
'@xsi:type': 'xs:string',
|
2022-02-21 14:31:47 +00:00
|
|
|
'#text': user.id,
|
2022-02-22 06:14:12 +00:00
|
|
|
},
|
2022-02-21 14:31:47 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'@Name': 'email',
|
|
|
|
|
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
|
|
|
|
|
'saml:AttributeValue': {
|
2022-02-23 08:20:49 +00:00
|
|
|
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
|
|
|
|
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
|
|
|
'@xsi:type': 'xs:string',
|
2022-02-21 14:31:47 +00:00
|
|
|
'#text': user.email,
|
2022-02-22 06:14:12 +00:00
|
|
|
},
|
2022-02-21 14:31:47 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'@Name': 'firstName',
|
|
|
|
|
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
|
|
|
|
|
'saml:AttributeValue': {
|
2022-02-23 08:20:49 +00:00
|
|
|
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
|
|
|
|
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
|
|
|
'@xsi:type': 'xs:string',
|
2022-02-21 14:31:47 +00:00
|
|
|
'#text': user.firstName,
|
2022-02-22 06:14:12 +00:00
|
|
|
},
|
2022-02-21 14:31:47 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'@Name': 'lastName',
|
|
|
|
|
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
|
|
|
|
|
'saml:AttributeValue': {
|
2022-02-23 08:20:49 +00:00
|
|
|
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
|
|
|
|
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
|
|
|
'@xsi:type': 'xs:string',
|
2022-02-21 14:31:47 +00:00
|
|
|
'#text': user.lastName,
|
2022-02-22 06:14:12 +00:00
|
|
|
},
|
2022-02-21 14:31:47 +00:00
|
|
|
},
|
2022-02-22 06:14:12 +00:00
|
|
|
],
|
|
|
|
|
};
|
2022-02-21 14:31:47 +00:00
|
|
|
|
|
|
|
|
const nodes = {
|
2022-02-22 06:14:12 +00:00
|
|
|
'samlp:Response': {
|
2022-02-21 14:31:47 +00:00
|
|
|
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
|
|
|
|
|
'@Version': '2.0',
|
2022-02-23 08:20:49 +00:00
|
|
|
'@ID': crypto.randomBytes(10).toString('hex'),
|
2022-02-21 14:31:47 +00:00
|
|
|
'@Destination': acsUrl,
|
|
|
|
|
'@InResponseTo': inResponseTo,
|
|
|
|
|
'@IssueInstant': authTimestamp,
|
2022-02-23 08:20:49 +00:00
|
|
|
'saml:Issuer': {
|
|
|
|
|
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
|
|
|
|
|
'@Format': 'urn:oasis:names:tc:SAML:2.0:assertion',
|
|
|
|
|
'#text': idpIdentityId,
|
|
|
|
|
},
|
2022-02-21 14:31:47 +00:00
|
|
|
'samlp:Status': {
|
|
|
|
|
'samlp:StatusCode': {
|
2022-02-22 06:14:12 +00:00
|
|
|
'@Value': 'urn:oasis:names:tc:SAML:2.0:status:Success',
|
|
|
|
|
},
|
2022-02-21 14:31:47 +00:00
|
|
|
},
|
|
|
|
|
'saml:Assertion': {
|
|
|
|
|
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
|
|
|
|
|
'@Version': '2.0',
|
2022-02-23 08:20:49 +00:00
|
|
|
'@ID': crypto.randomBytes(10).toString('hex'),
|
2022-02-21 14:31:47 +00:00
|
|
|
'@IssueInstant': authTimestamp,
|
|
|
|
|
'saml:Issuer': {
|
|
|
|
|
'#text': idpIdentityId,
|
|
|
|
|
},
|
|
|
|
|
'saml:Subject': {
|
|
|
|
|
'saml:NameID': {
|
|
|
|
|
'@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
|
|
|
|
|
'#text': user.email,
|
2022-02-22 06:14:12 +00:00
|
|
|
},
|
2022-02-21 14:31:47 +00:00
|
|
|
},
|
|
|
|
|
'saml:Conditions': {
|
|
|
|
|
'@NotBefore': notBefore,
|
|
|
|
|
'@NotOnOrAfter': notAfter,
|
|
|
|
|
'saml:AudienceRestriction': {
|
|
|
|
|
'saml:Audience': {
|
|
|
|
|
'#text': audience,
|
2022-02-22 06:14:12 +00:00
|
|
|
},
|
|
|
|
|
},
|
2022-02-21 14:31:47 +00:00
|
|
|
},
|
|
|
|
|
'saml:AuthnStatement': {
|
|
|
|
|
'@AuthnInstant': authTimestamp,
|
|
|
|
|
'@SessionIndex': '_YIlFoNFzLMDYxdwf-T_BuimfkGa5qhKg',
|
|
|
|
|
'saml:AuthnContext': {
|
|
|
|
|
'saml:AuthnContextClassRef': {
|
2022-02-22 06:14:12 +00:00
|
|
|
'#text': 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified',
|
|
|
|
|
},
|
|
|
|
|
},
|
2022-02-21 14:31:47 +00:00
|
|
|
},
|
|
|
|
|
'saml:AttributeStatement': attributeStatement,
|
|
|
|
|
},
|
2022-02-22 06:14:12 +00:00
|
|
|
},
|
|
|
|
|
};
|
2022-02-21 14:31:47 +00:00
|
|
|
|
2022-02-23 08:20:49 +00:00
|
|
|
return xmlbuilder.create(nodes, { encoding: 'UTF-8' }).end();
|
2022-02-21 14:31:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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> => {
|
2022-02-21 15:36:25 +00:00
|
|
|
const sig = new SignedXml();
|
2022-02-22 06:14:12 +00:00
|
|
|
const responseXPath =
|
|
|
|
|
'/*[local-name(.)="Response" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
|
|
|
|
|
const issuerXPath =
|
|
|
|
|
'/*[local-name(.)="Issuer" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:assertion"]';
|
2022-02-21 14:31:47 +00:00
|
|
|
|
2022-02-21 15:36:25 +00:00
|
|
|
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
|
2022-02-22 06:00:14 +00:00
|
|
|
|
|
|
|
|
// @ts-ignore
|
2022-02-23 08:20:49 +00:00
|
|
|
sig.keyInfoProvider = new GetKeyInfo(publicKey, {
|
2022-02-23 12:34:57 +00:00
|
|
|
prefix: '',
|
2022-02-23 08:20:49 +00:00
|
|
|
});
|
|
|
|
|
|
2022-02-21 15:36:25 +00:00
|
|
|
sig.signingKey = signingKey;
|
2022-02-21 14:31:47 +00:00
|
|
|
|
2022-02-22 06:14:12 +00:00
|
|
|
sig.addReference(
|
|
|
|
|
responseXPath,
|
|
|
|
|
['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'
|
|
|
|
|
);
|
2022-02-21 15:36:25 +00:00
|
|
|
|
|
|
|
|
sig.computeSignature(xml, {
|
|
|
|
|
location: { reference: responseXPath + issuerXPath, action: 'after' },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return sig.getSignedXml();
|
2022-02-22 06:14:12 +00:00
|
|
|
};
|
2022-02-21 14:31:47 +00:00
|
|
|
|
2022-02-22 06:14:12 +00:00
|
|
|
export { createResponseXML, createResponseForm, signResponseXML };
|