moved parsing of saml request to saml20 lib (#486)

This commit is contained in:
Deepak Prabhakara 2024-02-04 13:35:59 +00:00 committed by GitHub
parent 4028487c13
commit 0ad3b3bfac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 7 additions and 83 deletions

19
package-lock.json generated
View File

@ -9,20 +9,18 @@
"version": "1.3.1", "version": "1.3.1",
"license": "Apache 2.0", "license": "Apache 2.0",
"dependencies": { "dependencies": {
"@boxyhq/saml20": "1.4.2", "@boxyhq/saml20": "1.4.5",
"daisyui": "4.6.1", "daisyui": "4.6.1",
"next": "14.1.0", "next": "14.1.0",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-gtm-module": "2.0.11", "react-gtm-module": "2.0.11",
"xml2js": "0.6.2",
"xmlbuilder": "15.1.1" "xmlbuilder": "15.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "20.11.16", "@types/node": "20.11.16",
"@types/react": "18.2.51", "@types/react": "18.2.51",
"@types/react-gtm-module": "2.0.3", "@types/react-gtm-module": "2.0.3",
"@types/xml2js": "0.4.14",
"@typescript-eslint/parser": "6.20.0", "@typescript-eslint/parser": "6.20.0",
"autoprefixer": "10.4.17", "autoprefixer": "10.4.17",
"eslint": "8.56.0", "eslint": "8.56.0",
@ -250,9 +248,9 @@
} }
}, },
"node_modules/@boxyhq/saml20": { "node_modules/@boxyhq/saml20": {
"version": "1.4.2", "version": "1.4.5",
"resolved": "https://registry.npmjs.org/@boxyhq/saml20/-/saml20-1.4.2.tgz", "resolved": "https://registry.npmjs.org/@boxyhq/saml20/-/saml20-1.4.5.tgz",
"integrity": "sha512-eAYoPfAMOSawyw5YFHpe4wIg7vALDgza0c/D5jfGEfg7GoI8MOsa7bLCo8FR45kGDji2NSfM8HkCexr8pkqw6A==", "integrity": "sha512-iI+pEmCzl3r0RrIZ6L6QwuCoCZAkkcYTLJZbHLwNxll5JuDEXwg/tZu4Df7j5rO08aggf1w6cpIf/Ng/6hhLLw==",
"dependencies": { "dependencies": {
"@xmldom/xmldom": "0.8.10", "@xmldom/xmldom": "0.8.10",
"lodash": "4.17.21", "lodash": "4.17.21",
@ -1050,15 +1048,6 @@
"integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==", "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==",
"dev": true "dev": true
}, },
"node_modules/@types/xml2js": {
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "6.20.0", "version": "6.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz",

View File

@ -12,20 +12,18 @@
"release": "git checkout release && git merge origin/main && release-it && git checkout main && git merge origin/release && git push origin main" "release": "git checkout release && git merge origin/main && release-it && git checkout main && git merge origin/release && git push origin main"
}, },
"dependencies": { "dependencies": {
"@boxyhq/saml20": "1.4.2", "@boxyhq/saml20": "1.4.5",
"daisyui": "4.6.1", "daisyui": "4.6.1",
"next": "14.1.0", "next": "14.1.0",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-gtm-module": "2.0.11", "react-gtm-module": "2.0.11",
"xml2js": "0.6.2",
"xmlbuilder": "15.1.1" "xmlbuilder": "15.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "20.11.16", "@types/node": "20.11.16",
"@types/react": "18.2.51", "@types/react": "18.2.51",
"@types/react-gtm-module": "2.0.3", "@types/react-gtm-module": "2.0.3",
"@types/xml2js": "0.4.14",
"@typescript-eslint/parser": "6.20.0", "@typescript-eslint/parser": "6.20.0",
"autoprefixer": "10.4.17", "autoprefixer": "10.4.17",
"eslint": "8.56.0", "eslint": "8.56.0",

View File

@ -1,5 +1,4 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { decodeBase64, extractSAMLRequestAttributes } from 'utils';
import saml from '@boxyhq/saml20'; import saml from '@boxyhq/saml20';
export default async function handler(req: NextApiRequest, res: NextApiResponse<string>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<string>) {
@ -30,12 +29,9 @@ async function processSAMLRequest(req: NextApiRequest, res: NextApiResponse, isP
} }
try { try {
const rawRequest = await decodeBase64(samlRequest, isDeflated); const rawRequest = await saml.decodeBase64(samlRequest, isDeflated);
const { id, audience, acsUrl, providerName, publicKey } = await extractSAMLRequestAttributes( const { id, audience, acsUrl, providerName, publicKey } = await saml.parseSAMLRequest(rawRequest, isPost);
rawRequest,
isPost
);
if (isPost) { if (isPost) {
const { valid } = await saml.hasValidSignature(rawRequest, publicKey, null); const { valid } = await saml.hasValidSignature(rawRequest, publicKey, null);

View File

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

View File

@ -1,58 +0,0 @@
import { promisify } from 'util';
import xml2js from 'xml2js';
import { inflateRaw } from 'zlib';
const inflateRawAsync = promisify(inflateRaw);
// Parse XML
const parseXML = (xml: string): Promise<Record<string, any>> => {
return new Promise((resolve, reject) => {
xml2js.parseString(
xml,
{
tagNameProcessors: [xml2js.processors.stripPrefix],
strict: true,
},
(err: Error | null, result: any) => {
if (err) {
reject(err);
}
resolve(result);
}
);
});
};
// Decode the base64 string
const decodeBase64 = async (string: string, isDeflated: boolean) => {
return isDeflated
? (await inflateRawAsync(Buffer.from(string, 'base64'))).toString()
: Buffer.from(string, 'base64').toString();
};
// Parse SAMLRequest attributes
const extractSAMLRequestAttributes = async (rawRequest: string, isPost = true) => {
const result = await parseXML(rawRequest);
const attributes = result['AuthnRequest']['$'];
const issuer = result['AuthnRequest']['Issuer'];
const publicKey = result['AuthnRequest']['Signature']
? result['AuthnRequest']['Signature'][0]['KeyInfo'][0]['X509Data'][0]['X509Certificate'][0]
: null;
if (!publicKey && isPost) {
throw new Error('Missing signature');
}
return {
id: attributes.ID,
acsUrl: attributes.AssertionConsumerServiceURL,
providerName: attributes.ProviderName,
audience: issuer[0]['_'] ?? issuer[0],
publicKey,
};
};
export { extractSAMLRequestAttributes, decodeBase64 };