@opengovsg/formsg-sdk 0.10.0 → 0.11.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/.github/workflows/ci.yml +4 -4
- package/LICENSE.md +21 -0
- package/README.md +8 -1
- package/dist/crypto.d.ts +2 -2
- package/dist/crypto.js +20 -19
- package/dist/errors.js +4 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/resource/signing-keys.js +1 -0
- package/dist/resource/verification-keys.js +1 -0
- package/dist/types.d.ts +18 -18
- package/dist/util/crypto.d.ts +3 -3
- package/dist/util/crypto.js +22 -15
- package/dist/util/parser.d.ts +2 -2
- package/dist/util/parser.js +5 -2
- package/dist/util/publicKey.js +1 -0
- package/dist/util/signature.js +21 -4
- package/dist/util/validate.js +1 -0
- package/dist/util/webhooks.js +21 -4
- package/dist/verification/index.js +9 -9
- package/dist/verification/utils.js +6 -3
- package/dist/webhooks.js +27 -11
- package/package.json +8 -8
package/.github/workflows/ci.yml
CHANGED
|
@@ -6,13 +6,13 @@ on:
|
|
|
6
6
|
jobs:
|
|
7
7
|
build:
|
|
8
8
|
name: build
|
|
9
|
-
runs-on: ubuntu-
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
10
|
steps:
|
|
11
11
|
- uses: actions/checkout@v2
|
|
12
12
|
- name: Use Node.js
|
|
13
13
|
uses: actions/setup-node@v1
|
|
14
14
|
with:
|
|
15
|
-
node-version:
|
|
15
|
+
node-version: 18
|
|
16
16
|
- name: Cache Node.js modules
|
|
17
17
|
uses: actions/cache@v2
|
|
18
18
|
with:
|
|
@@ -27,13 +27,13 @@ jobs:
|
|
|
27
27
|
- run: npm run build
|
|
28
28
|
test:
|
|
29
29
|
name: test
|
|
30
|
-
runs-on: ubuntu-
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
31
|
steps:
|
|
32
32
|
- uses: actions/checkout@v2
|
|
33
33
|
- name: Use Node.js
|
|
34
34
|
uses: actions/setup-node@v1
|
|
35
35
|
with:
|
|
36
|
-
node-version:
|
|
36
|
+
node-version: 18
|
|
37
37
|
- name: Cache Node.js modules
|
|
38
38
|
uses: actions/cache@v2
|
|
39
39
|
with:
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Open Government Products
|
|
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
CHANGED
|
@@ -7,6 +7,10 @@ _Please note that this is an SDK for webhooks integration, and_ **_not_** _the F
|
|
|
7
7
|
|
|
8
8
|
This SDK provides convenient utilities for verifying FormSG webhooks and decrypting submissions in JavaScript and Node.js.
|
|
9
9
|
|
|
10
|
+
Not using Javascript? Check out our sister SDKs:
|
|
11
|
+
- [formsg-python-sdk](https://github.com/opengovsg/formsg-python-sdk)
|
|
12
|
+
- [formsg-ruby-sdk](https://github.com/opengovsg/formsg-ruby-sdk)
|
|
13
|
+
|
|
10
14
|
## Installation
|
|
11
15
|
|
|
12
16
|
Install the package with
|
|
@@ -132,7 +136,7 @@ of the returned object.
|
|
|
132
136
|
Note that due to end-to-end encryption, FormSG servers are unable to verify the data format.
|
|
133
137
|
|
|
134
138
|
However, the `decrypt` function exposed by this library [validates](https://github.com/opengovsg/formsg-javascript-sdk/blob/master/src/util/validate.ts) the decrypted content and will **return `null` if the
|
|
135
|
-
decrypted content does not
|
|
139
|
+
decrypted content does not contain all of the fields displayed in the schema below.**
|
|
136
140
|
|
|
137
141
|
| Key | Type | Description |
|
|
138
142
|
| ----------- | -------- | -------------------------------------------------------------------------------------------------------- |
|
|
@@ -142,6 +146,9 @@ decrypted content does not fit the schema displayed below.**
|
|
|
142
146
|
| fieldType | string | The type of field for the question. |
|
|
143
147
|
| \_id | string | A unique identifier of the form field. WARNING: Changes when new fields are created/removed in the form. |
|
|
144
148
|
|
|
149
|
+
**Important Note: **
|
|
150
|
+
Additional internal fields may be included in webhooks from time to time, which will then be published as part of our official schema once it is stable for public consumption. If you are applying your own validation, you should account for this e.g. by not rejecting the webhook if there are additional fields included.
|
|
151
|
+
|
|
145
152
|
The full schema can be viewed in
|
|
146
153
|
[`validate.ts`](https://github.com/opengovsg/formsg-javascript-sdk/tree/master/src/util/validate.ts).
|
|
147
154
|
|
package/dist/crypto.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DecryptedContent, DecryptedContentAndAttachments, DecryptParams, EncryptedFileContent } from './types';
|
|
1
|
+
import { DecryptedContent, DecryptedContentAndAttachments, DecryptParams, EncryptedContent, EncryptedFileContent } from './types';
|
|
2
2
|
export default class Crypto {
|
|
3
3
|
signingPublicKey?: string;
|
|
4
4
|
constructor({ signingPublicKey }?: {
|
|
@@ -11,7 +11,7 @@ export default class Crypto {
|
|
|
11
11
|
* @param signingPrivateKey Optional. Must be a base-64 encoded private key. If given, will be used to signing the given msg param prior to encrypting.
|
|
12
12
|
* @returns The encrypted basestring.
|
|
13
13
|
*/
|
|
14
|
-
encrypt: (msg: any, encryptionPublicKey: string, signingPrivateKey?: string
|
|
14
|
+
encrypt: (msg: any, encryptionPublicKey: string, signingPrivateKey?: string) => EncryptedContent;
|
|
15
15
|
/**
|
|
16
16
|
* Decrypts an encrypted submission and returns it.
|
|
17
17
|
* @param formSecretKey The base-64 secret key of the form to decrypt with.
|
package/dist/crypto.js
CHANGED
|
@@ -14,7 +14,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
14
14
|
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
15
|
function step(op) {
|
|
16
16
|
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
-
while (_) try {
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
18
|
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
19
|
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
20
|
switch (op[0]) {
|
|
@@ -47,8 +47,8 @@ var validate_1 = require("./util/validate");
|
|
|
47
47
|
var errors_1 = require("./errors");
|
|
48
48
|
var Crypto = /** @class */ (function () {
|
|
49
49
|
function Crypto(_a) {
|
|
50
|
+
var _b = _a === void 0 ? {} : _a, signingPublicKey = _b.signingPublicKey;
|
|
50
51
|
var _this = this;
|
|
51
|
-
var signingPublicKey = (_a === void 0 ? {} : _a).signingPublicKey;
|
|
52
52
|
/**
|
|
53
53
|
* Encrypt input with a unique keypair for each submission
|
|
54
54
|
* @param encryptionPublicKey The base-64 encoded public key for encrypting.
|
|
@@ -57,11 +57,11 @@ var Crypto = /** @class */ (function () {
|
|
|
57
57
|
* @returns The encrypted basestring.
|
|
58
58
|
*/
|
|
59
59
|
this.encrypt = function (msg, encryptionPublicKey, signingPrivateKey) {
|
|
60
|
-
var processedMsg = tweetnacl_util_1.decodeUTF8(JSON.stringify(msg));
|
|
60
|
+
var processedMsg = (0, tweetnacl_util_1.decodeUTF8)(JSON.stringify(msg));
|
|
61
61
|
if (signingPrivateKey) {
|
|
62
|
-
processedMsg = tweetnacl_1.default.sign(processedMsg, tweetnacl_util_1.decodeBase64(signingPrivateKey));
|
|
62
|
+
processedMsg = tweetnacl_1.default.sign(processedMsg, (0, tweetnacl_util_1.decodeBase64)(signingPrivateKey));
|
|
63
63
|
}
|
|
64
|
-
return crypto_1.encryptMessage(processedMsg, encryptionPublicKey);
|
|
64
|
+
return (0, crypto_1.encryptMessage)(processedMsg, encryptionPublicKey);
|
|
65
65
|
};
|
|
66
66
|
/**
|
|
67
67
|
* Decrypts an encrypted submission and returns it.
|
|
@@ -78,12 +78,12 @@ var Crypto = /** @class */ (function () {
|
|
|
78
78
|
var encryptedContent = decryptParams.encryptedContent, verifiedContent = decryptParams.verifiedContent;
|
|
79
79
|
// Do not return the transformed object in `_decrypt` function as a signed
|
|
80
80
|
// object is not encoded in UTF8 and is encoded in Base-64 instead.
|
|
81
|
-
var decryptedContent = crypto_1.decryptContent(formSecretKey, encryptedContent);
|
|
81
|
+
var decryptedContent = (0, crypto_1.decryptContent)(formSecretKey, encryptedContent);
|
|
82
82
|
if (!decryptedContent) {
|
|
83
83
|
throw new Error('Failed to decrypt content');
|
|
84
84
|
}
|
|
85
|
-
var decryptedObject = JSON.parse(tweetnacl_util_1.encodeUTF8(decryptedContent));
|
|
86
|
-
if (!validate_1.determineIsFormFields(decryptedObject)) {
|
|
85
|
+
var decryptedObject = JSON.parse((0, tweetnacl_util_1.encodeUTF8)(decryptedContent));
|
|
86
|
+
if (!(0, validate_1.determineIsFormFields)(decryptedObject)) {
|
|
87
87
|
throw new Error('Decrypted object does not fit expected shape');
|
|
88
88
|
}
|
|
89
89
|
var returnedObject = {
|
|
@@ -96,12 +96,12 @@ var Crypto = /** @class */ (function () {
|
|
|
96
96
|
// Only care if it is the correct shape if verifiedContent exists, since
|
|
97
97
|
// we need to append it to the end.
|
|
98
98
|
// Decrypted message must be able to be authenticated by the public key.
|
|
99
|
-
var decryptedVerifiedContent = crypto_1.decryptContent(formSecretKey, verifiedContent);
|
|
99
|
+
var decryptedVerifiedContent = (0, crypto_1.decryptContent)(formSecretKey, verifiedContent);
|
|
100
100
|
if (!decryptedVerifiedContent) {
|
|
101
101
|
// Returns null if decrypting verified content failed.
|
|
102
102
|
throw new Error('Failed to decrypt verified content');
|
|
103
103
|
}
|
|
104
|
-
var decryptedVerifiedObject = crypto_1.verifySignedMessage(decryptedVerifiedContent, _this.signingPublicKey);
|
|
104
|
+
var decryptedVerifiedObject = (0, crypto_1.verifySignedMessage)(decryptedVerifiedContent, _this.signingPublicKey);
|
|
105
105
|
returnedObject.verified = decryptedVerifiedObject;
|
|
106
106
|
}
|
|
107
107
|
return returnedObject;
|
|
@@ -132,10 +132,11 @@ var Crypto = /** @class */ (function () {
|
|
|
132
132
|
var internalValidationVersion = 1;
|
|
133
133
|
var cipherResponse = _this.encrypt(testResponse, publicKey);
|
|
134
134
|
// Use toString here since the return should be an empty array.
|
|
135
|
-
return (testResponse.toString() ===
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
return (testResponse.toString() ===
|
|
136
|
+
((_a = _this.decrypt(secretKey, {
|
|
137
|
+
encryptedContent: cipherResponse,
|
|
138
|
+
version: internalValidationVersion,
|
|
139
|
+
})) === null || _a === void 0 ? void 0 : _a.responses.toString()));
|
|
139
140
|
};
|
|
140
141
|
/**
|
|
141
142
|
* Encrypt given binary file with a unique keypair for each submission.
|
|
@@ -151,8 +152,8 @@ var Crypto = /** @class */ (function () {
|
|
|
151
152
|
nonce = tweetnacl_1.default.randomBytes(24);
|
|
152
153
|
return [2 /*return*/, {
|
|
153
154
|
submissionPublicKey: submissionKeypair.publicKey,
|
|
154
|
-
nonce: tweetnacl_util_1.encodeBase64(nonce),
|
|
155
|
-
binary: tweetnacl_1.default.box(binary, nonce, tweetnacl_util_1.decodeBase64(formPublicKey), tweetnacl_util_1.decodeBase64(submissionKeypair.secretKey)),
|
|
155
|
+
nonce: (0, tweetnacl_util_1.encodeBase64)(nonce),
|
|
156
|
+
binary: tweetnacl_1.default.box(binary, nonce, (0, tweetnacl_util_1.decodeBase64)(formPublicKey), (0, tweetnacl_util_1.decodeBase64)(submissionKeypair.secretKey)),
|
|
156
157
|
}];
|
|
157
158
|
});
|
|
158
159
|
}); };
|
|
@@ -168,7 +169,7 @@ var Crypto = /** @class */ (function () {
|
|
|
168
169
|
var submissionPublicKey = _a.submissionPublicKey, nonce = _a.nonce, encryptedBinary = _a.binary;
|
|
169
170
|
return __awaiter(_this, void 0, void 0, function () {
|
|
170
171
|
return __generator(this, function (_b) {
|
|
171
|
-
return [2 /*return*/, tweetnacl_1.default.box.open(encryptedBinary, tweetnacl_util_1.decodeBase64(nonce), tweetnacl_util_1.decodeBase64(submissionPublicKey), tweetnacl_util_1.decodeBase64(formSecretKey))];
|
|
172
|
+
return [2 /*return*/, tweetnacl_1.default.box.open(encryptedBinary, (0, tweetnacl_util_1.decodeBase64)(nonce), (0, tweetnacl_util_1.decodeBase64)(submissionPublicKey), (0, tweetnacl_util_1.decodeBase64)(formSecretKey))];
|
|
172
173
|
});
|
|
173
174
|
});
|
|
174
175
|
};
|
|
@@ -202,7 +203,7 @@ var Crypto = /** @class */ (function () {
|
|
|
202
203
|
});
|
|
203
204
|
fieldIds = Object.keys(attachmentRecords);
|
|
204
205
|
// Check if all fieldIds are within filenames
|
|
205
|
-
if (!crypto_1.areAttachmentFieldIdsValid(fieldIds, filenames)) {
|
|
206
|
+
if (!(0, crypto_1.areAttachmentFieldIdsValid)(fieldIds, filenames)) {
|
|
206
207
|
return [2 /*return*/, null];
|
|
207
208
|
}
|
|
208
209
|
downloadPromises = fieldIds.map(function (fieldId) {
|
|
@@ -214,7 +215,7 @@ var Crypto = /** @class */ (function () {
|
|
|
214
215
|
// Decrypt all the attachments
|
|
215
216
|
.then(function (_a) {
|
|
216
217
|
var downloadResponse = _a.data;
|
|
217
|
-
var encryptedFile = crypto_1.convertEncryptedAttachmentToFileContent(downloadResponse);
|
|
218
|
+
var encryptedFile = (0, crypto_1.convertEncryptedAttachmentToFileContent)(downloadResponse);
|
|
218
219
|
return _this.decryptFile(formSecretKey, encryptedFile);
|
|
219
220
|
})
|
|
220
221
|
.then(function (decryptedFile) {
|
package/dist/errors.js
CHANGED
|
@@ -3,16 +3,19 @@ var __extends = (this && this.__extends) || (function () {
|
|
|
3
3
|
var extendStatics = function (d, b) {
|
|
4
4
|
extendStatics = Object.setPrototypeOf ||
|
|
5
5
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
-
function (d, b) { for (var p in b) if (
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
7
|
return extendStatics(d, b);
|
|
8
8
|
};
|
|
9
9
|
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
10
12
|
extendStatics(d, b);
|
|
11
13
|
function __() { this.constructor = d; }
|
|
12
14
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
13
15
|
};
|
|
14
16
|
})();
|
|
15
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.AttachmentDecryptionError = exports.WebhookAuthenticateError = exports.MissingPublicKeyError = exports.MissingSecretKeyError = void 0;
|
|
16
19
|
var MissingSecretKeyError = /** @class */ (function (_super) {
|
|
17
20
|
__extends(MissingSecretKeyError, _super);
|
|
18
21
|
function MissingSecretKeyError(message) {
|
package/dist/index.d.ts
CHANGED
|
@@ -2,11 +2,6 @@ import Crypto from './crypto';
|
|
|
2
2
|
import { PackageInitParams } from './types';
|
|
3
3
|
import Verification from './verification';
|
|
4
4
|
import Webhooks from './webhooks';
|
|
5
|
-
declare const _default: (config?: PackageInitParams) => {
|
|
6
|
-
webhooks: Webhooks;
|
|
7
|
-
crypto: Crypto;
|
|
8
|
-
verification: Verification;
|
|
9
|
-
};
|
|
10
5
|
/**
|
|
11
6
|
* Entrypoint into the FormSG SDK
|
|
12
7
|
*
|
|
@@ -15,4 +10,9 @@ declare const _default: (config?: PackageInitParams) => {
|
|
|
15
10
|
* @param {string?} [config.webhookSecretKey] Optional. base64 secret key for signing webhooks. If provided, enables generating signature and headers to authenticate webhook data.
|
|
16
11
|
* @param {VerificationOptions?} [config.verificationOptions] Optional. If provided, enables the usage of the verification module.
|
|
17
12
|
*/
|
|
13
|
+
declare const _default: (config?: PackageInitParams) => {
|
|
14
|
+
webhooks: Webhooks;
|
|
15
|
+
crypto: Crypto;
|
|
16
|
+
verification: Verification;
|
|
17
|
+
};
|
|
18
18
|
export = _default;
|
package/dist/index.js
CHANGED
|
@@ -13,11 +13,11 @@ module.exports = function (config) {
|
|
|
13
13
|
* Public key is used for decrypting signed verified content in the `crypto` module, and
|
|
14
14
|
* also for verifying webhook signatures' authenticity in the `wehbooks` module.
|
|
15
15
|
*/
|
|
16
|
-
var signingPublicKey = publicKey_1.getSigningPublicKey(mode || 'production');
|
|
16
|
+
var signingPublicKey = (0, publicKey_1.getSigningPublicKey)(mode || 'production');
|
|
17
17
|
/**
|
|
18
18
|
* Public key is used for verifying verified field signatures' authenticity in the `verification` module.
|
|
19
19
|
*/
|
|
20
|
-
var verificationPublicKey = publicKey_1.getVerificationPublicKey(mode || 'production');
|
|
20
|
+
var verificationPublicKey = (0, publicKey_1.getVerificationPublicKey)(mode || 'production');
|
|
21
21
|
return {
|
|
22
22
|
webhooks: new webhooks_1.default({
|
|
23
23
|
publicKey: signingPublicKey,
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type PackageInitParams = {
|
|
2
2
|
/** base64 secret key for signing webhooks. If provided, enables generating signature and headers to authenticate webhook data. */
|
|
3
3
|
webhookSecretKey?: string;
|
|
4
4
|
/** If provided, enables the usage of the verification module. */
|
|
@@ -6,8 +6,8 @@ export declare type PackageInitParams = {
|
|
|
6
6
|
/** Initializes public key used for verifying and decrypting in this package. If not given, will default to "production". */
|
|
7
7
|
mode?: PackageMode;
|
|
8
8
|
};
|
|
9
|
-
export
|
|
10
|
-
export
|
|
9
|
+
export type FieldType = 'section' | 'radiobutton' | 'dropdown' | 'checkbox' | 'nric' | 'email' | 'table' | 'number' | 'rating' | 'yes_no' | 'decimal' | 'textfield' | 'textarea' | 'attachment' | 'date' | 'mobile' | 'homeno';
|
|
10
|
+
export type FormField = {
|
|
11
11
|
_id: string;
|
|
12
12
|
question: string;
|
|
13
13
|
fieldType: FieldType;
|
|
@@ -20,61 +20,61 @@ export declare type FormField = {
|
|
|
20
20
|
answer?: never;
|
|
21
21
|
answerArray: string[] | string[][];
|
|
22
22
|
});
|
|
23
|
-
export
|
|
24
|
-
export
|
|
23
|
+
export type EncryptedContent = string;
|
|
24
|
+
export type EncryptedAttachmentRecords = Record<string, string>;
|
|
25
25
|
export interface DecryptParams {
|
|
26
26
|
encryptedContent: EncryptedContent;
|
|
27
27
|
version: number;
|
|
28
28
|
verifiedContent?: EncryptedContent;
|
|
29
29
|
attachmentDownloadUrls?: EncryptedAttachmentRecords;
|
|
30
30
|
}
|
|
31
|
-
export
|
|
31
|
+
export type DecryptedContent = {
|
|
32
32
|
responses: FormField[];
|
|
33
33
|
verified?: Record<string, any>;
|
|
34
34
|
};
|
|
35
|
-
export
|
|
35
|
+
export type DecryptedFile = {
|
|
36
36
|
filename: string;
|
|
37
37
|
content: Uint8Array;
|
|
38
38
|
};
|
|
39
|
-
export
|
|
40
|
-
export
|
|
39
|
+
export type DecryptedAttachments = Record<string, DecryptedFile>;
|
|
40
|
+
export type DecryptedContentAndAttachments = {
|
|
41
41
|
content: DecryptedContent;
|
|
42
42
|
attachments: DecryptedAttachments;
|
|
43
43
|
};
|
|
44
|
-
export
|
|
44
|
+
export type EncryptedFileContent = {
|
|
45
45
|
submissionPublicKey: string;
|
|
46
46
|
nonce: string;
|
|
47
47
|
binary: Uint8Array;
|
|
48
48
|
};
|
|
49
|
-
export
|
|
49
|
+
export type EncryptedAttachmentContent = {
|
|
50
50
|
encryptedFile: {
|
|
51
51
|
submissionPublicKey: string;
|
|
52
52
|
nonce: string;
|
|
53
53
|
binary: string;
|
|
54
54
|
};
|
|
55
55
|
};
|
|
56
|
-
export
|
|
56
|
+
export type Keypair = {
|
|
57
57
|
publicKey: string;
|
|
58
58
|
secretKey: string;
|
|
59
59
|
};
|
|
60
|
-
export
|
|
61
|
-
export
|
|
60
|
+
export type PackageMode = 'staging' | 'production' | 'development' | 'test';
|
|
61
|
+
export type VerificationOptions = {
|
|
62
62
|
publicKey?: string;
|
|
63
63
|
secretKey?: string;
|
|
64
64
|
transactionExpiry?: number;
|
|
65
65
|
};
|
|
66
|
-
export
|
|
66
|
+
export type VerifiedAnswer = {
|
|
67
67
|
fieldId: string;
|
|
68
68
|
answer: string;
|
|
69
69
|
};
|
|
70
|
-
export
|
|
70
|
+
export type VerificationSignatureOptions = VerifiedAnswer & {
|
|
71
71
|
transactionId: string;
|
|
72
72
|
formId: string;
|
|
73
73
|
};
|
|
74
|
-
export
|
|
74
|
+
export type VerificationBasestringOptions = VerificationSignatureOptions & {
|
|
75
75
|
time: number;
|
|
76
76
|
};
|
|
77
|
-
export
|
|
77
|
+
export type VerificationAuthenticateOptions = VerifiedAnswer & {
|
|
78
78
|
signatureString: string;
|
|
79
79
|
submissionCreatedAt: number;
|
|
80
80
|
};
|
package/dist/util/crypto.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Keypair, EncryptedAttachmentContent, EncryptedFileContent } from '../types';
|
|
1
|
+
import { Keypair, EncryptedContent, EncryptedAttachmentContent, EncryptedFileContent } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* Helper method to generate a new keypair for encryption.
|
|
4
4
|
* @returns The generated keypair.
|
|
@@ -11,14 +11,14 @@ export declare const generateKeypair: () => Keypair;
|
|
|
11
11
|
* @returns The encrypted basestring
|
|
12
12
|
* @throws error if any of the encrypt methods fail
|
|
13
13
|
*/
|
|
14
|
-
export declare const encryptMessage: (msg: Uint8Array, theirPublicKey: string) =>
|
|
14
|
+
export declare const encryptMessage: (msg: Uint8Array, theirPublicKey: string) => EncryptedContent;
|
|
15
15
|
/**
|
|
16
16
|
* Helper method to decrypt an encrypted submission.
|
|
17
17
|
* @param formPrivateKey base64
|
|
18
18
|
* @param encryptedContent encrypted string encoded in base64
|
|
19
19
|
* @return The decrypted content, or null if decryption failed.
|
|
20
20
|
*/
|
|
21
|
-
export declare const decryptContent: (formPrivateKey: string, encryptedContent:
|
|
21
|
+
export declare const decryptContent: (formPrivateKey: string, encryptedContent: EncryptedContent) => Uint8Array | null;
|
|
22
22
|
/**
|
|
23
23
|
* Helper method to verify a signed message.
|
|
24
24
|
* @param msg the message to verify
|
package/dist/util/crypto.js
CHANGED
|
@@ -3,19 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.convertEncryptedAttachmentToFileContent = exports.areAttachmentFieldIdsValid = exports.verifySignedMessage = exports.decryptContent = exports.encryptMessage = exports.generateKeypair = void 0;
|
|
6
7
|
var tweetnacl_1 = __importDefault(require("tweetnacl"));
|
|
7
8
|
var tweetnacl_util_1 = require("tweetnacl-util");
|
|
8
9
|
/**
|
|
9
10
|
* Helper method to generate a new keypair for encryption.
|
|
10
11
|
* @returns The generated keypair.
|
|
11
12
|
*/
|
|
12
|
-
|
|
13
|
+
var generateKeypair = function () {
|
|
13
14
|
var kp = tweetnacl_1.default.box.keyPair();
|
|
14
15
|
return {
|
|
15
|
-
publicKey: tweetnacl_util_1.encodeBase64(kp.publicKey),
|
|
16
|
-
secretKey: tweetnacl_util_1.encodeBase64(kp.secretKey),
|
|
16
|
+
publicKey: (0, tweetnacl_util_1.encodeBase64)(kp.publicKey),
|
|
17
|
+
secretKey: (0, tweetnacl_util_1.encodeBase64)(kp.secretKey),
|
|
17
18
|
};
|
|
18
19
|
};
|
|
20
|
+
exports.generateKeypair = generateKeypair;
|
|
19
21
|
/**
|
|
20
22
|
* Helper function to encrypt input with a unique keypair for each submission.
|
|
21
23
|
* @param msg The message to encrypt
|
|
@@ -23,28 +25,30 @@ exports.generateKeypair = function () {
|
|
|
23
25
|
* @returns The encrypted basestring
|
|
24
26
|
* @throws error if any of the encrypt methods fail
|
|
25
27
|
*/
|
|
26
|
-
|
|
27
|
-
var submissionKeypair = exports.generateKeypair();
|
|
28
|
+
var encryptMessage = function (msg, theirPublicKey) {
|
|
29
|
+
var submissionKeypair = (0, exports.generateKeypair)();
|
|
28
30
|
var nonce = tweetnacl_1.default.randomBytes(24);
|
|
29
|
-
var encrypted = tweetnacl_util_1.encodeBase64(tweetnacl_1.default.box(msg, nonce, tweetnacl_util_1.decodeBase64(theirPublicKey), tweetnacl_util_1.decodeBase64(submissionKeypair.secretKey)));
|
|
30
|
-
return submissionKeypair.publicKey
|
|
31
|
+
var encrypted = (0, tweetnacl_util_1.encodeBase64)(tweetnacl_1.default.box(msg, nonce, (0, tweetnacl_util_1.decodeBase64)(theirPublicKey), (0, tweetnacl_util_1.decodeBase64)(submissionKeypair.secretKey)));
|
|
32
|
+
return "".concat(submissionKeypair.publicKey, ";").concat((0, tweetnacl_util_1.encodeBase64)(nonce), ":").concat(encrypted);
|
|
31
33
|
};
|
|
34
|
+
exports.encryptMessage = encryptMessage;
|
|
32
35
|
/**
|
|
33
36
|
* Helper method to decrypt an encrypted submission.
|
|
34
37
|
* @param formPrivateKey base64
|
|
35
38
|
* @param encryptedContent encrypted string encoded in base64
|
|
36
39
|
* @return The decrypted content, or null if decryption failed.
|
|
37
40
|
*/
|
|
38
|
-
|
|
41
|
+
var decryptContent = function (formPrivateKey, encryptedContent) {
|
|
39
42
|
try {
|
|
40
43
|
var _a = encryptedContent.split(';'), submissionPublicKey = _a[0], nonceEncrypted = _a[1];
|
|
41
44
|
var _b = nonceEncrypted.split(':').map(tweetnacl_util_1.decodeBase64), nonce = _b[0], encrypted = _b[1];
|
|
42
|
-
return tweetnacl_1.default.box.open(encrypted, nonce, tweetnacl_util_1.decodeBase64(submissionPublicKey), tweetnacl_util_1.decodeBase64(formPrivateKey));
|
|
45
|
+
return tweetnacl_1.default.box.open(encrypted, nonce, (0, tweetnacl_util_1.decodeBase64)(submissionPublicKey), (0, tweetnacl_util_1.decodeBase64)(formPrivateKey));
|
|
43
46
|
}
|
|
44
47
|
catch (err) {
|
|
45
48
|
return null;
|
|
46
49
|
}
|
|
47
50
|
};
|
|
51
|
+
exports.decryptContent = decryptContent;
|
|
48
52
|
/**
|
|
49
53
|
* Helper method to verify a signed message.
|
|
50
54
|
* @param msg the message to verify
|
|
@@ -52,28 +56,31 @@ exports.decryptContent = function (formPrivateKey, encryptedContent) {
|
|
|
52
56
|
* @returns the signed message if successful, else an error will be thrown
|
|
53
57
|
* @throws {Error} if the message cannot be verified
|
|
54
58
|
*/
|
|
55
|
-
|
|
56
|
-
var openedMessage = tweetnacl_1.default.sign.open(msg, tweetnacl_util_1.decodeBase64(publicKey));
|
|
59
|
+
var verifySignedMessage = function (msg, publicKey) {
|
|
60
|
+
var openedMessage = tweetnacl_1.default.sign.open(msg, (0, tweetnacl_util_1.decodeBase64)(publicKey));
|
|
57
61
|
if (!openedMessage)
|
|
58
62
|
throw new Error('Failed to open signed message with given public key');
|
|
59
|
-
return JSON.parse(tweetnacl_util_1.encodeUTF8(openedMessage));
|
|
63
|
+
return JSON.parse((0, tweetnacl_util_1.encodeUTF8)(openedMessage));
|
|
60
64
|
};
|
|
65
|
+
exports.verifySignedMessage = verifySignedMessage;
|
|
61
66
|
/**
|
|
62
67
|
* Helper method to check if all the field IDs given are within the filenames
|
|
63
68
|
* @param fieldIds the list of fieldIds to check
|
|
64
69
|
* @param filenames the filenames that should contain the fields
|
|
65
70
|
* @returns boolean indicating whether the fields are valid
|
|
66
71
|
*/
|
|
67
|
-
|
|
72
|
+
var areAttachmentFieldIdsValid = function (fieldIds, filenames) {
|
|
68
73
|
return fieldIds.every(function (fieldId) { return filenames[fieldId]; });
|
|
69
74
|
};
|
|
75
|
+
exports.areAttachmentFieldIdsValid = areAttachmentFieldIdsValid;
|
|
70
76
|
/**
|
|
71
77
|
* Converts an encrypted attachment to encrypted file content
|
|
72
78
|
* @param encryptedAttachment The encrypted attachment
|
|
73
79
|
* @returns EncryptedFileContent The encrypted file content
|
|
74
80
|
*/
|
|
75
|
-
|
|
81
|
+
var convertEncryptedAttachmentToFileContent = function (encryptedAttachment) { return ({
|
|
76
82
|
submissionPublicKey: encryptedAttachment.encryptedFile.submissionPublicKey,
|
|
77
83
|
nonce: encryptedAttachment.encryptedFile.nonce,
|
|
78
|
-
binary: tweetnacl_util_1.decodeBase64(encryptedAttachment.encryptedFile.binary),
|
|
84
|
+
binary: (0, tweetnacl_util_1.decodeBase64)(encryptedAttachment.encryptedFile.binary),
|
|
79
85
|
}); };
|
|
86
|
+
exports.convertEncryptedAttachmentToFileContent = convertEncryptedAttachmentToFileContent;
|
package/dist/util/parser.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type HeaderSignature = {
|
|
2
2
|
v1: string;
|
|
3
3
|
t: number;
|
|
4
4
|
s: string;
|
|
5
5
|
f: string;
|
|
6
6
|
};
|
|
7
|
-
export
|
|
7
|
+
export type VerificationSignature = {
|
|
8
8
|
v: string;
|
|
9
9
|
t: number;
|
|
10
10
|
s: string;
|
package/dist/util/parser.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseVerificationSignature = exports.parseSignatureHeader = void 0;
|
|
3
4
|
/**
|
|
4
5
|
* Helper function to retrieve keys-values in a signature.
|
|
5
6
|
* @param signature The signature to convert to a keymap
|
|
@@ -20,18 +21,20 @@ var signatureToKeyMap = function (signature) {
|
|
|
20
21
|
* @param header The X-FormSG-Signature header
|
|
21
22
|
* @returns The signature header constituents
|
|
22
23
|
*/
|
|
23
|
-
|
|
24
|
+
var parseSignatureHeader = function (header) {
|
|
24
25
|
var parsedSignature = signatureToKeyMap(header);
|
|
25
26
|
parsedSignature.t = Number(parsedSignature.t);
|
|
26
27
|
return parsedSignature;
|
|
27
28
|
};
|
|
29
|
+
exports.parseSignatureHeader = parseSignatureHeader;
|
|
28
30
|
/**
|
|
29
31
|
* Parses the verification signature into its constituent
|
|
30
32
|
* @param signature The verification signature
|
|
31
33
|
* @returns The verification signature constituents
|
|
32
34
|
*/
|
|
33
|
-
|
|
35
|
+
var parseVerificationSignature = function (signature) {
|
|
34
36
|
var parsedSignature = signatureToKeyMap(signature);
|
|
35
37
|
parsedSignature.t = Number(parsedSignature.t);
|
|
36
38
|
return parsedSignature;
|
|
37
39
|
};
|
|
40
|
+
exports.parseVerificationSignature = parseVerificationSignature;
|
package/dist/util/publicKey.js
CHANGED
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getVerificationPublicKey = exports.getSigningPublicKey = void 0;
|
|
6
7
|
var signing_keys_1 = require("../resource/signing-keys");
|
|
7
8
|
var verification_keys_1 = require("../resource/verification-keys");
|
|
8
9
|
var stage_1 = __importDefault(require("./stage"));
|
package/dist/util/signature.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
2
18
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
3
19
|
if (mod && mod.__esModule) return mod;
|
|
4
20
|
var result = {};
|
|
5
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result
|
|
6
|
-
result
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
7
23
|
return result;
|
|
8
24
|
};
|
|
9
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.verify = exports.sign = void 0;
|
|
10
27
|
var tweetnacl = __importStar(require("tweetnacl"));
|
|
11
28
|
var tweetnacl_util_1 = require("tweetnacl-util");
|
|
12
29
|
/**
|
|
@@ -16,7 +33,7 @@ var tweetnacl_util_1 = require("tweetnacl-util");
|
|
|
16
33
|
* @return base64 encoded signature
|
|
17
34
|
*/
|
|
18
35
|
function sign(basestring, secretKey) {
|
|
19
|
-
return tweetnacl_util_1.encodeBase64(tweetnacl.sign.detached(tweetnacl_util_1.decodeUTF8(basestring), tweetnacl_util_1.decodeBase64(secretKey)));
|
|
36
|
+
return (0, tweetnacl_util_1.encodeBase64)(tweetnacl.sign.detached((0, tweetnacl_util_1.decodeUTF8)(basestring), (0, tweetnacl_util_1.decodeBase64)(secretKey)));
|
|
20
37
|
}
|
|
21
38
|
exports.sign = sign;
|
|
22
39
|
/**
|
|
@@ -27,6 +44,6 @@ exports.sign = sign;
|
|
|
27
44
|
* @return True if verification checks out, false otherwise
|
|
28
45
|
*/
|
|
29
46
|
function verify(message, signature, publicKey) {
|
|
30
|
-
return tweetnacl.sign.detached.verify(tweetnacl_util_1.decodeUTF8(message), tweetnacl_util_1.decodeBase64(signature), tweetnacl_util_1.decodeBase64(publicKey));
|
|
47
|
+
return tweetnacl.sign.detached.verify((0, tweetnacl_util_1.decodeUTF8)(message), (0, tweetnacl_util_1.decodeBase64)(signature), (0, tweetnacl_util_1.decodeBase64)(publicKey));
|
|
31
48
|
}
|
|
32
49
|
exports.verify = verify;
|
package/dist/util/validate.js
CHANGED
package/dist/util/webhooks.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
2
18
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
3
19
|
if (mod && mod.__esModule) return mod;
|
|
4
20
|
var result = {};
|
|
5
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result
|
|
6
|
-
result
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
7
23
|
return result;
|
|
8
24
|
};
|
|
9
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.hasEpochExpired = exports.isSignatureHeaderValid = void 0;
|
|
10
27
|
var url = __importStar(require("url"));
|
|
11
28
|
var errors_1 = require("../errors");
|
|
12
29
|
var signature_1 = require("./signature");
|
|
@@ -23,8 +40,8 @@ var isSignatureHeaderValid = function (uri, signatureHeader, publicKey) {
|
|
|
23
40
|
if (!epoch || !signature || !submissionId || !formId) {
|
|
24
41
|
throw new errors_1.WebhookAuthenticateError('X-FormSG-Signature header is invalid');
|
|
25
42
|
}
|
|
26
|
-
var baseString = url.parse(uri).href
|
|
27
|
-
return signature_1.verify(baseString, signature, publicKey);
|
|
43
|
+
var baseString = "".concat(url.parse(uri).href, ".").concat(submissionId, ".").concat(formId, ".").concat(epoch);
|
|
44
|
+
return (0, signature_1.verify)(baseString, signature, publicKey);
|
|
28
45
|
};
|
|
29
46
|
exports.isSignatureHeaderValid = isSignatureHeaderValid;
|
|
30
47
|
/**
|
|
@@ -33,27 +33,27 @@ var Verification = /** @class */ (function () {
|
|
|
33
33
|
throw new errors_1.MissingPublicKeyError();
|
|
34
34
|
}
|
|
35
35
|
try {
|
|
36
|
-
var _b = parser_1.parseVerificationSignature(signatureString), transactionId = _b.v, time = _b.t, formId = _b.f, signature = _b.s;
|
|
36
|
+
var _b = (0, parser_1.parseVerificationSignature)(signatureString), transactionId = _b.v, time = _b.t, formId = _b.f, signature = _b.s;
|
|
37
37
|
if (!time) {
|
|
38
38
|
throw new Error('Malformed signature string was passed into function');
|
|
39
39
|
}
|
|
40
|
-
if (utils_1.isSignatureTimeValid(time, submissionCreatedAt, _this.transactionExpiry)) {
|
|
41
|
-
var data = utils_1.formatToBaseString({
|
|
40
|
+
if ((0, utils_1.isSignatureTimeValid)(time, submissionCreatedAt, _this.transactionExpiry)) {
|
|
41
|
+
var data = (0, utils_1.formatToBaseString)({
|
|
42
42
|
transactionId: transactionId,
|
|
43
43
|
formId: formId,
|
|
44
44
|
fieldId: fieldId,
|
|
45
45
|
answer: answer,
|
|
46
46
|
time: time,
|
|
47
47
|
});
|
|
48
|
-
return tweetnacl_1.default.sign.detached.verify(tweetnacl_util_1.decodeUTF8(data), tweetnacl_util_1.decodeBase64(signature), tweetnacl_util_1.decodeBase64(_this.verificationPublicKey));
|
|
48
|
+
return tweetnacl_1.default.sign.detached.verify((0, tweetnacl_util_1.decodeUTF8)(data), (0, tweetnacl_util_1.decodeBase64)(signature), (0, tweetnacl_util_1.decodeBase64)(_this.verificationPublicKey));
|
|
49
49
|
}
|
|
50
50
|
else {
|
|
51
|
-
console.info("Signature was expired for signatureString=\""
|
|
51
|
+
console.info("Signature was expired for signatureString=\"".concat(signatureString, "\" signatureDate=\"").concat(time, "\" submissionCreatedAt=\"").concat(submissionCreatedAt, "\""));
|
|
52
52
|
return false;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
catch (error) {
|
|
56
|
-
console.error("An error occurred for signatureString=\""
|
|
56
|
+
console.error("An error occurred for signatureString=\"".concat(signatureString, "\" submissionCreatedAt=\"").concat(submissionCreatedAt, "\" fieldId=\"").concat(fieldId, "\" answer=\"").concat(answer, "\" error=\"").concat(error, "\""));
|
|
57
57
|
return false;
|
|
58
58
|
}
|
|
59
59
|
};
|
|
@@ -63,15 +63,15 @@ var Verification = /** @class */ (function () {
|
|
|
63
63
|
throw new errors_1.MissingSecretKeyError('Provide a secret key when when initializing the Verification class to use this function.');
|
|
64
64
|
}
|
|
65
65
|
var time = Date.now();
|
|
66
|
-
var data = utils_1.formatToBaseString({
|
|
66
|
+
var data = (0, utils_1.formatToBaseString)({
|
|
67
67
|
transactionId: transactionId,
|
|
68
68
|
formId: formId,
|
|
69
69
|
fieldId: fieldId,
|
|
70
70
|
answer: answer,
|
|
71
71
|
time: time,
|
|
72
72
|
});
|
|
73
|
-
var signature = tweetnacl_1.default.sign.detached(tweetnacl_util_1.decodeUTF8(data), tweetnacl_util_1.decodeBase64(_this.verificationSecretKey));
|
|
74
|
-
return "f="
|
|
73
|
+
var signature = tweetnacl_1.default.sign.detached((0, tweetnacl_util_1.decodeUTF8)(data), (0, tweetnacl_util_1.decodeBase64)(_this.verificationSecretKey));
|
|
74
|
+
return "f=".concat(formId, ",v=").concat(transactionId, ",t=").concat(time, ",s=").concat((0, tweetnacl_util_1.encodeBase64)(signature));
|
|
75
75
|
};
|
|
76
76
|
this.verificationPublicKey = params === null || params === void 0 ? void 0 : params.publicKey;
|
|
77
77
|
this.verificationSecretKey = params === null || params === void 0 ? void 0 : params.secretKey;
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatToBaseString = exports.isSignatureTimeValid = void 0;
|
|
3
4
|
/**
|
|
4
5
|
* Checks if signature was made within the given expiry range before
|
|
5
6
|
* submission was created.
|
|
6
7
|
* @param signatureTime ms
|
|
7
8
|
* @param submissionCreatedAt ms
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
+
var isSignatureTimeValid = function (signatureTime, submissionCreatedAt, transactionExpiry) {
|
|
10
11
|
var maxTime = submissionCreatedAt;
|
|
11
12
|
var minTime = maxTime - transactionExpiry * 1000;
|
|
12
13
|
return signatureTime > minTime && signatureTime < maxTime;
|
|
13
14
|
};
|
|
15
|
+
exports.isSignatureTimeValid = isSignatureTimeValid;
|
|
14
16
|
/**
|
|
15
17
|
* Formats given data into a string for signing
|
|
16
18
|
*/
|
|
17
|
-
|
|
19
|
+
var formatToBaseString = function (_a) {
|
|
18
20
|
var transactionId = _a.transactionId, formId = _a.formId, fieldId = _a.fieldId, answer = _a.answer, time = _a.time;
|
|
19
|
-
return transactionId
|
|
21
|
+
return "".concat(transactionId, ".").concat(formId, ".").concat(fieldId, ".").concat(answer, ".").concat(time);
|
|
20
22
|
};
|
|
23
|
+
exports.formatToBaseString = formatToBaseString;
|
package/dist/webhooks.js
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
2
18
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
3
19
|
if (mod && mod.__esModule) return mod;
|
|
4
20
|
var result = {};
|
|
5
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result
|
|
6
|
-
result
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
7
23
|
return result;
|
|
8
24
|
};
|
|
9
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -14,8 +30,8 @@ var webhooks_1 = require("./util/webhooks");
|
|
|
14
30
|
var errors_1 = require("./errors");
|
|
15
31
|
var Webhooks = /** @class */ (function () {
|
|
16
32
|
function Webhooks(_a) {
|
|
17
|
-
var _this = this;
|
|
18
33
|
var publicKey = _a.publicKey, secretKey = _a.secretKey;
|
|
34
|
+
var _this = this;
|
|
19
35
|
/**
|
|
20
36
|
* Injects the webhook public key for authentication
|
|
21
37
|
* @param header X-FormSG-Signature header
|
|
@@ -25,15 +41,15 @@ var Webhooks = /** @class */ (function () {
|
|
|
25
41
|
*/
|
|
26
42
|
this.authenticate = function (header, uri) {
|
|
27
43
|
// Parse the header
|
|
28
|
-
var signatureHeader = parser_1.parseSignatureHeader(header);
|
|
44
|
+
var signatureHeader = (0, parser_1.parseSignatureHeader)(header);
|
|
29
45
|
var signature = signatureHeader.v1, epoch = signatureHeader.t, submissionId = signatureHeader.s, formId = signatureHeader.f;
|
|
30
46
|
// Verify signature authenticity
|
|
31
|
-
if (!webhooks_1.isSignatureHeaderValid(uri, signatureHeader, _this.publicKey)) {
|
|
32
|
-
throw new errors_1.WebhookAuthenticateError("Signature could not be verified for uri="
|
|
47
|
+
if (!(0, webhooks_1.isSignatureHeaderValid)(uri, signatureHeader, _this.publicKey)) {
|
|
48
|
+
throw new errors_1.WebhookAuthenticateError("Signature could not be verified for uri=".concat(uri, " submissionId=").concat(submissionId, " formId=").concat(formId, " epoch=").concat(epoch, " signature=").concat(signature));
|
|
33
49
|
}
|
|
34
50
|
// Verify epoch recency
|
|
35
|
-
if (webhooks_1.hasEpochExpired(epoch)) {
|
|
36
|
-
throw new errors_1.WebhookAuthenticateError("Signature is not recent for uri="
|
|
51
|
+
if ((0, webhooks_1.hasEpochExpired)(epoch)) {
|
|
52
|
+
throw new errors_1.WebhookAuthenticateError("Signature is not recent for uri=".concat(uri, " submissionId=").concat(submissionId, " formId=").concat(formId, " epoch=").concat(epoch, " signature=").concat(signature));
|
|
37
53
|
}
|
|
38
54
|
// All checks pass.
|
|
39
55
|
return true;
|
|
@@ -56,8 +72,8 @@ var Webhooks = /** @class */ (function () {
|
|
|
56
72
|
if (!submissionId || !uri || !formId || !epoch) {
|
|
57
73
|
throw new TypeError('submissionId, uri, formId, or epoch must be provided to generate a webhook signature');
|
|
58
74
|
}
|
|
59
|
-
var baseString = url.parse(uri).href
|
|
60
|
-
return signature_1.sign(baseString, _this.secretKey);
|
|
75
|
+
var baseString = "".concat(url.parse(uri).href, ".").concat(submissionId, ".").concat(formId, ".").concat(epoch);
|
|
76
|
+
return (0, signature_1.sign)(baseString, _this.secretKey);
|
|
61
77
|
};
|
|
62
78
|
/**
|
|
63
79
|
* Constructs the `X-FormSG-Signature` header
|
|
@@ -74,7 +90,7 @@ var Webhooks = /** @class */ (function () {
|
|
|
74
90
|
if (!_this.secretKey) {
|
|
75
91
|
throw new errors_1.MissingSecretKeyError();
|
|
76
92
|
}
|
|
77
|
-
return "t="
|
|
93
|
+
return "t=".concat(epoch, ",s=").concat(submissionId, ",f=").concat(formId, ",v1=").concat(signature);
|
|
78
94
|
};
|
|
79
95
|
this.publicKey = publicKey;
|
|
80
96
|
this.secretKey = secretKey;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengovsg/formsg-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/opengovsg/formsg-javascript-sdk.git"
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"author": "Open Government Products (FormSG)",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"axios": "^
|
|
27
|
+
"axios": "^1.6.2",
|
|
28
28
|
"tweetnacl": "^1.0.3",
|
|
29
29
|
"tweetnacl-util": "^0.15.1"
|
|
30
30
|
},
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"@babel/cli": "^7.8.4",
|
|
33
33
|
"@babel/core": "^7.9.0",
|
|
34
34
|
"@babel/preset-env": "^7.9.0",
|
|
35
|
-
"@types/jest": "^
|
|
36
|
-
"@types/node": "^
|
|
35
|
+
"@types/jest": "^29.5.8",
|
|
36
|
+
"@types/node": "^18.18.9",
|
|
37
37
|
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
|
38
38
|
"auto-changelog": "^2.4.0",
|
|
39
39
|
"coveralls": "^3.1.1",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"eslint-plugin-prettier": "^3.4.0",
|
|
44
44
|
"eslint-plugin-simple-import-sort": "^7.0.0",
|
|
45
45
|
"eslint-plugin-typesafe": "^0.5.2",
|
|
46
|
-
"jest": "^
|
|
47
|
-
"jest-mock-axios": "^4.
|
|
48
|
-
"ts-jest": "^
|
|
49
|
-
"typescript": "^
|
|
46
|
+
"jest": "^29.7.0",
|
|
47
|
+
"jest-mock-axios": "^4.7.3",
|
|
48
|
+
"ts-jest": "^29.1.1",
|
|
49
|
+
"typescript": "^4.9.5"
|
|
50
50
|
}
|
|
51
51
|
}
|