@opengovsg/formsg-sdk 7.4.1 → 7.5.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.
@@ -1,5 +1,11 @@
1
1
  import { FormFieldsV3 } from './types';
2
- import { AdaptV3ToV4Options, FieldResponsesV4 } from './types-v4';
2
+ import { AdaptV3ToV4Options, FieldResponsesV4, FormFieldMeta } from './types-v4';
3
+ /**
4
+ * Derives question text from form field metadata, prefixing MyInfo fields.
5
+ * Shared between V3→V4 adaptation and the V4 question backfill in
6
+ * CryptoV3.decryptToV4 so the two stay consistent.
7
+ */
8
+ export declare const deriveQuestionFromMeta: (meta?: FormFieldMeta) => string;
3
9
  /**
4
10
  * Converts V3 decrypted responses to V4 normalized shape.
5
11
  *
@@ -11,6 +11,7 @@ var __assign = (this && this.__assign) || function () {
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.deriveQuestionFromMeta = void 0;
14
15
  exports.adaptV3ToV4 = adaptV3ToV4;
15
16
  var constants_v4_1 = require("./constants-v4");
16
17
  var crypto_1 = require("./util/crypto");
@@ -81,6 +82,13 @@ var convertSignatureAnswer = function (answer) {
81
82
  type: 'draw',
82
83
  };
83
84
  };
85
+ /**
86
+ * Derives question text from form field metadata, prefixing MyInfo fields.
87
+ * Shared between V3→V4 adaptation and the V4 question backfill in
88
+ * CryptoV3.decryptToV4 so the two stay consistent.
89
+ */
90
+ var deriveQuestionFromMeta = function (meta) { var _a, _b; return (meta === null || meta === void 0 ? void 0 : meta.myInfo) ? "[Myinfo] ".concat((_a = meta.question) !== null && _a !== void 0 ? _a : '') : ((_b = meta === null || meta === void 0 ? void 0 : meta.question) !== null && _b !== void 0 ? _b : ''); };
91
+ exports.deriveQuestionFromMeta = deriveQuestionFromMeta;
84
92
  // since v3 answer types when decrypted in sdk are not well-typed, we do best-effort conversion based on field type
85
93
  var convertAnswer = function (fieldType, answer) {
86
94
  if (constants_v4_1.GENERIC_STRING_FIELD_TYPES.has(fieldType)) {
@@ -119,20 +127,18 @@ var convertAnswer = function (fieldType, answer) {
119
127
  * @returns V4 responses record
120
128
  */
121
129
  function adaptV3ToV4(v3Responses, options) {
122
- var _a, _b, _c, _d;
130
+ var _a, _b;
123
131
  if (options === void 0) { options = {}; }
124
132
  var provenance = (_a = options.provenance) !== null && _a !== void 0 ? _a : {
125
133
  submittedAt: new Date().toISOString(),
126
134
  };
127
135
  var formFields = (_b = options.formFields) !== null && _b !== void 0 ? _b : {};
128
136
  var v4Responses = {};
129
- for (var _i = 0, _e = Object.entries(v3Responses); _i < _e.length; _i++) {
130
- var _f = _e[_i], fieldId = _f[0], field = _f[1];
137
+ for (var _i = 0, _c = Object.entries(v3Responses); _i < _c.length; _i++) {
138
+ var _d = _c[_i], fieldId = _d[0], field = _d[1];
131
139
  var meta = formFields[fieldId];
132
140
  var myInfo = meta === null || meta === void 0 ? void 0 : meta.myInfo;
133
- var question = myInfo
134
- ? "[Myinfo] ".concat((_c = meta === null || meta === void 0 ? void 0 : meta.question) !== null && _c !== void 0 ? _c : '')
135
- : ((_d = meta === null || meta === void 0 ? void 0 : meta.question) !== null && _d !== void 0 ? _d : '');
141
+ var question = (0, exports.deriveQuestionFromMeta)(meta);
136
142
  v4Responses[fieldId] = __assign({ fieldType: field.fieldType, question: question, answer: convertAnswer(field.fieldType, field.answer), provenance: provenance }, (myInfo && { myInfo: myInfo }));
137
143
  }
138
144
  return v4Responses;
@@ -40,7 +40,8 @@ export default class CryptoV3 extends CryptoBase {
40
40
  decrypt: (formSecretKey: string, decryptParams: DecryptParamsV3) => DecryptedContentV3 | null;
41
41
  /**
42
42
  * Decrypts an encrypted submission and returns it in V4 format.
43
- * If the decrypted data is already in V4 format, it is returned directly.
43
+ * If the decrypted data is already in V4 format, it is returned with any
44
+ * missing question text backfilled from the provided formFields metadata.
44
45
  * If the decrypted data is in V3 format, it is adapted to V4 using the provided formFields metadata.
45
46
  * @param formSecretKey The base-64 encoded form secret key for decrypting the submission.
46
47
  * @param decryptParams The params containing encrypted content, encrypted submission key and information.
@@ -58,6 +58,30 @@ function isFieldResponsesV4(responses) {
58
58
  var first = entries[0];
59
59
  return typeof first.provenance === 'object' && first.provenance !== null;
60
60
  }
61
+ /**
62
+ * Backfills question text (and myInfo metadata) on V4 responses stored
63
+ * without them, in place. V4 blobs written from wire bodies lack question
64
+ * text: the receiver shim adapts V3 bodies without the form definition in
65
+ * scope, and thin V4 wire bodies omit `question` entirely (the server strips
66
+ * it). Enrich empty questions from the form definition at read time instead.
67
+ * Responses that already carry a question are left untouched.
68
+ */
69
+ function backfillMissingQuestions(responses, formFields) {
70
+ for (var _i = 0, _a = Object.entries(responses); _i < _a.length; _i++) {
71
+ var _b = _a[_i], fieldId = _b[0], response = _b[1];
72
+ if (response.question)
73
+ continue;
74
+ // Tolerate an omitted formFields map from untyped (JS) callers
75
+ var meta = formFields === null || formFields === void 0 ? void 0 : formFields[fieldId];
76
+ if (!meta)
77
+ continue;
78
+ response.question = (0, adapt_v3_to_v4_1.deriveQuestionFromMeta)(meta);
79
+ if (meta.myInfo && !response.myInfo) {
80
+ response.myInfo = meta.myInfo;
81
+ }
82
+ }
83
+ return responses;
84
+ }
61
85
  var CryptoV3 = /** @class */ (function (_super) {
62
86
  __extends(CryptoV3, _super);
63
87
  function CryptoV3(_a) {
@@ -167,7 +191,8 @@ var CryptoV3 = /** @class */ (function (_super) {
167
191
  };
168
192
  /**
169
193
  * Decrypts an encrypted submission and returns it in V4 format.
170
- * If the decrypted data is already in V4 format, it is returned directly.
194
+ * If the decrypted data is already in V4 format, it is returned with any
195
+ * missing question text backfilled from the provided formFields metadata.
171
196
  * If the decrypted data is in V3 format, it is adapted to V4 using the provided formFields metadata.
172
197
  * @param formSecretKey The base-64 encoded form secret key for decrypting the submission.
173
198
  * @param decryptParams The params containing encrypted content, encrypted submission key and information.
@@ -179,9 +204,10 @@ var CryptoV3 = /** @class */ (function (_super) {
179
204
  var decrypted = _this.decrypt(formSecretKey, decryptParams);
180
205
  if (!decrypted)
181
206
  return null;
182
- // If the decrypted responses are already in V4 format, return directly
207
+ // If the decrypted responses are already in V4 format, return them with
208
+ // any missing question text backfilled from the form definition.
183
209
  var responses = isFieldResponsesV4(decrypted.responses)
184
- ? decrypted.responses
210
+ ? backfillMissingQuestions(decrypted.responses, formFields)
185
211
  : (0, adapt_v3_to_v4_1.adaptV3ToV4)(decrypted.responses, { formFields: formFields });
186
212
  return {
187
213
  submissionSecretKey: decrypted.submissionSecretKey,
@@ -1,5 +1,11 @@
1
1
  import { FormFieldsV3 } from './types';
2
- import { AdaptV3ToV4Options, FieldResponsesV4 } from './types-v4';
2
+ import { AdaptV3ToV4Options, FieldResponsesV4, FormFieldMeta } from './types-v4';
3
+ /**
4
+ * Derives question text from form field metadata, prefixing MyInfo fields.
5
+ * Shared between V3→V4 adaptation and the V4 question backfill in
6
+ * CryptoV3.decryptToV4 so the two stay consistent.
7
+ */
8
+ export declare const deriveQuestionFromMeta: (meta?: FormFieldMeta) => string;
3
9
  /**
4
10
  * Converts V3 decrypted responses to V4 normalized shape.
5
11
  *
@@ -78,6 +78,12 @@ var convertSignatureAnswer = function (answer) {
78
78
  type: 'draw',
79
79
  };
80
80
  };
81
+ /**
82
+ * Derives question text from form field metadata, prefixing MyInfo fields.
83
+ * Shared between V3→V4 adaptation and the V4 question backfill in
84
+ * CryptoV3.decryptToV4 so the two stay consistent.
85
+ */
86
+ export var deriveQuestionFromMeta = function (meta) { var _a, _b; return (meta === null || meta === void 0 ? void 0 : meta.myInfo) ? "[Myinfo] ".concat((_a = meta.question) !== null && _a !== void 0 ? _a : '') : ((_b = meta === null || meta === void 0 ? void 0 : meta.question) !== null && _b !== void 0 ? _b : ''); };
81
87
  // since v3 answer types when decrypted in sdk are not well-typed, we do best-effort conversion based on field type
82
88
  var convertAnswer = function (fieldType, answer) {
83
89
  if (GENERIC_STRING_FIELD_TYPES.has(fieldType)) {
@@ -116,20 +122,18 @@ var convertAnswer = function (fieldType, answer) {
116
122
  * @returns V4 responses record
117
123
  */
118
124
  export function adaptV3ToV4(v3Responses, options) {
119
- var _a, _b, _c, _d;
125
+ var _a, _b;
120
126
  if (options === void 0) { options = {}; }
121
127
  var provenance = (_a = options.provenance) !== null && _a !== void 0 ? _a : {
122
128
  submittedAt: new Date().toISOString(),
123
129
  };
124
130
  var formFields = (_b = options.formFields) !== null && _b !== void 0 ? _b : {};
125
131
  var v4Responses = {};
126
- for (var _i = 0, _e = Object.entries(v3Responses); _i < _e.length; _i++) {
127
- var _f = _e[_i], fieldId = _f[0], field = _f[1];
132
+ for (var _i = 0, _c = Object.entries(v3Responses); _i < _c.length; _i++) {
133
+ var _d = _c[_i], fieldId = _d[0], field = _d[1];
128
134
  var meta = formFields[fieldId];
129
135
  var myInfo = meta === null || meta === void 0 ? void 0 : meta.myInfo;
130
- var question = myInfo
131
- ? "[Myinfo] ".concat((_c = meta === null || meta === void 0 ? void 0 : meta.question) !== null && _c !== void 0 ? _c : '')
132
- : ((_d = meta === null || meta === void 0 ? void 0 : meta.question) !== null && _d !== void 0 ? _d : '');
136
+ var question = deriveQuestionFromMeta(meta);
133
137
  v4Responses[fieldId] = __assign({ fieldType: field.fieldType, question: question, answer: convertAnswer(field.fieldType, field.answer), provenance: provenance }, (myInfo && { myInfo: myInfo }));
134
138
  }
135
139
  return v4Responses;
@@ -40,7 +40,8 @@ export default class CryptoV3 extends CryptoBase {
40
40
  decrypt: (formSecretKey: string, decryptParams: DecryptParamsV3) => DecryptedContentV3 | null;
41
41
  /**
42
42
  * Decrypts an encrypted submission and returns it in V4 format.
43
- * If the decrypted data is already in V4 format, it is returned directly.
43
+ * If the decrypted data is already in V4 format, it is returned with any
44
+ * missing question text backfilled from the provided formFields metadata.
44
45
  * If the decrypted data is in V3 format, it is adapted to V4 using the provided formFields metadata.
45
46
  * @param formSecretKey The base-64 encoded form secret key for decrypting the submission.
46
47
  * @param decryptParams The params containing encrypted content, encrypted submission key and information.
@@ -38,7 +38,7 @@ var __rest = (this && this.__rest) || function (s, e) {
38
38
  import { decodeBase64, decodeUTF8, encodeBase64, encodeUTF8, } from 'tweetnacl-util';
39
39
  import { decryptContent, encryptMessage, generateKeypair, verifySignedMessage, } from './util/crypto';
40
40
  import { determineIsFormFieldsV3 } from './util/validate';
41
- import { adaptV3ToV4 } from './adapt-v3-to-v4';
41
+ import { adaptV3ToV4, deriveQuestionFromMeta } from './adapt-v3-to-v4';
42
42
  import CryptoBase from './crypto-base';
43
43
  import { MissingPublicKeyError } from './errors';
44
44
  /**
@@ -52,6 +52,30 @@ export function isFieldResponsesV4(responses) {
52
52
  var first = entries[0];
53
53
  return typeof first.provenance === 'object' && first.provenance !== null;
54
54
  }
55
+ /**
56
+ * Backfills question text (and myInfo metadata) on V4 responses stored
57
+ * without them, in place. V4 blobs written from wire bodies lack question
58
+ * text: the receiver shim adapts V3 bodies without the form definition in
59
+ * scope, and thin V4 wire bodies omit `question` entirely (the server strips
60
+ * it). Enrich empty questions from the form definition at read time instead.
61
+ * Responses that already carry a question are left untouched.
62
+ */
63
+ function backfillMissingQuestions(responses, formFields) {
64
+ for (var _i = 0, _a = Object.entries(responses); _i < _a.length; _i++) {
65
+ var _b = _a[_i], fieldId = _b[0], response = _b[1];
66
+ if (response.question)
67
+ continue;
68
+ // Tolerate an omitted formFields map from untyped (JS) callers
69
+ var meta = formFields === null || formFields === void 0 ? void 0 : formFields[fieldId];
70
+ if (!meta)
71
+ continue;
72
+ response.question = deriveQuestionFromMeta(meta);
73
+ if (meta.myInfo && !response.myInfo) {
74
+ response.myInfo = meta.myInfo;
75
+ }
76
+ }
77
+ return responses;
78
+ }
55
79
  var CryptoV3 = /** @class */ (function (_super) {
56
80
  __extends(CryptoV3, _super);
57
81
  function CryptoV3(_a) {
@@ -161,7 +185,8 @@ var CryptoV3 = /** @class */ (function (_super) {
161
185
  };
162
186
  /**
163
187
  * Decrypts an encrypted submission and returns it in V4 format.
164
- * If the decrypted data is already in V4 format, it is returned directly.
188
+ * If the decrypted data is already in V4 format, it is returned with any
189
+ * missing question text backfilled from the provided formFields metadata.
165
190
  * If the decrypted data is in V3 format, it is adapted to V4 using the provided formFields metadata.
166
191
  * @param formSecretKey The base-64 encoded form secret key for decrypting the submission.
167
192
  * @param decryptParams The params containing encrypted content, encrypted submission key and information.
@@ -173,9 +198,10 @@ var CryptoV3 = /** @class */ (function (_super) {
173
198
  var decrypted = _this.decrypt(formSecretKey, decryptParams);
174
199
  if (!decrypted)
175
200
  return null;
176
- // If the decrypted responses are already in V4 format, return directly
201
+ // If the decrypted responses are already in V4 format, return them with
202
+ // any missing question text backfilled from the form definition.
177
203
  var responses = isFieldResponsesV4(decrypted.responses)
178
- ? decrypted.responses
204
+ ? backfillMissingQuestions(decrypted.responses, formFields)
179
205
  : adaptV3ToV4(decrypted.responses, { formFields: formFields });
180
206
  return {
181
207
  submissionSecretKey: decrypted.submissionSecretKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengovsg/formsg-sdk",
3
- "version": "7.4.1",
3
+ "version": "7.5.0",
4
4
  "main": "./cjs-entry.cjs",
5
5
  "module": "./dist/esm/index.js",
6
6
  "types": "./dist/cjs/index.d.ts",