@opengovsg/mockpass 2.9.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -5,12 +5,7 @@ const morgan = require('morgan')
5
5
  const path = require('path')
6
6
  require('dotenv').config()
7
7
 
8
- const {
9
- configSAML,
10
- configOIDC,
11
- configMyInfo,
12
- configSGID,
13
- } = require('./lib/express')
8
+ const { configOIDC, configMyInfo, configSGID } = require('./lib/express')
14
9
 
15
10
  const PORT = process.env.MOCKPASS_PORT || process.env.PORT || 5156
16
11
 
@@ -49,18 +44,6 @@ const cryptoConfig = {
49
44
 
50
45
  const options = {
51
46
  serviceProvider,
52
- idpConfig: {
53
- singPass: {
54
- id:
55
- process.env.SINGPASS_IDP_ID || 'http://localhost:5156/singpass/saml20',
56
- assertEndpoint: process.env.SINGPASS_ASSERT_ENDPOINT,
57
- },
58
- corpPass: {
59
- id:
60
- process.env.CORPPASS_IDP_ID || 'http://localhost:5156/corppass/saml20',
61
- assertEndpoint: process.env.CORPPASS_ASSERT_ENDPOINT,
62
- },
63
- },
64
47
  showLoginPage: (req) =>
65
48
  (req.header('X-Show-Login-Page') || process.env.SHOW_LOGIN_PAGE) === 'true',
66
49
  encryptMyInfo: process.env.ENCRYPT_MYINFO === 'true',
@@ -70,12 +53,10 @@ const options = {
70
53
  const app = express()
71
54
  app.use(morgan('combined'))
72
55
 
73
- configSAML(app, options)
74
56
  configOIDC(app, options)
75
57
  configSGID(app, options)
76
58
 
77
59
  configMyInfo.consent(app)
78
- configMyInfo.v2(app, options)
79
60
  configMyInfo.v3(app, options)
80
61
 
81
62
  app.enable('trust proxy')
package/lib/assertions.js CHANGED
@@ -1,20 +1,10 @@
1
- const base64 = require('base-64')
2
1
  const crypto = require('crypto')
3
2
  const fs = require('fs')
4
- const { render } = require('mustache')
5
- const moment = require('moment')
6
3
  const jose = require('node-jose')
7
4
  const path = require('path')
8
5
 
9
6
  const readFrom = (p) => fs.readFileSync(path.resolve(__dirname, p), 'utf8')
10
7
 
11
- const TEMPLATE = readFrom('../static/saml/unsigned-assertion.xml')
12
- const corpPassTemplate = readFrom('../static/saml/corppass.xml')
13
-
14
- const defaultAudience =
15
- process.env.SERVICE_PROVIDER_ENTITY_ID ||
16
- 'http://sp.example.com/demo1/metadata.php'
17
-
18
8
  const signingPem = fs.readFileSync(
19
9
  path.resolve(__dirname, '../static/certs/spcp-key.pem'),
20
10
  )
@@ -34,95 +24,9 @@ const hashToken = (token) => {
34
24
  }
35
25
 
36
26
  const myinfo = {
37
- v2: JSON.parse(readFrom('../static/myinfo/v2.json')),
38
27
  v3: JSON.parse(readFrom('../static/myinfo/v3.json')),
39
28
  }
40
29
 
41
- const saml = {
42
- singPass: [
43
- { nric: 'S8979373D' },
44
- { nric: 'S8116474F' },
45
- { nric: 'S8723211E' },
46
- { nric: 'S5062854Z' },
47
- { nric: 'T0066846F' },
48
- { nric: 'F9477325W' },
49
- { nric: 'S3000024B' },
50
- { nric: 'S6005040F' },
51
- { nric: 'S6005041D' },
52
- { nric: 'S6005042B' },
53
- { nric: 'S6005043J' },
54
- { nric: 'S6005044I' },
55
- { nric: 'S6005045G' },
56
- { nric: 'S6005046E' },
57
- { nric: 'S6005047C' },
58
- { nric: 'S6005064C' },
59
- { nric: 'S6005065A' },
60
- { nric: 'S6005066Z' },
61
- { nric: 'S6005037F' },
62
- { nric: 'S6005038D' },
63
- { nric: 'S6005039B' },
64
- { nric: 'G1612357P' },
65
- { nric: 'G1612358M' },
66
- { nric: 'F1612359P' },
67
- { nric: 'F1612360U' },
68
- { nric: 'F1612361R' },
69
- { nric: 'F1612362P' },
70
- { nric: 'F1612363M' },
71
- { nric: 'F1612364K' },
72
- { nric: 'F1612365W' },
73
- { nric: 'F1612366T' },
74
- { nric: 'F1612367Q' },
75
- { nric: 'F1612358R' },
76
- { nric: 'F1612354N' },
77
- { nric: 'F1612357U' },
78
- ...Object.keys(myinfo.v2.personas).map((nric) => ({ nric })),
79
- ],
80
- corpPass: [
81
- { nric: 'S8979373D', uen: '123456789A' },
82
- { nric: 'S8116474F', uen: '123456789A' },
83
- { nric: 'S8723211E', uen: '123456789A' },
84
- { nric: 'S5062854Z', uen: '123456789B' },
85
- { nric: 'T0066846F', uen: '123456789B' },
86
- { nric: 'F9477325W', uen: '123456789B' },
87
- { nric: 'S3000024B', uen: '123456789C' },
88
- { nric: 'S6005040F', uen: '123456789C' },
89
- ],
90
- create: {
91
- singPass: (
92
- { nric },
93
- issuer,
94
- recipient,
95
- inResponseTo,
96
- audience = defaultAudience,
97
- ) =>
98
- render(TEMPLATE, {
99
- name: 'UserName',
100
- value: nric,
101
- issueInstant: moment.utc().format(),
102
- recipient,
103
- issuer,
104
- inResponseTo,
105
- audience,
106
- }),
107
- corpPass: (
108
- { nric, uen },
109
- issuer,
110
- recipient,
111
- inResponseTo,
112
- audience = defaultAudience,
113
- ) =>
114
- render(TEMPLATE, {
115
- issueInstant: moment.utc().format(),
116
- name: uen,
117
- value: base64.encode(render(corpPassTemplate, { nric, uen })),
118
- recipient,
119
- issuer,
120
- inResponseTo,
121
- audience,
122
- }),
123
- },
124
- }
125
-
126
30
  const oidc = {
127
31
  singPass: [
128
32
  { nric: 'S8979373D', uuid: 'a9865837-7bd7-46ac-bef4-42a76a946424' },
@@ -321,7 +225,6 @@ const oidc = {
321
225
  }
322
226
 
323
227
  module.exports = {
324
- saml,
325
228
  oidc,
326
229
  myinfo,
327
230
  }
@@ -1,86 +1,5 @@
1
1
  const _ = require('lodash')
2
2
 
3
- const apex = function apex(authHeader, req, context = {}) {
4
- const authHeaderFieldPairs = _(authHeader)
5
- .replace(/"/g, '')
6
- .replace(/apex_l2_eg_/g, '')
7
- .split(',')
8
- .map((v) => v.replace('=', '~').split('~'))
9
-
10
- const authHeaderFields = _(authHeaderFieldPairs)
11
- .fromPairs()
12
- .mapKeys((v, k) => _.camelCase(k))
13
- .value()
14
-
15
- const url = `${req.protocol}://${req.get('host')}${req.baseUrl}${req.path}`
16
-
17
- const { clientSecret, redirectURI } = context
18
-
19
- const {
20
- method: httpMethod,
21
- query: { attributes, singpassEserviceId },
22
- } = req
23
-
24
- const { code } = req.body || {}
25
-
26
- const {
27
- signature,
28
- appId,
29
- appId: clientId,
30
- nonce,
31
- timestamp,
32
- } = authHeaderFields
33
-
34
- const baseString = req.path.endsWith('/token')
35
- ? httpMethod.toUpperCase() +
36
- // url string replacement was dictated by MyInfo docs - no explanation
37
- // was provided for why this is necessary
38
- '&' +
39
- url.replace('.api.gov.sg', '.e.api.gov.sg') +
40
- '&apex_l2_eg_app_id=' +
41
- appId +
42
- '&apex_l2_eg_nonce=' +
43
- nonce +
44
- '&apex_l2_eg_signature_method=SHA256withRSA' +
45
- '&apex_l2_eg_timestamp=' +
46
- timestamp +
47
- '&apex_l2_eg_version=1.0' +
48
- '&client_id=' +
49
- clientId +
50
- '&client_secret=' +
51
- clientSecret +
52
- '&code=' +
53
- code +
54
- '&grant_type=authorization_code' +
55
- '&redirect_uri=' +
56
- redirectURI
57
- : httpMethod.toUpperCase() +
58
- // url string replacement was dictated by MyInfo docs - no explanation
59
- // was provided for why this is necessary
60
- '&' +
61
- url.replace('.api.gov.sg', '.e.api.gov.sg') +
62
- '&apex_l2_eg_app_id=' +
63
- appId +
64
- '&apex_l2_eg_nonce=' +
65
- nonce +
66
- '&apex_l2_eg_signature_method=SHA256withRSA' +
67
- '&apex_l2_eg_timestamp=' +
68
- timestamp +
69
- '&apex_l2_eg_version=1.0' +
70
- '&attributes=' +
71
- attributes +
72
- '&client_id=' +
73
- clientId +
74
- (req.path.includes('/person-basic')
75
- ? '&singpassEserviceId=' + singpassEserviceId
76
- : '')
77
-
78
- return {
79
- signature,
80
- baseString,
81
- }
82
- }
83
-
84
3
  const pki = function pki(authHeader, req, context = {}) {
85
4
  const authHeaderFieldPairs = _(authHeader)
86
5
  .replace(/"/g, '')
@@ -150,4 +69,4 @@ const pki = function pki(authHeader, req, context = {}) {
150
69
  }
151
70
  }
152
71
 
153
- module.exports = { pki, apex }
72
+ module.exports = { pki }
@@ -1,5 +1,4 @@
1
1
  module.exports = {
2
- configSAML: require('./saml'),
3
2
  configOIDC: require('./oidc'),
4
3
  configMyInfo: require('./myinfo'),
5
4
  configSGID: require('./sgid'),
@@ -9,7 +9,6 @@ const { v1: uuid } = require('uuid')
9
9
 
10
10
  const assertions = require('../../assertions')
11
11
  const { lookUpByAuthCode } = require('../../auth-code')
12
- const { lookUpBySamlArtifact } = require('../../saml-artifact')
13
12
 
14
13
  const MYINFO_ASSERT_ENDPOINT = '/consent/myinfo-com'
15
14
  const AUTHORIZE_ENDPOINT = '/consent/oauth2/authorize'
@@ -43,11 +42,6 @@ const authorize = (redirectTo) => (req, res) => {
43
42
  res.redirect(redirectTo(relayState))
44
43
  }
45
44
 
46
- const authorizeViaSAML = authorize(
47
- (relayState) =>
48
- `/singpass/logininitial?esrvcID=MYINFO-CONSENTPLATFORM&PartnerId=${MYINFO_ASSERT_ENDPOINT}&Target=${relayState}`,
49
- )
50
-
51
45
  const authorizeViaOIDC = authorize(
52
46
  (state) =>
53
47
  `/singpass/authorize?client_id=MYINFO-CONSENTPLATFORM&redirect_uri=${MYINFO_ASSERT_ENDPOINT}&state=${state}`,
@@ -59,14 +53,9 @@ function config(app) {
59
53
  const artifact = rawArtifact.replace(/ /g, '+')
60
54
  const state = req.query.RelayState || req.query.state
61
55
 
62
- let profile, myinfoVersion
63
- if (req.query.code) {
64
- profile = lookUpByAuthCode(artifact).profile
65
- myinfoVersion = 'v3'
66
- } else {
67
- profile = lookUpBySamlArtifact(artifact)
68
- myinfoVersion = 'v2'
69
- }
56
+ const profile = lookUpByAuthCode(artifact).profile
57
+ const myinfoVersion = 'v3'
58
+
70
59
  const { nric: id } = profile
71
60
 
72
61
  const persona = assertions.myinfo[myinfoVersion].personas[id]
@@ -147,7 +136,6 @@ function config(app) {
147
136
  }
148
137
 
149
138
  module.exports = {
150
- authorizeViaSAML,
151
139
  authorizeViaOIDC,
152
140
  authorizations,
153
141
  config,
@@ -31,12 +31,9 @@ module.exports =
31
31
  }
32
32
 
33
33
  const encryptPersona = async (persona) => {
34
- const signedPersona =
35
- version === 'v3'
36
- ? jwt.sign(persona, MOCKPASS_PRIVATE_KEY, {
37
- algorithm: 'RS256',
38
- })
39
- : persona
34
+ const signedPersona = jwt.sign(persona, MOCKPASS_PRIVATE_KEY, {
35
+ algorithm: 'RS256',
36
+ })
40
37
  const serviceCertAsKey = await jose.JWK.asKey(serviceProvider.cert, 'pem')
41
38
  const encryptedAndSignedPersona = await jose.JWE.createEncrypt(
42
39
  { format: 'compact' },
@@ -126,10 +123,7 @@ module.exports =
126
123
  }
127
124
  })
128
125
 
129
- app.get(
130
- `/myinfo/${version}/authorise`,
131
- version === 'v3' ? consent.authorizeViaOIDC : consent.authorizeViaSAML,
132
- )
126
+ app.get(`/myinfo/${version}/authorise`, consent.authorizeViaOIDC)
133
127
 
134
128
  app.post(
135
129
  `/myinfo/${version}/token`,
@@ -1,10 +1,9 @@
1
1
  const { config: consent } = require('./consent')
2
2
  const controllers = require('./controllers')
3
3
 
4
- const { pki, apex } = require('../../crypto/myinfo-signature')
4
+ const { pki } = require('../../crypto/myinfo-signature')
5
5
 
6
6
  module.exports = {
7
7
  consent,
8
- v2: controllers('v2', apex),
9
8
  v3: controllers('v3', pki),
10
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengovsg/mockpass",
3
- "version": "2.9.5",
3
+ "version": "3.0.0",
4
4
  "description": "A mock SingPass/CorpPass server for dev purposes",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -44,13 +44,10 @@
44
44
  "express": "^4.16.3",
45
45
  "jsonwebtoken": "^8.4.0",
46
46
  "lodash": "^4.17.11",
47
- "moment": "^2.24.0",
48
47
  "morgan": "^1.9.1",
49
48
  "mustache": "^4.2.0",
50
49
  "node-jose": "^2.0.0",
51
50
  "uuid": "^9.0.0",
52
- "xml-crypto": "^3.0.0",
53
- "xml-encryption": "^3.0.0",
54
51
  "xpath": "0.0.32"
55
52
  },
56
53
  "devDependencies": {
@@ -1,61 +0,0 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const { SignedXml } = require('xml-crypto')
4
- const { encrypt } = require('xml-encryption')
5
- const xpath = require('xpath')
6
-
7
- module.exports = (serviceProvider) => {
8
- // NOTE - the typo in keyEncryptionAlgorighm is deliberate
9
- const ENCRYPT_OPTIONS = {
10
- rsa_pub: serviceProvider.pubKey,
11
- pem: serviceProvider.cert,
12
- encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
13
- keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-1_5',
14
- }
15
-
16
- return {
17
- verifySignature(xml) {
18
- const [signature] =
19
- xpath.select("//*[local-name(.)='Signature']", xml) || []
20
- const [artifactResolvePayload] =
21
- xpath.select("//*[local-name(.)='ArtifactResolve']", xml) || []
22
- const verifier = new SignedXml()
23
- verifier.keyInfoProvider = { getKey: () => ENCRYPT_OPTIONS.pem }
24
- verifier.loadSignature(signature.toString())
25
- return verifier.checkSignature(artifactResolvePayload.toString())
26
- },
27
-
28
- sign(payload, reference) {
29
- const sig = new SignedXml()
30
- const transforms = [
31
- 'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
32
- 'http://www.w3.org/2001/10/xml-exc-c14n#',
33
- ]
34
- const digestAlgorithm = 'http://www.w3.org/2001/04/xmlenc#sha256'
35
- sig.addReference(reference, transforms, digestAlgorithm)
36
-
37
- sig.signingKey = fs.readFileSync(
38
- path.resolve(__dirname, '../../static/certs/spcp-key.pem'),
39
- )
40
- sig.signatureAlgorithm =
41
- 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
42
- const options = {
43
- prefix: 'ds',
44
- location: { reference, action: 'prepend' },
45
- }
46
- sig.computeSignature(payload, options)
47
- return sig.getSignedXml()
48
- },
49
-
50
- promiseToEncryptAssertion: (assertion) =>
51
- new Promise((resolve, reject) => {
52
- encrypt(assertion, ENCRYPT_OPTIONS, (err, data) =>
53
- err
54
- ? reject(err)
55
- : resolve(
56
- `<saml:EncryptedAssertion>${data}</saml:EncryptedAssertion>`,
57
- ),
58
- )
59
- }),
60
- }
61
- }
@@ -1,195 +0,0 @@
1
- const express = require('express')
2
- const fs = require('fs')
3
- const { render } = require('mustache')
4
- const path = require('path')
5
- const { DOMParser } = require('@xmldom/xmldom')
6
- const xpath = require('xpath')
7
- const moment = require('moment')
8
-
9
- const assertions = require('../assertions')
10
- const crypto = require('../crypto')
11
- const {
12
- generateSamlArtifact,
13
- lookUpBySamlArtifact,
14
- } = require('../saml-artifact')
15
-
16
- const domParser = new DOMParser()
17
- const dom = (xmlString) => domParser.parseFromString(xmlString)
18
-
19
- const TEMPLATE = fs.readFileSync(
20
- path.resolve(__dirname, '../../static/saml/unsigned-response.xml'),
21
- 'utf8',
22
- )
23
- const LOGIN_TEMPLATE = fs.readFileSync(
24
- path.resolve(__dirname, '../../static/html/login-page.html'),
25
- 'utf8',
26
- )
27
-
28
- const MYINFO_ASSERT_ENDPOINT = '/consent/myinfo-com'
29
-
30
- const buildAssertURL = (assertEndpoint, samlArt, relayState) => {
31
- let assertURL = `${assertEndpoint}?SAMLart=${encodeURIComponent(samlArt)}`
32
- if (relayState !== undefined) {
33
- assertURL += `&RelayState=${encodeURIComponent(relayState)}`
34
- }
35
- return assertURL
36
- }
37
-
38
- const idGenerator = {
39
- singPass: ({ nric }) =>
40
- assertions.myinfo.v2.personas[nric] ? `${nric} [MyInfo]` : nric,
41
- corpPass: ({ nric, uen }) => `${nric} / UEN: ${uen}`,
42
- }
43
-
44
- const customProfileFromHeaders = {
45
- singPass: (req) => {
46
- const customNricHeader = req.header('X-Custom-NRIC')
47
- if (!customNricHeader) {
48
- return false
49
- }
50
- return { nric: customNricHeader }
51
- },
52
- corpPass: (req) => {
53
- const customNricHeader = req.header('X-Custom-NRIC')
54
- const customUenHeader = req.header('X-Custom-UEN')
55
- if (!customNricHeader || !customUenHeader) {
56
- return false
57
- }
58
- return { nric: customNricHeader, uen: customUenHeader }
59
- },
60
- }
61
-
62
- function config(
63
- app,
64
- { showLoginPage, serviceProvider, idpConfig, cryptoConfig },
65
- ) {
66
- const { verifySignature, sign, promiseToEncryptAssertion } =
67
- crypto(serviceProvider)
68
-
69
- for (const idp of ['singPass', 'corpPass']) {
70
- const partnerId = idpConfig[idp].id
71
- const partnerAssertEndpoint = idpConfig[idp].assertEndpoint
72
-
73
- const profiles = assertions.saml[idp]
74
- const defaultProfile =
75
- profiles.find((p) => p.nric === process.env.MOCKPASS_NRIC) || profiles[0]
76
-
77
- app.get(`/${idp.toLowerCase()}/logininitial`, (req, res) => {
78
- const assertEndpoint =
79
- req.query.esrvcID === 'MYINFO-CONSENTPLATFORM' && idp === 'singPass'
80
- ? MYINFO_ASSERT_ENDPOINT
81
- : partnerAssertEndpoint || req.query.PartnerId
82
- const relayState = req.query.Target
83
- if (showLoginPage(req)) {
84
- const values = profiles.map((profile) => {
85
- const samlArt = generateSamlArtifact(partnerId, profile)
86
- const assertURL = buildAssertURL(assertEndpoint, samlArt, relayState)
87
- const id = idGenerator[idp](profile)
88
- return { id, assertURL }
89
- })
90
- const response = render(LOGIN_TEMPLATE, {
91
- values,
92
- customProfileConfig: {
93
- endpoint: `/${idp.toLowerCase()}/logininitial/custom-profile`,
94
- showUuid: false,
95
- showUen: idp === 'corpPass',
96
- assertEndpoint,
97
- relayState,
98
- },
99
- })
100
- res.send(response)
101
- } else {
102
- const profile = customProfileFromHeaders[idp](req) || defaultProfile
103
- const samlArt = generateSamlArtifact(partnerId, profile)
104
- const assertURL = buildAssertURL(assertEndpoint, samlArt, relayState)
105
- console.warn(
106
- `Redirecting login from ${req.query.PartnerId} to ${assertURL}`,
107
- )
108
- res.redirect(assertURL)
109
- }
110
- })
111
-
112
- app.get(`/${idp.toLowerCase()}/logininitial/custom-profile`, (req, res) => {
113
- const { nric, uen, assertEndpoint, relayState } = req.query
114
-
115
- const profile = { nric }
116
- if (idp === 'corpPass') {
117
- profile.uen = uen
118
- }
119
-
120
- const samlArt = generateSamlArtifact(partnerId, profile)
121
- const assertURL = buildAssertURL(assertEndpoint, samlArt, relayState)
122
- res.redirect(assertURL)
123
- })
124
-
125
- app.post(
126
- `/${idp.toLowerCase()}/soap`,
127
- express.text({ type: 'text/xml' }),
128
- (req, res) => {
129
- // Extract the body of the SOAP request
130
- const { body } = req
131
- const xml = dom(body)
132
-
133
- if (
134
- cryptoConfig.resolveArtifactRequestSigned &&
135
- !verifySignature(xml)
136
- ) {
137
- res.status(400).send('Request has bad signature')
138
- } else {
139
- // Grab the SAML artifact
140
- // TODO: verify the SAML artifact is something we sent
141
- // TODO: do something about the partner entity id
142
- const samlArtifact = xpath.select(
143
- "string(//*[local-name(.)='Artifact'])",
144
- xml,
145
- )
146
- console.warn(`Received SAML Artifact ${samlArtifact}`)
147
- // Handle encoded base64 Artifact
148
- // Take the template and plug in the typical SingPass/CorpPass response
149
- // Sign and encrypt the assertion
150
- const profile = lookUpBySamlArtifact(samlArtifact)
151
-
152
- const samlArtifactResolveId = xpath.select(
153
- "string(//*[local-name(.)='ArtifactResolve']/@ID)",
154
- xml,
155
- )
156
-
157
- let result = assertions.saml.create[idp](
158
- profile,
159
- partnerId,
160
- partnerAssertEndpoint,
161
- samlArtifactResolveId,
162
- )
163
-
164
- if (cryptoConfig.signAssertion) {
165
- result = sign(result, "//*[local-name(.)='Assertion']")
166
- }
167
- const assertionPromise = cryptoConfig.encryptAssertion
168
- ? promiseToEncryptAssertion(result)
169
- : Promise.resolve(result)
170
-
171
- assertionPromise.then((assertion) => {
172
- let response = render(TEMPLATE, {
173
- assertion,
174
- issueInstant: moment.utc().format(),
175
- issuer: partnerId,
176
- destination: partnerAssertEndpoint,
177
- inResponseTo: samlArtifactResolveId,
178
- })
179
- if (cryptoConfig.signResponse) {
180
- response = sign(
181
- sign(response, "//*[local-name(.)='Response']"),
182
- "//*[local-name(.)='ArtifactResponse']",
183
- )
184
- }
185
- res.send(response)
186
- })
187
- }
188
- },
189
- )
190
- }
191
-
192
- return app
193
- }
194
-
195
- module.exports = config
@@ -1,39 +0,0 @@
1
- const ExpiryMap = require('expiry-map')
2
- const crypto = require('crypto')
3
-
4
- const SAML_ARTIFACT_TIMEOUT = 5 * 60 * 1000
5
- const profileStore = new ExpiryMap(SAML_ARTIFACT_TIMEOUT)
6
-
7
- /**
8
- * Construct a SingPass/CorpPass SAML artifact, a base64
9
- * encoding of a byte sequence consisting of the following:
10
- * - a two-byte type code, always 0x0004, per SAML 2.0;
11
- * - a two-byte endpoint index, currently always 0x0000;
12
- * - a 20-byte sha1 hash of the partner id, and;
13
- * - a 20-byte random sequence that is effectively the message id
14
- * @param {string} partnerId - the partner id
15
- * @param {string} profile - the profile (identity) to store
16
- * @return {string} the SAML artifact, a base64 string
17
- * containing the type code, the endpoint index,
18
- * the hash of the partner id, followed by 20 random bytes;
19
- * this can be used to look up the stored profile (identity)
20
- */
21
- function generateSamlArtifact(partnerId, profile) {
22
- const hashedPartnerId = crypto
23
- .createHash('sha1')
24
- .update(partnerId, 'utf8')
25
- .digest('hex')
26
- const randomBytes = crypto.randomBytes(20).toString('hex')
27
- const samlArtifact = Buffer.from(
28
- `00040000${hashedPartnerId}${randomBytes}`,
29
- 'hex',
30
- ).toString('base64')
31
- profileStore.set(samlArtifact, profile)
32
- return samlArtifact
33
- }
34
-
35
- function lookUpBySamlArtifact(samlArtifact) {
36
- return profileStore.get(samlArtifact)
37
- }
38
-
39
- module.exports = { generateSamlArtifact, lookUpBySamlArtifact }