@crvouga/mockingbird-service-dynamodb 3.10.6 → 3.10.8

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,18 @@
1
1
  # Changelog — @crvouga/mockingbird-service-dynamodb
2
2
 
3
+ ## 3.10.8 (2026-11-02)
4
+
5
+ ### Fixes and improvements
6
+
7
+ - compare the admin key in constant time ([2a43141](https://github.com/crvouga/mockingbird/commit/2a43141f50dab41e698c3bbce0c69bf65b40e24e))
8
+
9
+ ## 3.10.7 (2026-11-02)
10
+
11
+ ### Fixes and improvements
12
+
13
+ - place key-order helpers away from the condition-expression code ([af51e56](https://github.com/crvouga/mockingbird/commit/af51e5649cf406c8b4f7a16a4eb80b5da1fabdcb))
14
+ - order sort keys by DynamoDB's byte order ([7331cc1](https://github.com/crvouga/mockingbird/commit/7331cc1244fa256c82abeba9860589791b03d193))
15
+
3
16
  ## 3.10.6 (2026-11-02)
4
17
 
5
18
  ### Fixes and improvements
@@ -1778,8 +1778,8 @@ var requestBodyMap = (value) => {
1778
1778
  for (const [path, expected] of Object.entries(value)) {
1779
1779
  if (path === "" || path.split(".").includes(""))
1780
1780
  throw new ControlError(`fault.requestBody: ${JSON.stringify(path)} is not a dotted path`);
1781
- const scalar2 = expected === null || typeof expected === "string" || typeof expected === "boolean" || typeof expected === "number" && Number.isFinite(expected);
1782
- if (!scalar2)
1781
+ const scalar = expected === null || typeof expected === "string" || typeof expected === "boolean" || typeof expected === "number" && Number.isFinite(expected);
1782
+ if (!scalar)
1783
1783
  throw new ControlError(`fault.requestBody.${path}: expected a string, number, boolean or null`);
1784
1784
  }
1785
1785
  return value;
@@ -2507,6 +2507,17 @@ var createNamespaceControls = (options) => {
2507
2507
  };
2508
2508
  };
2509
2509
 
2510
+ // ../core/dist/signing.js
2511
+ var encoder = new TextEncoder();
2512
+ var timingSafeEqual = (a, b) => {
2513
+ if (a.length !== b.length)
2514
+ return false;
2515
+ let diff = 0;
2516
+ for (let i = 0; i < a.length; i++)
2517
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
2518
+ return diff === 0;
2519
+ };
2520
+
2510
2521
  // ../core/dist/surface.js
2511
2522
  var defineMock = (runtime) => runtime;
2512
2523
  var ADMIN_ROUTE_REFERENCE = {
@@ -2625,6 +2636,10 @@ var assertAdminPrefixAvailable = (prefix, paths) => {
2625
2636
  }
2626
2637
  };
2627
2638
  var ADMIN_KEY_HEADER = CONTROL_HEADERS.adminKey;
2639
+ var hasAdminKey = (headers, adminKey) => {
2640
+ const sent = headers.get(ADMIN_KEY_HEADER);
2641
+ return sent !== null && timingSafeEqual(sent, adminKey);
2642
+ };
2628
2643
  var NAMESPACE_HEADER = CONTROL_HEADERS.namespace;
2629
2644
  var ADMIN_DOCS_URL = "https://mockingbird.chrisvouga.dev/docs/admin-controls";
2630
2645
  var ADMIN_CONTROL_PAGES = [
@@ -3098,7 +3113,7 @@ var createControlPlane = (context) => {
3098
3113
  }
3099
3114
  const path = url.pathname.slice(adminPrefix.length) || "/";
3100
3115
  const shell = request.method === "GET" && (path === "/ui" || path === "/ui/");
3101
- if (!shell && context.adminKey !== void 0 && request.headers.get(ADMIN_KEY_HEADER) !== context.adminKey) {
3116
+ if (!shell && context.adminKey !== void 0 && !hasAdminKey(request.headers, context.adminKey)) {
3102
3117
  return adminError(401, `missing or wrong ${ADMIN_KEY_HEADER}`);
3103
3118
  }
3104
3119
  for (const route of routes) {
@@ -3861,7 +3876,7 @@ var stateAdminRoutes = (routes) => {
3861
3876
  };
3862
3877
 
3863
3878
  // ../core/dist/version.js
3864
- var PACKAGE_VERSION = true ? "3.10.6" : UNRELEASED_VERSION;
3879
+ var PACKAGE_VERSION = true ? "3.10.8" : UNRELEASED_VERSION;
3865
3880
 
3866
3881
  // ../core/dist/webhooks.js
3867
3882
  var json4 = (status, body) => new Response(JSON.stringify(body), {
@@ -4032,21 +4047,21 @@ var realSleep = (ms, signal) => new Promise((resolve2, reject) => {
4032
4047
  signal?.addEventListener("abort", abort, { once: true });
4033
4048
  });
4034
4049
  var NULL_BODY_STATUS = /* @__PURE__ */ new Set([101, 204, 205, 304]);
4035
- var truncateResponse = (response, bytes2) => {
4050
+ var truncateResponse = (response, bytes3) => {
4036
4051
  if (NULL_BODY_STATUS.has(response.status))
4037
4052
  return response;
4038
4053
  const reader = response.body?.getReader();
4039
4054
  let sent = 0;
4040
4055
  const body = new ReadableStream({
4041
4056
  async pull(controller) {
4042
- const chunk = sent < bytes2 && reader ? await reader.read() : void 0;
4057
+ const chunk = sent < bytes3 && reader ? await reader.read() : void 0;
4043
4058
  if (!chunk || chunk.done) {
4044
4059
  await reader?.cancel().catch(() => {
4045
4060
  });
4046
4061
  controller.error(new DroppedConnectionError("truncate"));
4047
4062
  return;
4048
4063
  }
4049
- const part = chunk.value.subarray(0, bytes2 - sent);
4064
+ const part = chunk.value.subarray(0, bytes3 - sent);
4050
4065
  sent += part.length;
4051
4066
  controller.enqueue(part);
4052
4067
  },
@@ -4840,7 +4855,7 @@ var createRuntime = (options) => {
4840
4855
  throw error;
4841
4856
  }
4842
4857
  const rewinds = at !== void 0 && selectedBranch === "main" && MUTATING_METHODS.has(request.method);
4843
- if ((requested || rewinds) && options.adminKey !== void 0 && request.headers.get(ADMIN_KEY_HEADER) !== options.adminKey)
4858
+ if ((requested || rewinds) && options.adminKey !== void 0 && !hasAdminKey(request.headers, options.adminKey))
4844
4859
  return refuse(401, `control headers need ${ADMIN_KEY_HEADER}`);
4845
4860
  controls.ensure(namespace);
4846
4861
  if (requested?.restore !== void 0 && !snapshotStore.get(namespace, requested.restore))
@@ -5515,7 +5530,6 @@ var createRuntime2 = (options = {}) => createRuntime({
5515
5530
  var DYNAMODB_NAMESPACE = "dynamodb";
5516
5531
  var accessKeyCredential = sigV4AccessKeyId;
5517
5532
  var clone = (value) => structuredClone(value);
5518
- var scalar = (value) => value?.S ?? (value?.N !== void 0 ? Number(value.N) : value?.BOOL ?? value?.B);
5519
5533
  var resolveName = (name2, names) => name2.split(".").map((part) => names[part] ?? part).join(".");
5520
5534
  var get = (item, path, names) => {
5521
5535
  const parts = resolveName(path.trim(), names).split(".");
@@ -5538,6 +5552,23 @@ var set = (item, path, value, names) => {
5538
5552
  }
5539
5553
  current[leaf] = clone(value);
5540
5554
  };
5555
+ var utf8 = new TextEncoder();
5556
+ var bytes2 = (value) => Uint8Array.from(atob(value), (char) => char.charCodeAt(0));
5557
+ var compareBytes = (left, right) => {
5558
+ for (let i = 0; i < Math.min(left.length, right.length); i++)
5559
+ if (left[i] !== right[i]) return left[i] - right[i];
5560
+ return left.length - right.length;
5561
+ };
5562
+ var compareKeyValues = (left, right) => {
5563
+ if (left === void 0 || right === void 0)
5564
+ return left === right ? 0 : left === void 0 ? 1 : -1;
5565
+ if (left.N !== void 0 && right.N !== void 0) return Number(left.N) - Number(right.N);
5566
+ if (left.S !== void 0 && right.S !== void 0)
5567
+ return compareBytes(utf8.encode(left.S), utf8.encode(right.S));
5568
+ if (left.B !== void 0 && right.B !== void 0)
5569
+ return compareBytes(bytes2(left.B), bytes2(right.B));
5570
+ return 0;
5571
+ };
5541
5572
  var remove = (item, path, names) => {
5542
5573
  const parts = resolveName(path.trim(), names).split(".");
5543
5574
  const leaf = parts.pop();
@@ -5647,10 +5678,8 @@ var DynamoAPI = class {
5647
5678
  }
5648
5679
  compare(table, left, right, schema = table.keySchema) {
5649
5680
  for (const key of schema) {
5650
- const a = scalar(left[key.AttributeName]);
5651
- const b = scalar(right[key.AttributeName]);
5652
- if (a === b) continue;
5653
- return a !== void 0 && b !== void 0 && a < b ? -1 : 1;
5681
+ const order2 = compareKeyValues(left[key.AttributeName], right[key.AttributeName]);
5682
+ if (order2 !== 0) return order2 < 0 ? -1 : 1;
5654
5683
  }
5655
5684
  return 0;
5656
5685
  }
@@ -5999,4 +6028,4 @@ export {
5999
6028
  accessKeyCredential,
6000
6029
  DynamoAPI
6001
6030
  };
6002
- //# sourceMappingURL=chunk-APTU4NSH.js.map
6031
+ //# sourceMappingURL=chunk-4LTVTO5I.js.map