@opengovsg/mockpass 3.0.0 → 3.0.2
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/README.md +6 -15
- package/index.js +0 -10
- package/lib/crypto/myinfo-signature.js +30 -58
- package/lib/express/myinfo/controllers.js +3 -3
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -18,22 +18,18 @@ A mock SingPass/CorpPass/MyInfo server for dev purposes
|
|
|
18
18
|
Configure your application to point to the following endpoints:
|
|
19
19
|
|
|
20
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
21
|
- http://localhost:5156/singpass/authorize - OIDC login redirect with optional page
|
|
24
22
|
- http://localhost:5156/singpass/token - receives OIDC authorization code and returns id_token
|
|
25
23
|
|
|
26
24
|
CorpPass:
|
|
27
|
-
- http://localhost:5156/corppass/logininitial
|
|
28
|
-
- http://localhost:5156/corppass/soap
|
|
29
25
|
- http://localhost:5156/corppass/authorize - OIDC login redirect with optional page
|
|
30
26
|
- http://localhost:5156/corppass/token - receives OIDC authorization code and returns id_token
|
|
31
27
|
|
|
32
28
|
MyInfo:
|
|
33
|
-
- http://localhost:5156/myinfo/
|
|
34
|
-
- http://localhost:5156/myinfo/
|
|
35
|
-
- http://localhost:5156/myinfo/
|
|
36
|
-
- http://localhost:5156/myinfo/
|
|
29
|
+
- http://localhost:5156/myinfo/v3/person-basic (exclusive to government systems)
|
|
30
|
+
- http://localhost:5156/myinfo/v3/authorise
|
|
31
|
+
- http://localhost:5156/myinfo/v3/token
|
|
32
|
+
- http://localhost:5156/myinfo/v3/person
|
|
37
33
|
|
|
38
34
|
sgID:
|
|
39
35
|
- http://localhost:5156/sgid/v1/oauth/authorize
|
|
@@ -49,11 +45,6 @@ Alternatively, provide the paths to your app cert as env vars
|
|
|
49
45
|
```
|
|
50
46
|
$ npm install @opengovsg/mockpass
|
|
51
47
|
|
|
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
48
|
# All values shown here are defaults
|
|
58
49
|
$ export MOCKPASS_PORT=5156
|
|
59
50
|
|
|
@@ -69,7 +60,7 @@ $ export ENCRYPT_ASSERTION=false
|
|
|
69
60
|
$ export SIGN_RESPONSE=false
|
|
70
61
|
$ export RESOLVE_ARTIFACT_REQUEST_SIGNED=false
|
|
71
62
|
|
|
72
|
-
# Encrypt payloads returned by /myinfo
|
|
63
|
+
# Encrypt payloads returned by /myinfo/v3/{person, person-basic},
|
|
73
64
|
# equivalent to MyInfo Auth Level L2 (testing and production)
|
|
74
65
|
$ export ENCRYPT_MYINFO=false
|
|
75
66
|
|
|
@@ -89,7 +80,7 @@ who then need to connect to the staging servers hosted by SingPass/CorpPass,
|
|
|
89
80
|
which may not always be available (eg, down for maintenance, or no Internet).
|
|
90
81
|
|
|
91
82
|
MockPass tries to solves this by providing an extremely lightweight implementation
|
|
92
|
-
of
|
|
83
|
+
of an OIDC Provider that returns mock SingPass and CorpPass assertions.
|
|
93
84
|
It optionally provides a mock login page that (badly) mimics the SingPass/CorpPass
|
|
94
85
|
login experience.
|
|
95
86
|
|
package/index.js
CHANGED
|
@@ -9,16 +9,6 @@ const { configOIDC, configMyInfo, configSGID } = require('./lib/express')
|
|
|
9
9
|
|
|
10
10
|
const PORT = process.env.MOCKPASS_PORT || process.env.PORT || 5156
|
|
11
11
|
|
|
12
|
-
if (
|
|
13
|
-
!process.env.SINGPASS_ASSERT_ENDPOINT &&
|
|
14
|
-
!process.env.CORPPASS_ASSERT_ENDPOINT
|
|
15
|
-
) {
|
|
16
|
-
console.warn(
|
|
17
|
-
'SINGPASS_ASSERT_ENDPOINT or CORPPASS_ASSERT_ENDPOINT is not set. ' +
|
|
18
|
-
'Value of `PartnerId` request query parameter in redirect URL will be used.',
|
|
19
|
-
)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
12
|
const serviceProvider = {
|
|
23
13
|
cert: fs.readFileSync(
|
|
24
14
|
path.resolve(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const _ = require('lodash')
|
|
2
|
+
const qs = require('node:querystring')
|
|
2
3
|
|
|
3
4
|
const pki = function pki(authHeader, req, context = {}) {
|
|
4
5
|
const authHeaderFieldPairs = _(authHeader)
|
|
@@ -6,67 +7,38 @@ const pki = function pki(authHeader, req, context = {}) {
|
|
|
6
7
|
.split(',')
|
|
7
8
|
.map((v) => v.replace('=', '~').split('~'))
|
|
8
9
|
|
|
9
|
-
const authHeaderFields =
|
|
10
|
-
.fromPairs()
|
|
11
|
-
.mapKeys((_v, k) => _.camelCase(k))
|
|
12
|
-
.value()
|
|
10
|
+
const authHeaderFields = Object.fromEntries(authHeaderFieldPairs)
|
|
13
11
|
|
|
14
12
|
const url = `${req.protocol}://${req.get('host')}${req.baseUrl}${req.path}`
|
|
15
13
|
|
|
16
|
-
const {
|
|
17
|
-
|
|
18
|
-
const {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'&code=' +
|
|
45
|
-
code +
|
|
46
|
-
'&grant_type=authorization_code' +
|
|
47
|
-
'&nonce=' +
|
|
48
|
-
nonce +
|
|
49
|
-
'&redirect_uri=' +
|
|
50
|
-
redirectURI +
|
|
51
|
-
'&signature_method=RS256' +
|
|
52
|
-
'×tamp=' +
|
|
53
|
-
timestamp
|
|
54
|
-
: httpMethod.toUpperCase() +
|
|
55
|
-
'&' +
|
|
56
|
-
url +
|
|
57
|
-
'&app_id=' +
|
|
58
|
-
appId +
|
|
59
|
-
'&attributes=' +
|
|
60
|
-
attributes +
|
|
61
|
-
'&client_id=' +
|
|
62
|
-
clientId +
|
|
63
|
-
'&nonce=' +
|
|
64
|
-
nonce +
|
|
65
|
-
'&signature_method=RS256' +
|
|
66
|
-
(req.path.includes('/person-basic') ? '&sp_esvcId=' + sp_esvcId : '') +
|
|
67
|
-
'×tamp=' +
|
|
68
|
-
timestamp,
|
|
69
|
-
}
|
|
14
|
+
const { method: httpMethod, query } = req
|
|
15
|
+
|
|
16
|
+
const { signature, app_id, nonce, timestamp } = authHeaderFields
|
|
17
|
+
|
|
18
|
+
const params = Object.assign(
|
|
19
|
+
{},
|
|
20
|
+
query,
|
|
21
|
+
{
|
|
22
|
+
nonce,
|
|
23
|
+
app_id,
|
|
24
|
+
signature_method: 'RS256',
|
|
25
|
+
timestamp,
|
|
26
|
+
},
|
|
27
|
+
context.client_secret && context.redirect_uri ? context : {},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const sortedParams = Object.fromEntries(
|
|
31
|
+
Object.entries(params).sort(([k1], [k2]) => k1.localeCompare(k2)),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const baseString =
|
|
35
|
+
httpMethod.toUpperCase() +
|
|
36
|
+
'&' +
|
|
37
|
+
url +
|
|
38
|
+
'&' +
|
|
39
|
+
qs.unescape(qs.stringify(sortedParams))
|
|
40
|
+
|
|
41
|
+
return { signature, baseString }
|
|
70
42
|
}
|
|
71
43
|
|
|
72
44
|
module.exports = { pki }
|
|
@@ -132,14 +132,14 @@ module.exports =
|
|
|
132
132
|
type: 'application/x-www-form-urlencoded',
|
|
133
133
|
}),
|
|
134
134
|
(req, res) => {
|
|
135
|
-
const [tokenTemplate,
|
|
135
|
+
const [tokenTemplate, redirect_uri] =
|
|
136
136
|
consent.authorizations[req.body.code]
|
|
137
137
|
const [, authHeader] = (req.get('Authorization') || '').split(' ')
|
|
138
138
|
|
|
139
139
|
const { signature, baseString } = MYINFO_SECRET
|
|
140
140
|
? myInfoSignature(authHeader, req, {
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
client_secret: MYINFO_SECRET,
|
|
142
|
+
redirect_uri,
|
|
143
143
|
})
|
|
144
144
|
: {}
|
|
145
145
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengovsg/mockpass",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "A mock SingPass/CorpPass server for dev purposes",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
"node": ">=8.0.0"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@xmldom/xmldom": "^0.8.0",
|
|
40
39
|
"base-64": "^1.0.0",
|
|
41
40
|
"cookie-parser": "^1.4.3",
|
|
42
41
|
"dotenv": "^16.0.0",
|