@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.
- package/.eslintrc.json +13 -0
- package/.gitattributes +2 -0
- package/.github/dependabot.yml +14 -0
- package/.github/mergify.yml +12 -0
- package/.github/workflows/ci.yml +27 -0
- package/.github/workflows/npmpublish.yml +22 -0
- package/.gitpod.yml +5 -0
- package/.husky/pre-commit +4 -0
- package/.husky/pre-push +4 -0
- package/.prettierrc.js +5 -0
- package/Dockerfile +11 -0
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/commitlint.config.js +7 -0
- package/index.js +87 -0
- package/lib/assertions.js +319 -0
- package/lib/crypto/index.js +61 -0
- package/lib/crypto/myinfo-signature.js +153 -0
- package/lib/express/index.js +6 -0
- package/lib/express/myinfo/consent.js +160 -0
- package/lib/express/myinfo/controllers.js +179 -0
- package/lib/express/myinfo/index.js +10 -0
- package/lib/express/oidc.js +131 -0
- package/lib/express/saml.js +171 -0
- package/lib/express/sgid.js +168 -0
- package/lib/saml-artifact.js +32 -0
- package/package.json +81 -0
- package/public/mockpass/resources/css/animate.css +43 -0
- package/public/mockpass/resources/css/common.css +121 -0
- package/public/mockpass/resources/css/reset.css +95 -0
- package/public/mockpass/resources/css/style-baseline-small-media.css +567 -0
- package/public/mockpass/resources/css/style-baseline.css +1006 -0
- package/public/mockpass/resources/css/style-common-small-media.css +156 -0
- package/public/mockpass/resources/css/style-common.css +510 -0
- package/public/mockpass/resources/css/style-homepage-small-media.css +588 -0
- package/public/mockpass/resources/css/style-homepage.css +674 -0
- package/public/mockpass/resources/css/style-main.css +9 -0
- package/public/mockpass/resources/img/ajax-loader.gif +0 -0
- package/public/mockpass/resources/img/ask_cheryl_tab.png +0 -0
- package/public/mockpass/resources/img/background/large-device/sp_bg.jpg +0 -0
- package/public/mockpass/resources/img/background/medium-device/ipad-bg.jpg +0 -0
- package/public/mockpass/resources/img/background/medium-device/ipad-landscape-sp-bg.jpg +0 -0
- package/public/mockpass/resources/img/background/small-device/mobile-sp-bg.jpg +0 -0
- package/public/mockpass/resources/img/carousel/large-device/how-to-setup-2fa-icon.png +0 -0
- package/public/mockpass/resources/img/carousel/large-device/register-icon.png +0 -0
- package/public/mockpass/resources/img/carousel/large-device/reset-password-icon.png +0 -0
- package/public/mockpass/resources/img/carousel/large-device/setup-2fa-icon.png +0 -0
- package/public/mockpass/resources/img/carousel/large-device/update-acct-icon.png +0 -0
- package/public/mockpass/resources/img/carousel/medium-device/ipad-register-icon.png +0 -0
- package/public/mockpass/resources/img/carousel/medium-device/ipad-reset-password-icon.png +0 -0
- package/public/mockpass/resources/img/carousel/medium-device/ipad-setup-2fa-icon.png +0 -0
- package/public/mockpass/resources/img/carousel/medium-device/ipad-update-acct-icon.png +0 -0
- package/public/mockpass/resources/img/carousel/small-device/mobile-register.png +0 -0
- package/public/mockpass/resources/img/carousel/small-device/mobile-reset-password-icon.png +0 -0
- package/public/mockpass/resources/img/carousel/small-device/mobile-update-acct-icon.png +0 -0
- package/public/mockpass/resources/img/close.png +0 -0
- package/public/mockpass/resources/img/id-pw-icon.png +0 -0
- package/public/mockpass/resources/img/logo/mockpass-logo.png +0 -0
- package/public/mockpass/resources/img/logo/mockpass-placeholder-logo.png +0 -0
- package/public/mockpass/resources/img/logo/mockpass_watermark.png +0 -0
- package/public/mockpass/resources/img/qr-icon.png +0 -0
- package/public/mockpass/resources/img/qr-shadow.png +0 -0
- package/public/mockpass/resources/img/refresh.jpg +0 -0
- package/public/mockpass/resources/img/sidebar-icons.png +0 -0
- package/public/mockpass/resources/img/sp-qr-unavailable.png +0 -0
- package/public/mockpass/resources/img/utility-icon-black.png +0 -0
- package/public/mockpass/resources/js/bootstrap.min.js +7 -0
- package/public/mockpass/resources/js/jquery-3.5.1.js +10872 -0
- package/public/mockpass/resources/js/login-common.js +849 -0
- package/public/mockpass/resources/plugins/bootstrap-3.3.6/css/bootstrap.min.css +6 -0
- package/public/mockpass/resources/plugins/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.woff2 +0 -0
- package/static/certs/csr.pem +17 -0
- package/static/certs/key.pem +28 -0
- package/static/certs/key.pub +9 -0
- package/static/certs/server.crt +21 -0
- package/static/certs/spcp-csr.pem +17 -0
- package/static/certs/spcp-key.pem +28 -0
- package/static/certs/spcp.crt +20 -0
- package/static/html/consent.html +40 -0
- package/static/html/login-page.html +271 -0
- package/static/myinfo/v2.json +6154 -0
- package/static/myinfo/v3.json +29386 -0
- package/static/saml/corppass.xml +21 -0
- package/static/saml/unsigned-assertion.xml +24 -0
- package/static/saml/unsigned-response.xml +19 -0
package/.eslintrc.json
ADDED
package/.gitattributes
ADDED
|
@@ -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
package/.husky/pre-push
ADDED
package/.prettierrc.js
ADDED
package/Dockerfile
ADDED
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
|
+
[](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.
|
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
|
+
}
|