@opengovsg/mockpass 2.7.9

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.
Files changed (85) hide show
  1. package/.eslintrc.json +13 -0
  2. package/.gitattributes +2 -0
  3. package/.github/dependabot.yml +14 -0
  4. package/.github/mergify.yml +12 -0
  5. package/.github/workflows/ci.yml +27 -0
  6. package/.github/workflows/npmpublish.yml +22 -0
  7. package/.gitpod.yml +5 -0
  8. package/.husky/pre-commit +4 -0
  9. package/.husky/pre-push +4 -0
  10. package/.prettierrc.js +5 -0
  11. package/Dockerfile +11 -0
  12. package/LICENSE +21 -0
  13. package/README.md +99 -0
  14. package/commitlint.config.js +7 -0
  15. package/index.js +87 -0
  16. package/lib/assertions.js +319 -0
  17. package/lib/crypto/index.js +61 -0
  18. package/lib/crypto/myinfo-signature.js +153 -0
  19. package/lib/express/index.js +6 -0
  20. package/lib/express/myinfo/consent.js +160 -0
  21. package/lib/express/myinfo/controllers.js +179 -0
  22. package/lib/express/myinfo/index.js +10 -0
  23. package/lib/express/oidc.js +131 -0
  24. package/lib/express/saml.js +171 -0
  25. package/lib/express/sgid.js +168 -0
  26. package/lib/saml-artifact.js +32 -0
  27. package/package.json +81 -0
  28. package/public/mockpass/resources/css/animate.css +43 -0
  29. package/public/mockpass/resources/css/common.css +121 -0
  30. package/public/mockpass/resources/css/reset.css +95 -0
  31. package/public/mockpass/resources/css/style-baseline-small-media.css +567 -0
  32. package/public/mockpass/resources/css/style-baseline.css +1006 -0
  33. package/public/mockpass/resources/css/style-common-small-media.css +156 -0
  34. package/public/mockpass/resources/css/style-common.css +510 -0
  35. package/public/mockpass/resources/css/style-homepage-small-media.css +588 -0
  36. package/public/mockpass/resources/css/style-homepage.css +674 -0
  37. package/public/mockpass/resources/css/style-main.css +9 -0
  38. package/public/mockpass/resources/img/ajax-loader.gif +0 -0
  39. package/public/mockpass/resources/img/ask_cheryl_tab.png +0 -0
  40. package/public/mockpass/resources/img/background/large-device/sp_bg.jpg +0 -0
  41. package/public/mockpass/resources/img/background/medium-device/ipad-bg.jpg +0 -0
  42. package/public/mockpass/resources/img/background/medium-device/ipad-landscape-sp-bg.jpg +0 -0
  43. package/public/mockpass/resources/img/background/small-device/mobile-sp-bg.jpg +0 -0
  44. package/public/mockpass/resources/img/carousel/large-device/how-to-setup-2fa-icon.png +0 -0
  45. package/public/mockpass/resources/img/carousel/large-device/register-icon.png +0 -0
  46. package/public/mockpass/resources/img/carousel/large-device/reset-password-icon.png +0 -0
  47. package/public/mockpass/resources/img/carousel/large-device/setup-2fa-icon.png +0 -0
  48. package/public/mockpass/resources/img/carousel/large-device/update-acct-icon.png +0 -0
  49. package/public/mockpass/resources/img/carousel/medium-device/ipad-register-icon.png +0 -0
  50. package/public/mockpass/resources/img/carousel/medium-device/ipad-reset-password-icon.png +0 -0
  51. package/public/mockpass/resources/img/carousel/medium-device/ipad-setup-2fa-icon.png +0 -0
  52. package/public/mockpass/resources/img/carousel/medium-device/ipad-update-acct-icon.png +0 -0
  53. package/public/mockpass/resources/img/carousel/small-device/mobile-register.png +0 -0
  54. package/public/mockpass/resources/img/carousel/small-device/mobile-reset-password-icon.png +0 -0
  55. package/public/mockpass/resources/img/carousel/small-device/mobile-update-acct-icon.png +0 -0
  56. package/public/mockpass/resources/img/close.png +0 -0
  57. package/public/mockpass/resources/img/id-pw-icon.png +0 -0
  58. package/public/mockpass/resources/img/logo/mockpass-logo.png +0 -0
  59. package/public/mockpass/resources/img/logo/mockpass-placeholder-logo.png +0 -0
  60. package/public/mockpass/resources/img/logo/mockpass_watermark.png +0 -0
  61. package/public/mockpass/resources/img/qr-icon.png +0 -0
  62. package/public/mockpass/resources/img/qr-shadow.png +0 -0
  63. package/public/mockpass/resources/img/refresh.jpg +0 -0
  64. package/public/mockpass/resources/img/sidebar-icons.png +0 -0
  65. package/public/mockpass/resources/img/sp-qr-unavailable.png +0 -0
  66. package/public/mockpass/resources/img/utility-icon-black.png +0 -0
  67. package/public/mockpass/resources/js/bootstrap.min.js +7 -0
  68. package/public/mockpass/resources/js/jquery-3.5.1.js +10872 -0
  69. package/public/mockpass/resources/js/login-common.js +849 -0
  70. package/public/mockpass/resources/plugins/bootstrap-3.3.6/css/bootstrap.min.css +6 -0
  71. package/public/mockpass/resources/plugins/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.woff2 +0 -0
  72. package/static/certs/csr.pem +17 -0
  73. package/static/certs/key.pem +28 -0
  74. package/static/certs/key.pub +9 -0
  75. package/static/certs/server.crt +21 -0
  76. package/static/certs/spcp-csr.pem +17 -0
  77. package/static/certs/spcp-key.pem +28 -0
  78. package/static/certs/spcp.crt +20 -0
  79. package/static/html/consent.html +40 -0
  80. package/static/html/login-page.html +271 -0
  81. package/static/myinfo/v2.json +6154 -0
  82. package/static/myinfo/v3.json +29386 -0
  83. package/static/saml/corppass.xml +21 -0
  84. package/static/saml/unsigned-assertion.xml +24 -0
  85. package/static/saml/unsigned-response.xml +19 -0
package/.eslintrc.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": [
3
+ "eslint:recommended",
4
+ "plugin:prettier/recommended"
5
+ ],
6
+ "parserOptions": {
7
+ "ecmaVersion": 2018
8
+ },
9
+ "env": {
10
+ "node": true,
11
+ "es6": true
12
+ }
13
+ }
package/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Set to not apply eol conversion for subresource integrity check
2
+ public/mockpass/resources/js/*.js -text
@@ -0,0 +1,14 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "npm" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "daily"
12
+ commit-message:
13
+ prefix: "chore(deps):"
14
+ target-branch: "dependabot"
@@ -0,0 +1,12 @@
1
+ pull_request_rules:
2
+ - name: command dependabot to squash and merge
3
+ conditions:
4
+ - 'author=dependabot[bot]'
5
+ - 'check-success~=CI'
6
+ - 'title~=bump [^\s]+ from ([\d]+)\..+ to \1\.'
7
+ - 'base=dependabot'
8
+ actions:
9
+ review:
10
+ type: APPROVE
11
+ merge:
12
+ method: squash
@@ -0,0 +1,27 @@
1
+ name: ci
2
+ on:
3
+ push:
4
+ pull_request:
5
+ types: [opened, reopened]
6
+ jobs:
7
+ ci:
8
+ name: CI
9
+ runs-on: ubuntu-18.04
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - name: Use Node.js
13
+ uses: actions/setup-node@v1
14
+ with:
15
+ node-version: '12.x'
16
+ - name: Cache Node.js modules
17
+ uses: actions/cache@v2
18
+ with:
19
+ # npm cache files are stored in `~/.npm` on Linux/macOS
20
+ path: ~/.npm
21
+ key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
22
+ restore-keys: |
23
+ ${{ runner.OS }}-node-
24
+ ${{ runner.OS }}-
25
+ - run: npm ci
26
+ - run: npx lockfile-lint --type npm --path package-lock.json --validate-https --allowed-hosts npm
27
+ - run: npm run lint
@@ -0,0 +1,22 @@
1
+ # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
+ # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3
+
4
+ name: Node.js Package
5
+
6
+ on:
7
+ release:
8
+ types: [created]
9
+
10
+ jobs:
11
+ publish-npm:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - uses: actions/setup-node@v1
16
+ with:
17
+ node-version: 12
18
+ registry-url: https://registry.npmjs.org/
19
+ - run: npm ci
20
+ - run: npm publish --access public
21
+ env:
22
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
package/.gitpod.yml ADDED
@@ -0,0 +1,5 @@
1
+ tasks:
2
+ - init: npm install
3
+ command: SHOW_LOGIN_PAGE=true npm start
4
+ ports:
5
+ - port: 5156
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ npx lint-staged
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ npx commitlint --from origin/master --to HEAD --verbose
package/.prettierrc.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ semi: false,
3
+ trailingComma: 'all',
4
+ singleQuote: true,
5
+ }
package/Dockerfile ADDED
@@ -0,0 +1,11 @@
1
+ FROM node:slim
2
+
3
+ WORKDIR /usr/src/mockpass
4
+
5
+ COPY package* ./
6
+
7
+ RUN npm ci
8
+
9
+ COPY . ./
10
+
11
+ CMD ["node", "index.js"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Open Government Products, Government Technology Agency of Singapore
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # MockPass
2
+
3
+ [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/opengovsg/mockpass)
4
+
5
+ A mock SingPass/CorpPass/MyInfo server for dev purposes
6
+
7
+ ## Quick Start (hosted by Gitpod)
8
+
9
+ - Click the ready-to-code badge above
10
+ - Wait for MockPass to start
11
+ - Make port 5156 public
12
+ - Open Browser to note the URL hosting MockPass
13
+ - Configure your application per local machine quick start, changing
14
+ the localhost:5156 to the Gitpod hostname
15
+
16
+ ## Quick Start (on local machine)
17
+
18
+ Configure your application to point to the following endpoints:
19
+
20
+ SingPass:
21
+ - http://localhost:5156/singpass/logininitial - SAML login redirect with optional page
22
+ - http://localhost:5156/singpass/soap - receives SAML artifact and returns assertion
23
+ - http://localhost:5156/singpass/authorize - OIDC login redirect with optional page
24
+ - http://localhost:5156/singpass/token - receives OIDC authorization code and returns id_token
25
+
26
+ CorpPass:
27
+ - http://localhost:5156/corppass/logininitial
28
+ - http://localhost:5156/corppass/soap
29
+ - http://localhost:5156/corppass/authorize - OIDC login redirect with optional page
30
+ - http://localhost:5156/corppass/token - receives OIDC authorization code and returns id_token
31
+
32
+ MyInfo:
33
+ - http://localhost:5156/myinfo/{v2,v3}/person-basic (exclusive to government systems)
34
+ - http://localhost:5156/myinfo/{v2,v3}/authorise
35
+ - http://localhost:5156/myinfo/{v2,v3}/token
36
+ - http://localhost:5156/myinfo/{v2,v3}/person
37
+
38
+ sgID:
39
+ - http://localhost:5156/sgid/v1/oauth/authorize
40
+ - http://localhost:5156/sgid/v1/oauth/token
41
+ - http://localhost:5156/sgid/v1/oauth/userinfo
42
+
43
+ Provide your application with the `spcp*` certs found in `static/certs`
44
+ and with application certs at `static/certs/{key.pem|server.crt}`
45
+
46
+ Alternatively, provide the paths to your app cert as env vars
47
+ `SERVICE_PROVIDER_CERT_PATH` and `SERVICE_PROVIDER_PUB_KEY`
48
+
49
+ ```
50
+ $ npm install @opengovsg/mockpass
51
+
52
+ # Some familiarity with SAML Artifact Binding is assumed
53
+ # Optional: Configure where MockPass should send SAML artifact to, default endpoint will be `PartnerId` in request query parameter.
54
+ $ export SINGPASS_ASSERT_ENDPOINT=http://localhost:5000/singpass/assert
55
+ $ export CORPPASS_ASSERT_ENDPOINT=http://localhost:5000/corppass/assert
56
+
57
+ # All values shown here are defaults
58
+ $ export MOCKPASS_PORT=5156
59
+ $ export MOCKPASS_NRIC=S8979373D
60
+ $ export MOCKPASS_UEN=123456789A
61
+
62
+ $ export SHOW_LOGIN_PAGE=true # Optional, defaults to `false`
63
+
64
+ # Disable signing/encryption (Optional, by default `true`)
65
+ $ export SIGN_ASSERTION=false
66
+ $ export ENCRYPT_ASSERTION=false
67
+ $ export SIGN_RESPONSE=false
68
+ $ export RESOLVE_ARTIFACT_REQUEST_SIGNED=false
69
+
70
+ # Encrypt payloads returned by /myinfo/*/{person, person-basic},
71
+ # equivalent to MyInfo Auth Level L2 (testing and production)
72
+ $ export ENCRYPT_MYINFO=false
73
+
74
+ # If specified, will verify token provided in Authorization header
75
+ # for requests to /myinfo/*/token
76
+ $ export SERVICE_PROVIDER_MYINFO_SECRET=<your secret here>
77
+
78
+ $ npx mockpass
79
+ MockPass listening on 5156
80
+ ```
81
+
82
+ ## Background
83
+
84
+ There currently is nothing widely available to test an application's integration
85
+ with SingPass/CorpPass using a dev machine alone. This is awkward for developers
86
+ who then need to connect to the staging servers hosted by SingPass/CorpPass,
87
+ which may not always be available (eg, down for maintenance, or no Internet).
88
+
89
+ MockPass tries to solves this by providing an extremely lightweight implementation
90
+ of a SAML 2.0 Identity Provider that returns mock SingPass and CorpPass assertions.
91
+ It optionally provides a mock login page that (badly) mimics the SingPass/CorpPass
92
+ login experience.
93
+
94
+ ## Contributing
95
+
96
+ We welcome contributions to code open-sourced by the Government Technology
97
+ Agency of Singapore. All contributors will be asked to sign a Contributor
98
+ License Agreement (CLA) in order to ensure that everybody is free to use their
99
+ contributions.
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ extends: ['@commitlint/config-conventional'],
3
+ rules: {
4
+ 'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
5
+ 'scope-case': [2, 'always', ['pascal-case', 'lower-case', 'camel-case']],
6
+ },
7
+ }
package/index.js ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs')
3
+ const express = require('express')
4
+ const morgan = require('morgan')
5
+ const path = require('path')
6
+ require('dotenv').config()
7
+
8
+ const {
9
+ configSAML,
10
+ configOIDC,
11
+ configMyInfo,
12
+ configSGID,
13
+ } = require('./lib/express')
14
+
15
+ const PORT = process.env.MOCKPASS_PORT || process.env.PORT || 5156
16
+
17
+ if (
18
+ !process.env.SINGPASS_ASSERT_ENDPOINT &&
19
+ !process.env.CORPPASS_ASSERT_ENDPOINT
20
+ ) {
21
+ console.warn(
22
+ 'SINGPASS_ASSERT_ENDPOINT or CORPPASS_ASSERT_ENDPOINT is not set. ' +
23
+ 'Value of `PartnerId` request query parameter in redirect URL will be used.',
24
+ )
25
+ }
26
+
27
+ const serviceProvider = {
28
+ cert: fs.readFileSync(
29
+ path.resolve(
30
+ __dirname,
31
+ process.env.SERVICE_PROVIDER_CERT_PATH || './static/certs/server.crt',
32
+ ),
33
+ ),
34
+ pubKey: fs.readFileSync(
35
+ path.resolve(
36
+ __dirname,
37
+ process.env.SERVICE_PROVIDER_PUB_KEY || './static/certs/key.pub',
38
+ ),
39
+ ),
40
+ }
41
+
42
+ const cryptoConfig = {
43
+ signAssertion: process.env.SIGN_ASSERTION !== 'false', // default to true to be backward compatable
44
+ signResponse: process.env.SIGN_RESPONSE !== 'false',
45
+ encryptAssertion: process.env.ENCRYPT_ASSERTION !== 'false',
46
+ resolveArtifactRequestSigned:
47
+ process.env.RESOLVE_ARTIFACT_REQUEST_SIGNED !== 'false',
48
+ }
49
+
50
+ const options = {
51
+ 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
+ showLoginPage: process.env.SHOW_LOGIN_PAGE === 'true',
65
+ encryptMyInfo: process.env.ENCRYPT_MYINFO === 'true',
66
+ cryptoConfig,
67
+ }
68
+
69
+ const app = express()
70
+ app.use(morgan('combined'))
71
+
72
+ configSAML(app, options)
73
+ configOIDC(app, options)
74
+ configSGID(app, options)
75
+
76
+ configMyInfo.consent(app)
77
+ configMyInfo.v2(app, options)
78
+ configMyInfo.v3(app, options)
79
+
80
+ app.enable('trust proxy')
81
+ app.use(express.static(path.join(__dirname, 'public')))
82
+
83
+ app.listen(PORT, (err) =>
84
+ err
85
+ ? console.error('Unable to start MockPass', err)
86
+ : console.warn(`MockPass listening on ${PORT}`),
87
+ )
@@ -0,0 +1,319 @@
1
+ const base64 = require('base-64')
2
+ const crypto = require('crypto')
3
+ const fs = require('fs')
4
+ const { render } = require('mustache')
5
+ const moment = require('moment')
6
+ const jose = require('node-jose')
7
+ const path = require('path')
8
+
9
+ const readFrom = (p) => fs.readFileSync(path.resolve(__dirname, p), 'utf8')
10
+
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
+ const signingPem = fs.readFileSync(
19
+ path.resolve(__dirname, '../static/certs/spcp-key.pem'),
20
+ )
21
+
22
+ const createRefreshToken = (uuid) => {
23
+ const prefixSize = (`${uuid}`.length + 1) / 2
24
+ const padding = Number.isInteger(prefixSize) ? '/' : '/f'
25
+
26
+ return (
27
+ uuid +
28
+ padding +
29
+ crypto.randomBytes(20 - Math.floor(prefixSize)).toString('hex')
30
+ )
31
+ }
32
+
33
+ const hashToken = (token) => {
34
+ const fullHash = crypto.createHash('sha256')
35
+ fullHash.update(token, 'utf8')
36
+ const fullDigest = fullHash.digest()
37
+ const digestBuffer = fullDigest.slice(0, fullDigest.length / 2)
38
+ if (Buffer.isEncoding('base64url')) {
39
+ return digestBuffer.toString('base64url')
40
+ } else {
41
+ const fromBase64 = (base64) =>
42
+ base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
43
+ return fromBase64(digestBuffer.toString('base64'))
44
+ }
45
+ }
46
+
47
+ const myinfo = {
48
+ v2: JSON.parse(readFrom('../static/myinfo/v2.json')),
49
+ v3: JSON.parse(readFrom('../static/myinfo/v3.json')),
50
+ }
51
+
52
+ const saml = {
53
+ singPass: [
54
+ 'S8979373D',
55
+ 'S8116474F',
56
+ 'S8723211E',
57
+ 'S5062854Z',
58
+ 'T0066846F',
59
+ 'F9477325W',
60
+ 'S3000024B',
61
+ 'S6005040F',
62
+ 'S6005041D',
63
+ 'S6005042B',
64
+ 'S6005043J',
65
+ 'S6005044I',
66
+ 'S6005045G',
67
+ 'S6005046E',
68
+ 'S6005047C',
69
+ 'S6005064C',
70
+ 'S6005065A',
71
+ 'S6005066Z',
72
+ 'S6005037F',
73
+ 'S6005038D',
74
+ 'S6005039B',
75
+ 'G1612357P',
76
+ 'G1612358M',
77
+ 'F1612359P',
78
+ 'F1612360U',
79
+ 'F1612361R',
80
+ 'F1612362P',
81
+ 'F1612363M',
82
+ 'F1612364K',
83
+ 'F1612365W',
84
+ 'F1612366T',
85
+ 'F1612367Q',
86
+ 'F1612358R',
87
+ 'F1612354N',
88
+ 'F1612357U',
89
+ ...Object.keys(myinfo.v2.personas),
90
+ ],
91
+ corpPass: [
92
+ { nric: 'S8979373D', uen: '123456789A' },
93
+ { nric: 'S8116474F', uen: '123456789A' },
94
+ { nric: 'S8723211E', uen: '123456789A' },
95
+ { nric: 'S5062854Z', uen: '123456789B' },
96
+ { nric: 'T0066846F', uen: '123456789B' },
97
+ { nric: 'F9477325W', uen: '123456789B' },
98
+ { nric: 'S3000024B', uen: '123456789C' },
99
+ { nric: 'S6005040F', uen: '123456789C' },
100
+ ],
101
+ create: {
102
+ singPass: (
103
+ nric,
104
+ issuer,
105
+ recipient,
106
+ inResponseTo,
107
+ audience = defaultAudience,
108
+ ) =>
109
+ render(TEMPLATE, {
110
+ name: 'UserName',
111
+ value: nric,
112
+ issueInstant: moment.utc().format(),
113
+ recipient,
114
+ issuer,
115
+ inResponseTo,
116
+ audience,
117
+ }),
118
+ corpPass: (
119
+ source,
120
+ issuer,
121
+ recipient,
122
+ inResponseTo,
123
+ audience = defaultAudience,
124
+ ) =>
125
+ render(TEMPLATE, {
126
+ issueInstant: moment.utc().format(),
127
+ name: source.uen,
128
+ value: base64.encode(render(corpPassTemplate, source)),
129
+ recipient,
130
+ issuer,
131
+ inResponseTo,
132
+ audience,
133
+ }),
134
+ },
135
+ }
136
+
137
+ const oidc = {
138
+ singPass: [
139
+ 'S8979373D',
140
+ 'S8116474F',
141
+ 'S8723211E',
142
+ 'S5062854Z',
143
+ 'T0066846F',
144
+ 'F9477325W',
145
+ 'S3000024B',
146
+ 'S6005040F',
147
+ 'S6005041D',
148
+ 'S6005042B',
149
+ 'S6005043J',
150
+ 'S6005044I',
151
+ 'S6005045G',
152
+ 'S6005046E',
153
+ 'S6005047C',
154
+ 'S6005064C',
155
+ 'S6005065A',
156
+ 'S6005066Z',
157
+ 'S6005037F',
158
+ 'S6005038D',
159
+ 'S6005039B',
160
+ 'G1612357P',
161
+ 'G1612358M',
162
+ 'F1612359P',
163
+ 'F1612360U',
164
+ 'F1612361R',
165
+ 'F1612362P',
166
+ 'F1612363M',
167
+ 'F1612364K',
168
+ 'F1612365W',
169
+ 'F1612366T',
170
+ 'F1612367Q',
171
+ 'F1612358R',
172
+ 'F1612354N',
173
+ 'F1612357U',
174
+ ...Object.keys(myinfo.v3.personas),
175
+ ],
176
+ corpPass: [
177
+ {
178
+ nric: 'S8979373D',
179
+ name: 'Name of S8979373D',
180
+ isSingPassHolder: true,
181
+ uen: '123456789A',
182
+ },
183
+ {
184
+ nric: 'S8116474F',
185
+ name: 'Name of S8116474F',
186
+ isSingPassHolder: true,
187
+ uen: '123456789A',
188
+ },
189
+ {
190
+ nric: 'S8723211E',
191
+ name: 'Name of S8723211E',
192
+ isSingPassHolder: true,
193
+ uen: '123456789A',
194
+ },
195
+ {
196
+ nric: 'S5062854Z',
197
+ name: 'Name of S5062854Z',
198
+ isSingPassHolder: true,
199
+ uen: '123456789B',
200
+ },
201
+ {
202
+ nric: 'T0066846F',
203
+ name: 'Name of T0066846F',
204
+ isSingPassHolder: true,
205
+ uen: '123456789B',
206
+ },
207
+ {
208
+ nric: 'F9477325W',
209
+ name: 'Name of F9477325W',
210
+ isSingPassHolder: false,
211
+ uen: '123456789B',
212
+ },
213
+ {
214
+ nric: 'S3000024B',
215
+ name: 'Name of S3000024B',
216
+ isSingPassHolder: true,
217
+ uen: '123456789C',
218
+ },
219
+ {
220
+ nric: 'S6005040F',
221
+ name: 'Name of S6005040F',
222
+ isSingPassHolder: true,
223
+ uen: '123456789C',
224
+ },
225
+ ],
226
+ create: {
227
+ singPass: (
228
+ uuid,
229
+ iss,
230
+ aud,
231
+ nonce,
232
+ accessToken = crypto.randomBytes(15).toString('hex'),
233
+ ) => {
234
+ const nric = oidc.singPass[uuid]
235
+ const sub = `s=${nric},u=${uuid}`
236
+
237
+ const refreshToken = createRefreshToken(uuid)
238
+ const accessTokenHash = hashToken(accessToken)
239
+ const refreshTokenHash = hashToken(refreshToken)
240
+
241
+ return {
242
+ accessToken,
243
+ refreshToken,
244
+ idTokenClaims: {
245
+ rt_hash: refreshTokenHash,
246
+ at_hash: accessTokenHash,
247
+ iat: Math.floor(Date.now() / 1000),
248
+ exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60,
249
+ iss,
250
+ amr: ['pwd'],
251
+ aud,
252
+ sub,
253
+ ...(nonce ? { nonce } : {}),
254
+ },
255
+ }
256
+ },
257
+ corpPass: async (uuid, iss, aud, nonce) => {
258
+ const baseClaims = {
259
+ iat: Math.floor(Date.now() / 1000),
260
+ exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60,
261
+ iss,
262
+ aud,
263
+ }
264
+
265
+ const profile = oidc.corpPass[uuid]
266
+ const sub = `s=${profile.nric},u=${uuid},c=SG`
267
+
268
+ const accessTokenClaims = {
269
+ ...baseClaims,
270
+ authorization: {
271
+ EntityInfo: {},
272
+ AccessInfo: {},
273
+ TPAccessInfo: {},
274
+ },
275
+ }
276
+
277
+ const signingKey = await jose.JWK.asKey(signingPem, 'pem')
278
+ const accessToken = await jose.JWS.createSign(
279
+ { format: 'compact' },
280
+ signingKey,
281
+ )
282
+ .update(JSON.stringify(accessTokenClaims))
283
+ .final()
284
+
285
+ const accessTokenHash = hashToken(accessToken)
286
+
287
+ return {
288
+ refreshToken: 'refresh',
289
+ accessToken,
290
+ idTokenClaims: {
291
+ ...baseClaims,
292
+ rt_hash: '',
293
+ at_hash: accessTokenHash,
294
+ amr: ['pwd'],
295
+ sub,
296
+ ...(nonce ? { nonce } : {}),
297
+ userInfo: {
298
+ CPAccType: 'User',
299
+ CPUID_FullName: profile.name,
300
+ ISSPHOLDER: profile.isSingPassHolder ? 'YES' : 'NO',
301
+ },
302
+ },
303
+ }
304
+ },
305
+ },
306
+ }
307
+
308
+ const singPassNric = process.env.MOCKPASS_NRIC || saml.singPass[0]
309
+ const corpPassNric = process.env.MOCKPASS_NRIC || saml.corpPass[0].nric
310
+ const uen = process.env.MOCKPASS_UEN || saml.corpPass[0].uen
311
+
312
+ module.exports = {
313
+ saml,
314
+ oidc,
315
+ myinfo,
316
+ singPassNric,
317
+ corpPassNric,
318
+ uen,
319
+ }