@couch-kit/core 0.10.1 → 0.10.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @couch-kit/core
2
2
 
3
+ ## 0.10.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#225](https://github.com/faluciano/react-native-couch-kit/pull/225) [`072bc40`](https://github.com/faluciano/react-native-couch-kit/commit/072bc40f923dd9169f2bcb344deb733223402323) Thanks [@faluciano](https://github.com/faluciano)! - **Security:** `actionValidator` drops an action whose validator throws. Before, the middleware error boundary passed it on to the reducer, so a malformed payload (exactly what makes a validator like `(a) => a.payload.points > 0` throw) skipped validation. `isValidSecret` now also rejects secrets longer than 128 hex characters (`MAX_SECRET_HEX_LENGTH`), so a client cannot make the host hash and store an arbitrarily long string.
8
+
3
9
  ## 0.10.1
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -84,7 +84,7 @@ actionLogger({ collapsed: false }); // expanded groups
84
84
 
85
85
  #### `actionValidator(schema)`
86
86
 
87
- Validates actions against a schema before they reach the reducer. If a validator returns `false`, the action is dropped and a warning is logged.
87
+ Validates actions against a schema before they reach the reducer. If a validator returns `false` or throws, the action is dropped and a warning is logged.
88
88
 
89
89
  ```typescript
90
90
  import { actionValidator, type ActionSchema } from "@couch-kit/core";
@@ -188,7 +188,7 @@ try {
188
188
 
189
189
  #### `isValidSecret(secret: string): boolean`
190
190
 
191
- Validates that a string is a valid UUID format (32+ hex characters, with or without dashes).
191
+ Validates that a string is a valid UUID format (32 to 128 hex characters, with or without dashes).
192
192
 
193
193
  #### `derivePlayerId(secret: string): Promise<string>`
194
194
 
package/dist/index.cjs CHANGED
@@ -52,6 +52,7 @@ __export(exports_src, {
52
52
  KEEPALIVE_TIMEOUT: () => KEEPALIVE_TIMEOUT,
53
53
  MAX_FRAME_SIZE: () => MAX_FRAME_SIZE,
54
54
  MAX_PENDING_PINGS: () => MAX_PENDING_PINGS,
55
+ MAX_SECRET_HEX_LENGTH: () => MAX_SECRET_HEX_LENGTH,
55
56
  MAX_SYNC_INTERVAL: () => MAX_SYNC_INTERVAL,
56
57
  MessageTypes: () => MessageTypes,
57
58
  SYNC_BACKOFF_FACTOR: () => SYNC_BACKOFF_FACTOR,
@@ -142,7 +143,15 @@ function actionValidator(schema) {
142
143
  if (getInternalTypes().has(action.type))
143
144
  return next(action);
144
145
  const validate = schema[action.type];
145
- if (validate && !validate(action)) {
146
+ if (!validate)
147
+ return next(action);
148
+ let valid;
149
+ try {
150
+ valid = validate(action);
151
+ } catch {
152
+ valid = false;
153
+ }
154
+ if (!valid) {
146
155
  console.warn(`[couch-kit] Action "${action.type}" failed validation, skipping`);
147
156
  return _api.getState();
148
157
  }
@@ -399,9 +408,12 @@ function generateId() {
399
408
  const b = Math.random().toString(36).substring(2, 10);
400
409
  return a + b;
401
410
  }
411
+ var MAX_SECRET_HEX_LENGTH = 128;
402
412
  function isValidSecret(secret) {
413
+ if (secret.length > MAX_SECRET_HEX_LENGTH * 2)
414
+ return false;
403
415
  const hex = secret.replace(/-/g, "");
404
- return hex.length >= 32 && /^[0-9a-f]+$/i.test(hex);
416
+ return hex.length >= 32 && hex.length <= MAX_SECRET_HEX_LENGTH && /^[0-9a-f]+$/i.test(hex);
405
417
  }
406
418
  async function derivePlayerId(secret) {
407
419
  if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle?.digest === "function") {
package/dist/index.js CHANGED
@@ -72,7 +72,15 @@ function actionValidator(schema) {
72
72
  if (getInternalTypes().has(action.type))
73
73
  return next(action);
74
74
  const validate = schema[action.type];
75
- if (validate && !validate(action)) {
75
+ if (!validate)
76
+ return next(action);
77
+ let valid;
78
+ try {
79
+ valid = validate(action);
80
+ } catch {
81
+ valid = false;
82
+ }
83
+ if (!valid) {
76
84
  console.warn(`[couch-kit] Action "${action.type}" failed validation, skipping`);
77
85
  return _api.getState();
78
86
  }
@@ -329,9 +337,12 @@ function generateId() {
329
337
  const b = Math.random().toString(36).substring(2, 10);
330
338
  return a + b;
331
339
  }
340
+ var MAX_SECRET_HEX_LENGTH = 128;
332
341
  function isValidSecret(secret) {
342
+ if (secret.length > MAX_SECRET_HEX_LENGTH * 2)
343
+ return false;
333
344
  const hex = secret.replace(/-/g, "");
334
- return hex.length >= 32 && /^[0-9a-f]+$/i.test(hex);
345
+ return hex.length >= 32 && hex.length <= MAX_SECRET_HEX_LENGTH && /^[0-9a-f]+$/i.test(hex);
335
346
  }
336
347
  async function derivePlayerId(secret) {
337
348
  if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle?.digest === "function") {
@@ -386,6 +397,7 @@ export {
386
397
  KEEPALIVE_TIMEOUT,
387
398
  MAX_FRAME_SIZE,
388
399
  MAX_PENDING_PINGS,
400
+ MAX_SECRET_HEX_LENGTH,
389
401
  MAX_SYNC_INTERVAL,
390
402
  MessageTypes,
391
403
  SYNC_BACKOFF_FACTOR,
@@ -42,8 +42,12 @@ export declare const KEEPALIVE_TIMEOUT = 10000;
42
42
  * falling back to `crypto.getRandomValues()` for older environments.
43
43
  */
44
44
  export declare function generateId(): string;
45
+ /** Longest secret, in hex characters, the host accepts. A UUID is 32. */
46
+ export declare const MAX_SECRET_HEX_LENGTH = 128;
45
47
  /**
46
- * Validates that a string looks like a UUID (with or without dashes, 32+ hex chars).
48
+ * Validates that a string looks like a UUID (with or without dashes, 32 to
49
+ * {@link MAX_SECRET_HEX_LENGTH} hex chars). The upper bound keeps a client from
50
+ * making the host hash and store an arbitrarily long string.
47
51
  */
48
52
  export declare function isValidSecret(secret: string): boolean;
49
53
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAIxD,oDAAoD;AACpD,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,0DAA0D;AAC1D,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,8FAA8F;AAC9F,eAAO,MAAM,cAAc,QAAc,CAAC;AAE1C,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,oEAAoE;AACpE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,QAAgB,CAAC;AAExD,8FAA8F;AAC9F,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAwBnC;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBpE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAIxD,oDAAoD;AACpD,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,0DAA0D;AAC1D,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,8FAA8F;AAC9F,eAAO,MAAM,cAAc,QAAc,CAAC;AAE1C,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,oEAAoE;AACpE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,QAAgB,CAAC;AAExD,8FAA8F;AAC9F,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAwBnC;AAED,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAQrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBpE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD"}
@@ -53,7 +53,8 @@ export type ActionSchema<A extends IAction> = {
53
53
  };
54
54
  /**
55
55
  * Middleware that validates actions against a schema before they reach the
56
- * reducer. Invalid actions are silently dropped (with a console warning).
56
+ * reducer. Invalid actions are dropped with a console warning. A validator
57
+ * that throws counts as a failed validation.
57
58
  *
58
59
  * Internal actions are never validated — they always pass through.
59
60
  *
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,OAAO,EACP,cAAc,EACd,WAAW,EACZ,MAAM,YAAY,CAAC;AAOpB,oDAAoD;AACpD,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,UAAU;IACjD,QAAQ,EAAE,MAAM,CAAC,CAAC;CACnB;AAED,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,IAAI,CACxE,MAAM,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,KAC1B,CAAC,CAAC;AAEP;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CACpB,CAAC,SAAS,UAAU,GAAG,UAAU,EACjC,CAAC,SAAS,OAAO,GAAG,OAAO,IACzB,CACF,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,KAClB,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAM9E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACrE,GAAG,WAAW,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GACjC,CACD,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,KAC3C,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAwDzC;AAMD,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EAClE,OAAO,CAAC,EAAE,mBAAmB,GAC5B,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAmBlB;AAeD;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,OAAO,IAAI;KAC3C,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,KAAK,OAAO;CACxD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACrE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAgBlB"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,OAAO,EACP,cAAc,EACd,WAAW,EACZ,MAAM,YAAY,CAAC;AAOpB,oDAAoD;AACpD,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,UAAU;IACjD,QAAQ,EAAE,MAAM,CAAC,CAAC;CACnB;AAED,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,IAAI,CACxE,MAAM,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,KAC1B,CAAC,CAAC;AAEP;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CACpB,CAAC,SAAS,UAAU,GAAG,UAAU,EACjC,CAAC,SAAS,OAAO,GAAG,OAAO,IACzB,CACF,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,KAClB,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAM9E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACrE,GAAG,WAAW,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GACjC,CACD,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,KAC3C,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAwDzC;AAMD,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EAClE,OAAO,CAAC,EAAE,mBAAmB,GAC5B,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAmBlB;AAeD;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,OAAO,IAAI;KAC3C,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,KAAK,OAAO;CACxD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACrE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CA6BlB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@couch-kit/core",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": true
package/src/constants.ts CHANGED
@@ -84,12 +84,22 @@ export function generateId(): string {
84
84
  return a + b;
85
85
  }
86
86
 
87
+ /** Longest secret, in hex characters, the host accepts. A UUID is 32. */
88
+ export const MAX_SECRET_HEX_LENGTH = 128;
89
+
87
90
  /**
88
- * Validates that a string looks like a UUID (with or without dashes, 32+ hex chars).
91
+ * Validates that a string looks like a UUID (with or without dashes, 32 to
92
+ * {@link MAX_SECRET_HEX_LENGTH} hex chars). The upper bound keeps a client from
93
+ * making the host hash and store an arbitrarily long string.
89
94
  */
90
95
  export function isValidSecret(secret: string): boolean {
96
+ if (secret.length > MAX_SECRET_HEX_LENGTH * 2) return false;
91
97
  const hex = secret.replace(/-/g, "");
92
- return hex.length >= 32 && /^[0-9a-f]+$/i.test(hex);
98
+ return (
99
+ hex.length >= 32 &&
100
+ hex.length <= MAX_SECRET_HEX_LENGTH &&
101
+ /^[0-9a-f]+$/i.test(hex)
102
+ );
93
103
  }
94
104
 
95
105
  /**
package/src/middleware.ts CHANGED
@@ -178,7 +178,8 @@ export type ActionSchema<A extends IAction> = {
178
178
 
179
179
  /**
180
180
  * Middleware that validates actions against a schema before they reach the
181
- * reducer. Invalid actions are silently dropped (with a console warning).
181
+ * reducer. Invalid actions are dropped with a console warning. A validator
182
+ * that throws counts as a failed validation.
182
183
  *
183
184
  * Internal actions are never validated — they always pass through.
184
185
  *
@@ -196,8 +197,21 @@ export function actionValidator<S extends IGameState, A extends IAction>(
196
197
  if (getInternalTypes().has(action.type)) return next(action);
197
198
 
198
199
  const validate = schema[action.type as A["type"]];
200
+ if (!validate) return next(action);
201
+
202
+ // A validator that throws has not said yes. Malformed client payloads are
203
+ // exactly what makes a validator like `(a) => a.payload.points > 0` throw,
204
+ // so a throw counts as a failed validation. Letting the middleware error
205
+ // boundary fall through to `next` would hand the reducer the very action
206
+ // the validator exists to stop.
207
+ let valid: boolean;
208
+ try {
209
+ valid = validate(action as A & { type: A["type"] });
210
+ } catch {
211
+ valid = false;
212
+ }
199
213
 
200
- if (validate && !validate(action as A & { type: A["type"] })) {
214
+ if (!valid) {
201
215
  console.warn(
202
216
  `[couch-kit] Action "${action.type}" failed validation, skipping`,
203
217
  );