@opengovsg/formsg-sdk 0.16.1 → 7.1.4
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 +8 -9
- package/cjs-entry.cjs +5 -0
- package/dist/{crypto-base.js → cjs/crypto-base.js} +7 -9
- package/dist/{crypto.js → cjs/crypto.js} +2 -2
- package/dist/{index.d.ts → cjs/index.d.ts} +2 -2
- package/dist/{index.js → cjs/index.js} +12 -2
- package/dist/{util → cjs/util}/publicKey.js +2 -3
- package/dist/{util → cjs/util}/signature.js +19 -10
- package/dist/{util → cjs/util}/validate.js +2 -3
- package/dist/{util → cjs/util}/webhooks.js +17 -7
- package/dist/{webhooks.js → cjs/webhooks.js} +17 -7
- package/dist/esm/crypto-base.d.ts +25 -0
- package/dist/esm/crypto-base.js +86 -0
- package/dist/esm/crypto-v3.d.ts +41 -0
- package/dist/esm/crypto-v3.js +168 -0
- package/dist/esm/crypto.d.ts +41 -0
- package/dist/esm/crypto.js +228 -0
- package/dist/esm/errors.d.ts +13 -0
- package/dist/esm/errors.js +64 -0
- package/dist/esm/index.d.ts +20 -0
- package/dist/esm/index.js +39 -0
- package/dist/esm/resource/signing-keys.d.ts +16 -0
- package/dist/esm/resource/signing-keys.js +19 -0
- package/dist/esm/resource/verification-keys.d.ts +16 -0
- package/dist/esm/resource/verification-keys.js +19 -0
- package/dist/esm/types.d.ts +101 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/util/crypto.d.ts +42 -0
- package/dist/esm/util/crypto.js +74 -0
- package/dist/esm/util/parser.d.ts +24 -0
- package/dist/esm/util/parser.js +35 -0
- package/dist/esm/util/publicKey.d.ts +14 -0
- package/dist/esm/util/publicKey.js +38 -0
- package/dist/esm/util/signature.d.ts +16 -0
- package/dist/esm/util/signature.js +22 -0
- package/dist/esm/util/stage.d.ts +5 -0
- package/dist/esm/util/stage.js +7 -0
- package/dist/esm/util/validate.d.ts +4 -0
- package/dist/esm/util/validate.js +32 -0
- package/dist/esm/util/webhooks.d.ts +20 -0
- package/dist/esm/util/webhooks.js +33 -0
- package/dist/esm/verification/index.d.ts +18 -0
- package/dist/esm/verification/index.js +77 -0
- package/dist/esm/verification/utils.d.ts +12 -0
- package/dist/esm/verification/utils.js +18 -0
- package/dist/esm/webhooks.d.ts +48 -0
- package/dist/esm/webhooks.js +75 -0
- package/package.json +48 -32
- package/.eslintrc +0 -75
- package/.github/workflows/ci.yml +0 -53
- package/.github/workflows/publish.yml +0 -25
- package/CHANGELOG.md +0 -190
- /package/dist/{crypto-base.d.ts → cjs/crypto-base.d.ts} +0 -0
- /package/dist/{crypto-v3.d.ts → cjs/crypto-v3.d.ts} +0 -0
- /package/dist/{crypto-v3.js → cjs/crypto-v3.js} +0 -0
- /package/dist/{crypto.d.ts → cjs/crypto.d.ts} +0 -0
- /package/dist/{errors.d.ts → cjs/errors.d.ts} +0 -0
- /package/dist/{errors.js → cjs/errors.js} +0 -0
- /package/dist/{resource → cjs/resource}/signing-keys.d.ts +0 -0
- /package/dist/{resource → cjs/resource}/signing-keys.js +0 -0
- /package/dist/{resource → cjs/resource}/verification-keys.d.ts +0 -0
- /package/dist/{resource → cjs/resource}/verification-keys.js +0 -0
- /package/dist/{types.d.ts → cjs/types.d.ts} +0 -0
- /package/dist/{types.js → cjs/types.js} +0 -0
- /package/dist/{util → cjs/util}/crypto.d.ts +0 -0
- /package/dist/{util → cjs/util}/crypto.js +0 -0
- /package/dist/{util → cjs/util}/parser.d.ts +0 -0
- /package/dist/{util → cjs/util}/parser.js +0 -0
- /package/dist/{util → cjs/util}/publicKey.d.ts +0 -0
- /package/dist/{util → cjs/util}/signature.d.ts +0 -0
- /package/dist/{util → cjs/util}/stage.d.ts +0 -0
- /package/dist/{util → cjs/util}/stage.js +0 -0
- /package/dist/{util → cjs/util}/validate.d.ts +0 -0
- /package/dist/{util → cjs/util}/webhooks.d.ts +0 -0
- /package/dist/{verification → cjs/verification}/index.d.ts +0 -0
- /package/dist/{verification → cjs/verification}/index.js +0 -0
- /package/dist/{verification → cjs/verification}/utils.d.ts +0 -0
- /package/dist/{verification → cjs/verification}/utils.js +0 -0
- /package/dist/{webhooks.d.ts → cjs/webhooks.d.ts} +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import CryptoBase from './crypto-base';
|
|
2
|
+
import { DecryptedContent, DecryptedContentAndAttachments, DecryptParams, EncryptedContent } from './types';
|
|
3
|
+
export default class Crypto extends CryptoBase {
|
|
4
|
+
signingPublicKey?: string;
|
|
5
|
+
constructor({ signingPublicKey }?: {
|
|
6
|
+
signingPublicKey?: string;
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Encrypt input with a unique keypair for each submission
|
|
10
|
+
* @param encryptionPublicKey The base-64 encoded public key for encrypting.
|
|
11
|
+
* @param msg The message to encrypt, will be stringified.
|
|
12
|
+
* @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.
|
|
13
|
+
* @returns The encrypted basestring.
|
|
14
|
+
*/
|
|
15
|
+
encrypt: (msg: any, encryptionPublicKey: string, signingPrivateKey?: string) => EncryptedContent;
|
|
16
|
+
/**
|
|
17
|
+
* Decrypts an encrypted submission and returns it.
|
|
18
|
+
* @param formSecretKey The base-64 secret key of the form to decrypt with.
|
|
19
|
+
* @param decryptParams The params containing encrypted content and information.
|
|
20
|
+
* @param decryptParams.encryptedContent The encrypted content encoded with base-64.
|
|
21
|
+
* @param decryptParams.version The version of the payload. Used to determine the decryption process to decrypt the content with.
|
|
22
|
+
* @param decryptParams.verifiedContent Optional. The encrypted and signed verified content. If given, the signingPublicKey will be used to attempt to open the signed message.
|
|
23
|
+
* @returns The decrypted content if successful. Else, null will be returned.
|
|
24
|
+
* @throws {MissingPublicKeyError} if a public key is not provided when instantiating this class and is needed for verifying signed content.
|
|
25
|
+
*/
|
|
26
|
+
decrypt: (formSecretKey: string, decryptParams: DecryptParams) => DecryptedContent | null;
|
|
27
|
+
/**
|
|
28
|
+
* Returns true if a pair of public & secret keys are associated with each other
|
|
29
|
+
* @param publicKey The public key to verify against.
|
|
30
|
+
* @param secretKey The private key to verify against.
|
|
31
|
+
*/
|
|
32
|
+
valid: (publicKey: string, secretKey: string) => boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Decrypts an encrypted submission, and also download and decrypt any attachments alongside it.
|
|
35
|
+
* @param formSecretKey Secret key as a base-64 string
|
|
36
|
+
* @param decryptParams The params containing encrypted content and information.
|
|
37
|
+
* @returns A promise of the decrypted submission, including attachments (if any). Or else returns null if a decryption error decrypting any part of the submission.
|
|
38
|
+
* @throws {MissingPublicKeyError} if a public key is not provided when instantiating this class and is needed for verifying signed content.
|
|
39
|
+
*/
|
|
40
|
+
decryptWithAttachments: (formSecretKey: string, decryptParams: DecryptParams) => Promise<DecryptedContentAndAttachments | null>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
var __extends = (this && this.__extends) || (function () {
|
|
2
|
+
var extendStatics = function (d, b) {
|
|
3
|
+
extendStatics = Object.setPrototypeOf ||
|
|
4
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
5
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
6
|
+
return extendStatics(d, b);
|
|
7
|
+
};
|
|
8
|
+
return function (d, b) {
|
|
9
|
+
if (typeof b !== "function" && b !== null)
|
|
10
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
11
|
+
extendStatics(d, b);
|
|
12
|
+
function __() { this.constructor = d; }
|
|
13
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
14
|
+
};
|
|
15
|
+
})();
|
|
16
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
17
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
18
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
19
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
20
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
21
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
22
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
26
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
27
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
28
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
29
|
+
function step(op) {
|
|
30
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
31
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
32
|
+
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;
|
|
33
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
34
|
+
switch (op[0]) {
|
|
35
|
+
case 0: case 1: t = op; break;
|
|
36
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
37
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
38
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
39
|
+
default:
|
|
40
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
41
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
42
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
43
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
44
|
+
if (t[2]) _.ops.pop();
|
|
45
|
+
_.trys.pop(); continue;
|
|
46
|
+
}
|
|
47
|
+
op = body.call(thisArg, _);
|
|
48
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
49
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
import axios from 'axios';
|
|
53
|
+
import nacl from 'tweetnacl';
|
|
54
|
+
import { decodeBase64, decodeUTF8, encodeUTF8 } from 'tweetnacl-util';
|
|
55
|
+
import { areAttachmentFieldIdsValid, convertEncryptedAttachmentToFileContent, decryptContent, encryptMessage, verifySignedMessage, } from './util/crypto';
|
|
56
|
+
import { determineIsFormFields } from './util/validate';
|
|
57
|
+
import CryptoBase from './crypto-base';
|
|
58
|
+
import { AttachmentDecryptionError, MissingPublicKeyError } from './errors';
|
|
59
|
+
var Crypto = /** @class */ (function (_super) {
|
|
60
|
+
__extends(Crypto, _super);
|
|
61
|
+
function Crypto(_a) {
|
|
62
|
+
var _b = _a === void 0 ? {} : _a, signingPublicKey = _b.signingPublicKey;
|
|
63
|
+
var _this = _super.call(this) || this;
|
|
64
|
+
/**
|
|
65
|
+
* Encrypt input with a unique keypair for each submission
|
|
66
|
+
* @param encryptionPublicKey The base-64 encoded public key for encrypting.
|
|
67
|
+
* @param msg The message to encrypt, will be stringified.
|
|
68
|
+
* @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.
|
|
69
|
+
* @returns The encrypted basestring.
|
|
70
|
+
*/
|
|
71
|
+
_this.encrypt = function (msg, encryptionPublicKey, signingPrivateKey) {
|
|
72
|
+
var processedMsg = decodeUTF8(JSON.stringify(msg));
|
|
73
|
+
if (signingPrivateKey) {
|
|
74
|
+
processedMsg = nacl.sign(processedMsg, decodeBase64(signingPrivateKey));
|
|
75
|
+
}
|
|
76
|
+
return encryptMessage(processedMsg, encryptionPublicKey);
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Decrypts an encrypted submission and returns it.
|
|
80
|
+
* @param formSecretKey The base-64 secret key of the form to decrypt with.
|
|
81
|
+
* @param decryptParams The params containing encrypted content and information.
|
|
82
|
+
* @param decryptParams.encryptedContent The encrypted content encoded with base-64.
|
|
83
|
+
* @param decryptParams.version The version of the payload. Used to determine the decryption process to decrypt the content with.
|
|
84
|
+
* @param decryptParams.verifiedContent Optional. The encrypted and signed verified content. If given, the signingPublicKey will be used to attempt to open the signed message.
|
|
85
|
+
* @returns The decrypted content if successful. Else, null will be returned.
|
|
86
|
+
* @throws {MissingPublicKeyError} if a public key is not provided when instantiating this class and is needed for verifying signed content.
|
|
87
|
+
*/
|
|
88
|
+
_this.decrypt = function (formSecretKey, decryptParams) {
|
|
89
|
+
try {
|
|
90
|
+
var encryptedContent = decryptParams.encryptedContent, verifiedContent = decryptParams.verifiedContent;
|
|
91
|
+
// Do not return the transformed object in `_decrypt` function as a signed
|
|
92
|
+
// object is not encoded in UTF8 and is encoded in Base-64 instead.
|
|
93
|
+
var decryptedContent = decryptContent(formSecretKey, encryptedContent);
|
|
94
|
+
if (!decryptedContent) {
|
|
95
|
+
throw new Error('Failed to decrypt content');
|
|
96
|
+
}
|
|
97
|
+
var decryptedObject = JSON.parse(encodeUTF8(decryptedContent));
|
|
98
|
+
if (!determineIsFormFields(decryptedObject)) {
|
|
99
|
+
throw new Error('Decrypted object does not fit expected shape');
|
|
100
|
+
}
|
|
101
|
+
var returnedObject = {
|
|
102
|
+
responses: decryptedObject,
|
|
103
|
+
};
|
|
104
|
+
if (verifiedContent) {
|
|
105
|
+
if (!_this.signingPublicKey) {
|
|
106
|
+
throw new MissingPublicKeyError('Public signing key must be provided when instantiating the Crypto class in order to verify verified content');
|
|
107
|
+
}
|
|
108
|
+
// Only care if it is the correct shape if verifiedContent exists, since
|
|
109
|
+
// we need to append it to the end.
|
|
110
|
+
// Decrypted message must be able to be authenticated by the public key.
|
|
111
|
+
var decryptedVerifiedContent = decryptContent(formSecretKey, verifiedContent);
|
|
112
|
+
if (!decryptedVerifiedContent) {
|
|
113
|
+
// Returns null if decrypting verified content failed.
|
|
114
|
+
throw new Error('Failed to decrypt verified content');
|
|
115
|
+
}
|
|
116
|
+
var decryptedVerifiedObject = verifySignedMessage(decryptedVerifiedContent, _this.signingPublicKey);
|
|
117
|
+
returnedObject.verified = decryptedVerifiedObject;
|
|
118
|
+
}
|
|
119
|
+
return returnedObject;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
// Should only throw if MissingPublicKeyError.
|
|
123
|
+
// This library should be able to be used to encrypt and decrypt content
|
|
124
|
+
// if the content does not contain verified fields.
|
|
125
|
+
if (err instanceof MissingPublicKeyError) {
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Returns true if a pair of public & secret keys are associated with each other
|
|
133
|
+
* @param publicKey The public key to verify against.
|
|
134
|
+
* @param secretKey The private key to verify against.
|
|
135
|
+
*/
|
|
136
|
+
_this.valid = function (publicKey, secretKey) {
|
|
137
|
+
var _a;
|
|
138
|
+
var testResponse = [];
|
|
139
|
+
var internalValidationVersion = 1;
|
|
140
|
+
var cipherResponse = _this.encrypt(testResponse, publicKey);
|
|
141
|
+
// Use toString here since the return should be an empty array.
|
|
142
|
+
return (testResponse.toString() ===
|
|
143
|
+
((_a = _this.decrypt(secretKey, {
|
|
144
|
+
encryptedContent: cipherResponse,
|
|
145
|
+
version: internalValidationVersion,
|
|
146
|
+
})) === null || _a === void 0 ? void 0 : _a.responses.toString()));
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Decrypts an encrypted submission, and also download and decrypt any attachments alongside it.
|
|
150
|
+
* @param formSecretKey Secret key as a base-64 string
|
|
151
|
+
* @param decryptParams The params containing encrypted content and information.
|
|
152
|
+
* @returns A promise of the decrypted submission, including attachments (if any). Or else returns null if a decryption error decrypting any part of the submission.
|
|
153
|
+
* @throws {MissingPublicKeyError} if a public key is not provided when instantiating this class and is needed for verifying signed content.
|
|
154
|
+
*/
|
|
155
|
+
_this.decryptWithAttachments = function (formSecretKey, decryptParams) { return __awaiter(_this, void 0, void 0, function () {
|
|
156
|
+
var decryptedRecords, filenames, attachmentRecords, decryptedContent, fieldIds, downloadPromises, _a;
|
|
157
|
+
var _this = this;
|
|
158
|
+
var _b;
|
|
159
|
+
return __generator(this, function (_c) {
|
|
160
|
+
switch (_c.label) {
|
|
161
|
+
case 0:
|
|
162
|
+
decryptedRecords = {};
|
|
163
|
+
filenames = {};
|
|
164
|
+
attachmentRecords = (_b = decryptParams.attachmentDownloadUrls) !== null && _b !== void 0 ? _b : {};
|
|
165
|
+
decryptedContent = this.decrypt(formSecretKey, decryptParams);
|
|
166
|
+
if (decryptedContent === null)
|
|
167
|
+
return [2 /*return*/, null
|
|
168
|
+
// Retrieve all original filenames for attachments for easy lookup
|
|
169
|
+
];
|
|
170
|
+
// Retrieve all original filenames for attachments for easy lookup
|
|
171
|
+
decryptedContent.responses.forEach(function (response) {
|
|
172
|
+
if (response.fieldType === 'attachment' && response.answer) {
|
|
173
|
+
filenames[response._id] = response.answer;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
fieldIds = Object.keys(attachmentRecords);
|
|
177
|
+
// Check if all fieldIds are within filenames
|
|
178
|
+
if (!areAttachmentFieldIdsValid(fieldIds, filenames)) {
|
|
179
|
+
return [2 /*return*/, null];
|
|
180
|
+
}
|
|
181
|
+
downloadPromises = fieldIds.map(function (fieldId) {
|
|
182
|
+
return (axios
|
|
183
|
+
// Retrieve all the attachments as JSON
|
|
184
|
+
.get(attachmentRecords[fieldId], {
|
|
185
|
+
responseType: 'json',
|
|
186
|
+
})
|
|
187
|
+
// Decrypt all the attachments
|
|
188
|
+
.then(function (_a) {
|
|
189
|
+
var downloadResponse = _a.data;
|
|
190
|
+
var encryptedFile = convertEncryptedAttachmentToFileContent(downloadResponse);
|
|
191
|
+
return _this.decryptFile(formSecretKey, encryptedFile);
|
|
192
|
+
})
|
|
193
|
+
.then(function (decryptedFile) {
|
|
194
|
+
// Check if the file exists and set the filename accordingly; otherwise, throw an error
|
|
195
|
+
if (decryptedFile) {
|
|
196
|
+
decryptedRecords[fieldId] = {
|
|
197
|
+
filename: filenames[fieldId],
|
|
198
|
+
content: decryptedFile,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
throw new AttachmentDecryptionError();
|
|
203
|
+
}
|
|
204
|
+
}));
|
|
205
|
+
});
|
|
206
|
+
_c.label = 1;
|
|
207
|
+
case 1:
|
|
208
|
+
_c.trys.push([1, 3, , 4]);
|
|
209
|
+
return [4 /*yield*/, Promise.all(downloadPromises)];
|
|
210
|
+
case 2:
|
|
211
|
+
_c.sent();
|
|
212
|
+
return [3 /*break*/, 4];
|
|
213
|
+
case 3:
|
|
214
|
+
_a = _c.sent();
|
|
215
|
+
return [2 /*return*/, null];
|
|
216
|
+
case 4: return [2 /*return*/, {
|
|
217
|
+
content: decryptedContent,
|
|
218
|
+
attachments: decryptedRecords,
|
|
219
|
+
}];
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}); };
|
|
223
|
+
_this.signingPublicKey = signingPublicKey;
|
|
224
|
+
return _this;
|
|
225
|
+
}
|
|
226
|
+
return Crypto;
|
|
227
|
+
}(CryptoBase));
|
|
228
|
+
export default Crypto;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare class MissingSecretKeyError extends Error {
|
|
2
|
+
constructor(message?: string);
|
|
3
|
+
}
|
|
4
|
+
declare class MissingPublicKeyError extends Error {
|
|
5
|
+
constructor(message?: string);
|
|
6
|
+
}
|
|
7
|
+
declare class WebhookAuthenticateError extends Error {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
declare class AttachmentDecryptionError extends Error {
|
|
11
|
+
constructor(message?: string);
|
|
12
|
+
}
|
|
13
|
+
export { MissingSecretKeyError, MissingPublicKeyError, WebhookAuthenticateError, AttachmentDecryptionError, };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
var __extends = (this && this.__extends) || (function () {
|
|
2
|
+
var extendStatics = function (d, b) {
|
|
3
|
+
extendStatics = Object.setPrototypeOf ||
|
|
4
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
5
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
6
|
+
return extendStatics(d, b);
|
|
7
|
+
};
|
|
8
|
+
return function (d, b) {
|
|
9
|
+
if (typeof b !== "function" && b !== null)
|
|
10
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
11
|
+
extendStatics(d, b);
|
|
12
|
+
function __() { this.constructor = d; }
|
|
13
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
14
|
+
};
|
|
15
|
+
})();
|
|
16
|
+
var MissingSecretKeyError = /** @class */ (function (_super) {
|
|
17
|
+
__extends(MissingSecretKeyError, _super);
|
|
18
|
+
function MissingSecretKeyError(message) {
|
|
19
|
+
if (message === void 0) { message = 'Provide a secret key when initializing the FormSG SDK to use this function.'; }
|
|
20
|
+
var _this = _super.call(this, message) || this;
|
|
21
|
+
_this.name = _this.constructor.name;
|
|
22
|
+
// Set the prototype explicitly.
|
|
23
|
+
// See https://github.com/facebook/jest/issues/8279
|
|
24
|
+
Object.setPrototypeOf(_this, MissingSecretKeyError.prototype);
|
|
25
|
+
return _this;
|
|
26
|
+
}
|
|
27
|
+
return MissingSecretKeyError;
|
|
28
|
+
}(Error));
|
|
29
|
+
var MissingPublicKeyError = /** @class */ (function (_super) {
|
|
30
|
+
__extends(MissingPublicKeyError, _super);
|
|
31
|
+
function MissingPublicKeyError(message) {
|
|
32
|
+
if (message === void 0) { message = 'Provide a public key when initializing the FormSG SDK to use this function.'; }
|
|
33
|
+
var _this = _super.call(this, message) || this;
|
|
34
|
+
_this.name = _this.constructor.name;
|
|
35
|
+
// Set the prototype explicitly.
|
|
36
|
+
// See https://github.com/facebook/jest/issues/8279
|
|
37
|
+
Object.setPrototypeOf(_this, MissingPublicKeyError.prototype);
|
|
38
|
+
return _this;
|
|
39
|
+
}
|
|
40
|
+
return MissingPublicKeyError;
|
|
41
|
+
}(Error));
|
|
42
|
+
var WebhookAuthenticateError = /** @class */ (function (_super) {
|
|
43
|
+
__extends(WebhookAuthenticateError, _super);
|
|
44
|
+
function WebhookAuthenticateError(message) {
|
|
45
|
+
var _this = _super.call(this, message) || this;
|
|
46
|
+
_this.name = _this.constructor.name;
|
|
47
|
+
// Set the prototype explicitly.
|
|
48
|
+
// See https://github.com/facebook/jest/issues/8279
|
|
49
|
+
Object.setPrototypeOf(_this, WebhookAuthenticateError.prototype);
|
|
50
|
+
return _this;
|
|
51
|
+
}
|
|
52
|
+
return WebhookAuthenticateError;
|
|
53
|
+
}(Error));
|
|
54
|
+
var AttachmentDecryptionError = /** @class */ (function (_super) {
|
|
55
|
+
__extends(AttachmentDecryptionError, _super);
|
|
56
|
+
function AttachmentDecryptionError(message) {
|
|
57
|
+
if (message === void 0) { message = 'Attachment decryption with the given nonce failed.'; }
|
|
58
|
+
var _this = _super.call(this, message) || this;
|
|
59
|
+
_this.name = _this.constructor.name;
|
|
60
|
+
return _this;
|
|
61
|
+
}
|
|
62
|
+
return AttachmentDecryptionError;
|
|
63
|
+
}(Error));
|
|
64
|
+
export { MissingSecretKeyError, MissingPublicKeyError, WebhookAuthenticateError, AttachmentDecryptionError, };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type { DecryptedContent, DecryptedContentV3, DecryptParams, DecryptParamsV3, EncryptedAttachmentContent, EncryptedAttachmentRecords, EncryptedContent, EncryptedContentV3, EncryptedFileContent, FieldType, FormField, FormFieldsV3, Keypair, PackageMode, VerificationOptions, } from './types';
|
|
2
|
+
import Crypto from './crypto';
|
|
3
|
+
import CryptoV3 from './crypto-v3';
|
|
4
|
+
import { PackageInitParams } from './types';
|
|
5
|
+
import Verification from './verification';
|
|
6
|
+
import Webhooks from './webhooks';
|
|
7
|
+
/**
|
|
8
|
+
* Entrypoint into the FormSG SDK
|
|
9
|
+
*
|
|
10
|
+
* @param {PackageInitParams} config Package initialization config parameters
|
|
11
|
+
* @param {string?} [config.mode] Optional. Initializes public key used for verifying and decrypting in this package. If `config.signingPublicKey` is given, this param will be ignored.
|
|
12
|
+
* @param {string?} [config.webhookSecretKey] Optional. base64 secret key for signing webhooks. If provided, enables generating signature and headers to authenticate webhook data.
|
|
13
|
+
* @param {VerificationOptions?} [config.verificationOptions] Optional. If provided, enables the usage of the verification module.
|
|
14
|
+
*/
|
|
15
|
+
export default function (config?: PackageInitParams): {
|
|
16
|
+
webhooks: Webhooks;
|
|
17
|
+
crypto: Crypto;
|
|
18
|
+
cryptoV3: CryptoV3;
|
|
19
|
+
verification: Verification;
|
|
20
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getSigningPublicKey, getVerificationPublicKey } from './util/publicKey';
|
|
2
|
+
import Crypto from './crypto';
|
|
3
|
+
import CryptoV3 from './crypto-v3';
|
|
4
|
+
import Verification from './verification';
|
|
5
|
+
import Webhooks from './webhooks';
|
|
6
|
+
/**
|
|
7
|
+
* Entrypoint into the FormSG SDK
|
|
8
|
+
*
|
|
9
|
+
* @param {PackageInitParams} config Package initialization config parameters
|
|
10
|
+
* @param {string?} [config.mode] Optional. Initializes public key used for verifying and decrypting in this package. If `config.signingPublicKey` is given, this param will be ignored.
|
|
11
|
+
* @param {string?} [config.webhookSecretKey] Optional. base64 secret key for signing webhooks. If provided, enables generating signature and headers to authenticate webhook data.
|
|
12
|
+
* @param {VerificationOptions?} [config.verificationOptions] Optional. If provided, enables the usage of the verification module.
|
|
13
|
+
*/
|
|
14
|
+
export default function (config) {
|
|
15
|
+
if (config === void 0) { config = {}; }
|
|
16
|
+
var webhookSecretKey = config.webhookSecretKey, mode = config.mode, verificationOptions = config.verificationOptions;
|
|
17
|
+
/**
|
|
18
|
+
* Public key is used for decrypting signed verified content in the `crypto` module, and
|
|
19
|
+
* also for verifying webhook signatures' authenticity in the `wehbooks` module.
|
|
20
|
+
*/
|
|
21
|
+
var signingPublicKey = getSigningPublicKey(mode || 'production');
|
|
22
|
+
/**
|
|
23
|
+
* Public key is used for verifying verified field signatures' authenticity in the `verification` module.
|
|
24
|
+
*/
|
|
25
|
+
var verificationPublicKey = getVerificationPublicKey(mode || 'production');
|
|
26
|
+
return {
|
|
27
|
+
webhooks: new Webhooks({
|
|
28
|
+
publicKey: signingPublicKey,
|
|
29
|
+
secretKey: webhookSecretKey,
|
|
30
|
+
}),
|
|
31
|
+
crypto: new Crypto({ signingPublicKey: signingPublicKey }),
|
|
32
|
+
cryptoV3: new CryptoV3({ signingPublicKey: signingPublicKey }),
|
|
33
|
+
verification: new Verification({
|
|
34
|
+
publicKey: verificationPublicKey,
|
|
35
|
+
secretKey: verificationOptions === null || verificationOptions === void 0 ? void 0 : verificationOptions.secretKey,
|
|
36
|
+
transactionExpiry: verificationOptions === null || verificationOptions === void 0 ? void 0 : verificationOptions.transactionExpiry,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const SIGNING_KEYS: {
|
|
2
|
+
staging: {
|
|
3
|
+
publicKey: string;
|
|
4
|
+
};
|
|
5
|
+
development: {
|
|
6
|
+
publicKey: string;
|
|
7
|
+
secretKey: string;
|
|
8
|
+
};
|
|
9
|
+
production: {
|
|
10
|
+
publicKey: string;
|
|
11
|
+
};
|
|
12
|
+
test: {
|
|
13
|
+
publicKey: string;
|
|
14
|
+
secretKey: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// keys generated using nacl.sign.keyPair() from tweetnacl
|
|
2
|
+
export var SIGNING_KEYS = {
|
|
3
|
+
staging: {
|
|
4
|
+
// staging must never contain secret keys
|
|
5
|
+
publicKey: 'rjv41kYqZwcbe3r6ymMEEKQ+Vd+DPuogN+Gzq3lP2Og=',
|
|
6
|
+
},
|
|
7
|
+
development: {
|
|
8
|
+
publicKey: 'Tl5gfszlKcQj99/0uafLwVpT6JAu4C0dHGvLq1cHzFE=',
|
|
9
|
+
secretKey: 'HDBXpu+2/gu10bLHpy8HjpN89xbA6boH9GwibPGJA8BOXmB+zOUpxCP33/S5p8vBWlPokC7gLR0ca8urVwfMUQ==',
|
|
10
|
+
},
|
|
11
|
+
production: {
|
|
12
|
+
// production must never contain secret keys
|
|
13
|
+
publicKey: '3Tt8VduXsjjd4IrpdCd7BAkdZl/vUCstu9UvTX84FWw=',
|
|
14
|
+
},
|
|
15
|
+
test: {
|
|
16
|
+
publicKey: 'KUY1XT30ar+XreVjsS1w/c3EpDs2oASbF6G3evvaUJM=',
|
|
17
|
+
secretKey: '/u+LP57Ib9y5Ytpud56FzuitSC9O6lJ4EOLOFHpsHlYpRjVdPfRqv5et5WOxLXD9zcSkOzagBJsXobd6+9pQkw==',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const VERIFICATION_KEYS: {
|
|
2
|
+
staging: {
|
|
3
|
+
publicKey: string;
|
|
4
|
+
};
|
|
5
|
+
development: {
|
|
6
|
+
publicKey: string;
|
|
7
|
+
secretKey: string;
|
|
8
|
+
};
|
|
9
|
+
production: {
|
|
10
|
+
publicKey: string;
|
|
11
|
+
};
|
|
12
|
+
test: {
|
|
13
|
+
publicKey: string;
|
|
14
|
+
secretKey: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// keys generated using nacl.sign.keyPair() from tweetnacl
|
|
2
|
+
export var VERIFICATION_KEYS = {
|
|
3
|
+
staging: {
|
|
4
|
+
// staging must never contain secret keys
|
|
5
|
+
publicKey: 'bDgK1223JbrDNePFIrj7b0z02Z5nSiBzkRYRqDdVPfA=',
|
|
6
|
+
},
|
|
7
|
+
development: {
|
|
8
|
+
publicKey: 'SZ4pV0JXgj8dhFU69uHllqYcxTtliYmi+d6Ml56lnQU=',
|
|
9
|
+
secretKey: 'iGkfOuI6uxrlfw+7CZFFUZBwk86I+pu6v+g7EWA6qJpJnilXQleCPx2EVTr24eWWphzFO2WJiaL53oyXnqWdBQ==',
|
|
10
|
+
},
|
|
11
|
+
production: {
|
|
12
|
+
// production must never contain secret keys
|
|
13
|
+
publicKey: 'W/lf24kRJ9PVvSK1Ubjjhc9zHjp1amKr+3Q+Nmsy4w4=',
|
|
14
|
+
},
|
|
15
|
+
test: {
|
|
16
|
+
publicKey: 'ileDo328P/UApBPANuS/xO6P4BuHSgPnjRRBifgQYvs=',
|
|
17
|
+
secretKey: 'zLnXIV0cGjODell5w1usEHcGOJ/xsQDuDOw2BPcPEQOKV4Ojfbw/9QCkE8A25L/E7o/gG4dKA+eNFEGJ+BBi+w==',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export type PackageInitParams = {
|
|
2
|
+
/** base64 secret key for signing webhooks. If provided, enables generating signature and headers to authenticate webhook data. */
|
|
3
|
+
webhookSecretKey?: string;
|
|
4
|
+
/** If provided, enables the usage of the verification module. */
|
|
5
|
+
verificationOptions?: VerificationOptions;
|
|
6
|
+
/** Initializes public key used for verifying and decrypting in this package. If not given, will default to "production". */
|
|
7
|
+
mode?: PackageMode;
|
|
8
|
+
};
|
|
9
|
+
export type FieldType = 'section' | 'radiobutton' | 'dropdown' | 'checkbox' | 'nric' | 'email' | 'table' | 'number' | 'rating' | 'yes_no' | 'decimal' | 'textfield' | 'textarea' | 'attachment' | 'date' | 'mobile' | 'homeno' | 'statement' | 'image' | 'country_region' | 'uen' | 'children' | 'address' | 'signature';
|
|
10
|
+
export type FormField = {
|
|
11
|
+
_id: string;
|
|
12
|
+
question: string;
|
|
13
|
+
fieldType: FieldType;
|
|
14
|
+
isHeader?: boolean;
|
|
15
|
+
signature?: string;
|
|
16
|
+
} & ({
|
|
17
|
+
answer: string;
|
|
18
|
+
answerArray?: never;
|
|
19
|
+
} | {
|
|
20
|
+
answer?: never;
|
|
21
|
+
answerArray: string[] | string[][];
|
|
22
|
+
});
|
|
23
|
+
export type FormFieldsV3 = Record<string, {
|
|
24
|
+
fieldType: FieldType;
|
|
25
|
+
answer: any;
|
|
26
|
+
}>;
|
|
27
|
+
export type EncryptedContent = string;
|
|
28
|
+
export type EncryptedContentV3 = {
|
|
29
|
+
submissionPublicKey: string;
|
|
30
|
+
submissionSecretKey: string;
|
|
31
|
+
encryptedContent: EncryptedContent;
|
|
32
|
+
encryptedSubmissionSecretKey: EncryptedContent;
|
|
33
|
+
};
|
|
34
|
+
export type EncryptedAttachmentRecords = Record<string, string>;
|
|
35
|
+
export interface DecryptParams {
|
|
36
|
+
encryptedContent: EncryptedContent;
|
|
37
|
+
version: number;
|
|
38
|
+
verifiedContent?: EncryptedContent;
|
|
39
|
+
attachmentDownloadUrls?: EncryptedAttachmentRecords;
|
|
40
|
+
}
|
|
41
|
+
export interface DecryptParamsV3 {
|
|
42
|
+
encryptedContent: EncryptedContent;
|
|
43
|
+
encryptedSubmissionSecretKey: EncryptedContent;
|
|
44
|
+
verifiedContent?: EncryptedContent;
|
|
45
|
+
version: number;
|
|
46
|
+
}
|
|
47
|
+
export type DecryptedContent = {
|
|
48
|
+
responses: FormField[];
|
|
49
|
+
verified?: Record<string, any>;
|
|
50
|
+
};
|
|
51
|
+
export type DecryptedContentV3 = {
|
|
52
|
+
submissionSecretKey: string;
|
|
53
|
+
responses: FormFieldsV3;
|
|
54
|
+
verified?: Record<string, any>;
|
|
55
|
+
};
|
|
56
|
+
export type DecryptedFile = {
|
|
57
|
+
filename: string;
|
|
58
|
+
content: Uint8Array;
|
|
59
|
+
};
|
|
60
|
+
export type DecryptedAttachments = Record<string, DecryptedFile>;
|
|
61
|
+
export type DecryptedContentAndAttachments = {
|
|
62
|
+
content: DecryptedContent;
|
|
63
|
+
attachments: DecryptedAttachments;
|
|
64
|
+
};
|
|
65
|
+
export type EncryptedFileContent = {
|
|
66
|
+
submissionPublicKey: string;
|
|
67
|
+
nonce: string;
|
|
68
|
+
binary: Uint8Array;
|
|
69
|
+
};
|
|
70
|
+
export type EncryptedAttachmentContent = {
|
|
71
|
+
encryptedFile: {
|
|
72
|
+
submissionPublicKey: string;
|
|
73
|
+
nonce: string;
|
|
74
|
+
binary: string;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
export type Keypair = {
|
|
78
|
+
publicKey: string;
|
|
79
|
+
secretKey: string;
|
|
80
|
+
};
|
|
81
|
+
export type PackageMode = 'staging' | 'production' | 'development' | 'test';
|
|
82
|
+
export type VerificationOptions = {
|
|
83
|
+
publicKey?: string;
|
|
84
|
+
secretKey?: string;
|
|
85
|
+
transactionExpiry?: number;
|
|
86
|
+
};
|
|
87
|
+
export type VerifiedAnswer = {
|
|
88
|
+
fieldId: string;
|
|
89
|
+
answer: string;
|
|
90
|
+
};
|
|
91
|
+
export type VerificationSignatureOptions = VerifiedAnswer & {
|
|
92
|
+
transactionId: string;
|
|
93
|
+
formId: string;
|
|
94
|
+
};
|
|
95
|
+
export type VerificationBasestringOptions = VerificationSignatureOptions & {
|
|
96
|
+
time: number;
|
|
97
|
+
};
|
|
98
|
+
export type VerificationAuthenticateOptions = VerifiedAnswer & {
|
|
99
|
+
signatureString: string;
|
|
100
|
+
submissionCreatedAt: number;
|
|
101
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EncryptedAttachmentContent, EncryptedContent, EncryptedFileContent, Keypair } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Helper method to generate a new keypair for encryption.
|
|
4
|
+
* @returns The generated keypair.
|
|
5
|
+
*/
|
|
6
|
+
export declare const generateKeypair: () => Keypair;
|
|
7
|
+
/**
|
|
8
|
+
* Helper function to encrypt input with a unique keypair for each submission.
|
|
9
|
+
* @param msg The message to encrypt
|
|
10
|
+
* @param theirPublicKey The base-64 encoded public key
|
|
11
|
+
* @returns The encrypted basestring
|
|
12
|
+
* @throws error if any of the encrypt methods fail
|
|
13
|
+
*/
|
|
14
|
+
export declare const encryptMessage: (msg: Uint8Array, theirPublicKey: string) => EncryptedContent;
|
|
15
|
+
/**
|
|
16
|
+
* Helper method to decrypt an encrypted submission.
|
|
17
|
+
* @param formPrivateKey base64
|
|
18
|
+
* @param encryptedContent encrypted string encoded in base64
|
|
19
|
+
* @return The decrypted content, or null if decryption failed.
|
|
20
|
+
*/
|
|
21
|
+
export declare const decryptContent: (formPrivateKey: string, encryptedContent: EncryptedContent) => Uint8Array | null;
|
|
22
|
+
/**
|
|
23
|
+
* Helper method to verify a signed message.
|
|
24
|
+
* @param msg the message to verify
|
|
25
|
+
* @param publicKey the public key to authenticate the signed message with
|
|
26
|
+
* @returns the signed message if successful, else an error will be thrown
|
|
27
|
+
* @throws {Error} if the message cannot be verified
|
|
28
|
+
*/
|
|
29
|
+
export declare const verifySignedMessage: (msg: Uint8Array, publicKey: string) => Record<string, any>;
|
|
30
|
+
/**
|
|
31
|
+
* Helper method to check if all the field IDs given are within the filenames
|
|
32
|
+
* @param fieldIds the list of fieldIds to check
|
|
33
|
+
* @param filenames the filenames that should contain the fields
|
|
34
|
+
* @returns boolean indicating whether the fields are valid
|
|
35
|
+
*/
|
|
36
|
+
export declare const areAttachmentFieldIdsValid: (fieldIds: string[], filenames: Record<string, string>) => boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Converts an encrypted attachment to encrypted file content
|
|
39
|
+
* @param encryptedAttachment The encrypted attachment
|
|
40
|
+
* @returns EncryptedFileContent The encrypted file content
|
|
41
|
+
*/
|
|
42
|
+
export declare const convertEncryptedAttachmentToFileContent: (encryptedAttachment: EncryptedAttachmentContent) => EncryptedFileContent;
|