@opengovsg/formsg-sdk 7.6.0 → 8.0.1

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.
@@ -40,8 +40,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
40
40
  };
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
42
  var tweetnacl_1 = __importDefault(require("tweetnacl"));
43
- var tweetnacl_util_1 = require("tweetnacl-util");
44
43
  var crypto_1 = require("./util/crypto");
44
+ var encoding_1 = require("./util/encoding");
45
45
  var CryptoBase = /** @class */ (function () {
46
46
  function CryptoBase() {
47
47
  var _this = this;
@@ -66,8 +66,8 @@ var CryptoBase = /** @class */ (function () {
66
66
  //! NOTE: submissionPublicKey here is a misnomer as a new keypair is generated per file.
67
67
  // The naming is only retained for backward-compatibility purposes.
68
68
  submissionPublicKey: fileKeypair.publicKey,
69
- nonce: (0, tweetnacl_util_1.encodeBase64)(nonce),
70
- binary: tweetnacl_1.default.box(binary, nonce, (0, tweetnacl_util_1.decodeBase64)(publicKey), (0, tweetnacl_util_1.decodeBase64)(fileKeypair.secretKey)),
69
+ nonce: (0, encoding_1.encodeBase64)(nonce),
70
+ binary: tweetnacl_1.default.box(binary, nonce, (0, encoding_1.decodeBase64)(publicKey), (0, encoding_1.decodeBase64)(fileKeypair.secretKey)),
71
71
  }];
72
72
  });
73
73
  }); };
@@ -82,7 +82,7 @@ var CryptoBase = /** @class */ (function () {
82
82
  this.decryptFile = function (secretKey_1, _a) { return __awaiter(_this, [secretKey_1, _a], void 0, function (secretKey, _b) {
83
83
  var filePublicKey = _b.submissionPublicKey, nonce = _b.nonce, encryptedBinary = _b.binary;
84
84
  return __generator(this, function (_c) {
85
- return [2 /*return*/, tweetnacl_1.default.box.open(encryptedBinary, (0, tweetnacl_util_1.decodeBase64)(nonce), (0, tweetnacl_util_1.decodeBase64)(filePublicKey), (0, tweetnacl_util_1.decodeBase64)(secretKey))];
85
+ return [2 /*return*/, tweetnacl_1.default.box.open(encryptedBinary, (0, encoding_1.decodeBase64)(nonce), (0, encoding_1.decodeBase64)(filePublicKey), (0, encoding_1.decodeBase64)(secretKey))];
86
86
  });
87
87
  }); };
88
88
  }
@@ -1,6 +1,6 @@
1
1
  import CryptoBase from './crypto-base';
2
2
  import { DecryptedContentV3, DecryptParams, DecryptParamsV3, EncryptedContentV3 } from './types';
3
- import { DecryptedContentV4, FieldResponsesV4, FormFieldMeta } from './types-v4';
3
+ import { DecryptedContentV4, DecryptParamsV4, FieldResponsesV4, FormFieldMeta } from './types-v4';
4
4
  /**
5
5
  * Checks whether decrypted responses are already in V4 format.
6
6
  * V4 entries have a `provenance` object, which V3 entries lack.
@@ -49,7 +49,7 @@ export default class CryptoV3 extends CryptoBase {
49
49
  * @returns The decrypted content in V4 format if successful. Else, null will be returned.
50
50
  * @throws {MissingPublicKeyError} if a public key is not provided when instantiating this class and is needed for verifying signed content.
51
51
  */
52
- decryptToV4: (formSecretKey: string, decryptParams: DecryptParamsV3, formFields: Record<string, FormFieldMeta>) => DecryptedContentV4 | null;
52
+ decryptToV4: (formSecretKey: string, decryptParams: DecryptParamsV4, formFields: Record<string, FormFieldMeta>) => DecryptedContentV4 | null;
53
53
  /**
54
54
  * Returns true if a pair of public & secret keys are associated with each other
55
55
  * @param publicKey The public key to verify against.
@@ -41,8 +41,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
41
41
  };
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
43
  exports.isFieldResponsesV4 = isFieldResponsesV4;
44
- var tweetnacl_util_1 = require("tweetnacl-util");
45
44
  var crypto_1 = require("./util/crypto");
45
+ var encoding_1 = require("./util/encoding");
46
46
  var validate_1 = require("./util/validate");
47
47
  var adapt_v3_to_v4_1 = require("./adapt-v3-to-v4");
48
48
  var crypto_base_1 = __importDefault(require("./crypto-base"));
@@ -96,8 +96,8 @@ var CryptoV3 = /** @class */ (function (_super) {
96
96
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
97
  _this.encrypt = function (msg, formPublicKey) {
98
98
  var submissionKeypair = (0, crypto_1.generateKeypair)();
99
- var encryptedSubmissionSecretKey = (0, crypto_1.encryptMessage)((0, tweetnacl_util_1.decodeBase64)(submissionKeypair.secretKey), formPublicKey);
100
- var processedMsg = (0, tweetnacl_util_1.decodeUTF8)(JSON.stringify(msg));
99
+ var encryptedSubmissionSecretKey = (0, crypto_1.encryptMessage)((0, encoding_1.decodeBase64)(submissionKeypair.secretKey), formPublicKey);
100
+ var processedMsg = (0, encoding_1.decodeUTF8)(JSON.stringify(msg));
101
101
  var encryptedContent = (0, crypto_1.encryptMessage)(processedMsg, submissionKeypair.publicKey);
102
102
  return {
103
103
  submissionPublicKey: submissionKeypair.publicKey,
@@ -123,7 +123,7 @@ var CryptoV3 = /** @class */ (function (_super) {
123
123
  if (!decryptedContent) {
124
124
  throw new Error('Failed to decrypt content');
125
125
  }
126
- var decryptedObject = JSON.parse((0, tweetnacl_util_1.encodeUTF8)(decryptedContent));
126
+ var decryptedObject = JSON.parse((0, encoding_1.encodeUTF8)(decryptedContent));
127
127
  if (!(0, validate_1.determineIsFormFieldsV3)(decryptedObject)) {
128
128
  throw new Error('Decrypted object does not fit expected shape');
129
129
  }
@@ -179,7 +179,7 @@ var CryptoV3 = /** @class */ (function (_super) {
179
179
  var submissionSecretKey = (0, crypto_1.decryptContent)(formSecretKey, encryptedSubmissionSecretKey);
180
180
  if (submissionSecretKey === null)
181
181
  return null;
182
- return _this.decryptFromSubmissionKey((0, tweetnacl_util_1.encodeBase64)(submissionSecretKey), rest);
182
+ return _this.decryptFromSubmissionKey((0, encoding_1.encodeBase64)(submissionSecretKey), rest);
183
183
  }
184
184
  catch (err) {
185
185
  if (err instanceof errors_1.MissingPublicKeyError) {
@@ -209,8 +209,16 @@ var CryptoV3 = /** @class */ (function (_super) {
209
209
  var responses = isFieldResponsesV4(decrypted.responses)
210
210
  ? backfillMissingQuestions(decrypted.responses, formFields)
211
211
  : (0, adapt_v3_to_v4_1.adaptV3ToV4)(decrypted.responses, { formFields: formFields });
212
+ var stepToken = undefined;
213
+ if (decryptParams.encryptedStepToken) {
214
+ var decryptedStepToken = (0, crypto_1.decryptContent)(formSecretKey, decryptParams.encryptedStepToken);
215
+ if (decryptedStepToken) {
216
+ stepToken = (0, encoding_1.encodeUTF8)(decryptedStepToken);
217
+ }
218
+ }
212
219
  return {
213
220
  submissionSecretKey: decrypted.submissionSecretKey,
221
+ stepToken: stepToken,
214
222
  responses: responses,
215
223
  verified: decrypted.verified,
216
224
  };
@@ -56,8 +56,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
56
56
  Object.defineProperty(exports, "__esModule", { value: true });
57
57
  var axios_1 = __importDefault(require("axios"));
58
58
  var tweetnacl_1 = __importDefault(require("tweetnacl"));
59
- var tweetnacl_util_1 = require("tweetnacl-util");
60
59
  var crypto_1 = require("./util/crypto");
60
+ var encoding_1 = require("./util/encoding");
61
61
  var validate_1 = require("./util/validate");
62
62
  var crypto_base_1 = __importDefault(require("./crypto-base"));
63
63
  var errors_1 = require("./errors");
@@ -74,9 +74,9 @@ var Crypto = /** @class */ (function (_super) {
74
74
  * @returns The encrypted basestring.
75
75
  */
76
76
  _this.encrypt = function (msg, encryptionPublicKey, signingPrivateKey) {
77
- var processedMsg = (0, tweetnacl_util_1.decodeUTF8)(JSON.stringify(msg));
77
+ var processedMsg = (0, encoding_1.decodeUTF8)(JSON.stringify(msg));
78
78
  if (signingPrivateKey) {
79
- processedMsg = tweetnacl_1.default.sign(processedMsg, (0, tweetnacl_util_1.decodeBase64)(signingPrivateKey));
79
+ processedMsg = tweetnacl_1.default.sign(processedMsg, (0, encoding_1.decodeBase64)(signingPrivateKey));
80
80
  }
81
81
  return (0, crypto_1.encryptMessage)(processedMsg, encryptionPublicKey);
82
82
  };
@@ -99,7 +99,7 @@ var Crypto = /** @class */ (function (_super) {
99
99
  if (!decryptedContent) {
100
100
  throw new Error('Failed to decrypt content');
101
101
  }
102
- var decryptedObject = JSON.parse((0, tweetnacl_util_1.encodeUTF8)(decryptedContent));
102
+ var decryptedObject = JSON.parse((0, encoding_1.encodeUTF8)(decryptedContent));
103
103
  if (!(0, validate_1.determineIsFormFields)(decryptedObject)) {
104
104
  throw new Error('Decrypted object does not fit expected shape');
105
105
  }
@@ -7,6 +7,7 @@ export { adaptV3ToV4 } from './adapt-v3-to-v4';
7
7
  export { adaptV4ToV3 } from './adapt-v4-to-v3';
8
8
  export { isFieldResponsesV4 } from './crypto-v3';
9
9
  export { ADDRESS_SUBFIELD_KEYS, GENERIC_STRING_FIELD_TYPES, } from './constants-v4';
10
+ export { generateUUID } from './util/crypto';
10
11
  export type { DecryptedContent, DecryptedContentV3, DecryptParams, DecryptParamsV3, EncryptedAttachmentContent, EncryptedAttachmentRecords, EncryptedContent, EncryptedContentV3, EncryptedFileContent, FieldType, FormField, FormFieldsV3, Keypair, PackageMode, VerificationOptions, } from './types';
11
12
  export type { AdaptV3ToV4Options, AnswerV4, AddressAnswerV4, AttachmentAnswerV4, CheckboxAnswerV4, ChildrenAnswerV4, DecryptedContentV4, FieldResponsesV4, FieldResponseV4, FormFieldMeta, FormFieldV4, FormFieldsV4, RadioAnswerV4, ResponseProvenance, SignatureAnswerV4, StringAnswerV4, TableAnswerV4, VerifiableAnswerV4, } from './types-v4';
12
13
  /**
package/dist/cjs/index.js CHANGED
@@ -3,7 +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.GENERIC_STRING_FIELD_TYPES = exports.ADDRESS_SUBFIELD_KEYS = exports.isFieldResponsesV4 = exports.adaptV4ToV3 = exports.adaptV3ToV4 = void 0;
6
+ exports.generateUUID = exports.GENERIC_STRING_FIELD_TYPES = exports.ADDRESS_SUBFIELD_KEYS = exports.isFieldResponsesV4 = exports.adaptV4ToV3 = exports.adaptV3ToV4 = void 0;
7
7
  exports.default = default_1;
8
8
  var publicKey_1 = require("./util/publicKey");
9
9
  var crypto_1 = __importDefault(require("./crypto"));
@@ -19,6 +19,8 @@ Object.defineProperty(exports, "isFieldResponsesV4", { enumerable: true, get: fu
19
19
  var constants_v4_1 = require("./constants-v4");
20
20
  Object.defineProperty(exports, "ADDRESS_SUBFIELD_KEYS", { enumerable: true, get: function () { return constants_v4_1.ADDRESS_SUBFIELD_KEYS; } });
21
21
  Object.defineProperty(exports, "GENERIC_STRING_FIELD_TYPES", { enumerable: true, get: function () { return constants_v4_1.GENERIC_STRING_FIELD_TYPES; } });
22
+ var crypto_2 = require("./util/crypto");
23
+ Object.defineProperty(exports, "generateUUID", { enumerable: true, get: function () { return crypto_2.generateUUID; } });
22
24
  /**
23
25
  * Entrypoint into the FormSG SDK
24
26
  *
@@ -1,4 +1,4 @@
1
- import { FieldType } from './types';
1
+ import { DecryptParamsV3, EncryptedContent, FieldType } from './types';
2
2
  export type ResponseProvenance = {
3
3
  submittedAt?: string;
4
4
  stepNumber?: number;
@@ -140,8 +140,12 @@ export type AdaptV3ToV4Options = {
140
140
  /** Form field definitions keyed by field ID, used to populate question text and myInfo. */
141
141
  formFields?: Record<string, FormFieldMeta>;
142
142
  };
143
+ export interface DecryptParamsV4 extends DecryptParamsV3 {
144
+ encryptedStepToken?: EncryptedContent;
145
+ }
143
146
  export type DecryptedContentV4 = {
144
147
  submissionSecretKey: string;
148
+ stepToken?: string;
145
149
  responses: FieldResponsesV4;
146
150
  verified?: Record<string, any>;
147
151
  };
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.generateUUID = exports.convertEncryptedAttachmentToFileContent = exports.areAttachmentFieldIdsValid = exports.verifySignedMessage = exports.decryptContent = exports.encryptMessage = exports.generateKeypair = void 0;
7
7
  var tweetnacl_1 = __importDefault(require("tweetnacl"));
8
- var tweetnacl_util_1 = require("tweetnacl-util");
8
+ var encoding_1 = require("./encoding");
9
9
  /**
10
10
  * Helper method to generate a new keypair for encryption.
11
11
  * @returns The generated keypair.
@@ -13,8 +13,8 @@ var tweetnacl_util_1 = require("tweetnacl-util");
13
13
  var generateKeypair = function () {
14
14
  var kp = tweetnacl_1.default.box.keyPair();
15
15
  return {
16
- publicKey: (0, tweetnacl_util_1.encodeBase64)(kp.publicKey),
17
- secretKey: (0, tweetnacl_util_1.encodeBase64)(kp.secretKey),
16
+ publicKey: (0, encoding_1.encodeBase64)(kp.publicKey),
17
+ secretKey: (0, encoding_1.encodeBase64)(kp.secretKey),
18
18
  };
19
19
  };
20
20
  exports.generateKeypair = generateKeypair;
@@ -28,8 +28,8 @@ exports.generateKeypair = generateKeypair;
28
28
  var encryptMessage = function (msg, theirPublicKey) {
29
29
  var submissionKeypair = (0, exports.generateKeypair)();
30
30
  var nonce = tweetnacl_1.default.randomBytes(24);
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
+ var encrypted = (0, encoding_1.encodeBase64)(tweetnacl_1.default.box(msg, nonce, (0, encoding_1.decodeBase64)(theirPublicKey), (0, encoding_1.decodeBase64)(submissionKeypair.secretKey)));
32
+ return "".concat(submissionKeypair.publicKey, ";").concat((0, encoding_1.encodeBase64)(nonce), ":").concat(encrypted);
33
33
  };
34
34
  exports.encryptMessage = encryptMessage;
35
35
  /**
@@ -41,8 +41,8 @@ exports.encryptMessage = encryptMessage;
41
41
  var decryptContent = function (formPrivateKey, encryptedContent) {
42
42
  try {
43
43
  var _a = encryptedContent.split(';'), submissionPublicKey = _a[0], nonceEncrypted = _a[1];
44
- var _b = nonceEncrypted.split(':').map(tweetnacl_util_1.decodeBase64), nonce = _b[0], encrypted = _b[1];
45
- return tweetnacl_1.default.box.open(encrypted, nonce, (0, tweetnacl_util_1.decodeBase64)(submissionPublicKey), (0, tweetnacl_util_1.decodeBase64)(formPrivateKey));
44
+ var _b = nonceEncrypted.split(':').map(encoding_1.decodeBase64), nonce = _b[0], encrypted = _b[1];
45
+ return tweetnacl_1.default.box.open(encrypted, nonce, (0, encoding_1.decodeBase64)(submissionPublicKey), (0, encoding_1.decodeBase64)(formPrivateKey));
46
46
  }
47
47
  catch (err) {
48
48
  return null;
@@ -57,10 +57,10 @@ exports.decryptContent = decryptContent;
57
57
  * @throws {Error} if the message cannot be verified
58
58
  */
59
59
  var verifySignedMessage = function (msg, publicKey) {
60
- var openedMessage = tweetnacl_1.default.sign.open(msg, (0, tweetnacl_util_1.decodeBase64)(publicKey));
60
+ var openedMessage = tweetnacl_1.default.sign.open(msg, (0, encoding_1.decodeBase64)(publicKey));
61
61
  if (!openedMessage)
62
62
  throw new Error('Failed to open signed message with given public key');
63
- return JSON.parse((0, tweetnacl_util_1.encodeUTF8)(openedMessage));
63
+ return JSON.parse((0, encoding_1.encodeUTF8)(openedMessage));
64
64
  };
65
65
  exports.verifySignedMessage = verifySignedMessage;
66
66
  /**
@@ -81,7 +81,7 @@ exports.areAttachmentFieldIdsValid = areAttachmentFieldIdsValid;
81
81
  var convertEncryptedAttachmentToFileContent = function (encryptedAttachment) { return ({
82
82
  submissionPublicKey: encryptedAttachment.encryptedFile.submissionPublicKey,
83
83
  nonce: encryptedAttachment.encryptedFile.nonce,
84
- binary: (0, tweetnacl_util_1.decodeBase64)(encryptedAttachment.encryptedFile.binary),
84
+ binary: (0, encoding_1.decodeBase64)(encryptedAttachment.encryptedFile.binary),
85
85
  }); };
86
86
  exports.convertEncryptedAttachmentToFileContent = convertEncryptedAttachmentToFileContent;
87
87
  /**
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Loop-based binary <-> string conversions, replacing tweetnacl-util.
3
+ *
4
+ * tweetnacl-util's decodeBase64 validates its input with a regex over the
5
+ * entire payload; on engines with stack-bound regex backtracking (Hermes,
6
+ * older JSC/V8) this throws "Maximum call stack size exceeded" for
7
+ * multi-megabyte attachments. @stablelib decodes in a plain loop.
8
+ *
9
+ * Exported names keep tweetnacl-util's convention (decodeUTF8: string ->
10
+ * bytes, encodeUTF8: bytes -> string), which is the reverse of
11
+ * @stablelib/utf8's encode/decode naming.
12
+ */
13
+ export declare const encodeBase64: (arr: Uint8Array) => string;
14
+ export declare const decodeBase64: (s: string) => Uint8Array;
15
+ export declare const decodeUTF8: (s: string) => Uint8Array;
16
+ export declare const encodeUTF8: (arr: Uint8Array) => string;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeUTF8 = exports.decodeUTF8 = exports.decodeBase64 = exports.encodeBase64 = void 0;
4
+ var base64_1 = require("@stablelib/base64");
5
+ var utf8_1 = require("@stablelib/utf8");
6
+ /**
7
+ * Loop-based binary <-> string conversions, replacing tweetnacl-util.
8
+ *
9
+ * tweetnacl-util's decodeBase64 validates its input with a regex over the
10
+ * entire payload; on engines with stack-bound regex backtracking (Hermes,
11
+ * older JSC/V8) this throws "Maximum call stack size exceeded" for
12
+ * multi-megabyte attachments. @stablelib decodes in a plain loop.
13
+ *
14
+ * Exported names keep tweetnacl-util's convention (decodeUTF8: string ->
15
+ * bytes, encodeUTF8: bytes -> string), which is the reverse of
16
+ * @stablelib/utf8's encode/decode naming.
17
+ */
18
+ var encodeBase64 = function (arr) { return (0, base64_1.encode)(arr); };
19
+ exports.encodeBase64 = encodeBase64;
20
+ var decodeBase64 = function (s) { return (0, base64_1.decode)(s); };
21
+ exports.decodeBase64 = decodeBase64;
22
+ var decodeUTF8 = function (s) { return (0, utf8_1.encode)(s); };
23
+ exports.decodeUTF8 = decodeUTF8;
24
+ var encodeUTF8 = function (arr) { return (0, utf8_1.decode)(arr); };
25
+ exports.encodeUTF8 = encodeUTF8;
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.sign = sign;
37
37
  exports.verify = verify;
38
38
  var tweetnacl = __importStar(require("tweetnacl"));
39
- var tweetnacl_util_1 = require("tweetnacl-util");
39
+ var encoding_1 = require("./encoding");
40
40
  /**
41
41
  * Returns a signature from a basestring and secret key
42
42
  * @param basestring The data you want to sign.
@@ -44,7 +44,7 @@ var tweetnacl_util_1 = require("tweetnacl-util");
44
44
  * @return base64 encoded signature
45
45
  */
46
46
  function sign(basestring, secretKey) {
47
- return (0, tweetnacl_util_1.encodeBase64)(tweetnacl.sign.detached((0, tweetnacl_util_1.decodeUTF8)(basestring), (0, tweetnacl_util_1.decodeBase64)(secretKey)));
47
+ return (0, encoding_1.encodeBase64)(tweetnacl.sign.detached((0, encoding_1.decodeUTF8)(basestring), (0, encoding_1.decodeBase64)(secretKey)));
48
48
  }
49
49
  /**
50
50
  * Verifies a signature against a message and public key
@@ -54,5 +54,5 @@ function sign(basestring, secretKey) {
54
54
  * @return True if verification checks out, false otherwise
55
55
  */
56
56
  function verify(message, signature, publicKey) {
57
- return tweetnacl.sign.detached.verify((0, tweetnacl_util_1.decodeUTF8)(message), (0, tweetnacl_util_1.decodeBase64)(signature), (0, tweetnacl_util_1.decodeBase64)(publicKey));
57
+ return tweetnacl.sign.detached.verify((0, encoding_1.decodeUTF8)(message), (0, encoding_1.decodeBase64)(signature), (0, encoding_1.decodeBase64)(publicKey));
58
58
  }
@@ -8,8 +8,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  * @author Jean Tan
9
9
  */
10
10
  var tweetnacl_1 = __importDefault(require("tweetnacl"));
11
- var tweetnacl_util_1 = require("tweetnacl-util");
12
11
  var errors_1 = require("../errors");
12
+ var encoding_1 = require("../util/encoding");
13
13
  var parser_1 = require("../util/parser");
14
14
  var utils_1 = require("./utils");
15
15
  var Verification = /** @class */ (function () {
@@ -45,7 +45,7 @@ var Verification = /** @class */ (function () {
45
45
  answer: answer,
46
46
  time: time,
47
47
  });
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));
48
+ return tweetnacl_1.default.sign.detached.verify((0, encoding_1.decodeUTF8)(data), (0, encoding_1.decodeBase64)(signature), (0, encoding_1.decodeBase64)(_this.verificationPublicKey));
49
49
  }
50
50
  else {
51
51
  console.info("Signature was expired for signatureString=\"".concat(signatureString, "\" signatureDate=\"").concat(time, "\" submissionCreatedAt=\"").concat(submissionCreatedAt, "\""));
@@ -70,8 +70,8 @@ var Verification = /** @class */ (function () {
70
70
  answer: answer,
71
71
  time: time,
72
72
  });
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));
73
+ var signature = tweetnacl_1.default.sign.detached((0, encoding_1.decodeUTF8)(data), (0, encoding_1.decodeBase64)(_this.verificationSecretKey));
74
+ return "f=".concat(formId, ",v=").concat(transactionId, ",t=").concat(time, ",s=").concat((0, encoding_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;
@@ -35,8 +35,8 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
35
35
  }
36
36
  };
37
37
  import nacl from 'tweetnacl';
38
- import { decodeBase64, encodeBase64 } from 'tweetnacl-util';
39
38
  import { generateKeypair } from './util/crypto';
39
+ import { decodeBase64, encodeBase64 } from './util/encoding';
40
40
  var CryptoBase = /** @class */ (function () {
41
41
  function CryptoBase() {
42
42
  var _this = this;
@@ -1,6 +1,6 @@
1
1
  import CryptoBase from './crypto-base';
2
2
  import { DecryptedContentV3, DecryptParams, DecryptParamsV3, EncryptedContentV3 } from './types';
3
- import { DecryptedContentV4, FieldResponsesV4, FormFieldMeta } from './types-v4';
3
+ import { DecryptedContentV4, DecryptParamsV4, FieldResponsesV4, FormFieldMeta } from './types-v4';
4
4
  /**
5
5
  * Checks whether decrypted responses are already in V4 format.
6
6
  * V4 entries have a `provenance` object, which V3 entries lack.
@@ -49,7 +49,7 @@ export default class CryptoV3 extends CryptoBase {
49
49
  * @returns The decrypted content in V4 format if successful. Else, null will be returned.
50
50
  * @throws {MissingPublicKeyError} if a public key is not provided when instantiating this class and is needed for verifying signed content.
51
51
  */
52
- decryptToV4: (formSecretKey: string, decryptParams: DecryptParamsV3, formFields: Record<string, FormFieldMeta>) => DecryptedContentV4 | null;
52
+ decryptToV4: (formSecretKey: string, decryptParams: DecryptParamsV4, formFields: Record<string, FormFieldMeta>) => DecryptedContentV4 | null;
53
53
  /**
54
54
  * Returns true if a pair of public & secret keys are associated with each other
55
55
  * @param publicKey The public key to verify against.
@@ -35,8 +35,8 @@ var __rest = (this && this.__rest) || function (s, e) {
35
35
  }
36
36
  return t;
37
37
  };
38
- import { decodeBase64, decodeUTF8, encodeBase64, encodeUTF8, } from 'tweetnacl-util';
39
38
  import { decryptContent, encryptMessage, generateKeypair, verifySignedMessage, } from './util/crypto';
39
+ import { decodeBase64, decodeUTF8, encodeBase64, encodeUTF8, } from './util/encoding';
40
40
  import { determineIsFormFieldsV3 } from './util/validate';
41
41
  import { adaptV3ToV4, deriveQuestionFromMeta } from './adapt-v3-to-v4';
42
42
  import CryptoBase from './crypto-base';
@@ -203,8 +203,16 @@ var CryptoV3 = /** @class */ (function (_super) {
203
203
  var responses = isFieldResponsesV4(decrypted.responses)
204
204
  ? backfillMissingQuestions(decrypted.responses, formFields)
205
205
  : adaptV3ToV4(decrypted.responses, { formFields: formFields });
206
+ var stepToken = undefined;
207
+ if (decryptParams.encryptedStepToken) {
208
+ var decryptedStepToken = decryptContent(formSecretKey, decryptParams.encryptedStepToken);
209
+ if (decryptedStepToken) {
210
+ stepToken = encodeUTF8(decryptedStepToken);
211
+ }
212
+ }
206
213
  return {
207
214
  submissionSecretKey: decrypted.submissionSecretKey,
215
+ stepToken: stepToken,
208
216
  responses: responses,
209
217
  verified: decrypted.verified,
210
218
  };
@@ -51,8 +51,8 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
51
51
  };
52
52
  import axios from 'axios';
53
53
  import nacl from 'tweetnacl';
54
- import { decodeBase64, decodeUTF8, encodeUTF8 } from 'tweetnacl-util';
55
54
  import { areAttachmentFieldIdsValid, convertEncryptedAttachmentToFileContent, decryptContent, encryptMessage, verifySignedMessage, } from './util/crypto';
55
+ import { decodeBase64, decodeUTF8, encodeUTF8 } from './util/encoding';
56
56
  import { determineIsFormFields } from './util/validate';
57
57
  import CryptoBase from './crypto-base';
58
58
  import { AttachmentDecryptionError, MissingPublicKeyError } from './errors';
@@ -7,6 +7,7 @@ export { adaptV3ToV4 } from './adapt-v3-to-v4';
7
7
  export { adaptV4ToV3 } from './adapt-v4-to-v3';
8
8
  export { isFieldResponsesV4 } from './crypto-v3';
9
9
  export { ADDRESS_SUBFIELD_KEYS, GENERIC_STRING_FIELD_TYPES, } from './constants-v4';
10
+ export { generateUUID } from './util/crypto';
10
11
  export type { DecryptedContent, DecryptedContentV3, DecryptParams, DecryptParamsV3, EncryptedAttachmentContent, EncryptedAttachmentRecords, EncryptedContent, EncryptedContentV3, EncryptedFileContent, FieldType, FormField, FormFieldsV3, Keypair, PackageMode, VerificationOptions, } from './types';
11
12
  export type { AdaptV3ToV4Options, AnswerV4, AddressAnswerV4, AttachmentAnswerV4, CheckboxAnswerV4, ChildrenAnswerV4, DecryptedContentV4, FieldResponsesV4, FieldResponseV4, FormFieldMeta, FormFieldV4, FormFieldsV4, RadioAnswerV4, ResponseProvenance, SignatureAnswerV4, StringAnswerV4, TableAnswerV4, VerifiableAnswerV4, } from './types-v4';
12
13
  /**
package/dist/esm/index.js CHANGED
@@ -7,6 +7,7 @@ export { adaptV3ToV4 } from './adapt-v3-to-v4';
7
7
  export { adaptV4ToV3 } from './adapt-v4-to-v3';
8
8
  export { isFieldResponsesV4 } from './crypto-v3';
9
9
  export { ADDRESS_SUBFIELD_KEYS, GENERIC_STRING_FIELD_TYPES, } from './constants-v4';
10
+ export { generateUUID } from './util/crypto';
10
11
  /**
11
12
  * Entrypoint into the FormSG SDK
12
13
  *
@@ -1,4 +1,4 @@
1
- import { FieldType } from './types';
1
+ import { DecryptParamsV3, EncryptedContent, FieldType } from './types';
2
2
  export type ResponseProvenance = {
3
3
  submittedAt?: string;
4
4
  stepNumber?: number;
@@ -140,8 +140,12 @@ export type AdaptV3ToV4Options = {
140
140
  /** Form field definitions keyed by field ID, used to populate question text and myInfo. */
141
141
  formFields?: Record<string, FormFieldMeta>;
142
142
  };
143
+ export interface DecryptParamsV4 extends DecryptParamsV3 {
144
+ encryptedStepToken?: EncryptedContent;
145
+ }
143
146
  export type DecryptedContentV4 = {
144
147
  submissionSecretKey: string;
148
+ stepToken?: string;
145
149
  responses: FieldResponsesV4;
146
150
  verified?: Record<string, any>;
147
151
  };
@@ -1,5 +1,5 @@
1
1
  import nacl from 'tweetnacl';
2
- import { decodeBase64, encodeBase64, encodeUTF8 } from 'tweetnacl-util';
2
+ import { decodeBase64, encodeBase64, encodeUTF8 } from './encoding';
3
3
  /**
4
4
  * Helper method to generate a new keypair for encryption.
5
5
  * @returns The generated keypair.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Loop-based binary <-> string conversions, replacing tweetnacl-util.
3
+ *
4
+ * tweetnacl-util's decodeBase64 validates its input with a regex over the
5
+ * entire payload; on engines with stack-bound regex backtracking (Hermes,
6
+ * older JSC/V8) this throws "Maximum call stack size exceeded" for
7
+ * multi-megabyte attachments. @stablelib decodes in a plain loop.
8
+ *
9
+ * Exported names keep tweetnacl-util's convention (decodeUTF8: string ->
10
+ * bytes, encodeUTF8: bytes -> string), which is the reverse of
11
+ * @stablelib/utf8's encode/decode naming.
12
+ */
13
+ export declare const encodeBase64: (arr: Uint8Array) => string;
14
+ export declare const decodeBase64: (s: string) => Uint8Array;
15
+ export declare const decodeUTF8: (s: string) => Uint8Array;
16
+ export declare const encodeUTF8: (arr: Uint8Array) => string;
@@ -0,0 +1,18 @@
1
+ import { decode as base64ToBytes, encode as bytesToBase64, } from '@stablelib/base64';
2
+ import { decode as bytesToUtf8String, encode as utf8StringToBytes, } from '@stablelib/utf8';
3
+ /**
4
+ * Loop-based binary <-> string conversions, replacing tweetnacl-util.
5
+ *
6
+ * tweetnacl-util's decodeBase64 validates its input with a regex over the
7
+ * entire payload; on engines with stack-bound regex backtracking (Hermes,
8
+ * older JSC/V8) this throws "Maximum call stack size exceeded" for
9
+ * multi-megabyte attachments. @stablelib decodes in a plain loop.
10
+ *
11
+ * Exported names keep tweetnacl-util's convention (decodeUTF8: string ->
12
+ * bytes, encodeUTF8: bytes -> string), which is the reverse of
13
+ * @stablelib/utf8's encode/decode naming.
14
+ */
15
+ export var encodeBase64 = function (arr) { return bytesToBase64(arr); };
16
+ export var decodeBase64 = function (s) { return base64ToBytes(s); };
17
+ export var decodeUTF8 = function (s) { return utf8StringToBytes(s); };
18
+ export var encodeUTF8 = function (arr) { return bytesToUtf8String(arr); };
@@ -1,5 +1,5 @@
1
1
  import * as tweetnacl from 'tweetnacl';
2
- import { decodeBase64, decodeUTF8, encodeBase64 } from 'tweetnacl-util';
2
+ import { decodeBase64, decodeUTF8, encodeBase64 } from './encoding';
3
3
  /**
4
4
  * Returns a signature from a basestring and secret key
5
5
  * @param basestring The data you want to sign.
@@ -3,8 +3,8 @@
3
3
  * @author Jean Tan
4
4
  */
5
5
  import nacl from 'tweetnacl';
6
- import { decodeBase64, decodeUTF8, encodeBase64 } from 'tweetnacl-util';
7
6
  import { MissingPublicKeyError, MissingSecretKeyError } from '../errors';
7
+ import { decodeBase64, decodeUTF8, encodeBase64 } from '../util/encoding';
8
8
  import { parseVerificationSignature } from '../util/parser';
9
9
  import { formatToBaseString, isSignatureTimeValid } from './utils';
10
10
  var Verification = /** @class */ (function () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengovsg/formsg-sdk",
3
- "version": "7.6.0",
3
+ "version": "8.0.1",
4
4
  "main": "./cjs-entry.cjs",
5
5
  "module": "./dist/esm/index.js",
6
6
  "types": "./dist/cjs/index.d.ts",
@@ -65,9 +65,10 @@
65
65
  "directory": "packages/sdk"
66
66
  },
67
67
  "dependencies": {
68
+ "@stablelib/base64": "^1.0.1",
69
+ "@stablelib/utf8": "^1.0.2",
68
70
  "axios": "^1.15.2",
69
- "tweetnacl": "^1.0.3",
70
- "tweetnacl-util": "^0.15.1"
71
+ "tweetnacl": "^1.0.3"
71
72
  },
72
73
  "devDependencies": {
73
74
  "@types/jest": "^29.5.8",