@proteinjs/user-server 1.11.0 → 1.12.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.
- package/CHANGELOG.md +32 -0
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/generated/index.js +3 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/src/migrations/SweepMalformedAccessGrants.d.ts +76 -0
- package/dist/src/migrations/SweepMalformedAccessGrants.d.ts.map +1 -0
- package/dist/src/migrations/SweepMalformedAccessGrants.js +354 -0
- package/dist/src/migrations/SweepMalformedAccessGrants.js.map +1 -0
- package/dist/src/routes/devLogin.d.ts +5 -0
- package/dist/src/routes/devLogin.d.ts.map +1 -1
- package/dist/src/routes/devLogin.js +13 -0
- package/dist/src/routes/devLogin.js.map +1 -1
- package/dist/src/services/Roles.d.ts +2 -0
- package/dist/src/services/Roles.d.ts.map +1 -1
- package/dist/src/services/Roles.js +22 -2
- package/dist/src/services/Roles.js.map +1 -1
- package/dist/test/AccessGrantPrincipalReference.integration.test.d.ts +2 -0
- package/dist/test/AccessGrantPrincipalReference.integration.test.d.ts.map +1 -0
- package/dist/test/AccessGrantPrincipalReference.integration.test.js +219 -0
- package/dist/test/AccessGrantPrincipalReference.integration.test.js.map +1 -0
- package/dist/test/DevLogin.test.js +41 -0
- package/dist/test/DevLogin.test.js.map +1 -1
- package/dist/test/MachineAccounts.integration.test.js +91 -7
- package/dist/test/MachineAccounts.integration.test.js.map +1 -1
- package/dist/test/Roles.integration.test.js +141 -2
- package/dist/test/Roles.integration.test.js.map +1 -1
- package/dist/test/SweepMalformedAccessGrants.integration.test.d.ts +2 -0
- package/dist/test/SweepMalformedAccessGrants.integration.test.d.ts.map +1 -0
- package/dist/test/SweepMalformedAccessGrants.integration.test.js +424 -0
- package/dist/test/SweepMalformedAccessGrants.integration.test.js.map +1 -0
- package/generated/index.ts +3 -1
- package/package.json +4 -3
- package/src/migrations/SweepMalformedAccessGrants.ts +229 -0
- package/src/routes/devLogin.ts +15 -0
- package/src/services/Roles.ts +28 -2
- package/test/AccessGrantPrincipalReference.integration.test.ts +132 -0
- package/test/DevLogin.test.ts +26 -0
- package/test/MachineAccounts.integration.test.ts +76 -33
- package/test/Roles.integration.test.ts +67 -2
- package/test/SweepMalformedAccessGrants.integration.test.ts +255 -0
|
@@ -114,7 +114,14 @@ describe('Roles service — grant/revoke outcomes and audit trail', function ()
|
|
|
114
114
|
];
|
|
115
115
|
reflection_1.SourceRepository.get().objectCache['@proteinjs/user/RoleCatalogEntry'] = [
|
|
116
116
|
{ role: 'ops', description: 'Run the ops cockpit' },
|
|
117
|
+
{ role: 'dev', description: 'Drive the dev tooling' },
|
|
118
|
+
// Admin-grant-only entry (the consumer compliance-grant shape): granting requires the
|
|
119
|
+
// caller to BE an admin; the ordinary 'roles' grant does not cover it.
|
|
120
|
+
{ role: 'data-access', description: 'Compliance decrypt grant', adminGrantOnly: true },
|
|
117
121
|
];
|
|
122
|
+
// The admin-grant-only check resolves the CALLER through UserAuth — same session-backed
|
|
123
|
+
// repo the services use.
|
|
124
|
+
reflection_1.SourceRepository.get().objectCache['@proteinjs/user-auth/AuthenticatedUserRepo'] = [new user_1.UserRepo()];
|
|
118
125
|
jest.spyOn(db_1.Db, 'getDefaultDbDriver').mockImplementation(function () { return spannerDriver; });
|
|
119
126
|
return [4 /*yield*/, test_1.SpannerEmulatorProvisioner.ensureProvisioned({
|
|
120
127
|
projectId: 'proteinjs-test',
|
|
@@ -257,7 +264,7 @@ describe('Roles service — grant/revoke outcomes and audit trail', function ()
|
|
|
257
264
|
return [4 /*yield*/, roles.grantRole(targetId, 'ops')];
|
|
258
265
|
case 1:
|
|
259
266
|
_b.sent();
|
|
260
|
-
return [4 /*yield*/, roles.grantRole(targetId, '
|
|
267
|
+
return [4 /*yield*/, roles.grantRole(targetId, 'dev')];
|
|
261
268
|
case 2:
|
|
262
269
|
_b.sent();
|
|
263
270
|
return [4 /*yield*/, roles.revokeRole(targetId, 'ops')];
|
|
@@ -266,7 +273,7 @@ describe('Roles service — grant/revoke outcomes and audit trail', function ()
|
|
|
266
273
|
_a = expect;
|
|
267
274
|
return [4 /*yield*/, targetRoles()];
|
|
268
275
|
case 4:
|
|
269
|
-
_a.apply(void 0, [_b.sent()]).toEqual(['
|
|
276
|
+
_a.apply(void 0, [_b.sent()]).toEqual(['dev']);
|
|
270
277
|
return [4 /*yield*/, auditRows()];
|
|
271
278
|
case 5:
|
|
272
279
|
events = _b.sent();
|
|
@@ -277,6 +284,58 @@ describe('Roles service — grant/revoke outcomes and audit trail', function ()
|
|
|
277
284
|
}
|
|
278
285
|
});
|
|
279
286
|
}); }, 60000);
|
|
287
|
+
it('refuses to grant the break-glass role, and writes nothing — the only path to admin is a manual UPDATE in Spanner Studio', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
288
|
+
var _a, _b;
|
|
289
|
+
return __generator(this, function (_c) {
|
|
290
|
+
switch (_c.label) {
|
|
291
|
+
case 0:
|
|
292
|
+
// The error teaches: names break-glass, refuses the grant, and points at the one real path.
|
|
293
|
+
return [4 /*yield*/, expect(new Roles_1.Roles().grantRole(targetId, 'admin')).rejects.toThrow(/break-glass role — this service refuses to grant it.*manual UPDATE on the user row in Spanner Studio/)];
|
|
294
|
+
case 1:
|
|
295
|
+
// The error teaches: names break-glass, refuses the grant, and points at the one real path.
|
|
296
|
+
_c.sent();
|
|
297
|
+
// A refusal is not a role change: no user write, no audit row (the trail records what
|
|
298
|
+
// happened, and nothing happened).
|
|
299
|
+
_a = expect;
|
|
300
|
+
return [4 /*yield*/, targetRoles()];
|
|
301
|
+
case 2:
|
|
302
|
+
// A refusal is not a role change: no user write, no audit row (the trail records what
|
|
303
|
+
// happened, and nothing happened).
|
|
304
|
+
_a.apply(void 0, [_c.sent()]).toEqual([]);
|
|
305
|
+
_b = expect;
|
|
306
|
+
return [4 /*yield*/, auditRows()];
|
|
307
|
+
case 3:
|
|
308
|
+
_b.apply(void 0, [_c.sent()]).toHaveLength(0);
|
|
309
|
+
return [2 /*return*/];
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}); }, 60000);
|
|
313
|
+
it('revokes the break-glass role — de-escalation through the audited path stays available', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
314
|
+
var _a, events;
|
|
315
|
+
return __generator(this, function (_b) {
|
|
316
|
+
switch (_b.label) {
|
|
317
|
+
case 0:
|
|
318
|
+
// Admin arrives only via the manual database path; mirror that here with a system write.
|
|
319
|
+
return [4 /*yield*/, (0, db_1.getDbAsSystem)().update(user_1.tables.User, { id: targetId, roles: ['admin'] })];
|
|
320
|
+
case 1:
|
|
321
|
+
// Admin arrives only via the manual database path; mirror that here with a system write.
|
|
322
|
+
_b.sent();
|
|
323
|
+
return [4 /*yield*/, new Roles_1.Roles().revokeRole(targetId, 'admin')];
|
|
324
|
+
case 2:
|
|
325
|
+
_b.sent();
|
|
326
|
+
_a = expect;
|
|
327
|
+
return [4 /*yield*/, targetRoles()];
|
|
328
|
+
case 3:
|
|
329
|
+
_a.apply(void 0, [_b.sent()]).toEqual([]);
|
|
330
|
+
return [4 /*yield*/, auditRows()];
|
|
331
|
+
case 4:
|
|
332
|
+
events = _b.sent();
|
|
333
|
+
expect(events).toHaveLength(1);
|
|
334
|
+
expect(events[0]).toMatchObject({ actor: 'actor-1', target: targetId, role: 'admin', action: 'revoke' });
|
|
335
|
+
return [2 /*return*/];
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}); }, 60000);
|
|
280
339
|
it('revoking a role the user does not hold changes nothing and writes no audit row', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
281
340
|
var _a, _b;
|
|
282
341
|
return __generator(this, function (_c) {
|
|
@@ -330,5 +389,85 @@ describe('Roles service — grant/revoke outcomes and audit trail', function ()
|
|
|
330
389
|
}
|
|
331
390
|
});
|
|
332
391
|
}); }, 60000);
|
|
392
|
+
it('refuses to grant an admin-grant-only role when the caller is not an admin — and writes nothing', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
393
|
+
var _a, _b;
|
|
394
|
+
return __generator(this, function (_c) {
|
|
395
|
+
switch (_c.label) {
|
|
396
|
+
case 0:
|
|
397
|
+
// A user-admin's full day-to-day grant set — pointedly including 'roles' (the door this
|
|
398
|
+
// service sits behind) — must NOT be able to hand out an admin-grant-only role.
|
|
399
|
+
new user_1.UserRepo().setUser(__assign(__assign({}, actor), { roles: ['users', 'roles', 'sessions'] }));
|
|
400
|
+
_c.label = 1;
|
|
401
|
+
case 1:
|
|
402
|
+
_c.trys.push([1, , 5, 6]);
|
|
403
|
+
return [4 /*yield*/, expect(new Roles_1.Roles().grantRole(targetId, 'data-access')).rejects.toThrow(/can only be granted by an admin/)];
|
|
404
|
+
case 2:
|
|
405
|
+
_c.sent();
|
|
406
|
+
_a = expect;
|
|
407
|
+
return [4 /*yield*/, targetRoles()];
|
|
408
|
+
case 3:
|
|
409
|
+
_a.apply(void 0, [_c.sent()]).toEqual([]);
|
|
410
|
+
_b = expect;
|
|
411
|
+
return [4 /*yield*/, auditRows()];
|
|
412
|
+
case 4:
|
|
413
|
+
_b.apply(void 0, [_c.sent()]).toHaveLength(0);
|
|
414
|
+
return [3 /*break*/, 6];
|
|
415
|
+
case 5:
|
|
416
|
+
new user_1.UserRepo().setUser(actor);
|
|
417
|
+
return [7 /*endfinally*/];
|
|
418
|
+
case 6: return [2 /*return*/];
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}); }, 60000);
|
|
422
|
+
it('grants an admin-grant-only role when the caller IS an admin, audited like any grant', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
423
|
+
var _a, events;
|
|
424
|
+
return __generator(this, function (_b) {
|
|
425
|
+
switch (_b.label) {
|
|
426
|
+
case 0: return [4 /*yield*/, new Roles_1.Roles().grantRole(targetId, 'data-access')];
|
|
427
|
+
case 1:
|
|
428
|
+
_b.sent();
|
|
429
|
+
_a = expect;
|
|
430
|
+
return [4 /*yield*/, targetRoles()];
|
|
431
|
+
case 2:
|
|
432
|
+
_a.apply(void 0, [_b.sent()]).toEqual(['data-access']);
|
|
433
|
+
return [4 /*yield*/, auditRows()];
|
|
434
|
+
case 3:
|
|
435
|
+
events = _b.sent();
|
|
436
|
+
expect(events).toHaveLength(1);
|
|
437
|
+
expect(events[0]).toMatchObject({ actor: 'actor-1', target: targetId, role: 'data-access', action: 'grant' });
|
|
438
|
+
return [2 /*return*/];
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
}); }, 60000);
|
|
442
|
+
it('a roles-holder can still REVOKE an admin-grant-only role — de-escalation stays open (the break-glass precedent)', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
443
|
+
var _a, _b;
|
|
444
|
+
return __generator(this, function (_c) {
|
|
445
|
+
switch (_c.label) {
|
|
446
|
+
case 0: return [4 /*yield*/, new Roles_1.Roles().grantRole(targetId, 'data-access')];
|
|
447
|
+
case 1:
|
|
448
|
+
_c.sent();
|
|
449
|
+
new user_1.UserRepo().setUser(__assign(__assign({}, actor), { roles: ['roles'] }));
|
|
450
|
+
_c.label = 2;
|
|
451
|
+
case 2:
|
|
452
|
+
_c.trys.push([2, , 6, 7]);
|
|
453
|
+
return [4 /*yield*/, new Roles_1.Roles().revokeRole(targetId, 'data-access')];
|
|
454
|
+
case 3:
|
|
455
|
+
_c.sent();
|
|
456
|
+
_a = expect;
|
|
457
|
+
return [4 /*yield*/, targetRoles()];
|
|
458
|
+
case 4:
|
|
459
|
+
_a.apply(void 0, [_c.sent()]).toEqual([]);
|
|
460
|
+
_b = expect;
|
|
461
|
+
return [4 /*yield*/, auditRows()];
|
|
462
|
+
case 5:
|
|
463
|
+
_b.apply(void 0, [(_c.sent()).map(function (event) { return event.action; }).sort()]).toEqual(['grant', 'revoke']);
|
|
464
|
+
return [3 /*break*/, 7];
|
|
465
|
+
case 6:
|
|
466
|
+
new user_1.UserRepo().setUser(actor);
|
|
467
|
+
return [7 /*endfinally*/];
|
|
468
|
+
case 7: return [2 /*return*/];
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}); }, 60000);
|
|
333
472
|
});
|
|
334
473
|
//# sourceMappingURL=Roles.integration.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Roles.integration.test.js","sourceRoot":"","sources":["../../test/Roles.integration.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAA4B;AAC5B,oCAAyD;AACzD,kEAA6D;AAC7D,0DAAiG;AACjG,oDAAyD;AACzD,oDAAiF;AACjF,wCAAuF;AACvF,+CAA8C;AAE9C;;;;;;;GAOG;AAEH;IAAA;QACE,gBAAW,GAAG,MAAgB,CAAC;IAUjC,CAAC;IAPC,wCAAO,GAAP,UAAQ,IAAiB;QACvB,sBAAsB,CAAC,YAAY,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IAC5D,CAAC;IAED,wCAAO,GAAP;QACE,OAAO,sBAAsB,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;IAC5D,CAAC;IARM,mCAAY,GAAkC,EAAE,AAApC,CAAqC;IAS1D,6BAAC;CAAA,AAXD,IAWC;AAED,IAAM,aAAa,GAAG,IAAI,iCAAa,CAAC;IACtC,SAAS,EAAE,gBAAgB;IAC3B,YAAY,EAAE,gBAAgB;IAC9B,YAAY,EAAE,MAAM;CACrB,CAAC,CAAC;AAEH;IAAA;IAIA,CAAC;IAHC,qCAAW,GAAX;QACE,OAAO,aAAa,CAAC;IACvB,CAAC;IACH,sBAAC;AAAD,CAAC,AAJD,IAIC;AAED,IAAM,KAAK,GAAG;IACZ,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,oBAAoB;IAC3B,QAAQ,EAAE,MAAM;IAChB,aAAa,EAAE,IAAI;IACnB,KAAK,EAAE,CAAC,OAAO,CAAC;IAChB,OAAO,EAAE,IAAA,gBAAM,GAAE;IACjB,OAAO,EAAE,IAAA,gBAAM,GAAE;IACjB,EAAE,EAAE,SAAS;CACd,CAAC;AAEF,IAAM,SAAS,GAAG,IAAA,uBAAgB,EAAC,aAAa,CAAC,CAAC;AAElD,QAAQ,CAAC,uDAAuD,EAAE;IAChE,SAAS,CAAC;;;;oBACP,6BAAgB,CAAC,GAAG,EAAU,CAAC,WAAW,CAAC,sCAAsC,CAAC,GAAG,CAAC,IAAI,eAAe,EAAE,CAAC,CAAC;oBAC7G,6BAAgB,CAAC,GAAG,EAAU,CAAC,WAAW,CAAC,0CAA0C,CAAC,GAAG;wBACxF,IAAI,sBAAsB,EAAE;qBAC7B,CAAC;oBACD,6BAAgB,CAAC,GAAG,EAAU,CAAC,WAAW,CAAC,kCAAkC,CAAC,GAAG;wBAChF,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAsB;
|
|
1
|
+
{"version":3,"file":"Roles.integration.test.js","sourceRoot":"","sources":["../../test/Roles.integration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SweepMalformedAccessGrants.integration.test.d.ts","sourceRoot":"","sources":["../../test/SweepMalformedAccessGrants.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
var db_1 = require("@proteinjs/db");
|
|
40
|
+
var reflection_1 = require("@proteinjs/reflection");
|
|
41
|
+
var user_1 = require("@proteinjs/user");
|
|
42
|
+
var SweepMalformedAccessGrants_1 = require("../src/migrations/SweepMalformedAccessGrants");
|
|
43
|
+
var UserServerTestEnvironment_1 = require("./UserServerTestEnvironment");
|
|
44
|
+
var testEnv = new UserServerTestEnvironment_1.UserServerTestEnvironment();
|
|
45
|
+
var objectCache = function () { return reflection_1.SourceRepository.get().objectCache; };
|
|
46
|
+
/** Same harness cast as MachineAccounts.integration.test.ts: recompute the static watcher map on the next Db. */
|
|
47
|
+
var resetTableWatcherMap = function () {
|
|
48
|
+
var runner = (0, db_1.getDbAsSystem)().tableWatcherRunner;
|
|
49
|
+
runner.constructor.tableWatcherMap = undefined;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Observes access_grant deletes the way the app's watchers do (ContentReferenceAccessGrantTableWatcher
|
|
53
|
+
* lives in space-common, above this package): a delete that rides the Db layer lands here with the
|
|
54
|
+
* deleted rows; a raw-SQL delete never would.
|
|
55
|
+
*/
|
|
56
|
+
var RecordingAccessGrantWatcher = /** @class */ (function () {
|
|
57
|
+
function RecordingAccessGrantWatcher() {
|
|
58
|
+
}
|
|
59
|
+
RecordingAccessGrantWatcher.prototype.name = function () {
|
|
60
|
+
return 'RecordingAccessGrantWatcher';
|
|
61
|
+
};
|
|
62
|
+
RecordingAccessGrantWatcher.prototype.table = function () {
|
|
63
|
+
return user_1.tables.AccessGrant;
|
|
64
|
+
};
|
|
65
|
+
RecordingAccessGrantWatcher.prototype.afterDelete = function (_count, deletedRecords) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
67
|
+
var _i, deletedRecords_1, record;
|
|
68
|
+
return __generator(this, function (_a) {
|
|
69
|
+
for (_i = 0, deletedRecords_1 = deletedRecords; _i < deletedRecords_1.length; _i++) {
|
|
70
|
+
record = deletedRecords_1[_i];
|
|
71
|
+
RecordingAccessGrantWatcher.deletedIds.push(record.id);
|
|
72
|
+
}
|
|
73
|
+
return [2 /*return*/];
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
RecordingAccessGrantWatcher.deletedIds = [];
|
|
78
|
+
return RecordingAccessGrantWatcher;
|
|
79
|
+
}());
|
|
80
|
+
/**
|
|
81
|
+
* SweepMalformedAccessGrants (one-time migration): the AccessGrant well-formedness invariant now
|
|
82
|
+
* refuses a grant with no principal or resource id, but the rows its two pre-fix producers left
|
|
83
|
+
* behind (the test environment's NULL-principal `thought` grant that crashed Deploy to Test
|
|
84
|
+
* 32614670162) are still there. The sweep deletes EXACTLY the invariant's complement
|
|
85
|
+
* (`principal IS NULL OR resource IS NULL`) by id through the Db layer — watchers fire — and
|
|
86
|
+
* nothing else: well-formed grants survive, and DANGLING grants (non-null ids pointing at purged
|
|
87
|
+
* rows) are reported, never deleted. Idempotent.
|
|
88
|
+
*
|
|
89
|
+
* Red-first: written against the contract; a no-op sweep fails the delete legs, and a sweep on
|
|
90
|
+
* the wrong predicate fails the well-formed-survives legs.
|
|
91
|
+
*/
|
|
92
|
+
describe('SweepMalformedAccessGrants — deletes exactly the malformed rows through the Db layer; reports dangling; idempotent', function () {
|
|
93
|
+
var owner;
|
|
94
|
+
var grantee;
|
|
95
|
+
beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
96
|
+
return __generator(this, function (_a) {
|
|
97
|
+
switch (_a.label) {
|
|
98
|
+
case 0: return [4 /*yield*/, testEnv.beforeAll()];
|
|
99
|
+
case 1:
|
|
100
|
+
_a.sent();
|
|
101
|
+
// The dangling report resolves resource tables by name off the source graph (none is loaded
|
|
102
|
+
// in tests) — register exactly this package's tables, as the app's composition would.
|
|
103
|
+
objectCache()['@proteinjs/db/Table'] = Object.values(user_1.tables);
|
|
104
|
+
objectCache()['@proteinjs/db/TableWatcher'] = [new RecordingAccessGrantWatcher()];
|
|
105
|
+
resetTableWatcherMap();
|
|
106
|
+
return [2 /*return*/];
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}); }, 120000);
|
|
110
|
+
afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
111
|
+
return __generator(this, function (_a) {
|
|
112
|
+
switch (_a.label) {
|
|
113
|
+
case 0:
|
|
114
|
+
delete objectCache()['@proteinjs/db/Table'];
|
|
115
|
+
delete objectCache()['@proteinjs/db/TableWatcher'];
|
|
116
|
+
resetTableWatcherMap();
|
|
117
|
+
return [4 /*yield*/, testEnv.afterAll()];
|
|
118
|
+
case 1:
|
|
119
|
+
_a.sent();
|
|
120
|
+
return [2 /*return*/];
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}); }, 60000);
|
|
124
|
+
beforeEach(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
125
|
+
var db;
|
|
126
|
+
return __generator(this, function (_a) {
|
|
127
|
+
switch (_a.label) {
|
|
128
|
+
case 0:
|
|
129
|
+
db = (0, db_1.getDbAsSystem)();
|
|
130
|
+
return [4 /*yield*/, db.delete(user_1.tables.AccessGrant, {})];
|
|
131
|
+
case 1:
|
|
132
|
+
_a.sent();
|
|
133
|
+
return [4 /*yield*/, db.delete(user_1.tables.User, {})];
|
|
134
|
+
case 2:
|
|
135
|
+
_a.sent();
|
|
136
|
+
RecordingAccessGrantWatcher.deletedIds = [];
|
|
137
|
+
return [4 /*yield*/, testEnv.createUser({ name: 'Owner', email: 'owner@test.local' })];
|
|
138
|
+
case 3:
|
|
139
|
+
owner = _a.sent();
|
|
140
|
+
return [4 /*yield*/, testEnv.createUser({ name: 'Grantee', email: 'grantee@test.local' })];
|
|
141
|
+
case 4:
|
|
142
|
+
grantee = _a.sent();
|
|
143
|
+
return [2 /*return*/];
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}); });
|
|
147
|
+
/** A well-formed grant, inserted as system (the invariant admits it). Resource rides the user table here. */
|
|
148
|
+
function insertGrant(args) {
|
|
149
|
+
var _a;
|
|
150
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
151
|
+
var resourceTable;
|
|
152
|
+
return __generator(this, function (_b) {
|
|
153
|
+
switch (_b.label) {
|
|
154
|
+
case 0:
|
|
155
|
+
resourceTable = (_a = args.resourceTable) !== null && _a !== void 0 ? _a : user_1.tables.User.name;
|
|
156
|
+
return [4 /*yield*/, (0, db_1.getDbAsSystem)().insert(user_1.tables.AccessGrant, {
|
|
157
|
+
principal: new db_1.Reference(user_1.tables.User.name, args.principalId),
|
|
158
|
+
resource: new db_1.Reference(resourceTable, args.resourceId),
|
|
159
|
+
resourceTable: resourceTable,
|
|
160
|
+
accessLevel: args.accessLevel,
|
|
161
|
+
})];
|
|
162
|
+
case 1: return [2 /*return*/, _b.sent()];
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Seed the malformed shape BENEATH the insert invariant (which rightly refuses it now): insert
|
|
169
|
+
* well-formed, then null the reference in place — the row the environment actually holds.
|
|
170
|
+
*/
|
|
171
|
+
function nullOut(grantId, field) {
|
|
172
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
173
|
+
var _a;
|
|
174
|
+
return __generator(this, function (_b) {
|
|
175
|
+
switch (_b.label) {
|
|
176
|
+
case 0: return [4 /*yield*/, (0, db_1.getDbAsSystem)().update(user_1.tables.AccessGrant, (_a = {}, _a[field] = null, _a), { id: grantId })];
|
|
177
|
+
case 1:
|
|
178
|
+
_b.sent();
|
|
179
|
+
return [2 /*return*/];
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function grantById(id) {
|
|
185
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
186
|
+
return __generator(this, function (_a) {
|
|
187
|
+
switch (_a.label) {
|
|
188
|
+
case 0: return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(user_1.tables.AccessGrant, { id: id })];
|
|
189
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function allGrantIds() {
|
|
195
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
196
|
+
var grants;
|
|
197
|
+
return __generator(this, function (_a) {
|
|
198
|
+
switch (_a.label) {
|
|
199
|
+
case 0: return [4 /*yield*/, (0, db_1.getDbAsSystem)().query(user_1.tables.AccessGrant, new db_1.QueryBuilder(user_1.tables.AccessGrant.name))];
|
|
200
|
+
case 1:
|
|
201
|
+
grants = _a.sent();
|
|
202
|
+
return [2 /*return*/, grants.map(function (grant) { return grant.id; }).sort()];
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
it('dry run reports without deleting; real run deletes exactly the malformed rows (watchers fire); well-formed and dangling survive; re-run is a no-op', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
208
|
+
var ownerGrant, readGrant, nullPrincipal, nullResource, purgedPrincipal, purgedResource, unregisteredTable, _a, _b, seededIds, _c, sweep, dry, _d, real, _e, _f, _g, _h, _j, _k, _l, _m, dangling, _o, again, _p;
|
|
209
|
+
var _q, _r, _s, _t, _u, _v;
|
|
210
|
+
return __generator(this, function (_w) {
|
|
211
|
+
switch (_w.label) {
|
|
212
|
+
case 0: return [4 /*yield*/, insertGrant({ principalId: owner.id, resourceId: owner.id, accessLevel: 'owner' })];
|
|
213
|
+
case 1:
|
|
214
|
+
ownerGrant = _w.sent();
|
|
215
|
+
return [4 /*yield*/, insertGrant({ principalId: grantee.id, resourceId: owner.id, accessLevel: 'read' })];
|
|
216
|
+
case 2:
|
|
217
|
+
readGrant = _w.sent();
|
|
218
|
+
return [4 /*yield*/, insertGrant({ principalId: grantee.id, resourceId: owner.id, accessLevel: 'write' })];
|
|
219
|
+
case 3:
|
|
220
|
+
nullPrincipal = _w.sent();
|
|
221
|
+
return [4 /*yield*/, nullOut(nullPrincipal.id, 'principal')];
|
|
222
|
+
case 4:
|
|
223
|
+
_w.sent();
|
|
224
|
+
return [4 /*yield*/, insertGrant({ principalId: grantee.id, resourceId: owner.id, accessLevel: 'admin' })];
|
|
225
|
+
case 5:
|
|
226
|
+
nullResource = _w.sent();
|
|
227
|
+
return [4 /*yield*/, nullOut(nullResource.id, 'resource')];
|
|
228
|
+
case 6:
|
|
229
|
+
_w.sent();
|
|
230
|
+
return [4 /*yield*/, insertGrant({
|
|
231
|
+
principalId: 'purged-user',
|
|
232
|
+
resourceId: owner.id,
|
|
233
|
+
accessLevel: 'read',
|
|
234
|
+
})];
|
|
235
|
+
case 7:
|
|
236
|
+
purgedPrincipal = _w.sent();
|
|
237
|
+
return [4 /*yield*/, insertGrant({
|
|
238
|
+
principalId: grantee.id,
|
|
239
|
+
resourceId: 'purged-row',
|
|
240
|
+
accessLevel: 'read',
|
|
241
|
+
})];
|
|
242
|
+
case 8:
|
|
243
|
+
purgedResource = _w.sent();
|
|
244
|
+
return [4 /*yield*/, insertGrant({
|
|
245
|
+
principalId: grantee.id,
|
|
246
|
+
resourceId: 'some-thought',
|
|
247
|
+
resourceTable: 'thought',
|
|
248
|
+
accessLevel: 'write',
|
|
249
|
+
})];
|
|
250
|
+
case 9:
|
|
251
|
+
unregisteredTable = _w.sent();
|
|
252
|
+
_a = expect;
|
|
253
|
+
return [4 /*yield*/, grantById(nullPrincipal.id)];
|
|
254
|
+
case 10:
|
|
255
|
+
_a.apply(void 0, [(_r = (_q = (_w.sent())) === null || _q === void 0 ? void 0 : _q.principal) !== null && _r !== void 0 ? _r : null]).toBeNull();
|
|
256
|
+
_b = expect;
|
|
257
|
+
return [4 /*yield*/, grantById(nullResource.id)];
|
|
258
|
+
case 11:
|
|
259
|
+
_b.apply(void 0, [(_t = (_s = (_w.sent())) === null || _s === void 0 ? void 0 : _s.resource) !== null && _t !== void 0 ? _t : null]).toBeNull();
|
|
260
|
+
seededIds = [
|
|
261
|
+
ownerGrant.id,
|
|
262
|
+
readGrant.id,
|
|
263
|
+
nullPrincipal.id,
|
|
264
|
+
nullResource.id,
|
|
265
|
+
purgedPrincipal.id,
|
|
266
|
+
purgedResource.id,
|
|
267
|
+
unregisteredTable.id,
|
|
268
|
+
].sort();
|
|
269
|
+
_c = expect;
|
|
270
|
+
return [4 /*yield*/, allGrantIds()];
|
|
271
|
+
case 12:
|
|
272
|
+
_c.apply(void 0, [_w.sent()]).toEqual(seededIds);
|
|
273
|
+
sweep = new SweepMalformedAccessGrants_1.SweepMalformedAccessGrants();
|
|
274
|
+
return [4 /*yield*/, sweep.sweep({ dryRun: true })];
|
|
275
|
+
case 13:
|
|
276
|
+
dry = _w.sent();
|
|
277
|
+
expect(dry.dryRun).toBe(true);
|
|
278
|
+
expect(dry.malformed).toBe(2);
|
|
279
|
+
expect(dry.rows.map(function (row) { return row.id; }).sort()).toEqual([nullPrincipal.id, nullResource.id].sort());
|
|
280
|
+
expect(dry.rows.find(function (row) { return row.id === nullPrincipal.id; })).toMatchObject({
|
|
281
|
+
resourceTable: user_1.tables.User.name,
|
|
282
|
+
accessLevel: 'write',
|
|
283
|
+
});
|
|
284
|
+
expect(dry.rows.every(function (row) { return typeof row.created === 'string' && row.created.length > 0; })).toBe(true);
|
|
285
|
+
_d = expect;
|
|
286
|
+
return [4 /*yield*/, allGrantIds()];
|
|
287
|
+
case 14:
|
|
288
|
+
_d.apply(void 0, [_w.sent()]).toEqual(seededIds);
|
|
289
|
+
expect(RecordingAccessGrantWatcher.deletedIds).toEqual([]);
|
|
290
|
+
return [4 /*yield*/, sweep.sweep({ dryRun: false })];
|
|
291
|
+
case 15:
|
|
292
|
+
real = _w.sent();
|
|
293
|
+
expect(real.dryRun).toBe(false);
|
|
294
|
+
expect(real.malformed).toBe(2);
|
|
295
|
+
_e = expect;
|
|
296
|
+
return [4 /*yield*/, grantById(nullPrincipal.id)];
|
|
297
|
+
case 16:
|
|
298
|
+
_e.apply(void 0, [_w.sent()]).toBeUndefined();
|
|
299
|
+
_f = expect;
|
|
300
|
+
return [4 /*yield*/, grantById(nullResource.id)];
|
|
301
|
+
case 17:
|
|
302
|
+
_f.apply(void 0, [_w.sent()]).toBeUndefined();
|
|
303
|
+
// The after-delete watchers saw precisely the deleted rows — the delete rode the Db layer.
|
|
304
|
+
expect(RecordingAccessGrantWatcher.deletedIds.sort()).toEqual([nullPrincipal.id, nullResource.id].sort());
|
|
305
|
+
// Well-formed rows intact, levels unchanged.
|
|
306
|
+
_g = expect;
|
|
307
|
+
return [4 /*yield*/, grantById(ownerGrant.id)];
|
|
308
|
+
case 18:
|
|
309
|
+
// Well-formed rows intact, levels unchanged.
|
|
310
|
+
_g.apply(void 0, [(_u = (_w.sent())) === null || _u === void 0 ? void 0 : _u.accessLevel]).toBe('owner');
|
|
311
|
+
_h = expect;
|
|
312
|
+
return [4 /*yield*/, grantById(readGrant.id)];
|
|
313
|
+
case 19:
|
|
314
|
+
_h.apply(void 0, [(_v = (_w.sent())) === null || _v === void 0 ? void 0 : _v.accessLevel]).toBe('read');
|
|
315
|
+
// Dangling rows intact — reported below, not deleted.
|
|
316
|
+
_j = expect;
|
|
317
|
+
return [4 /*yield*/, grantById(purgedPrincipal.id)];
|
|
318
|
+
case 20:
|
|
319
|
+
// Dangling rows intact — reported below, not deleted.
|
|
320
|
+
_j.apply(void 0, [_w.sent()]).toBeDefined();
|
|
321
|
+
_k = expect;
|
|
322
|
+
return [4 /*yield*/, grantById(purgedResource.id)];
|
|
323
|
+
case 21:
|
|
324
|
+
_k.apply(void 0, [_w.sent()]).toBeDefined();
|
|
325
|
+
_l = expect;
|
|
326
|
+
return [4 /*yield*/, grantById(unregisteredTable.id)];
|
|
327
|
+
case 22:
|
|
328
|
+
_l.apply(void 0, [_w.sent()]).toBeDefined();
|
|
329
|
+
_m = expect;
|
|
330
|
+
return [4 /*yield*/, allGrantIds()];
|
|
331
|
+
case 23:
|
|
332
|
+
_m.apply(void 0, [_w.sent()]).toEqual([ownerGrant.id, readGrant.id, purgedPrincipal.id, purgedResource.id, unregisteredTable.id].sort());
|
|
333
|
+
return [4 /*yield*/, sweep.reportDangling()];
|
|
334
|
+
case 24:
|
|
335
|
+
dangling = _w.sent();
|
|
336
|
+
expect(dangling.danglingPrincipal).toBe(1);
|
|
337
|
+
expect(dangling.danglingResource).toBe(2);
|
|
338
|
+
expect(dangling.rows.find(function (row) { return row.id === purgedPrincipal.id; })).toMatchObject({
|
|
339
|
+
principal: 'purged-user',
|
|
340
|
+
reason: 'principal missing',
|
|
341
|
+
});
|
|
342
|
+
expect(dangling.rows.find(function (row) { return row.id === purgedResource.id; })).toMatchObject({
|
|
343
|
+
resource: 'purged-row',
|
|
344
|
+
reason: 'resource missing',
|
|
345
|
+
});
|
|
346
|
+
expect(dangling.rows.find(function (row) { return row.id === unregisteredTable.id; })).toMatchObject({
|
|
347
|
+
resourceTable: 'thought',
|
|
348
|
+
reason: 'resource table not registered',
|
|
349
|
+
});
|
|
350
|
+
expect(dangling.rows.map(function (row) { return row.id; })).not.toContain(ownerGrant.id);
|
|
351
|
+
expect(dangling.rows.map(function (row) { return row.id; })).not.toContain(readGrant.id);
|
|
352
|
+
_o = expect;
|
|
353
|
+
return [4 /*yield*/, allGrantIds()];
|
|
354
|
+
case 25:
|
|
355
|
+
_o.apply(void 0, [_w.sent()]).toEqual([ownerGrant.id, readGrant.id, purgedPrincipal.id, purgedResource.id, unregisteredTable.id].sort());
|
|
356
|
+
// ---------- IDEMPOTENCY: a second real run finds nothing, deletes nothing ----------
|
|
357
|
+
RecordingAccessGrantWatcher.deletedIds = [];
|
|
358
|
+
return [4 /*yield*/, sweep.sweep({ dryRun: false })];
|
|
359
|
+
case 26:
|
|
360
|
+
again = _w.sent();
|
|
361
|
+
expect(again.malformed).toBe(0);
|
|
362
|
+
expect(again.rows).toEqual([]);
|
|
363
|
+
expect(RecordingAccessGrantWatcher.deletedIds).toEqual([]);
|
|
364
|
+
_p = expect;
|
|
365
|
+
return [4 /*yield*/, allGrantIds()];
|
|
366
|
+
case 27:
|
|
367
|
+
_p.apply(void 0, [_w.sent()]).toEqual([ownerGrant.id, readGrant.id, purgedPrincipal.id, purgedResource.id, unregisteredTable.id].sort());
|
|
368
|
+
return [2 /*return*/];
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}); }, 120000);
|
|
372
|
+
it('the migration record runs dry-run pass, delete pass, and dangling report; its output carries the rows; a second run is zero', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
373
|
+
var ownerGrant, nullPrincipal, purgedPrincipal, migration, output, _a, second, _b;
|
|
374
|
+
return __generator(this, function (_c) {
|
|
375
|
+
switch (_c.label) {
|
|
376
|
+
case 0: return [4 /*yield*/, insertGrant({ principalId: owner.id, resourceId: owner.id, accessLevel: 'owner' })];
|
|
377
|
+
case 1:
|
|
378
|
+
ownerGrant = _c.sent();
|
|
379
|
+
return [4 /*yield*/, insertGrant({ principalId: grantee.id, resourceId: owner.id, accessLevel: 'read' })];
|
|
380
|
+
case 2:
|
|
381
|
+
nullPrincipal = _c.sent();
|
|
382
|
+
return [4 /*yield*/, nullOut(nullPrincipal.id, 'principal')];
|
|
383
|
+
case 3:
|
|
384
|
+
_c.sent();
|
|
385
|
+
return [4 /*yield*/, insertGrant({
|
|
386
|
+
principalId: 'purged-user',
|
|
387
|
+
resourceId: owner.id,
|
|
388
|
+
accessLevel: 'read',
|
|
389
|
+
})];
|
|
390
|
+
case 4:
|
|
391
|
+
purgedPrincipal = _c.sent();
|
|
392
|
+
migration = new SweepMalformedAccessGrants_1.SweepMalformedAccessGrants();
|
|
393
|
+
expect(migration.table.name).toBe('migration');
|
|
394
|
+
return [4 /*yield*/, migration.record.run()];
|
|
395
|
+
case 5:
|
|
396
|
+
output = _c.sent();
|
|
397
|
+
expect(output).toMatchObject({
|
|
398
|
+
malformedDryRun: 1,
|
|
399
|
+
malformed: 1,
|
|
400
|
+
danglingPrincipal: 1,
|
|
401
|
+
danglingResource: 0,
|
|
402
|
+
});
|
|
403
|
+
expect(output.malformedRows.map(function (row) { return row.id; })).toEqual([nullPrincipal.id]);
|
|
404
|
+
expect(output.danglingRows.map(function (row) { return row.id; })).toEqual([purgedPrincipal.id]);
|
|
405
|
+
_a = expect;
|
|
406
|
+
return [4 /*yield*/, allGrantIds()];
|
|
407
|
+
case 6:
|
|
408
|
+
_a.apply(void 0, [_c.sent()]).toEqual([ownerGrant.id, purgedPrincipal.id].sort());
|
|
409
|
+
expect(RecordingAccessGrantWatcher.deletedIds).toEqual([nullPrincipal.id]);
|
|
410
|
+
return [4 /*yield*/, migration.record.run()];
|
|
411
|
+
case 7:
|
|
412
|
+
second = _c.sent();
|
|
413
|
+
expect(second).toMatchObject({ malformedDryRun: 0, malformed: 0, danglingPrincipal: 1, danglingResource: 0 });
|
|
414
|
+
expect(second.malformedRows).toEqual([]);
|
|
415
|
+
_b = expect;
|
|
416
|
+
return [4 /*yield*/, allGrantIds()];
|
|
417
|
+
case 8:
|
|
418
|
+
_b.apply(void 0, [_c.sent()]).toEqual([ownerGrant.id, purgedPrincipal.id].sort());
|
|
419
|
+
return [2 /*return*/];
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}); }, 120000);
|
|
423
|
+
});
|
|
424
|
+
//# sourceMappingURL=SweepMalformedAccessGrants.integration.test.js.map
|