diff --git a/lib/assertions.js b/lib/assertions.js index e60d7d8..bca3816 100644 --- a/lib/assertions.js +++ b/lib/assertions.js @@ -8,12 +8,71 @@ const readFrom = p => fs.readFileSync(path.resolve(__dirname, p), 'utf8') const TEMPLATE = readFrom('../static/saml/unsigned-assertion.xml') const corpPassTemplate = readFrom('../static/saml/corppass.xml') -const NRIC = process.env.MOCKPASS_NRIC || 'S8979373D' -const UEN = process.env.MOCKPASS_UEN || '123456789A' +const identities = { + singPass: [ + 'S8979373D', + 'S8116474F', + 'S8723211E', + 'S5062854Z', + 'T0066846F', + 'F9477325W', + 'S3000024B', + 'S6005040F', + 'S6005041D', + 'S6005042B', + 'S6005043J', + 'S6005044I', + 'S6005045G', + 'S6005046E', + 'S6005047C', + 'S6005064C', + 'S6005065A', + 'S6005066Z', + 'S6005037F', + 'S6005038D', + 'S6005039B', + 'G1612357P', + 'G1612358M', + 'F1612359P', + 'F1612360U', + 'F1612361R', + 'F1612362P', + 'F1612363M', + 'F1612364K', + 'F1612365W', + 'F1612366T', + 'F1612367Q', + 'F1612358R', + 'F1612354N', + 'F1612357U', + ], + corpPass: [ + { NRIC: 'S8979373D', UEN: '123456789A' }, + ], +} + +const makeCorpPass = ({ NRIC, UEN }) => render( + TEMPLATE, + { + name: UEN, + value: base64.encode(render(corpPassTemplate, { NRIC, UEN })), + } +) + +const makeSingPass = NRIC => render(TEMPLATE, { name: 'UserName', value: NRIC }) -const CORPPASS = base64.encode(render(corpPassTemplate, { NRIC, UEN })) +const NRIC = process.env.MOCKPASS_NRIC || identities.singPass[0] +const CORPPASS_NRIC = process.env.MOCKPASS_NRIC || identities.corpPass[0].NRIC +const UEN = process.env.MOCKPASS_UEN || identities.corpPass[0].UEN module.exports = { - singPass: render(TEMPLATE, { name: 'UserName', value: NRIC }), - corpPass: render(TEMPLATE, { name: UEN, value: CORPPASS }), + singPass: { + default: makeSingPass(NRIC), + create: makeSingPass, + }, + corpPass: { + default: makeCorpPass({ NRIC: CORPPASS_NRIC, UEN }), + create: makeCorpPass, + }, + identities, } diff --git a/lib/express.js b/lib/express.js index a5941c9..dc32a1c 100644 --- a/lib/express.js +++ b/lib/express.js @@ -23,15 +23,24 @@ function config (app, { showLoginPage, serviceProvider, idpConfig }) { for (const idp of ['singPass', 'corpPass']) { app.get(`/${idp.toLowerCase()}/logininitial`, (req, res) => { const relayState = encodeURIComponent(req.query.Target) - const samlArt = samlArtifact(idpConfig[idp].id) - const assertURL = - `${idpConfig[idp].assertEndpoint}?SAMLart=${samlArt}&RelayState=${relayState}` - console.warn(`Redirecting login from ${req.query.PartnerId} to ${assertURL}`) if (showLoginPage) { - res.send(` -
Click to login here - `) + const identities = assertions.identities[idp] + const body = identities + .map((value, index) => { + const samlArt = samlArtifact(idpConfig[idp].id, index) + const assertURL = + `${idpConfig[idp].assertEndpoint}?SAMLart=${samlArt}&RelayState=${relayState}` + return ` +Click to login as ${value}
+ ` + }) + .join('\n') + res.send(`${body}`) } else { + const samlArt = samlArtifact(idpConfig[idp].id) + const assertURL = + `${idpConfig[idp].assertEndpoint}?SAMLart=${samlArt}&RelayState=${relayState}` + console.warn(`Redirecting login from ${req.query.PartnerId} to ${assertURL}`) res.redirect(assertURL) } }) @@ -54,7 +63,12 @@ function config (app, { showLoginPage, serviceProvider, idpConfig }) { console.warn(`Received SAML Artifact ${samlArtifact}`) // Take the template and plug in the typical SingPass/CorpPass response // Sign and encrypt the assertion - const signedAssertion = sign(assertions[idp], "//*[local-name(.)='Assertion']") + const samlArtifactBuffer = Buffer.from(samlArtifact, 'base64') + const index = samlArtifactBuffer.readInt8(samlArtifactBuffer.length - 1) + const assertion = assertions.identities[idp][index] + ? assertions[idp].create(assertions.identities[idp][index]) + : assertions[idp].default + const signedAssertion = sign(assertion, "//*[local-name(.)='Assertion']") promiseToEncryptAssertion(signedAssertion) .then(assertion => { const response = render(TEMPLATE, { assertion }) diff --git a/lib/saml-artifact.js b/lib/saml-artifact.js index 71ed7e8..b9018d2 100644 --- a/lib/saml-artifact.js +++ b/lib/saml-artifact.js @@ -8,16 +8,20 @@ const crypto = require('crypto') * - a 20-byte sha1 hash of the partner id, and; * - a 20-byte random sequence that is effectively the message id * @param {string} partnerId - the partner id + * @param {number} [index] - represents the nth identity to use. Defaults to -1 * @return {string} the SAML artifact, a base64 string * containing the type code, the endpoint index, * the hash of the partner id, followed by 20 random bytes */ -function samlArtifact (partnerId) { +function samlArtifact (partnerId, index) { let hashedPartnerId = crypto.createHash('sha1') .update(partnerId, 'utf8') .digest('hex') - const randomBytes = crypto.randomBytes(20).toString('hex') - return Buffer.from(`00040000${hashedPartnerId}${randomBytes}`, 'hex') + const randomBytes = crypto.randomBytes(19).toString('hex') + const indexBuffer = Buffer.alloc(1) + indexBuffer.writeInt8((index || index === 0) ? index : -1) + const indexString = indexBuffer.toString('hex') + return Buffer.from(`00040000${hashedPartnerId}${randomBytes}${indexString}`, 'hex') .toString('base64') }