@things-factory/auth-base 10.1.192 → 10.1.198

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 (98) hide show
  1. package/dist-client/tsconfig.tsbuildinfo +1 -1
  2. package/dist-server/controllers/signin.d.ts +5 -0
  3. package/dist-server/controllers/signin.js +25 -0
  4. package/dist-server/controllers/signin.js.map +1 -1
  5. package/dist-server/index.js +7 -0
  6. package/dist-server/index.js.map +1 -1
  7. package/dist-server/middlewares/audit-scope-middleware.d.ts +7 -0
  8. package/dist-server/middlewares/audit-scope-middleware.js +15 -0
  9. package/dist-server/middlewares/audit-scope-middleware.js.map +1 -0
  10. package/dist-server/middlewares/index.js +3 -0
  11. package/dist-server/middlewares/index.js.map +1 -1
  12. package/dist-server/role-tasks.d.ts +2 -0
  13. package/dist-server/role-tasks.js +20 -0
  14. package/dist-server/role-tasks.js.map +1 -0
  15. package/dist-server/router/auth-checkin-router.js +3 -5
  16. package/dist-server/router/auth-checkin-router.js.map +1 -1
  17. package/dist-server/router/auth-public-process-router.js +7 -1
  18. package/dist-server/router/auth-public-process-router.js.map +1 -1
  19. package/dist-server/router/site-root-router.d.ts +2 -0
  20. package/dist-server/router/site-root-router.js +2 -0
  21. package/dist-server/router/site-root-router.js.map +1 -1
  22. package/dist-server/service/access-request/index.d.ts +1 -1
  23. package/dist-server/service/audit/audit-action.d.ts +36 -0
  24. package/dist-server/service/audit/audit-action.js +137 -0
  25. package/dist-server/service/audit/audit-action.js.map +1 -0
  26. package/dist-server/service/audit/audit-kinds.d.ts +19 -0
  27. package/dist-server/service/audit/audit-kinds.js +70 -0
  28. package/dist-server/service/audit/audit-kinds.js.map +1 -0
  29. package/dist-server/service/audit/audit-query.d.ts +11 -0
  30. package/dist-server/service/audit/audit-query.js +295 -0
  31. package/dist-server/service/audit/audit-query.js.map +1 -0
  32. package/dist-server/service/audit/audit-recorder.d.ts +77 -0
  33. package/dist-server/service/audit/audit-recorder.js +247 -0
  34. package/dist-server/service/audit/audit-recorder.js.map +1 -0
  35. package/dist-server/service/audit/audit-request.d.ts +28 -0
  36. package/dist-server/service/audit/audit-request.js +96 -0
  37. package/dist-server/service/audit/audit-request.js.map +1 -0
  38. package/dist-server/service/audit/audit-retention.d.ts +12 -0
  39. package/dist-server/service/audit/audit-retention.js +42 -0
  40. package/dist-server/service/audit/audit-retention.js.map +1 -0
  41. package/dist-server/service/audit/audit-subscriber.d.ts +39 -0
  42. package/dist-server/service/audit/audit-subscriber.js +191 -0
  43. package/dist-server/service/audit/audit-subscriber.js.map +1 -0
  44. package/dist-server/service/audit/audit-target.d.ts +19 -0
  45. package/dist-server/service/audit/audit-target.js +46 -0
  46. package/dist-server/service/audit/audit-target.js.map +1 -0
  47. package/dist-server/service/audit/audit-types.d.ts +44 -0
  48. package/dist-server/service/audit/audit-types.js +161 -0
  49. package/dist-server/service/audit/audit-types.js.map +1 -0
  50. package/dist-server/service/audit/index.d.ts +6 -0
  51. package/dist-server/service/audit/index.js +11 -0
  52. package/dist-server/service/audit/index.js.map +1 -0
  53. package/dist-server/service/domain-owner/domain-owner-mutation.js +3 -0
  54. package/dist-server/service/domain-owner/domain-owner-mutation.js.map +1 -1
  55. package/dist-server/service/index.d.ts +9 -2
  56. package/dist-server/service/index.js +19 -4
  57. package/dist-server/service/index.js.map +1 -1
  58. package/dist-server/service/open-fields.js +1 -1
  59. package/dist-server/service/open-fields.js.map +1 -1
  60. package/dist-server/service/privilege/privilege-directive.js +25 -1
  61. package/dist-server/service/privilege/privilege-directive.js.map +1 -1
  62. package/dist-server/service/role/role-mutation.js +8 -1
  63. package/dist-server/service/role/role-mutation.js.map +1 -1
  64. package/dist-server/service/role-template/index.d.ts +1 -1
  65. package/dist-server/service/role-template/role-task.d.ts +74 -0
  66. package/dist-server/service/role-template/role-task.js +113 -0
  67. package/dist-server/service/role-template/role-task.js.map +1 -0
  68. package/dist-server/service/role-template/role-template-gap-resolver.d.ts +9 -5
  69. package/dist-server/service/role-template/role-template-gap-resolver.js +41 -13
  70. package/dist-server/service/role-template/role-template-gap-resolver.js.map +1 -1
  71. package/dist-server/service/role-template/role-template-mutation.js +10 -1
  72. package/dist-server/service/role-template/role-template-mutation.js.map +1 -1
  73. package/dist-server/service/role-template/role-template-query.js +4 -2
  74. package/dist-server/service/role-template/role-template-query.js.map +1 -1
  75. package/dist-server/service/role-template/role-template-types.d.ts +24 -0
  76. package/dist-server/service/role-template/role-template-types.js +63 -1
  77. package/dist-server/service/role-template/role-template-types.js.map +1 -1
  78. package/dist-server/service/role-template/role-template.d.ts +63 -10
  79. package/dist-server/service/role-template/role-template.js +92 -18
  80. package/dist-server/service/role-template/role-template.js.map +1 -1
  81. package/dist-server/tsconfig.tsbuildinfo +1 -1
  82. package/dist-server/utils/check-permission.d.ts +6 -0
  83. package/dist-server/utils/check-permission.js +23 -8
  84. package/dist-server/utils/check-permission.js.map +1 -1
  85. package/package.json +3 -2
  86. package/tests/audit-db.test.ts +394 -0
  87. package/tests/auth-base-role-tasks.test.ts +58 -0
  88. package/tests/list-inheritance-db.test.ts +0 -7
  89. package/tests/open-doors.test.ts +2 -2
  90. package/tests/refusal-cause.test.ts +1 -1
  91. package/tests/role-task.test.ts +168 -0
  92. package/tests/role-template-gap-db.test.ts +77 -2
  93. package/translations/en.json +13 -1
  94. package/translations/es.json +13 -1
  95. package/translations/ja.json +13 -1
  96. package/translations/ko.json +13 -1
  97. package/translations/ms.json +13 -1
  98. package/translations/zh.json +13 -1
@@ -0,0 +1,295 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuditQuery = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const typeorm_1 = require("typeorm");
7
+ const shell_1 = require("@things-factory/shell");
8
+ const domain_ownership_js_1 = require("../../utils/domain-ownership.js");
9
+ const get_domain_users_js_1 = require("../../utils/get-domain-users.js");
10
+ const privilege_js_1 = require("../privilege/privilege.js");
11
+ const role_js_1 = require("../role/role.js");
12
+ const user_js_1 = require("../user/user.js");
13
+ const audit_action_js_1 = require("./audit-action.js");
14
+ const audit_recorder_js_1 = require("./audit-recorder.js");
15
+ const audit_request_js_1 = require("./audit-request.js");
16
+ const audit_types_js_1 = require("./audit-types.js");
17
+ /*
18
+ * Everybody's audit is `audit:query` (ADR-0141 §5). The domain owner does not pass on ownership - this is
19
+ * a sensitive door (ADR-0139) - and the superuser does, as a stated exception whose basis the check records.
20
+ * Every answer is this domain's only.
21
+ */
22
+ const AUDIT_QUERY = '@privilege(category: "audit", privilege: "query", superUserGranted: true)';
23
+ /* Reading the audit is recorded like any door, and left out of usage counts (§5-8 · §6.3). */
24
+ const AUDIT_DOOR = 'audit:query';
25
+ const MAX_PAGE = 200;
26
+ function period(from, to) {
27
+ return { from: new Date(from), to: new Date(to) };
28
+ }
29
+ let AuditQuery = class AuditQuery {
30
+ async auditRecords(filter, context) {
31
+ const { domain } = context.state;
32
+ const { from, to } = period(filter.from, filter.to);
33
+ const limit = Math.min(Math.max(filter.limit || 50, 1), MAX_PAGE);
34
+ const page = Math.max(filter.page || 1, 1);
35
+ const qb = (0, shell_1.getRepository)(audit_request_js_1.AuditRequest)
36
+ .createQueryBuilder('request')
37
+ .leftJoinAndSelect('request.actor', 'actor')
38
+ .where('request.domain = :domainId', { domainId: domain.id })
39
+ .andWhere('request.createdAt >= :from AND request.createdAt <= :to', { from, to });
40
+ if (filter.actorId)
41
+ qb.andWhere('request.actor = :actorId', { actorId: filter.actorId });
42
+ /* A request is kept when one of its actions matches - the request line alone says nothing of doors. */
43
+ const conditions = [];
44
+ const params = {};
45
+ if (filter.door) {
46
+ conditions.push('action.door = :door');
47
+ params.door = filter.door;
48
+ }
49
+ if (filter.kind) {
50
+ conditions.push('action.kind = :kind');
51
+ params.kind = filter.kind;
52
+ }
53
+ if (filter.outcome === 'denied')
54
+ conditions.push("action.decision = 'denied'");
55
+ if (filter.outcome === 'error')
56
+ conditions.push("action.outcome = 'error' AND action.kind = 'door.result'");
57
+ if (filter.outcome === 'failed')
58
+ conditions.push("action.outcome = 'error' AND action.kind <> 'door.result'");
59
+ if (conditions.length) {
60
+ const exists = qb
61
+ .subQuery()
62
+ .select('1')
63
+ .from(audit_action_js_1.AuditAction, 'action')
64
+ .where('action.requestId = request.id')
65
+ .andWhere(conditions.join(' AND '))
66
+ .getQuery();
67
+ qb.andWhere(`EXISTS ${exists}`, params);
68
+ }
69
+ const [requests, total] = await qb
70
+ .orderBy('request.createdAt', 'DESC')
71
+ .skip((page - 1) * limit)
72
+ .take(limit)
73
+ .getManyAndCount();
74
+ const ids = requests.map(request => request.id);
75
+ const actions = ids.length
76
+ ? await (0, shell_1.getRepository)(audit_action_js_1.AuditAction)
77
+ .createQueryBuilder('action')
78
+ .where('action.requestId IN (:...ids)', { ids })
79
+ .andWhere(new typeorm_1.Brackets(where => where.where('action.domain = :domainId', { domainId: domain.id }).orWhere('action.domain IS NULL')))
80
+ .orderBy('action.createdAt', 'ASC')
81
+ .getMany()
82
+ : [];
83
+ return {
84
+ items: requests.map(request => ({ request, actions: actions.filter(action => action.requestId === request.id) })),
85
+ total
86
+ };
87
+ }
88
+ async auditPeople(from, to, context) {
89
+ const { domain } = context.state;
90
+ const range = period(from, to);
91
+ const members = await (0, get_domain_users_js_1.getDomainUsers)(domain);
92
+ const ownerIds = [...(await (0, domain_ownership_js_1.findDomainOwnerUserIds)(domain))].filter(id => !members.some(one => one.id === id));
93
+ const people = [...members, ...(ownerIds.length ? await (0, shell_1.getRepository)(user_js_1.User).find({ where: { id: (0, typeorm_1.In)(ownerIds) } }) : [])];
94
+ if (!people.length)
95
+ return [];
96
+ const ids = people.map(one => one.id);
97
+ const roleRows = await (0, shell_1.getRepository)(role_js_1.Role)
98
+ .createQueryBuilder('role')
99
+ .innerJoin('role.users', 'holder')
100
+ .select(['role.name AS "name"', 'holder.id AS "userId"'])
101
+ .where('role.domain = :domainId', { domainId: domain.id })
102
+ .andWhere('holder.id IN (:...ids)', { ids })
103
+ .getRawMany();
104
+ const sums = (rows) => new Map(rows.map(row => [row.actorId, row]));
105
+ const checkins = sums(await (0, shell_1.getRepository)(audit_action_js_1.AuditAction)
106
+ .createQueryBuilder('action')
107
+ .innerJoin('action.actor', 'actor')
108
+ .select('actor.id', 'actorId')
109
+ .addSelect('COUNT(*)', 'inPeriod')
110
+ .where("action.kind = 'auth.checkin'")
111
+ .andWhere('action.domain = :domainId', { domainId: domain.id })
112
+ .andWhere('action.createdAt >= :from AND action.createdAt <= :to', range)
113
+ .andWhere('actor.id IN (:...ids)', { ids })
114
+ .groupBy('actor.id')
115
+ .getRawMany());
116
+ /*
117
+ * The last check-in is read as rows, not as a raw MAX(): a raw value is the driver's text - sqlite
118
+ * gives UTC without a zone - and only the entity path turns it into the right moment.
119
+ */
120
+ const lastCheckins = new Map((await (0, shell_1.getRepository)(audit_action_js_1.AuditAction)
121
+ .createQueryBuilder('action')
122
+ .leftJoinAndSelect('action.actor', 'actor')
123
+ .where("action.kind = 'auth.checkin'")
124
+ .andWhere('action.domain = :domainId', { domainId: domain.id })
125
+ .andWhere('actor.id IN (:...ids)', { ids })
126
+ .andWhere(qb => {
127
+ const latest = qb
128
+ .subQuery()
129
+ .select('MAX(latest.createdAt)')
130
+ .from(audit_action_js_1.AuditAction, 'latest')
131
+ .where("latest.kind = 'auth.checkin'")
132
+ .andWhere('latest.domain = :domainId')
133
+ .andWhere('latest.actor = action.actor')
134
+ .getQuery();
135
+ return `action.createdAt = ${latest}`;
136
+ })
137
+ .getMany()).map(action => [action.actor?.id, action.createdAt]));
138
+ /* A sign-in may come before any domain is chosen, so failures are the member's wherever they were. */
139
+ const failures = sums(await (0, shell_1.getRepository)(audit_action_js_1.AuditAction)
140
+ .createQueryBuilder('action')
141
+ .innerJoin('action.actor', 'actor')
142
+ .select('actor.id', 'actorId')
143
+ .addSelect('COUNT(*)', 'failed')
144
+ .where("action.kind = 'auth.signin' AND action.outcome = 'error'")
145
+ .andWhere('action.createdAt >= :from AND action.createdAt <= :to', range)
146
+ .andWhere(new typeorm_1.Brackets(where => where.where('action.domain = :domainId', { domainId: domain.id }).orWhere('action.domain IS NULL')))
147
+ .andWhere('actor.id IN (:...ids)', { ids })
148
+ .groupBy('actor.id')
149
+ .getRawMany());
150
+ const doors = sums(await (0, shell_1.getRepository)(audit_action_js_1.AuditAction)
151
+ .createQueryBuilder('action')
152
+ .innerJoin('action.actor', 'actor')
153
+ .select('actor.id', 'actorId')
154
+ .addSelect("SUM(CASE WHEN action.decision = 'granted' THEN action.count ELSE 0 END)", 'granted')
155
+ .addSelect("SUM(CASE WHEN action.decision = 'denied' THEN action.count ELSE 0 END)", 'denied')
156
+ .where("action.kind = 'door.check'")
157
+ .andWhere('action.door <> :auditDoor', { auditDoor: AUDIT_DOOR })
158
+ .andWhere('action.domain = :domainId', { domainId: domain.id })
159
+ .andWhere('action.createdAt >= :from AND action.createdAt <= :to', range)
160
+ .groupBy('actor.id')
161
+ .getRawMany());
162
+ return people.map(user => ({
163
+ user,
164
+ roles: roleRows.filter(row => row.userId === user.id).map(row => row.name),
165
+ lastCheckinAt: lastCheckins.get(user.id),
166
+ checkins: Number(checkins.get(user.id)?.inPeriod || 0),
167
+ failedSignins: Number(failures.get(user.id)?.failed || 0),
168
+ granted: Number(doors.get(user.id)?.granted || 0),
169
+ denied: Number(doors.get(user.id)?.denied || 0)
170
+ }));
171
+ }
172
+ async auditDoors(from, to, context) {
173
+ const { domain } = context.state;
174
+ const observed = await (0, shell_1.getRepository)(audit_action_js_1.AuditAction)
175
+ .createQueryBuilder('action')
176
+ .leftJoin('action.actor', 'actor')
177
+ .select('action.door', 'door')
178
+ .addSelect('COUNT(DISTINCT actor.id)', 'people')
179
+ .addSelect("SUM(CASE WHEN action.decision = 'granted' THEN action.count ELSE 0 END)", 'granted')
180
+ .addSelect("SUM(CASE WHEN action.decision = 'denied' THEN action.count ELSE 0 END)", 'denied')
181
+ .where("action.kind = 'door.check'")
182
+ .andWhere('action.door <> :auditDoor', { auditDoor: AUDIT_DOOR })
183
+ .andWhere('action.domain = :domainId', { domainId: domain.id })
184
+ .andWhere('action.createdAt >= :from AND action.createdAt <= :to', period(from, to))
185
+ .groupBy('action.door')
186
+ .getRawMany();
187
+ const byDoor = new Map(observed.map(row => [row.door, row]));
188
+ const declared = await (0, shell_1.getRepository)(privilege_js_1.Privilege).find({ order: { category: 'ASC', name: 'ASC' } });
189
+ const doors = new Map();
190
+ for (const one of declared) {
191
+ const door = `${one.category}:${one.name}`;
192
+ if (door === AUDIT_DOOR)
193
+ continue;
194
+ doors.set(door, { door, category: one.category, privilege: one.name, people: 0, granted: 0, denied: 0 });
195
+ }
196
+ for (const [door, row] of byDoor) {
197
+ const [category, privilege] = door.includes(':') ? door.split(':') : [undefined, undefined];
198
+ doors.set(door, {
199
+ door,
200
+ category,
201
+ privilege,
202
+ people: Number(row.people || 0),
203
+ granted: Number(row.granted || 0),
204
+ denied: Number(row.denied || 0)
205
+ });
206
+ }
207
+ return [...doors.values()];
208
+ }
209
+ async auditHealth() {
210
+ return (0, audit_recorder_js_1.auditHealth)();
211
+ }
212
+ /*
213
+ * One's own record is what one did (ADR-0141 §5-5): the actor is the session's user, whatever is asked.
214
+ * What somebody else did to me is not here.
215
+ */
216
+ async myAuditActions(kind, limit, context) {
217
+ const { domain, user } = context.state;
218
+ if (!user)
219
+ throw new Error('No user specified');
220
+ const qb = (0, shell_1.getRepository)(audit_action_js_1.AuditAction)
221
+ .createQueryBuilder('action')
222
+ .leftJoinAndSelect('action.domain', 'domain')
223
+ .where('action.actor = :userId', { userId: user.id })
224
+ .andWhere(new typeorm_1.Brackets(where => where.where('action.domain = :domainId', { domainId: domain?.id }).orWhere('action.domain IS NULL')));
225
+ if (kind)
226
+ qb.andWhere('action.kind = :kind', { kind });
227
+ return await qb
228
+ .orderBy('action.createdAt', 'DESC')
229
+ .take(Math.min(Math.max(limit || 20, 1), MAX_PAGE))
230
+ .getMany();
231
+ }
232
+ /* Where an action came from - its request's address and browser. */
233
+ async request(action) {
234
+ return action.requestId ? await (0, shell_1.getRepository)(audit_request_js_1.AuditRequest).findOneBy({ id: action.requestId }) : null;
235
+ }
236
+ };
237
+ exports.AuditQuery = AuditQuery;
238
+ tslib_1.__decorate([
239
+ (0, type_graphql_1.Directive)(AUDIT_QUERY),
240
+ (0, type_graphql_1.Query)(returns => audit_types_js_1.AuditRecordList, { description: "This domain's audit records - requests and what was done in them." }),
241
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('filter')),
242
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
243
+ tslib_1.__metadata("design:type", Function),
244
+ tslib_1.__metadata("design:paramtypes", [audit_types_js_1.AuditRecordFilter, Object]),
245
+ tslib_1.__metadata("design:returntype", Promise)
246
+ ], AuditQuery.prototype, "auditRecords", null);
247
+ tslib_1.__decorate([
248
+ (0, type_graphql_1.Directive)(AUDIT_QUERY),
249
+ (0, type_graphql_1.Query)(returns => [audit_types_js_1.AuditPerson], { description: "Each member's audit summary over a period, in this domain." }),
250
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('from')),
251
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('to')),
252
+ tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
253
+ tslib_1.__metadata("design:type", Function),
254
+ tslib_1.__metadata("design:paramtypes", [Date, Date, Object]),
255
+ tslib_1.__metadata("design:returntype", Promise)
256
+ ], AuditQuery.prototype, "auditPeople", null);
257
+ tslib_1.__decorate([
258
+ (0, type_graphql_1.Directive)(AUDIT_QUERY),
259
+ (0, type_graphql_1.Query)(returns => [audit_types_js_1.AuditDoor], {
260
+ description: 'Each door over a period, in this domain - every declared privilege, a door with no record in the period too.'
261
+ }),
262
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('from')),
263
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('to')),
264
+ tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
265
+ tslib_1.__metadata("design:type", Function),
266
+ tslib_1.__metadata("design:paramtypes", [Date, Date, Object]),
267
+ tslib_1.__metadata("design:returntype", Promise)
268
+ ], AuditQuery.prototype, "auditDoors", null);
269
+ tslib_1.__decorate([
270
+ (0, type_graphql_1.Directive)(AUDIT_QUERY),
271
+ (0, type_graphql_1.Query)(returns => audit_types_js_1.AuditHealth, { description: 'Audit writes this server process failed to store.' }),
272
+ tslib_1.__metadata("design:type", Function),
273
+ tslib_1.__metadata("design:paramtypes", []),
274
+ tslib_1.__metadata("design:returntype", Promise)
275
+ ], AuditQuery.prototype, "auditHealth", null);
276
+ tslib_1.__decorate([
277
+ (0, type_graphql_1.Query)(returns => [audit_action_js_1.AuditAction], { description: 'What I did - in this domain, and my sign-ins.' }),
278
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('kind', { nullable: true })),
279
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('limit', type => type_graphql_1.Int, { nullable: true })),
280
+ tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
281
+ tslib_1.__metadata("design:type", Function),
282
+ tslib_1.__metadata("design:paramtypes", [String, Number, Object]),
283
+ tslib_1.__metadata("design:returntype", Promise)
284
+ ], AuditQuery.prototype, "myAuditActions", null);
285
+ tslib_1.__decorate([
286
+ (0, type_graphql_1.FieldResolver)(type => audit_request_js_1.AuditRequest, { nullable: true }),
287
+ tslib_1.__param(0, (0, type_graphql_1.Root)()),
288
+ tslib_1.__metadata("design:type", Function),
289
+ tslib_1.__metadata("design:paramtypes", [audit_action_js_1.AuditAction]),
290
+ tslib_1.__metadata("design:returntype", Promise)
291
+ ], AuditQuery.prototype, "request", null);
292
+ exports.AuditQuery = AuditQuery = tslib_1.__decorate([
293
+ (0, type_graphql_1.Resolver)(audit_action_js_1.AuditAction)
294
+ ], AuditQuery);
295
+ //# sourceMappingURL=audit-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-query.js","sourceRoot":"","sources":["../../../server/service/audit/audit-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, FieldResolver, Int, Query, Resolver, Root } from 'type-graphql'\nimport { Brackets, In } from 'typeorm'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { findDomainOwnerUserIds } from '../../utils/domain-ownership.js'\nimport { getDomainUsers } from '../../utils/get-domain-users.js'\nimport { Privilege } from '../privilege/privilege.js'\nimport { Role } from '../role/role.js'\nimport { User } from '../user/user.js'\nimport { AuditAction } from './audit-action.js'\nimport { auditHealth } from './audit-recorder.js'\nimport { AuditRequest } from './audit-request.js'\nimport { AuditDoor, AuditHealth, AuditPerson, AuditRecordFilter, AuditRecordList } from './audit-types.js'\n\n/*\n * Everybody's audit is `audit:query` (ADR-0141 §5). The domain owner does not pass on ownership - this is\n * a sensitive door (ADR-0139) - and the superuser does, as a stated exception whose basis the check records.\n * Every answer is this domain's only.\n */\nconst AUDIT_QUERY = '@privilege(category: \"audit\", privilege: \"query\", superUserGranted: true)'\n\n/* Reading the audit is recorded like any door, and left out of usage counts (§5-8 · §6.3). */\nconst AUDIT_DOOR = 'audit:query'\n\nconst MAX_PAGE = 200\n\nfunction period(from: Date, to: Date) {\n return { from: new Date(from), to: new Date(to) }\n}\n\n@Resolver(AuditAction)\nexport class AuditQuery {\n @Directive(AUDIT_QUERY)\n @Query(returns => AuditRecordList, { description: \"This domain's audit records - requests and what was done in them.\" })\n async auditRecords(@Arg('filter') filter: AuditRecordFilter, @Ctx() context: ResolverContext): Promise<AuditRecordList> {\n const { domain } = context.state\n const { from, to } = period(filter.from, filter.to)\n const limit = Math.min(Math.max(filter.limit || 50, 1), MAX_PAGE)\n const page = Math.max(filter.page || 1, 1)\n\n const qb = getRepository(AuditRequest)\n .createQueryBuilder('request')\n .leftJoinAndSelect('request.actor', 'actor')\n .where('request.domain = :domainId', { domainId: domain.id })\n .andWhere('request.createdAt >= :from AND request.createdAt <= :to', { from, to })\n\n if (filter.actorId) qb.andWhere('request.actor = :actorId', { actorId: filter.actorId })\n\n /* A request is kept when one of its actions matches - the request line alone says nothing of doors. */\n const conditions: string[] = []\n const params: Record<string, any> = {}\n if (filter.door) {\n conditions.push('action.door = :door')\n params.door = filter.door\n }\n if (filter.kind) {\n conditions.push('action.kind = :kind')\n params.kind = filter.kind\n }\n if (filter.outcome === 'denied') conditions.push(\"action.decision = 'denied'\")\n if (filter.outcome === 'error') conditions.push(\"action.outcome = 'error' AND action.kind = 'door.result'\")\n if (filter.outcome === 'failed') conditions.push(\"action.outcome = 'error' AND action.kind <> 'door.result'\")\n\n if (conditions.length) {\n const exists = qb\n .subQuery()\n .select('1')\n .from(AuditAction, 'action')\n .where('action.requestId = request.id')\n .andWhere(conditions.join(' AND '))\n .getQuery()\n qb.andWhere(`EXISTS ${exists}`, params)\n }\n\n const [requests, total] = await qb\n .orderBy('request.createdAt', 'DESC')\n .skip((page - 1) * limit)\n .take(limit)\n .getManyAndCount()\n\n const ids = requests.map(request => request.id)\n const actions = ids.length\n ? await getRepository(AuditAction)\n .createQueryBuilder('action')\n .where('action.requestId IN (:...ids)', { ids })\n .andWhere(new Brackets(where => where.where('action.domain = :domainId', { domainId: domain.id }).orWhere('action.domain IS NULL')))\n .orderBy('action.createdAt', 'ASC')\n .getMany()\n : []\n\n return {\n items: requests.map(request => ({ request, actions: actions.filter(action => action.requestId === request.id) })),\n total\n }\n }\n\n @Directive(AUDIT_QUERY)\n @Query(returns => [AuditPerson], { description: \"Each member's audit summary over a period, in this domain.\" })\n async auditPeople(@Arg('from') from: Date, @Arg('to') to: Date, @Ctx() context: ResolverContext): Promise<AuditPerson[]> {\n const { domain } = context.state\n const range = period(from, to)\n\n const members = await getDomainUsers(domain)\n const ownerIds = [...(await findDomainOwnerUserIds(domain))].filter(id => !members.some(one => one.id === id))\n const people = [...members, ...(ownerIds.length ? await getRepository(User).find({ where: { id: In(ownerIds) } }) : [])]\n if (!people.length) return []\n\n const ids = people.map(one => one.id)\n\n const roleRows = await getRepository(Role)\n .createQueryBuilder('role')\n .innerJoin('role.users', 'holder')\n .select(['role.name AS \"name\"', 'holder.id AS \"userId\"'])\n .where('role.domain = :domainId', { domainId: domain.id })\n .andWhere('holder.id IN (:...ids)', { ids })\n .getRawMany()\n\n const sums = (rows: any[]) => new Map(rows.map(row => [row.actorId, row]))\n\n const checkins = sums(\n await getRepository(AuditAction)\n .createQueryBuilder('action')\n .innerJoin('action.actor', 'actor')\n .select('actor.id', 'actorId')\n .addSelect('COUNT(*)', 'inPeriod')\n .where(\"action.kind = 'auth.checkin'\")\n .andWhere('action.domain = :domainId', { domainId: domain.id })\n .andWhere('action.createdAt >= :from AND action.createdAt <= :to', range)\n .andWhere('actor.id IN (:...ids)', { ids })\n .groupBy('actor.id')\n .getRawMany()\n )\n\n /*\n * The last check-in is read as rows, not as a raw MAX(): a raw value is the driver's text - sqlite\n * gives UTC without a zone - and only the entity path turns it into the right moment.\n */\n const lastCheckins = new Map(\n (\n await getRepository(AuditAction)\n .createQueryBuilder('action')\n .leftJoinAndSelect('action.actor', 'actor')\n .where(\"action.kind = 'auth.checkin'\")\n .andWhere('action.domain = :domainId', { domainId: domain.id })\n .andWhere('actor.id IN (:...ids)', { ids })\n .andWhere(qb => {\n const latest = qb\n .subQuery()\n .select('MAX(latest.createdAt)')\n .from(AuditAction, 'latest')\n .where(\"latest.kind = 'auth.checkin'\")\n .andWhere('latest.domain = :domainId')\n .andWhere('latest.actor = action.actor')\n .getQuery()\n return `action.createdAt = ${latest}`\n })\n .getMany()\n ).map(action => [action.actor?.id, action.createdAt] as [string, Date])\n )\n\n /* A sign-in may come before any domain is chosen, so failures are the member's wherever they were. */\n const failures = sums(\n await getRepository(AuditAction)\n .createQueryBuilder('action')\n .innerJoin('action.actor', 'actor')\n .select('actor.id', 'actorId')\n .addSelect('COUNT(*)', 'failed')\n .where(\"action.kind = 'auth.signin' AND action.outcome = 'error'\")\n .andWhere('action.createdAt >= :from AND action.createdAt <= :to', range)\n .andWhere(new Brackets(where => where.where('action.domain = :domainId', { domainId: domain.id }).orWhere('action.domain IS NULL')))\n .andWhere('actor.id IN (:...ids)', { ids })\n .groupBy('actor.id')\n .getRawMany()\n )\n\n const doors = sums(\n await getRepository(AuditAction)\n .createQueryBuilder('action')\n .innerJoin('action.actor', 'actor')\n .select('actor.id', 'actorId')\n .addSelect(\"SUM(CASE WHEN action.decision = 'granted' THEN action.count ELSE 0 END)\", 'granted')\n .addSelect(\"SUM(CASE WHEN action.decision = 'denied' THEN action.count ELSE 0 END)\", 'denied')\n .where(\"action.kind = 'door.check'\")\n .andWhere('action.door <> :auditDoor', { auditDoor: AUDIT_DOOR })\n .andWhere('action.domain = :domainId', { domainId: domain.id })\n .andWhere('action.createdAt >= :from AND action.createdAt <= :to', range)\n .groupBy('actor.id')\n .getRawMany()\n )\n\n return people.map(user => ({\n user,\n roles: roleRows.filter(row => row.userId === user.id).map(row => row.name),\n lastCheckinAt: lastCheckins.get(user.id),\n checkins: Number(checkins.get(user.id)?.inPeriod || 0),\n failedSignins: Number(failures.get(user.id)?.failed || 0),\n granted: Number(doors.get(user.id)?.granted || 0),\n denied: Number(doors.get(user.id)?.denied || 0)\n }))\n }\n\n @Directive(AUDIT_QUERY)\n @Query(returns => [AuditDoor], {\n description: 'Each door over a period, in this domain - every declared privilege, a door with no record in the period too.'\n })\n async auditDoors(@Arg('from') from: Date, @Arg('to') to: Date, @Ctx() context: ResolverContext): Promise<AuditDoor[]> {\n const { domain } = context.state\n\n const observed = await getRepository(AuditAction)\n .createQueryBuilder('action')\n .leftJoin('action.actor', 'actor')\n .select('action.door', 'door')\n .addSelect('COUNT(DISTINCT actor.id)', 'people')\n .addSelect(\"SUM(CASE WHEN action.decision = 'granted' THEN action.count ELSE 0 END)\", 'granted')\n .addSelect(\"SUM(CASE WHEN action.decision = 'denied' THEN action.count ELSE 0 END)\", 'denied')\n .where(\"action.kind = 'door.check'\")\n .andWhere('action.door <> :auditDoor', { auditDoor: AUDIT_DOOR })\n .andWhere('action.domain = :domainId', { domainId: domain.id })\n .andWhere('action.createdAt >= :from AND action.createdAt <= :to', period(from, to))\n .groupBy('action.door')\n .getRawMany()\n\n const byDoor = new Map(observed.map(row => [row.door, row]))\n const declared = await getRepository(Privilege).find({ order: { category: 'ASC', name: 'ASC' } })\n\n const doors = new Map<string, AuditDoor>()\n for (const one of declared) {\n const door = `${one.category}:${one.name}`\n if (door === AUDIT_DOOR) continue\n doors.set(door, { door, category: one.category, privilege: one.name, people: 0, granted: 0, denied: 0 })\n }\n for (const [door, row] of byDoor) {\n const [category, privilege] = door.includes(':') ? door.split(':') : [undefined, undefined]\n doors.set(door, {\n door,\n category,\n privilege,\n people: Number(row.people || 0),\n granted: Number(row.granted || 0),\n denied: Number(row.denied || 0)\n })\n }\n\n return [...doors.values()]\n }\n\n @Directive(AUDIT_QUERY)\n @Query(returns => AuditHealth, { description: 'Audit writes this server process failed to store.' })\n async auditHealth(): Promise<AuditHealth> {\n return auditHealth() as AuditHealth\n }\n\n /*\n * One's own record is what one did (ADR-0141 §5-5): the actor is the session's user, whatever is asked.\n * What somebody else did to me is not here.\n */\n @Query(returns => [AuditAction], { description: 'What I did - in this domain, and my sign-ins.' })\n async myAuditActions(\n @Arg('kind', { nullable: true }) kind: string,\n @Arg('limit', type => Int, { nullable: true }) limit: number,\n @Ctx() context: ResolverContext\n ): Promise<AuditAction[]> {\n const { domain, user } = context.state\n if (!user) throw new Error('No user specified')\n\n const qb = getRepository(AuditAction)\n .createQueryBuilder('action')\n .leftJoinAndSelect('action.domain', 'domain')\n .where('action.actor = :userId', { userId: user.id })\n .andWhere(new Brackets(where => where.where('action.domain = :domainId', { domainId: domain?.id }).orWhere('action.domain IS NULL')))\n\n if (kind) qb.andWhere('action.kind = :kind', { kind })\n\n return await qb\n .orderBy('action.createdAt', 'DESC')\n .take(Math.min(Math.max(limit || 20, 1), MAX_PAGE))\n .getMany()\n }\n\n /* Where an action came from - its request's address and browser. */\n @FieldResolver(type => AuditRequest, { nullable: true })\n async request(@Root() action: AuditAction): Promise<AuditRequest | null> {\n return action.requestId ? await getRepository(AuditRequest).findOneBy({ id: action.requestId }) : null\n }\n}\n"]}
@@ -0,0 +1,77 @@
1
+ import { EntityManager } from 'typeorm';
2
+ import { AuditDetailValue } from './audit-kinds.js';
3
+ export type AuditChannel = 'graphql' | 'http';
4
+ export interface AuditActionInput {
5
+ kind: string;
6
+ door?: string;
7
+ decision?: 'granted' | 'denied';
8
+ basis?: string;
9
+ outcome?: 'ok' | 'error';
10
+ code?: string;
11
+ returned?: number;
12
+ targetKind?: string;
13
+ targetIds?: string[];
14
+ targetRole?: 'requested' | 'handled';
15
+ objectKind?: string;
16
+ objectId?: string;
17
+ detail?: Record<string, AuditDetailValue>;
18
+ }
19
+ interface MergedAction extends AuditActionInput {
20
+ count: number;
21
+ targets: Set<string>;
22
+ truncated: boolean;
23
+ }
24
+ export interface AuditScope {
25
+ requestId: string;
26
+ channel: AuditChannel;
27
+ context: any;
28
+ startedAt: number;
29
+ merged: Map<string, MergedAction>;
30
+ recorded: boolean;
31
+ eventActorId?: string;
32
+ eventDomainId?: string;
33
+ }
34
+ /** Runs `work` as one audited request; the request row and merged actions are written when it ends. */
35
+ export declare function runInAuditScope<T>(context: any, channel: AuditChannel, work: () => Promise<T>): Promise<T>;
36
+ export declare function currentAuditScope(): AuditScope | undefined;
37
+ /**
38
+ * Notes a door check or result in the running request. Equal ones become one row with a count; their
39
+ * targets are kept together, up to 100 (ADR-0141 §3.2). Outside any request this does nothing - a
40
+ * subscription stream or a boot task is not audited at the door (§9 step 4).
41
+ */
42
+ export declare function noteAuditAction(input: AuditActionInput): void;
43
+ export interface RecordOptions {
44
+ /** The business transaction. Given, the event is written in it and a failure fails the change (§4-1). */
45
+ manager?: EntityManager;
46
+ /** Who did it, when the context does not know yet (a sign-in). `null` says nobody is known. */
47
+ actorId?: string | null;
48
+ /** Where, when it is not the request's domain (a role of another domain, a sign-in with no domain). */
49
+ domainId?: string | null;
50
+ }
51
+ /**
52
+ * Writes one event now. With `manager` it shares the caller's transaction and throws with it;
53
+ * without, it is written on its own and a failure is only counted (ADR-0141 §4-2 · §4-3 · §4-5).
54
+ */
55
+ export declare function recordAuditAction(input: AuditActionInput, options?: RecordOptions): Promise<void>;
56
+ /** The address a request came from - the first `x-forwarded-for` value, as the check-in record always took it. */
57
+ export declare function addressOf(context: any): string | undefined;
58
+ /**
59
+ * What a GraphQL request asked: its operation type and the root fields by their schema names - never
60
+ * an argument or a variable (ADR-0141 §3.4). A field with no `@privilege` leaves no action, so these
61
+ * names are what says which open door a request used. The parsed document is Apollo's and is not
62
+ * reachable from here; parsing again is the cost of knowing.
63
+ */
64
+ export declare function operationOf(body: any): {
65
+ operationType?: string;
66
+ operationName?: string;
67
+ fields?: string;
68
+ };
69
+ /** Failures this server process detected since it started. Not those it never got to try. */
70
+ export declare function auditHealth(): {
71
+ since: Date;
72
+ failures: number;
73
+ firstFailedAt: Date | null;
74
+ lastFailedAt: Date | null;
75
+ };
76
+ export declare function flushAudit(): Promise<void>;
77
+ export {};