@opengovsg/mockpass 4.3.5 → 4.4.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MockPass
2
2
 
3
- A mock Singpass/Corppass/Myinfo v3/sgID v2 server for dev purposes
3
+ A mock Singpass/Corppass/Myinfo v3/sgID v2/Sign v3 server for dev purposes
4
4
 
5
5
  ## Quick Start (hosted remotely on Gitpod)
6
6
 
@@ -164,6 +164,23 @@ Self-signed or untrusted certificates are supported.
164
164
  Alternatively, provide your application with the certificate and private key
165
165
  from `static/certs/(server.crt|key.pem)`.
166
166
 
167
+ ### Sign v3
168
+ Check out Sign V3 documentation [here](https://docs.sign.singpass.gov.sg/for-relying-parties/api-documentation/document-signing-v3).
169
+
170
+ Sign v3 URLs
171
+ - http://localhost:5156/sign-v3/sign-requests - Create sign request
172
+ - http://localhost:5156/sign-v3/sign-requests/:request_id/signed-doc - Get signed document
173
+ - http://localhost:5156/sign-v3/jwks - JWKS URL
174
+
175
+ - http://localhost:5156/sign-v3/sign - The stubbed signing portal path
176
+
177
+ Configure MockPass with your application client details:
178
+ - Client ID: SIGNV3_CLIENT_ID, default mockpass-sign-v3-client
179
+ - JWKS URL: SIGNV3_CLIENT_JWKS_URL, default http://localhost:4000/jwks
180
+ - Redirect URL: SIGNV3_CLIENT_REDIRECT_URL, default http://localhost:4000/redirect
181
+ - Webhook URL: SIGNV3_CLIENT_WEBHOOK_URL, default http://localhost:4000/webhook
182
+ - Server host URL: MOCKPASS_SERVER_HOST: default: http://localhost:5156
183
+
167
184
  ### Run MockPass
168
185
 
169
186
  Common configuration:
package/app.js CHANGED
@@ -10,6 +10,7 @@ const {
10
10
  configOIDCv2,
11
11
  configMyInfo,
12
12
  configSGID,
13
+ configSignV3,
13
14
  } = require('./lib/express')
14
15
 
15
16
  const serviceProvider = {
@@ -52,6 +53,7 @@ app.use(morgan('combined'))
52
53
  configOIDC(app, options)
53
54
  configOIDCv2(app, options)
54
55
  configSGID(app, options)
56
+ configSignV3(app, options)
55
57
 
56
58
  configMyInfo.consent(app, options)
57
59
  configMyInfo.v3(app, options)
@@ -2,4 +2,5 @@ module.exports = {
2
2
  ...require('./oidc'),
3
3
  configMyInfo: require('./myinfo'),
4
4
  configSGID: require('./sgid'),
5
+ configSignV3: require('./signv3'),
5
6
  }
@@ -0,0 +1,98 @@
1
+ const jose = require('jose')
2
+ const assert = require('node:assert')
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const { SignJWT, importJWK } = require('jose')
6
+
7
+ const PATH_PREFIX = '/sign-v3'
8
+ const CLIENT_JWKS_URL =
9
+ process.env.SIGNV3_CLIENT_JWKS_URL || 'http://localhost:4000/jwks'
10
+ const CLIENT_ID = process.env.SIGNV3_CLIENT_ID || 'mockpass-sign-v3-client'
11
+ const MOCKPASS_SERVER_HOST =
12
+ process.env.MOCKPASS_SERVER_HOST || 'http://localhost:5156'
13
+ const CLIENT_REDIRECT_URI =
14
+ process.env.SIGNV3_CLIENT_REDIRECT_URI || 'http://localhost:4000/redirect'
15
+ const CLIENT_WEBHOOK_URL =
16
+ process.env.SIGNV3_CLIENT_WEBHOOK_URL || 'http://localhost:4000/webhook'
17
+ const SIGNED_DOC_URL = `${MOCKPASS_SERVER_HOST}/mockpass/resources/dummy-signed.pdf`
18
+ const MOCKPASS_SIGNV3_PRIVATE_JWK = JSON.parse(
19
+ fs.readFileSync(
20
+ path.resolve(__dirname, '../../static/certs/sign-v3-secret.json'),
21
+ ),
22
+ )
23
+
24
+ function config(app) {
25
+ app.post(`${PATH_PREFIX}/sign-requests`, async (req, res) => {
26
+ try {
27
+ assert(req.headers['content-type'] === 'application/octet-stream')
28
+ assert(req.headers.authorization)
29
+ const { payload } = await jose.jwtVerify(
30
+ req.headers.authorization,
31
+ jose.createRemoteJWKSet(new URL(CLIENT_JWKS_URL)),
32
+ { requiredClaims: ['client_id', 'x', 'y', 'page', 'doc_name'] },
33
+ )
34
+ assert(payload.client_id === CLIENT_ID)
35
+ } catch (err) {
36
+ console.error(err)
37
+ return res
38
+ .status(400)
39
+ .json({ error: 'UNAUTHORIZED', error_description: 'Unauthorized.' })
40
+ }
41
+
42
+ const request_id = `signv3-${crypto.randomUUID()}`
43
+ console.info(`Creating sign request: ${request_id}`)
44
+ return res.status(200).json({
45
+ signing_url: `${MOCKPASS_SERVER_HOST}/sign-v3/sign?request_id=${request_id}`,
46
+ request_id,
47
+ exchange_code: crypto.randomUUID(),
48
+ })
49
+ })
50
+
51
+ app.get(`${PATH_PREFIX}/sign`, async (req, res) => {
52
+ const { request_id } = req.query
53
+ await sendSignedDocWebhook(request_id)
54
+
55
+ const redirect_uri = new URL(CLIENT_REDIRECT_URI)
56
+ redirect_uri.searchParams.set('request_id', request_id)
57
+ return res.redirect(redirect_uri)
58
+ })
59
+
60
+ app.get(`${PATH_PREFIX}/sign-requests/:request_id/signed_doc`, (req, res) => {
61
+ console.info(`Retrieving signed doc of: ${req.params.request_id}`)
62
+ return res.status(200).json({ signed_doc_url: SIGNED_DOC_URL })
63
+ })
64
+
65
+ app.get(`${PATH_PREFIX}/jwks`, async (req, res) => {
66
+ // eslint-disable-next-line no-unused-vars
67
+ const { d, ...publicJwk } = { ...MOCKPASS_SIGNV3_PRIVATE_JWK }
68
+ return res.status(200).json({ keys: [publicJwk] })
69
+ })
70
+ }
71
+
72
+ const sendSignedDocWebhook = async (request_id) => {
73
+ await fetch(CLIENT_WEBHOOK_URL, {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'application/json' },
76
+ body: JSON.stringify({
77
+ token: await new SignJWT({
78
+ signed_doc_url: SIGNED_DOC_URL,
79
+ request_type: 'signed_doc_url',
80
+ request_id,
81
+ })
82
+ .setIssuedAt()
83
+ .setProtectedHeader({
84
+ alg: 'ES256',
85
+ kid: MOCKPASS_SIGNV3_PRIVATE_JWK.kid,
86
+ })
87
+ .setExpirationTime('120s')
88
+ .sign(await importJWK(MOCKPASS_SIGNV3_PRIVATE_JWK)),
89
+ }),
90
+ }).then((response) => {
91
+ if (!response.ok) {
92
+ console.error('signed doc webhook failed', response)
93
+ }
94
+ console.info(`signed doc webhook success ${CLIENT_WEBHOOK_URL}`)
95
+ })
96
+ }
97
+
98
+ module.exports = config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengovsg/mockpass",
3
- "version": "4.3.5",
3
+ "version": "4.4.0",
4
4
  "description": "A mock SingPass/CorpPass server for dev purposes",
5
5
  "main": "app.js",
6
6
  "bin": {
@@ -0,0 +1,10 @@
1
+ {
2
+ "kty": "EC",
3
+ "d": "AxQ5ckZCNkTpCNG_7anl7mZ2ccesghaJS7MamwIHkHg",
4
+ "use": "sig",
5
+ "crv": "P-256",
6
+ "kid": "_dCTwISv-TbO7mJ6vuAXnm_ucqdYlaInsLozbT1MQQc",
7
+ "x": "OpMaeVXELLmjNVTw9OIfNQHyIi14Rzo18sE4dvJ4FBE",
8
+ "y": "td5SW3US0az5N3ssQdFzNbDCRj0fKF6O1ov9tETSQ4Q",
9
+ "alg": "ES256"
10
+ }