move createSAMLResponse to saml20 (#489)

This commit is contained in:
Deepak Prabhakara 2024-02-05 11:14:10 +00:00 committed by GitHub
parent b63842b23b
commit 895e61b55d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 14 additions and 162 deletions

8
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "1.3.2",
"license": "Apache 2.0",
"dependencies": {
"@boxyhq/saml20": "1.4.5",
"@boxyhq/saml20": "1.4.9",
"daisyui": "4.6.1",
"next": "14.1.0",
"react": "18.2.0",
@ -248,9 +248,9 @@
}
},
"node_modules/@boxyhq/saml20": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@boxyhq/saml20/-/saml20-1.4.5.tgz",
"integrity": "sha512-iI+pEmCzl3r0RrIZ6L6QwuCoCZAkkcYTLJZbHLwNxll5JuDEXwg/tZu4Df7j5rO08aggf1w6cpIf/Ng/6hhLLw==",
"version": "1.4.9",
"resolved": "https://registry.npmjs.org/@boxyhq/saml20/-/saml20-1.4.9.tgz",
"integrity": "sha512-S86wTvQkFDbCuwCeyu3AUbHUh3qWtqge557RwIeo2T0L3kL2xMeOJK1QcRBEfborxUXdL9zRmn5sUqceCuWYjQ==",
"dependencies": {
"@xmldom/xmldom": "0.8.10",
"lodash": "4.17.21",

View File

@ -14,7 +14,7 @@
"release": "git checkout release && git merge origin/main && release-it && git checkout main && git merge origin/release && git push origin main"
},
"dependencies": {
"@boxyhq/saml20": "1.4.5",
"@boxyhq/saml20": "1.4.9",
"daisyui": "4.6.1",
"next": "14.1.0",
"react": "18.2.0",

View File

@ -2,7 +2,6 @@ import { createHash } from 'crypto';
import config from 'lib/env';
import type { NextApiRequest, NextApiResponse } from 'next';
import type { User } from 'types';
import { createResponseXML, signResponseXML } from 'utils';
import saml from '@boxyhq/saml20';
import { getEntityId } from 'lib/entity-id';
@ -24,15 +23,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
lastName: userName,
};
const xml = await createResponseXML({
idpIdentityId: getEntityId(config.entityId, req.query.namespace as any),
const xmlSigned = await saml.createSAMLResponse({
issuer: getEntityId(config.entityId, req.query.namespace as any),
audience,
acsUrl,
samlReqId: id,
user: user,
requestId: id,
claims: {
email: user.email,
raw: user,
},
privateKey: config.privateKey,
publicKey: config.publicKey,
});
const xmlSigned = await signResponseXML(xml, config.privateKey, config.publicKey);
const encodedSamlResponse = Buffer.from(xmlSigned).toString('base64');
const html = saml.createPostForm(acsUrl, [
{

View File

@ -1,3 +1,2 @@
export * from './certificate';
export * from './idp';
export * from './response';

View File

@ -1,150 +0,0 @@
import crypto from 'crypto';
import xmlbuilder from 'xmlbuilder';
import { User } from '../types';
import saml from '@boxyhq/saml20';
const responseXPath =
'/*[local-name(.)="Response" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
const randomId = () => {
return '_' + crypto.randomBytes(10).toString('hex');
};
const createResponseXML = async (params: {
idpIdentityId: string;
audience: string;
acsUrl: string;
samlReqId: string;
user: User;
}): Promise<string> => {
const { idpIdentityId, audience, acsUrl, user, samlReqId } = 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 = samlReqId;
// 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': {
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xsi:type': 'xs:string',
'#text': user.id,
},
},
{
'@Name': 'email',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xsi:type': 'xs:string',
'#text': user.email,
},
},
{
'@Name': 'firstName',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xsi:type': 'xs:string',
'#text': user.firstName,
},
},
{
'@Name': 'lastName',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xsi:type': 'xs:string',
'#text': user.lastName,
},
},
],
};
const nodes = {
'samlp:Response': {
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
'@Version': '2.0',
'@ID': randomId(),
'@Destination': acsUrl,
'@InResponseTo': inResponseTo,
'@IssueInstant': authTimestamp,
'saml:Issuer': {
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
'@Format': 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity',
'#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': randomId(),
'@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:SubjectConfirmation': {
'@Method': 'urn:oasis:names:tc:SAML:2.0:cm:bearer',
'saml:SubjectConfirmationData': {
'@InResponseTo': inResponseTo,
'@NotOnOrAfter': notAfter,
'@Recipient': acsUrl,
},
},
},
'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, { encoding: 'UTF-8' }).end();
};
const signResponseXML = async (xml: string, signingKey: any, publicKey: any): Promise<string> => {
return await saml.sign(xml, signingKey, publicKey, responseXPath);
};
export { createResponseXML, signResponseXML };