@stratum-hq/lib 0.5.0 → 0.6.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.
Files changed (56) hide show
  1. package/dist/__tests__/webhook-signature.test.d.ts +2 -0
  2. package/dist/__tests__/webhook-signature.test.d.ts.map +1 -0
  3. package/dist/__tests__/webhook-signature.test.js +103 -0
  4. package/dist/__tests__/webhook-signature.test.js.map +1 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +5 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/services/__tests__/api-key-service.test.d.ts +2 -0
  10. package/dist/services/__tests__/api-key-service.test.d.ts.map +1 -0
  11. package/dist/services/__tests__/api-key-service.test.js +76 -0
  12. package/dist/services/__tests__/api-key-service.test.js.map +1 -0
  13. package/dist/services/__tests__/event-service.test.js +32 -0
  14. package/dist/services/__tests__/event-service.test.js.map +1 -1
  15. package/dist/services/__tests__/key-rotation-service.test.d.ts +2 -0
  16. package/dist/services/__tests__/key-rotation-service.test.d.ts.map +1 -0
  17. package/dist/services/__tests__/key-rotation-service.test.js +132 -0
  18. package/dist/services/__tests__/key-rotation-service.test.js.map +1 -0
  19. package/dist/services/__tests__/role-service.test.js +29 -2
  20. package/dist/services/__tests__/role-service.test.js.map +1 -1
  21. package/dist/services/__tests__/tenant-service.test.js +34 -0
  22. package/dist/services/__tests__/tenant-service.test.js.map +1 -1
  23. package/dist/services/abac-service.d.ts.map +1 -1
  24. package/dist/services/abac-service.js +7 -3
  25. package/dist/services/abac-service.js.map +1 -1
  26. package/dist/services/api-key-service.d.ts +14 -0
  27. package/dist/services/api-key-service.d.ts.map +1 -1
  28. package/dist/services/api-key-service.js +13 -0
  29. package/dist/services/api-key-service.js.map +1 -1
  30. package/dist/services/event-service.d.ts.map +1 -1
  31. package/dist/services/event-service.js +34 -22
  32. package/dist/services/event-service.js.map +1 -1
  33. package/dist/services/key-rotation-service.d.ts +3 -2
  34. package/dist/services/key-rotation-service.d.ts.map +1 -1
  35. package/dist/services/key-rotation-service.js +17 -7
  36. package/dist/services/key-rotation-service.js.map +1 -1
  37. package/dist/services/permission-service.d.ts.map +1 -1
  38. package/dist/services/permission-service.js +7 -4
  39. package/dist/services/permission-service.js.map +1 -1
  40. package/dist/services/role-service.d.ts +15 -2
  41. package/dist/services/role-service.d.ts.map +1 -1
  42. package/dist/services/role-service.js +24 -5
  43. package/dist/services/role-service.js.map +1 -1
  44. package/dist/services/tenant-service.d.ts +21 -1
  45. package/dist/services/tenant-service.d.ts.map +1 -1
  46. package/dist/services/tenant-service.js +24 -3
  47. package/dist/services/tenant-service.js.map +1 -1
  48. package/dist/stratum.d.ts +21 -4
  49. package/dist/stratum.d.ts.map +1 -1
  50. package/dist/stratum.js +20 -9
  51. package/dist/stratum.js.map +1 -1
  52. package/dist/webhook-signature.d.ts +36 -0
  53. package/dist/webhook-signature.d.ts.map +1 -0
  54. package/dist/webhook-signature.js +55 -0
  55. package/dist/webhook-signature.js.map +1 -0
  56. package/package.json +3 -2
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=webhook-signature.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-signature.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/webhook-signature.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const webhook_signature_js_1 = require("../webhook-signature.js");
5
+ const SECRET = "whsec_test_secret";
6
+ const PAYLOAD = JSON.stringify({ id: "evt_1", type: "tenant.created" });
7
+ (0, vitest_1.describe)("signWebhookPayload", () => {
8
+ (0, vitest_1.it)("binds the timestamp into the signature", () => {
9
+ const t1 = "2026-07-24T00:00:00.000Z";
10
+ const t2 = "2026-07-24T00:00:01.000Z";
11
+ (0, vitest_1.expect)((0, webhook_signature_js_1.signWebhookPayload)(SECRET, t1, PAYLOAD)).not.toBe((0, webhook_signature_js_1.signWebhookPayload)(SECRET, t2, PAYLOAD));
12
+ });
13
+ (0, vitest_1.it)("produces a sha256= prefixed hex digest", () => {
14
+ const sig = (0, webhook_signature_js_1.signWebhookPayload)(SECRET, "2026-07-24T00:00:00.000Z", PAYLOAD);
15
+ (0, vitest_1.expect)(sig).toMatch(/^sha256=[0-9a-f]{64}$/);
16
+ });
17
+ });
18
+ (0, vitest_1.describe)("verifyWebhookSignature", () => {
19
+ const now = Date.parse("2026-07-24T12:00:00.000Z");
20
+ const timestamp = "2026-07-24T12:00:00.000Z";
21
+ const signature = (0, webhook_signature_js_1.signWebhookPayload)(SECRET, timestamp, PAYLOAD);
22
+ (0, vitest_1.it)("accepts a fresh, correctly signed delivery", () => {
23
+ (0, vitest_1.expect)((0, webhook_signature_js_1.verifyWebhookSignature)({ secret: SECRET, payload: PAYLOAD, signature, timestamp, now })).toBe(true);
24
+ });
25
+ (0, vitest_1.it)("rejects a tampered payload", () => {
26
+ (0, vitest_1.expect)((0, webhook_signature_js_1.verifyWebhookSignature)({
27
+ secret: SECRET,
28
+ payload: PAYLOAD + "x",
29
+ signature,
30
+ timestamp,
31
+ now,
32
+ })).toBe(false);
33
+ });
34
+ (0, vitest_1.it)("rejects a wrong secret", () => {
35
+ (0, vitest_1.expect)((0, webhook_signature_js_1.verifyWebhookSignature)({
36
+ secret: "whsec_wrong",
37
+ payload: PAYLOAD,
38
+ signature,
39
+ timestamp,
40
+ now,
41
+ })).toBe(false);
42
+ });
43
+ (0, vitest_1.it)("rejects a replayed delivery once it ages past the window", () => {
44
+ const stale = now + (webhook_signature_js_1.DEFAULT_WEBHOOK_TOLERANCE_SECONDS + 1) * 1000;
45
+ (0, vitest_1.expect)((0, webhook_signature_js_1.verifyWebhookSignature)({
46
+ secret: SECRET,
47
+ payload: PAYLOAD,
48
+ signature,
49
+ timestamp,
50
+ now: stale,
51
+ })).toBe(false);
52
+ });
53
+ (0, vitest_1.it)("rejects a timestamp too far in the future", () => {
54
+ const future = now - (webhook_signature_js_1.DEFAULT_WEBHOOK_TOLERANCE_SECONDS + 1) * 1000;
55
+ (0, vitest_1.expect)((0, webhook_signature_js_1.verifyWebhookSignature)({
56
+ secret: SECRET,
57
+ payload: PAYLOAD,
58
+ signature,
59
+ timestamp,
60
+ now: future,
61
+ })).toBe(false);
62
+ });
63
+ (0, vitest_1.it)("accepts a delivery at the edge of the window", () => {
64
+ const edge = now + webhook_signature_js_1.DEFAULT_WEBHOOK_TOLERANCE_SECONDS * 1000;
65
+ (0, vitest_1.expect)((0, webhook_signature_js_1.verifyWebhookSignature)({
66
+ secret: SECRET,
67
+ payload: PAYLOAD,
68
+ signature,
69
+ timestamp,
70
+ now: edge,
71
+ })).toBe(true);
72
+ });
73
+ (0, vitest_1.it)("respects a custom tolerance", () => {
74
+ const drift = now + 10_000; // 10s later
75
+ (0, vitest_1.expect)((0, webhook_signature_js_1.verifyWebhookSignature)({
76
+ secret: SECRET,
77
+ payload: PAYLOAD,
78
+ signature,
79
+ timestamp,
80
+ toleranceSeconds: 5,
81
+ now: drift,
82
+ })).toBe(false);
83
+ });
84
+ (0, vitest_1.it)("fails closed on a malformed timestamp", () => {
85
+ (0, vitest_1.expect)((0, webhook_signature_js_1.verifyWebhookSignature)({
86
+ secret: SECRET,
87
+ payload: PAYLOAD,
88
+ signature,
89
+ timestamp: "not-a-date",
90
+ now,
91
+ })).toBe(false);
92
+ });
93
+ (0, vitest_1.it)("rejects a malformed signature without throwing", () => {
94
+ (0, vitest_1.expect)((0, webhook_signature_js_1.verifyWebhookSignature)({
95
+ secret: SECRET,
96
+ payload: PAYLOAD,
97
+ signature: "sha256=deadbeef",
98
+ timestamp,
99
+ now,
100
+ })).toBe(false);
101
+ });
102
+ });
103
+ //# sourceMappingURL=webhook-signature.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-signature.test.js","sourceRoot":"","sources":["../../src/__tests__/webhook-signature.test.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { Stratum, type StratumOptions } from "./stratum.js";
2
2
  export { migrate, type MigrateOptions } from "./migrate.js";
3
3
  export { migrateAllSchemas, type MigrateSchemasOptions, type MigrateSchemasResult, } from "./migrate-schemas.js";
4
4
  export { withClient, withTransaction } from "./pool-helpers.js";
5
+ export { verifyWebhookSignature, signWebhookPayload, DEFAULT_WEBHOOK_TOLERANCE_SECONDS, type VerifyWebhookOptions, } from "./webhook-signature.js";
5
6
  export { traced, getTracer, isTracingEnabled } from "./telemetry.js";
6
7
  export type { TenantNode, TenantContext, CreateTenantInput, UpdateTenantInput, MoveTenantInput, PaginationInput, PaginatedResult, ConfigEntry, SetConfigInput, ResolvedConfigEntry, ResolvedConfig, PermissionPolicy, CreatePermissionInput, UpdatePermissionInput, ResolvedPermission, } from "@stratum-hq/core";
7
8
  export type { ApiKeyRecord, CreatedApiKey, ValidatedApiKey, CreateApiKeyOptions } from "./services/api-key-service.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGrE,YAAY,EACV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,eAAe,EACf,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACvH,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAGzF,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,iCAAiC,EACjC,KAAK,oBAAoB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGrE,YAAY,EACV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,eAAe,EACf,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACvH,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAGzF,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noopLogger = exports.defaultLogger = exports.isTracingEnabled = exports.getTracer = exports.traced = exports.withTransaction = exports.withClient = exports.migrateAllSchemas = exports.migrate = exports.Stratum = void 0;
3
+ exports.noopLogger = exports.defaultLogger = exports.isTracingEnabled = exports.getTracer = exports.traced = exports.DEFAULT_WEBHOOK_TOLERANCE_SECONDS = exports.signWebhookPayload = exports.verifyWebhookSignature = exports.withTransaction = exports.withClient = exports.migrateAllSchemas = exports.migrate = exports.Stratum = void 0;
4
4
  var stratum_js_1 = require("./stratum.js");
5
5
  Object.defineProperty(exports, "Stratum", { enumerable: true, get: function () { return stratum_js_1.Stratum; } });
6
6
  var migrate_js_1 = require("./migrate.js");
@@ -10,6 +10,10 @@ Object.defineProperty(exports, "migrateAllSchemas", { enumerable: true, get: fun
10
10
  var pool_helpers_js_1 = require("./pool-helpers.js");
11
11
  Object.defineProperty(exports, "withClient", { enumerable: true, get: function () { return pool_helpers_js_1.withClient; } });
12
12
  Object.defineProperty(exports, "withTransaction", { enumerable: true, get: function () { return pool_helpers_js_1.withTransaction; } });
13
+ var webhook_signature_js_1 = require("./webhook-signature.js");
14
+ Object.defineProperty(exports, "verifyWebhookSignature", { enumerable: true, get: function () { return webhook_signature_js_1.verifyWebhookSignature; } });
15
+ Object.defineProperty(exports, "signWebhookPayload", { enumerable: true, get: function () { return webhook_signature_js_1.signWebhookPayload; } });
16
+ Object.defineProperty(exports, "DEFAULT_WEBHOOK_TOLERANCE_SECONDS", { enumerable: true, get: function () { return webhook_signature_js_1.DEFAULT_WEBHOOK_TOLERANCE_SECONDS; } });
13
17
  var telemetry_js_1 = require("./telemetry.js");
14
18
  Object.defineProperty(exports, "traced", { enumerable: true, get: function () { return telemetry_js_1.traced; } });
15
19
  Object.defineProperty(exports, "getTracer", { enumerable: true, get: function () { return telemetry_js_1.getTracer; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAA4D;AAAnD,qGAAA,OAAO,OAAA;AAChB,2CAA4D;AAAnD,qGAAA,OAAO,OAAA;AAChB,2DAI8B;AAH5B,uHAAA,iBAAiB,OAAA;AAInB,qDAAgE;AAAvD,6GAAA,UAAU,OAAA;AAAE,kHAAA,eAAe,OAAA;AACpC,+CAAqE;AAA5D,sGAAA,MAAM,OAAA;AAAE,yGAAA,SAAS,OAAA;AAAE,gHAAA,gBAAgB,OAAA;AA8B5C,yCAAwD;AAA/C,0GAAA,aAAa,OAAA;AAAE,uGAAA,UAAU,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAA4D;AAAnD,qGAAA,OAAO,OAAA;AAChB,2CAA4D;AAAnD,qGAAA,OAAO,OAAA;AAChB,2DAI8B;AAH5B,uHAAA,iBAAiB,OAAA;AAInB,qDAAgE;AAAvD,6GAAA,UAAU,OAAA;AAAE,kHAAA,eAAe,OAAA;AACpC,+DAKgC;AAJ9B,8HAAA,sBAAsB,OAAA;AACtB,0HAAA,kBAAkB,OAAA;AAClB,yIAAA,iCAAiC,OAAA;AAGnC,+CAAqE;AAA5D,sGAAA,MAAM,OAAA;AAAE,yGAAA,SAAS,OAAA;AAAE,gHAAA,gBAAgB,OAAA;AA8B5C,yCAAwD;AAA/C,0GAAA,aAAa,OAAA;AAAE,uGAAA,UAAU,OAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api-key-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-service.test.d.ts","sourceRoot":"","sources":["../../../src/services/__tests__/api-key-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const vitest_1 = require("vitest");
37
+ // Mock pool-helpers before importing api-key-service
38
+ vitest_1.vi.mock("../../pool-helpers.js", () => ({
39
+ withClient: vitest_1.vi.fn(),
40
+ withTransaction: vitest_1.vi.fn(),
41
+ }));
42
+ const poolHelpers = __importStar(require("../../pool-helpers.js"));
43
+ const apiKeyService = __importStar(require("../api-key-service.js"));
44
+ const test_helpers_js_1 = require("./test-helpers.js");
45
+ /** Wire withClient to call the callback with a { query } client. */
46
+ function withMockQuery(mockQuery) {
47
+ vitest_1.vi.mocked(poolHelpers.withClient).mockImplementation(async (_pool, fn) => {
48
+ const client = { query: mockQuery };
49
+ return fn(client);
50
+ });
51
+ }
52
+ (0, vitest_1.beforeEach)(() => {
53
+ vitest_1.vi.clearAllMocks();
54
+ });
55
+ (0, vitest_1.describe)("getApiKey", () => {
56
+ (0, vitest_1.it)("selects a key by id and returns its owning tenant", async () => {
57
+ const pool = (0, test_helpers_js_1.makeMockPool)();
58
+ const row = { id: "key-1", tenant_id: "tenant-1", name: null, created_at: new Date(), last_used_at: null, revoked_at: null, expires_at: null };
59
+ const mockQuery = vitest_1.vi.fn().mockResolvedValueOnce({ rows: [row] });
60
+ withMockQuery(mockQuery);
61
+ const result = await apiKeyService.getApiKey(pool, "key-1");
62
+ (0, vitest_1.expect)(result).toEqual(row);
63
+ const [sql, params] = mockQuery.mock.calls[0];
64
+ (0, vitest_1.expect)(sql).toContain("FROM api_keys WHERE id = $1");
65
+ (0, vitest_1.expect)(sql).toContain("tenant_id");
66
+ (0, vitest_1.expect)(params).toEqual(["key-1"]);
67
+ });
68
+ (0, vitest_1.it)("returns null when no key has that id", async () => {
69
+ const pool = (0, test_helpers_js_1.makeMockPool)();
70
+ const mockQuery = vitest_1.vi.fn().mockResolvedValueOnce({ rows: [] });
71
+ withMockQuery(mockQuery);
72
+ const result = await apiKeyService.getApiKey(pool, "ghost");
73
+ (0, vitest_1.expect)(result).toBeNull();
74
+ });
75
+ });
76
+ //# sourceMappingURL=api-key-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/api-key-service.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,mCAA8D;AAE9D,qDAAqD;AACrD,WAAE,CAAC,IAAI,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAAC,CAAC;IACtC,UAAU,EAAE,WAAE,CAAC,EAAE,EAAE;IACnB,eAAe,EAAE,WAAE,CAAC,EAAE,EAAE;CACzB,CAAC,CAAC,CAAC;AAEJ,mEAAqD;AACrD,qEAAuD;AACvD,uDAAiD;AAEjD,oEAAoE;AACpE,SAAS,aAAa,CAAC,SAAmC;IACxD,WAAE,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QACvE,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,SAAS,EAAwC,CAAC;QAC1E,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAA,mBAAU,EAAC,GAAG,EAAE;IACd,WAAE,CAAC,aAAa,EAAE,CAAC;AACrB,CAAC,CAAC,CAAC;AAEH,IAAA,iBAAQ,EAAC,WAAW,EAAE,GAAG,EAAE;IACzB,IAAA,WAAE,EAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,IAAI,GAAG,IAAA,8BAAY,GAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QAC/I,MAAM,SAAS,GAAG,WAAE,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjE,aAAa,CAAC,SAAS,CAAC,CAAC;QAEzB,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE5D,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9C,IAAA,eAAM,EAAC,GAAG,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;QACrD,IAAA,eAAM,EAAC,GAAG,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACnC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,IAAI,GAAG,IAAA,8BAAY,GAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,WAAE,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC9D,aAAa,CAAC,SAAS,CAAC,CAAC;QAEzB,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5D,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -82,6 +82,38 @@ function signPayload(secret, payload) {
82
82
  (0, vitest_1.expect)(sig1).not.toBe(sig2);
83
83
  });
84
84
  });
85
+ (0, vitest_1.describe)("webhook egress filter (SSRF protection)", () => {
86
+ // Regression: bracketed IPv6 literals must be rejected in every notation.
87
+ // The URL parser exposes IPv6 hosts wrapped in brackets, so a filter that
88
+ // only matches the unbracketed textual form lets internal targets through.
89
+ const mustReject = [
90
+ ["bracketed IPv6 loopback", "http://[::1]/hook"],
91
+ ["expanded IPv6 loopback", "http://[0:0:0:0:0:0:0:1]/hook"],
92
+ ["IPv6 link-local", "http://[fe80::1]/hook"],
93
+ ["IPv6 unique-local", "http://[fd00:ec2::254]/hook"],
94
+ ["IPv4-mapped loopback", "http://[::ffff:127.0.0.1]/hook"],
95
+ ["IPv4-mapped metadata address", "http://[::ffff:169.254.169.254]/hook"],
96
+ ["unspecified IPv6 address", "http://[::]/hook"],
97
+ ["integer-coerced loopback", "http://2130706433/hook"],
98
+ ];
99
+ for (const [label, url] of mustReject) {
100
+ (0, vitest_1.it)(`rejects ${label}`, () => {
101
+ (0, vitest_1.expect)(() => eventService.validateWebhookUrl(url)).toThrow();
102
+ });
103
+ }
104
+ // Unbracketed forms that already worked must keep working.
105
+ (0, vitest_1.it)("still rejects IPv4 loopback and metadata", () => {
106
+ (0, vitest_1.expect)(() => eventService.validateWebhookUrl("http://127.0.0.1/hook")).toThrow();
107
+ (0, vitest_1.expect)(() => eventService.validateWebhookUrl("http://169.254.169.254/hook")).toThrow();
108
+ (0, vitest_1.expect)(() => eventService.validateWebhookUrl("http://localhost/hook")).toThrow();
109
+ });
110
+ (0, vitest_1.it)("allows a public https target", () => {
111
+ (0, vitest_1.expect)(() => eventService.validateWebhookUrl("https://example.com/hook")).not.toThrow();
112
+ });
113
+ (0, vitest_1.it)("fails closed on an unparseable URL", () => {
114
+ (0, vitest_1.expect)(() => eventService.validateWebhookUrl("http://[not-an-ip/hook")).toThrow();
115
+ });
116
+ });
85
117
  (0, vitest_1.describe)("retry backoff calculation", () => {
86
118
  // attempts^2 * 5000ms
87
119
  const cases = [
@@ -1 +1 @@
1
- {"version":3,"file":"event-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/event-service.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,mCAA8D;AAC9D,8DAAiC;AAEjC,mDAAmD;AACnD,WAAE,CAAC,IAAI,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAAC,CAAC;IACtC,UAAU,EAAE,WAAE,CAAC,EAAE,EAAE;IACnB,eAAe,EAAE,WAAE,CAAC,EAAE,EAAE;CACzB,CAAC,CAAC,CAAC;AAEJ,uBAAuB;AACvB,WAAE,CAAC,IAAI,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAAC,CAAC;IACtC,mBAAmB,EAAE,WAAE,CAAC,EAAE,EAAE;IAC5B,aAAa,EAAE,WAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,uBAAuB,CAAC;CAChE,CAAC,CAAC,CAAC;AAEJ,mEAAqD;AACrD,0EAA4D;AAC5D,kEAAoD;AACpD,2CAA+C;AAE/C,SAAS,YAAY;IACnB,OAAO,EAAuB,CAAC;AACjC,CAAC;AAED,SAAS,WAAW,CAAC,MAAc,EAAE,OAAe;IAClD,OAAO,SAAS,GAAG,qBAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACvF,CAAC;AAED,IAAA,mBAAU,EAAC,GAAG,EAAE;IACd,WAAE,CAAC,aAAa,EAAE,CAAC;AACrB,CAAC,CAAC,CAAC;AAEH,IAAA,iBAAQ,EAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,IAAA,WAAE,EAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,GAAG,GAAG,WAAW,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QACvD,IAAA,eAAM,EAAC,GAAG,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,IAAI,GAAG,WAAW,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,WAAW,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QACxD,IAAA,eAAM,EAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,IAAI,GAAG,WAAW,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,WAAW,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;QAC3D,IAAA,eAAM,EAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,IAAI,GAAG,WAAW,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,WAAW,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACvD,IAAA,eAAM,EAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAA,iBAAQ,EAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,sBAAsB;IACtB,MAAM,KAAK,GAAuB;QAChC,CAAC,CAAC,EAAE,IAAI,CAAC;QACT,CAAC,CAAC,EAAE,KAAK,CAAC;QACV,CAAC,CAAC,EAAE,KAAK,CAAC;QACV,CAAC,CAAC,EAAE,KAAK,CAAC;QACV,CAAC,CAAC,EAAE,MAAM,CAAC;KACZ,CAAC;IAEF,KAAK,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,KAAK,EAAE,CAAC;QAC3C,IAAA,WAAE,EAAC,WAAW,QAAQ,OAAO,UAAU,UAAU,EAAE,GAAG,EAAE;YACtD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;YAC7C,IAAA,eAAM,EAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAA,iBAAQ,EAAC,WAAW,EAAE,GAAG,EAAE;IACzB,IAAA,WAAE,EAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACvF,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG;YAChB,EAAE,EAAE,YAAY;YAChB,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,aAAa;YACxB,IAAI,EAAE,EAAE;YACR,UAAU,EAAE,0BAA0B;SACvC,CAAC;QACF,MAAM,WAAW,GAAG;YAClB,EAAE,EAAE,cAAc;YAClB,SAAS,EAAE,IAAI;YACf,GAAG,EAAE,0BAA0B;YAC/B,WAAW,EAAE,QAAQ;YACrB,MAAM,EAAE,CAAC,gBAAgB,CAAC;YAC1B,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,0BAA0B;YACtC,UAAU,EAAE,0BAA0B;SACvC,CAAC;QAEF,MAAM,gBAAgB,GAAG,WAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC1E,MAAM,mBAAmB,GAAG,WAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAEpE,sCAAsC;QACtC,4CAA4C;QAC5C,WAAE,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC;aAC9B,sBAAsB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YAC1C,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,gBAAgB,EAAwC,CAAC;YACjF,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC;aACD,sBAAsB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YAC1C,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,mBAAmB,EAAwC,CAAC;YACpF,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QAEL,WAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,iBAAiB,CAAC,CAAC,WAAkB,CAAC,CAAC,CAAC;QAE1F,MAAM,YAAY,CAAC,SAAS,CAAC,IAAI,EAAE,kBAAW,CAAC,cAAc,EAAE,aAAa,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QAE9F,IAAA,eAAM,EAAC,gBAAgB,CAAC,CAAC,oBAAoB,EAAE,CAAC;QAChD,IAAA,eAAM,EAAC,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,oBAAoB,CACjE,IAAI,EACJ,kBAAW,CAAC,cAAc,EAC1B,aAAa,CACd,CAAC;QACF,IAAA,eAAM,EAAC,mBAAmB,CAAC,CAAC,oBAAoB,CAC9C,eAAM,CAAC,gBAAgB,CAAC,gCAAgC,CAAC,EACzD,CAAC,cAAc,EAAE,YAAY,CAAC,CAC/B,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG;YAChB,EAAE,EAAE,YAAY;YAChB,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,aAAa;YACxB,IAAI,EAAE,EAAE;YACR,UAAU,EAAE,0BAA0B;SACvC,CAAC;QAEF,WAAE,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,sBAAsB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YAC3E,MAAM,MAAM,GAAG;gBACb,KAAK,EAAE,WAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;aAClB,CAAC;YACxC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,WAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;QAExE,MAAM,YAAY,CAAC,SAAS,CAAC,IAAI,EAAE,kBAAW,CAAC,cAAc,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;QAElF,wFAAwF;QACxF,IAAA,eAAM,EAAC,WAAW,CAAC,UAAU,CAAC,CAAC,oBAAoB,EAAE,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"event-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/event-service.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=key-rotation-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-rotation-service.test.d.ts","sourceRoot":"","sources":["../../../src/services/__tests__/key-rotation-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const key_rotation_service_js_1 = require("../key-rotation-service.js");
5
+ const crypto_js_1 = require("../../crypto.js");
6
+ function paramFor(sql, re, params) {
7
+ const m = sql.match(re);
8
+ return m ? params[Number(m[1]) - 1] : undefined;
9
+ }
10
+ function makeFakePool(store) {
11
+ const client = {
12
+ query: async (sql, params = []) => {
13
+ const s = sql.trim();
14
+ if (/^(BEGIN|COMMIT|ROLLBACK|RESET|SET)\b/.test(s)) {
15
+ return { rows: [], rowCount: 0 };
16
+ }
17
+ const limit = Number(paramFor(s, /LIMIT \$(\d+)/, params));
18
+ const gtId = paramFor(s, /id > \$(\d+)/, params);
19
+ const ordered = /ORDER BY id/.test(s);
20
+ // The previous query tried to exclude already-rotated rows with a
21
+ // `value NOT LIKE 'rotated:%'` predicate. Emulate that literally.
22
+ const excludeRotatedPrefix = /NOT LIKE 'rotated:%'/.test(s);
23
+ const byId = (rows) => ordered
24
+ ? [...rows].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
25
+ : rows;
26
+ if (/SELECT id, value FROM config_entries/.test(s)) {
27
+ let rows = store.config.filter((r) => r.sensitive === true);
28
+ if (excludeRotatedPrefix)
29
+ rows = rows.filter((r) => !r.value.startsWith("rotated:"));
30
+ if (gtId !== undefined)
31
+ rows = rows.filter((r) => r.id > gtId);
32
+ rows = byId(rows).slice(0, limit);
33
+ return {
34
+ rows: rows.map((r) => ({ id: r.id, value: r.value })),
35
+ rowCount: rows.length,
36
+ };
37
+ }
38
+ if (/UPDATE config_entries SET value/.test(s)) {
39
+ const [value, id] = params;
40
+ const row = store.config.find((r) => r.id === id);
41
+ if (row)
42
+ row.value = value;
43
+ return { rows: [], rowCount: row ? 1 : 0 };
44
+ }
45
+ if (/SELECT id, secret_hash FROM webhooks/.test(s)) {
46
+ let rows = store.webhooks.filter((r) => r.secret_hash != null);
47
+ if (gtId !== undefined)
48
+ rows = rows.filter((r) => r.id > gtId);
49
+ rows = byId(rows).slice(0, limit);
50
+ return {
51
+ rows: rows.map((r) => ({ id: r.id, secret_hash: r.secret_hash })),
52
+ rowCount: rows.length,
53
+ };
54
+ }
55
+ if (/UPDATE webhooks SET secret_hash/.test(s)) {
56
+ const [secret, id] = params;
57
+ const row = store.webhooks.find((r) => r.id === id);
58
+ if (row)
59
+ row.secret_hash = secret;
60
+ return { rows: [], rowCount: row ? 1 : 0 };
61
+ }
62
+ throw new Error(`unexpected SQL in test double: ${s}`);
63
+ },
64
+ release: () => { },
65
+ };
66
+ return { connect: async () => client };
67
+ }
68
+ // Deterministic ascending ids, all strictly greater than the zero UUID so a
69
+ // keyset cursor starting there sees every row.
70
+ function seedId(i) {
71
+ return `00000000-0000-0000-0000-${String(i + 1).padStart(12, "0")}`;
72
+ }
73
+ const OLD_KEY = "old-key-material-abc";
74
+ const NEW_KEY = "new-key-material-xyz";
75
+ (0, vitest_1.describe)("rotateEncryptionKey batching", () => {
76
+ const savedEnv = { ...process.env };
77
+ (0, vitest_1.beforeEach)(() => {
78
+ delete process.env.STRATUM_ENCRYPTION_KEY_PREVIOUS;
79
+ });
80
+ (0, vitest_1.afterEach)(() => {
81
+ process.env = { ...savedEnv };
82
+ });
83
+ (0, vitest_1.it)("re-encrypts every sensitive config row exactly once past the first batch", async () => {
84
+ const batchSize = 100;
85
+ const total = 250; // spans three batches
86
+ // Seed rows encrypted under the old key.
87
+ process.env.STRATUM_ENCRYPTION_KEY = OLD_KEY;
88
+ const plaintexts = [];
89
+ const config = [];
90
+ for (let i = 0; i < total; i++) {
91
+ const pt = `config-secret-${i}`;
92
+ plaintexts.push(pt);
93
+ config.push({
94
+ id: seedId(i),
95
+ value: JSON.stringify((0, crypto_js_1.encrypt)(pt)),
96
+ sensitive: true,
97
+ });
98
+ }
99
+ const store = { config, webhooks: [] };
100
+ const pool = makeFakePool(store);
101
+ const result = await (0, key_rotation_service_js_1.rotateEncryptionKey)(pool, OLD_KEY, NEW_KEY, batchSize);
102
+ (0, vitest_1.expect)(result.config_entries_rotated).toBe(total);
103
+ // Every row must now decrypt under the NEW key back to its original
104
+ // plaintext — i.e. rotated exactly once, none skipped or double-encrypted.
105
+ process.env.STRATUM_ENCRYPTION_KEY = NEW_KEY;
106
+ for (let i = 0; i < total; i++) {
107
+ const blob = JSON.parse(store.config[i].value);
108
+ (0, vitest_1.expect)((0, crypto_js_1.decrypt)(blob)).toBe(plaintexts[i]);
109
+ }
110
+ });
111
+ (0, vitest_1.it)("re-encrypts every webhook secret exactly once past the first batch", async () => {
112
+ const batchSize = 100;
113
+ const total = 250;
114
+ process.env.STRATUM_ENCRYPTION_KEY = OLD_KEY;
115
+ const plaintexts = [];
116
+ const webhooks = [];
117
+ for (let i = 0; i < total; i++) {
118
+ const pt = `webhook-secret-${i}`;
119
+ plaintexts.push(pt);
120
+ webhooks.push({ id: seedId(i), secret_hash: (0, crypto_js_1.encrypt)(pt) });
121
+ }
122
+ const store = { config: [], webhooks };
123
+ const pool = makeFakePool(store);
124
+ const result = await (0, key_rotation_service_js_1.rotateEncryptionKey)(pool, OLD_KEY, NEW_KEY, batchSize);
125
+ (0, vitest_1.expect)(result.webhooks_rotated).toBe(total);
126
+ process.env.STRATUM_ENCRYPTION_KEY = NEW_KEY;
127
+ for (let i = 0; i < total; i++) {
128
+ (0, vitest_1.expect)((0, crypto_js_1.decrypt)(store.webhooks[i].secret_hash)).toBe(plaintexts[i]);
129
+ }
130
+ });
131
+ });
132
+ //# sourceMappingURL=key-rotation-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-rotation-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/key-rotation-service.test.ts"],"names":[],"mappings":"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"}
@@ -62,7 +62,25 @@ function withMockQuery(mockQuery) {
62
62
  const [sql, params] = mockQuery.mock.calls[0];
63
63
  (0, vitest_1.expect)(sql).toContain("INSERT INTO principal_roles");
64
64
  (0, vitest_1.expect)(sql).toContain("ON CONFLICT");
65
- (0, vitest_1.expect)(params).toEqual(["user", "user-1", "role-1"]);
65
+ (0, vitest_1.expect)(params).toEqual(["user", "user-1", "role-1", null]);
66
+ });
67
+ (0, vitest_1.it)("scopes the role to a tenant when tenantId is passed", async () => {
68
+ const pool = (0, test_helpers_js_1.makeMockPool)();
69
+ const mockQuery = vitest_1.vi.fn().mockResolvedValueOnce({ rows: [{ role_id: "role-1" }] });
70
+ withMockQuery(mockQuery);
71
+ await roleService.assignRole(pool, "user", "user-1", "role-1", "tenant-1");
72
+ const [sql, params] = mockQuery.mock.calls[0];
73
+ (0, vitest_1.expect)(sql).toContain("WHERE EXISTS");
74
+ (0, vitest_1.expect)(sql).toContain("tenant_id = $4 OR tenant_id IS NULL");
75
+ (0, vitest_1.expect)(params).toEqual(["user", "user-1", "role-1", "tenant-1"]);
76
+ });
77
+ (0, vitest_1.it)("returns false when the role belongs to another tenant (refused)", async () => {
78
+ const pool = (0, test_helpers_js_1.makeMockPool)();
79
+ // The guarded INSERT ... SELECT ... WHERE EXISTS writes no row.
80
+ const mockQuery = vitest_1.vi.fn().mockResolvedValueOnce({ rows: [] });
81
+ withMockQuery(mockQuery);
82
+ const ok = await roleService.assignRole(pool, "user", "user-1", "foreign-role", "tenant-1");
83
+ (0, vitest_1.expect)(ok).toBe(false);
66
84
  });
67
85
  });
68
86
  (0, vitest_1.describe)("removeRole", () => {
@@ -94,7 +112,16 @@ function withMockQuery(mockQuery) {
94
112
  (0, vitest_1.expect)(scopes).toEqual(["read", "write"]);
95
113
  const [sql, params] = mockQuery.mock.calls[0];
96
114
  (0, vitest_1.expect)(sql).toContain("JOIN roles r ON r.id = pr.role_id");
97
- (0, vitest_1.expect)(params).toEqual(["user", "user-1"]);
115
+ (0, vitest_1.expect)(params).toEqual(["user", "user-1", null]);
116
+ });
117
+ (0, vitest_1.it)("scopes resolution to a tenant when tenantId is passed", async () => {
118
+ const pool = (0, test_helpers_js_1.makeMockPool)();
119
+ const mockQuery = vitest_1.vi.fn().mockResolvedValueOnce({ rows: [{ scopes: ["read"] }] });
120
+ withMockQuery(mockQuery);
121
+ await roleService.resolvePrincipalScopes(pool, "user", "user-1", "tenant-1");
122
+ const [sql, params] = mockQuery.mock.calls[0];
123
+ (0, vitest_1.expect)(sql).toContain("r.tenant_id = $3 OR r.tenant_id IS NULL");
124
+ (0, vitest_1.expect)(params).toEqual(["user", "user-1", "tenant-1"]);
98
125
  });
99
126
  (0, vitest_1.it)("fails closed with [] when the principal has no role", async () => {
100
127
  const pool = (0, test_helpers_js_1.makeMockPool)();
@@ -1 +1 @@
1
- {"version":3,"file":"role-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/role-service.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/role-service.test.ts"],"names":[],"mappings":"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"}
@@ -489,6 +489,40 @@ function makeTenant(overrides = {}) {
489
489
  (0, vitest_1.expect)(result[0].id).toBe("child-1");
490
490
  (0, vitest_1.expect)(result[1].id).toBe("grandchild-1");
491
491
  });
492
+ // These are SQL-string assertions per this repo's testing contract: the mock
493
+ // client does not run Postgres, so what they prove is that the subtree query
494
+ // carries the right predicate. The three states (active / archived / soft-
495
+ // deleted) are exercised for real against a database in
496
+ // packages/integration-tests/src/tenant-descendants.integration.test.ts.
497
+ (0, vitest_1.it)("excludes archived and soft-deleted descendants by default", async () => {
498
+ const pool = (0, test_helpers_js_1.makeMockPool)();
499
+ const mockQuery = vitest_1.vi.fn();
500
+ mockQuery.mockResolvedValueOnce({ rows: [{ id: "parent-id" }] });
501
+ mockQuery.mockResolvedValueOnce({ rows: [] });
502
+ vitest_1.vi.mocked(poolHelpers.withClient).mockImplementation(async (_pool, fn) => {
503
+ const client = { query: mockQuery };
504
+ return fn(client);
505
+ });
506
+ await tenantService.getDescendants(pool, "parent-id");
507
+ // The subtree query (2nd call) filters to active rows only. deleteTenant
508
+ // sets status='archived' alongside deleted_at, so this one predicate drops
509
+ // both archived and soft-deleted descendants — matching getChildren.
510
+ const subtreeSql = mockQuery.mock.calls[1][0];
511
+ (0, vitest_1.expect)(subtreeSql).toContain("status = 'active'");
512
+ });
513
+ (0, vitest_1.it)("includes descendants of any status when includeArchived is true", async () => {
514
+ const pool = (0, test_helpers_js_1.makeMockPool)();
515
+ const mockQuery = vitest_1.vi.fn();
516
+ mockQuery.mockResolvedValueOnce({ rows: [{ id: "parent-id" }] });
517
+ mockQuery.mockResolvedValueOnce({ rows: [] });
518
+ vitest_1.vi.mocked(poolHelpers.withClient).mockImplementation(async (_pool, fn) => {
519
+ const client = { query: mockQuery };
520
+ return fn(client);
521
+ });
522
+ await tenantService.getDescendants(pool, "parent-id", true);
523
+ const subtreeSql = mockQuery.mock.calls[1][0];
524
+ (0, vitest_1.expect)(subtreeSql).not.toContain("status = 'active'");
525
+ });
492
526
  (0, vitest_1.it)("returns empty array when no descendants exist", async () => {
493
527
  const pool = (0, test_helpers_js_1.makeMockPool)();
494
528
  const mockQuery = vitest_1.vi.fn();