@things-factory/auth-base 10.1.192 → 10.1.202

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 (128) hide show
  1. package/dist-client/tsconfig.tsbuildinfo +1 -1
  2. package/dist-server/ai-entry/access-assistant.js +4 -3
  3. package/dist-server/ai-entry/access-assistant.js.map +1 -1
  4. package/dist-server/controllers/access-request.d.ts +8 -2
  5. package/dist-server/controllers/access-request.js +33 -6
  6. package/dist-server/controllers/access-request.js.map +1 -1
  7. package/dist-server/controllers/signin.d.ts +5 -0
  8. package/dist-server/controllers/signin.js +25 -0
  9. package/dist-server/controllers/signin.js.map +1 -1
  10. package/dist-server/index.js +7 -0
  11. package/dist-server/index.js.map +1 -1
  12. package/dist-server/middlewares/audit-scope-middleware.d.ts +7 -0
  13. package/dist-server/middlewares/audit-scope-middleware.js +15 -0
  14. package/dist-server/middlewares/audit-scope-middleware.js.map +1 -0
  15. package/dist-server/middlewares/index.js +3 -0
  16. package/dist-server/middlewares/index.js.map +1 -1
  17. package/dist-server/role-tasks.d.ts +2 -0
  18. package/dist-server/role-tasks.js +20 -0
  19. package/dist-server/role-tasks.js.map +1 -0
  20. package/dist-server/router/auth-checkin-router.js +3 -5
  21. package/dist-server/router/auth-checkin-router.js.map +1 -1
  22. package/dist-server/router/auth-public-process-router.js +7 -1
  23. package/dist-server/router/auth-public-process-router.js.map +1 -1
  24. package/dist-server/router/site-root-router.d.ts +2 -0
  25. package/dist-server/router/site-root-router.js +2 -0
  26. package/dist-server/router/site-root-router.js.map +1 -1
  27. package/dist-server/service/access-request/access-request-query.d.ts +7 -2
  28. package/dist-server/service/access-request/access-request-query.js +25 -4
  29. package/dist-server/service/access-request/access-request-query.js.map +1 -1
  30. package/dist-server/service/access-request/access-request-types.d.ts +7 -0
  31. package/dist-server/service/access-request/access-request-types.js +27 -1
  32. package/dist-server/service/access-request/access-request-types.js.map +1 -1
  33. package/dist-server/service/access-request/access-request.d.ts +1 -0
  34. package/dist-server/service/access-request/access-request.js +5 -0
  35. package/dist-server/service/access-request/access-request.js.map +1 -1
  36. package/dist-server/service/audit/audit-action.d.ts +36 -0
  37. package/dist-server/service/audit/audit-action.js +137 -0
  38. package/dist-server/service/audit/audit-action.js.map +1 -0
  39. package/dist-server/service/audit/audit-kinds.d.ts +19 -0
  40. package/dist-server/service/audit/audit-kinds.js +70 -0
  41. package/dist-server/service/audit/audit-kinds.js.map +1 -0
  42. package/dist-server/service/audit/audit-query.d.ts +11 -0
  43. package/dist-server/service/audit/audit-query.js +295 -0
  44. package/dist-server/service/audit/audit-query.js.map +1 -0
  45. package/dist-server/service/audit/audit-recorder.d.ts +77 -0
  46. package/dist-server/service/audit/audit-recorder.js +247 -0
  47. package/dist-server/service/audit/audit-recorder.js.map +1 -0
  48. package/dist-server/service/audit/audit-request.d.ts +28 -0
  49. package/dist-server/service/audit/audit-request.js +96 -0
  50. package/dist-server/service/audit/audit-request.js.map +1 -0
  51. package/dist-server/service/audit/audit-retention.d.ts +12 -0
  52. package/dist-server/service/audit/audit-retention.js +42 -0
  53. package/dist-server/service/audit/audit-retention.js.map +1 -0
  54. package/dist-server/service/audit/audit-subscriber.d.ts +39 -0
  55. package/dist-server/service/audit/audit-subscriber.js +191 -0
  56. package/dist-server/service/audit/audit-subscriber.js.map +1 -0
  57. package/dist-server/service/audit/audit-target.d.ts +19 -0
  58. package/dist-server/service/audit/audit-target.js +46 -0
  59. package/dist-server/service/audit/audit-target.js.map +1 -0
  60. package/dist-server/service/audit/audit-types.d.ts +44 -0
  61. package/dist-server/service/audit/audit-types.js +161 -0
  62. package/dist-server/service/audit/audit-types.js.map +1 -0
  63. package/dist-server/service/audit/index.d.ts +6 -0
  64. package/dist-server/service/audit/index.js +11 -0
  65. package/dist-server/service/audit/index.js.map +1 -0
  66. package/dist-server/service/domain-owner/domain-owner-mutation.js +3 -0
  67. package/dist-server/service/domain-owner/domain-owner-mutation.js.map +1 -1
  68. package/dist-server/service/index.d.ts +9 -2
  69. package/dist-server/service/index.js +19 -4
  70. package/dist-server/service/index.js.map +1 -1
  71. package/dist-server/service/open-fields.js +2 -1
  72. package/dist-server/service/open-fields.js.map +1 -1
  73. package/dist-server/service/privilege/privilege-directive.js +25 -1
  74. package/dist-server/service/privilege/privilege-directive.js.map +1 -1
  75. package/dist-server/service/role/role-mutation.js +8 -1
  76. package/dist-server/service/role/role-mutation.js.map +1 -1
  77. package/dist-server/service/role-template/role-task.d.ts +81 -0
  78. package/dist-server/service/role-template/role-task.js +129 -0
  79. package/dist-server/service/role-template/role-task.js.map +1 -0
  80. package/dist-server/service/role-template/role-template-gap-resolver.d.ts +9 -5
  81. package/dist-server/service/role-template/role-template-gap-resolver.js +41 -13
  82. package/dist-server/service/role-template/role-template-gap-resolver.js.map +1 -1
  83. package/dist-server/service/role-template/role-template-mutation.js +10 -1
  84. package/dist-server/service/role-template/role-template-mutation.js.map +1 -1
  85. package/dist-server/service/role-template/role-template-query.js +4 -2
  86. package/dist-server/service/role-template/role-template-query.js.map +1 -1
  87. package/dist-server/service/role-template/role-template-types.d.ts +24 -0
  88. package/dist-server/service/role-template/role-template-types.js +63 -1
  89. package/dist-server/service/role-template/role-template-types.js.map +1 -1
  90. package/dist-server/service/role-template/role-template.d.ts +63 -10
  91. package/dist-server/service/role-template/role-template.js +92 -18
  92. package/dist-server/service/role-template/role-template.js.map +1 -1
  93. package/dist-server/tsconfig.tsbuildinfo +1 -1
  94. package/dist-server/utils/access-request-review.d.ts +12 -2
  95. package/dist-server/utils/access-request-review.js +13 -3
  96. package/dist-server/utils/access-request-review.js.map +1 -1
  97. package/dist-server/utils/check-permission.d.ts +6 -0
  98. package/dist-server/utils/check-permission.js +23 -8
  99. package/dist-server/utils/check-permission.js.map +1 -1
  100. package/package.json +3 -2
  101. package/tests/access-assistant-db.test.ts +1 -1
  102. package/tests/access-request-db.test.ts +71 -0
  103. package/tests/access-request-review.test.ts +30 -0
  104. package/tests/audit-db.test.ts +394 -0
  105. package/tests/auth-base-role-tasks.test.ts +58 -0
  106. package/tests/list-inheritance-db.test.ts +0 -7
  107. package/tests/open-doors.test.ts +2 -2
  108. package/tests/refusal-cause.test.ts +1 -1
  109. package/tests/role-task.test.ts +168 -0
  110. package/tests/role-template-gap-db.test.ts +77 -2
  111. package/translations/en.json +14 -1
  112. package/translations/es.json +14 -1
  113. package/translations/ja.json +14 -1
  114. package/translations/ko.json +14 -1
  115. package/translations/ms.json +14 -1
  116. package/translations/zh.json +14 -1
  117. package/dist-server/service/login-history/index.d.ts +0 -4
  118. package/dist-server/service/login-history/index.js +0 -8
  119. package/dist-server/service/login-history/index.js.map +0 -1
  120. package/dist-server/service/login-history/login-history-query.d.ts +0 -10
  121. package/dist-server/service/login-history/login-history-query.js +0 -80
  122. package/dist-server/service/login-history/login-history-query.js.map +0 -1
  123. package/dist-server/service/login-history/login-history-type.d.ts +0 -5
  124. package/dist-server/service/login-history/login-history-type.js +0 -21
  125. package/dist-server/service/login-history/login-history-type.js.map +0 -1
  126. package/dist-server/service/login-history/login-history.d.ts +0 -12
  127. package/dist-server/service/login-history/login-history.js +0 -58
  128. package/dist-server/service/login-history/login-history.js.map +0 -1
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuditAction = 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 user_js_1 = require("../user/user.js");
9
+ /**
10
+ * One thing done, as the audit keeps it (ADR-0141 §3.2 · §3.3).
11
+ *
12
+ * `kind` says which: `door.check` (a `@privilege` judgement), `door.result` (what a passed door
13
+ * did), or an event a service or the audit subscriber wrote (`role.grant`, `auth.signin`, ...).
14
+ * Only the columns below exist, and `detail` takes only the keys its kind registered
15
+ * (`audit-kinds.ts`) - no business value, secret or request text has a place to go.
16
+ *
17
+ * Immutable like `AuditRequest`, and for the same reason without foreign keys.
18
+ */
19
+ let AuditAction = class AuditAction {
20
+ };
21
+ exports.AuditAction = AuditAction;
22
+ tslib_1.__decorate([
23
+ (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
24
+ (0, type_graphql_1.Field)(type => type_graphql_1.ID, { description: 'Unique identifier.' }),
25
+ tslib_1.__metadata("design:type", String)
26
+ ], AuditAction.prototype, "id", void 0);
27
+ tslib_1.__decorate([
28
+ (0, typeorm_1.Column)({ nullable: true, length: 36 }),
29
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The request this was done in. Empty for work outside any request.' }),
30
+ tslib_1.__metadata("design:type", String)
31
+ ], AuditAction.prototype, "requestId", void 0);
32
+ tslib_1.__decorate([
33
+ (0, typeorm_1.ManyToOne)(type => shell_1.Domain, { nullable: true, createForeignKeyConstraints: false }),
34
+ (0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true, description: 'The domain it was done in.' }),
35
+ tslib_1.__metadata("design:type", shell_1.Domain)
36
+ ], AuditAction.prototype, "domain", void 0);
37
+ tslib_1.__decorate([
38
+ (0, typeorm_1.RelationId)((action) => action.domain),
39
+ tslib_1.__metadata("design:type", String)
40
+ ], AuditAction.prototype, "domainId", void 0);
41
+ tslib_1.__decorate([
42
+ (0, typeorm_1.ManyToOne)(type => user_js_1.User, { nullable: true, createForeignKeyConstraints: false }),
43
+ (0, type_graphql_1.Field)(type => user_js_1.User, { nullable: true, description: 'Who did it. Empty when nobody is known - a sign-in to an account that does not exist.' }),
44
+ tslib_1.__metadata("design:type", user_js_1.User)
45
+ ], AuditAction.prototype, "actor", void 0);
46
+ tslib_1.__decorate([
47
+ (0, typeorm_1.RelationId)((action) => action.actor),
48
+ tslib_1.__metadata("design:type", String)
49
+ ], AuditAction.prototype, "actorId", void 0);
50
+ tslib_1.__decorate([
51
+ (0, typeorm_1.CreateDateColumn)(),
52
+ (0, type_graphql_1.Field)({ description: 'When it was written.' }),
53
+ tslib_1.__metadata("design:type", Date)
54
+ ], AuditAction.prototype, "createdAt", void 0);
55
+ tslib_1.__decorate([
56
+ (0, typeorm_1.Column)({ length: 64 }),
57
+ (0, type_graphql_1.Field)({ description: 'What kind of action: door.check, door.result, role.grant, auth.signin, ...' }),
58
+ tslib_1.__metadata("design:type", String)
59
+ ], AuditAction.prototype, "kind", void 0);
60
+ tslib_1.__decorate([
61
+ (0, typeorm_1.Column)({ nullable: true, length: 128 }),
62
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The door, as category:privilege, or owner / super for a door that asks ownership only.' }),
63
+ tslib_1.__metadata("design:type", String)
64
+ ], AuditAction.prototype, "door", void 0);
65
+ tslib_1.__decorate([
66
+ (0, typeorm_1.Column)({ nullable: true, length: 16 }),
67
+ (0, type_graphql_1.Field)({ nullable: true, description: 'granted or denied - for a door check.' }),
68
+ tslib_1.__metadata("design:type", String)
69
+ ], AuditAction.prototype, "decision", void 0);
70
+ tslib_1.__decorate([
71
+ (0, typeorm_1.Column)({ nullable: true, length: 32 }),
72
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Why: role, domain-owner, superuser when granted; role, address, license, ownership, unknown when denied.' }),
73
+ tslib_1.__metadata("design:type", String)
74
+ ], AuditAction.prototype, "basis", void 0);
75
+ tslib_1.__decorate([
76
+ (0, typeorm_1.Column)({ nullable: true, length: 16 }),
77
+ (0, type_graphql_1.Field)({ nullable: true, description: 'ok or error - what was done after the door, or how an event ended.' }),
78
+ tslib_1.__metadata("design:type", String)
79
+ ], AuditAction.prototype, "outcome", void 0);
80
+ tslib_1.__decorate([
81
+ (0, typeorm_1.Column)({ nullable: true, length: 64 }),
82
+ (0, type_graphql_1.Field)({ nullable: true, description: 'A fixed code for an error or a refusal. Never a message.' }),
83
+ tslib_1.__metadata("design:type", String)
84
+ ], AuditAction.prototype, "code", void 0);
85
+ tslib_1.__decorate([
86
+ (0, typeorm_1.Column)({ type: 'int', default: 1 }),
87
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'How many times this happened in the request - equal checks are kept as one row.' }),
88
+ tslib_1.__metadata("design:type", Number)
89
+ ], AuditAction.prototype, "count", void 0);
90
+ tslib_1.__decorate([
91
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
92
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'How many items a list returned, summed over the request.' }),
93
+ tslib_1.__metadata("design:type", Number)
94
+ ], AuditAction.prototype, "returned", void 0);
95
+ tslib_1.__decorate([
96
+ (0, typeorm_1.Column)({ nullable: true, length: 64 }),
97
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Kind of the target - User, Role, ...' }),
98
+ tslib_1.__metadata("design:type", String)
99
+ ], AuditAction.prototype, "targetKind", void 0);
100
+ tslib_1.__decorate([
101
+ (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
102
+ (0, type_graphql_1.Field)(type => [String], { nullable: true, description: 'Target ids, up to 100.' }),
103
+ tslib_1.__metadata("design:type", Array)
104
+ ], AuditAction.prototype, "targetIds", void 0);
105
+ tslib_1.__decorate([
106
+ (0, typeorm_1.Column)({ default: false }),
107
+ (0, type_graphql_1.Field)({ description: 'Whether more targets than were kept.' }),
108
+ tslib_1.__metadata("design:type", Boolean)
109
+ ], AuditAction.prototype, "truncated", void 0);
110
+ tslib_1.__decorate([
111
+ (0, typeorm_1.Column)({ nullable: true, length: 16 }),
112
+ (0, type_graphql_1.Field)({ nullable: true, description: 'requested - named by the caller; handled - confirmed by the service.' }),
113
+ tslib_1.__metadata("design:type", String)
114
+ ], AuditAction.prototype, "targetRole", void 0);
115
+ tslib_1.__decorate([
116
+ (0, typeorm_1.Column)({ nullable: true, length: 64 }),
117
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Kind of the second object - the Role granted, the Privilege added.' }),
118
+ tslib_1.__metadata("design:type", String)
119
+ ], AuditAction.prototype, "objectKind", void 0);
120
+ tslib_1.__decorate([
121
+ (0, typeorm_1.Column)({ nullable: true, length: 128 }),
122
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Id or name of the second object.' }),
123
+ tslib_1.__metadata("design:type", String)
124
+ ], AuditAction.prototype, "objectId", void 0);
125
+ tslib_1.__decorate([
126
+ (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
127
+ (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'Further facts, only the keys the kind registered.' }),
128
+ tslib_1.__metadata("design:type", Object)
129
+ ], AuditAction.prototype, "detail", void 0);
130
+ exports.AuditAction = AuditAction = tslib_1.__decorate([
131
+ (0, typeorm_1.Entity)(),
132
+ (0, typeorm_1.Index)('ix_audit_action_0', (action) => [action.domain, action.createdAt]),
133
+ (0, typeorm_1.Index)('ix_audit_action_1', (action) => [action.actor, action.createdAt]),
134
+ (0, typeorm_1.Index)('ix_audit_action_2', (action) => [action.requestId]),
135
+ (0, type_graphql_1.ObjectType)({ description: 'One thing done, as the audit keeps it.' })
136
+ ], AuditAction);
137
+ //# sourceMappingURL=audit-action.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-action.js","sourceRoot":"","sources":["../../../server/service/audit/audit-action.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, ID, Int, ObjectType } from 'type-graphql'\nimport { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId } from 'typeorm'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\nimport { User } from '../user/user.js'\n\n/**\n * One thing done, as the audit keeps it (ADR-0141 §3.2 · §3.3).\n *\n * `kind` says which: `door.check` (a `@privilege` judgement), `door.result` (what a passed door\n * did), or an event a service or the audit subscriber wrote (`role.grant`, `auth.signin`, ...).\n * Only the columns below exist, and `detail` takes only the keys its kind registered\n * (`audit-kinds.ts`) - no business value, secret or request text has a place to go.\n *\n * Immutable like `AuditRequest`, and for the same reason without foreign keys.\n */\n@Entity()\n@Index('ix_audit_action_0', (action: AuditAction) => [action.domain, action.createdAt])\n@Index('ix_audit_action_1', (action: AuditAction) => [action.actor, action.createdAt])\n@Index('ix_audit_action_2', (action: AuditAction) => [action.requestId])\n@ObjectType({ description: 'One thing done, as the audit keeps it.' })\nexport class AuditAction {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier.' })\n readonly id: string\n\n @Column({ nullable: true, length: 36 })\n @Field({ nullable: true, description: 'The request this was done in. Empty for work outside any request.' })\n requestId?: string\n\n @ManyToOne(type => Domain, { nullable: true, createForeignKeyConstraints: false })\n @Field(type => Domain, { nullable: true, description: 'The domain it was done in.' })\n domain?: Domain\n\n @RelationId((action: AuditAction) => action.domain)\n domainId?: string\n\n @ManyToOne(type => User, { nullable: true, createForeignKeyConstraints: false })\n @Field(type => User, { nullable: true, description: 'Who did it. Empty when nobody is known - a sign-in to an account that does not exist.' })\n actor?: User\n\n @RelationId((action: AuditAction) => action.actor)\n actorId?: string\n\n @CreateDateColumn()\n @Field({ description: 'When it was written.' })\n createdAt: Date\n\n @Column({ length: 64 })\n @Field({ description: 'What kind of action: door.check, door.result, role.grant, auth.signin, ...' })\n kind: string\n\n @Column({ nullable: true, length: 128 })\n @Field({ nullable: true, description: 'The door, as category:privilege, or owner / super for a door that asks ownership only.' })\n door?: string\n\n @Column({ nullable: true, length: 16 })\n @Field({ nullable: true, description: 'granted or denied - for a door check.' })\n decision?: string\n\n @Column({ nullable: true, length: 32 })\n @Field({ nullable: true, description: 'Why: role, domain-owner, superuser when granted; role, address, license, ownership, unknown when denied.' })\n basis?: string\n\n @Column({ nullable: true, length: 16 })\n @Field({ nullable: true, description: 'ok or error - what was done after the door, or how an event ended.' })\n outcome?: string\n\n @Column({ nullable: true, length: 64 })\n @Field({ nullable: true, description: 'A fixed code for an error or a refusal. Never a message.' })\n code?: string\n\n @Column({ type: 'int', default: 1 })\n @Field(type => Int, { description: 'How many times this happened in the request - equal checks are kept as one row.' })\n count: number\n\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'How many items a list returned, summed over the request.' })\n returned?: number\n\n @Column({ nullable: true, length: 64 })\n @Field({ nullable: true, description: 'Kind of the target - User, Role, ...' })\n targetKind?: string\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => [String], { nullable: true, description: 'Target ids, up to 100.' })\n targetIds?: string[]\n\n @Column({ default: false })\n @Field({ description: 'Whether more targets than were kept.' })\n truncated: boolean\n\n @Column({ nullable: true, length: 16 })\n @Field({ nullable: true, description: 'requested - named by the caller; handled - confirmed by the service.' })\n targetRole?: string\n\n @Column({ nullable: true, length: 64 })\n @Field({ nullable: true, description: 'Kind of the second object - the Role granted, the Privilege added.' })\n objectKind?: string\n\n @Column({ nullable: true, length: 128 })\n @Field({ nullable: true, description: 'Id or name of the second object.' })\n objectId?: string\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Further facts, only the keys the kind registered.' })\n detail?: Record<string, string | number | boolean | null>\n}\n"]}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The kinds of audit action, and the `detail` keys each may carry (ADR-0141 §3.4).
3
+ *
4
+ * The audit stores only fields it allows. The fixed columns of `AuditAction` hold no business
5
+ * value; `detail` is the one open place, so it takes only the keys its kind registered here, and
6
+ * a kind nobody registered is refused - a module that wants a new event says so here, at import,
7
+ * and a typo does not become a new kind quietly.
8
+ *
9
+ * A key holds a security state or a count, never a business value: a user's status (`activated` →
10
+ * `locked`) is the account's security state; a salary is not, and has no key to go in.
11
+ */
12
+ export type AuditDetailValue = string | number | boolean | null;
13
+ export declare function registerAuditKind(kind: string, detail?: readonly string[]): void;
14
+ export declare function isAuditKind(kind: string): boolean;
15
+ /**
16
+ * The detail as it may be stored: unknown kinds throw, keys the kind did not register throw, and
17
+ * values are cut to 64 characters. Throwing is right - this is a program writing the wrong thing.
18
+ */
19
+ export declare function allowedDetail(kind: string, detail?: Record<string, AuditDetailValue>): Record<string, AuditDetailValue> | undefined;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * The kinds of audit action, and the `detail` keys each may carry (ADR-0141 §3.4).
4
+ *
5
+ * The audit stores only fields it allows. The fixed columns of `AuditAction` hold no business
6
+ * value; `detail` is the one open place, so it takes only the keys its kind registered here, and
7
+ * a kind nobody registered is refused - a module that wants a new event says so here, at import,
8
+ * and a typo does not become a new kind quietly.
9
+ *
10
+ * A key holds a security state or a count, never a business value: a user's status (`activated` →
11
+ * `locked`) is the account's security state; a salary is not, and has no key to go in.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.registerAuditKind = registerAuditKind;
15
+ exports.isAuditKind = isAuditKind;
16
+ exports.allowedDetail = allowedDetail;
17
+ const KINDS = new Map();
18
+ function registerAuditKind(kind, detail = []) {
19
+ const known = KINDS.get(kind);
20
+ if (known && known.join() !== detail.join()) {
21
+ throw new Error(`Audit kind "${kind}" is registered twice with different detail keys.`);
22
+ }
23
+ KINDS.set(kind, detail);
24
+ }
25
+ function isAuditKind(kind) {
26
+ return KINDS.has(kind);
27
+ }
28
+ /**
29
+ * The detail as it may be stored: unknown kinds throw, keys the kind did not register throw, and
30
+ * values are cut to 64 characters. Throwing is right - this is a program writing the wrong thing.
31
+ */
32
+ function allowedDetail(kind, detail) {
33
+ const keys = KINDS.get(kind);
34
+ if (!keys) {
35
+ throw new Error(`Audit kind "${kind}" is not registered. Register it with registerAuditKind() first.`);
36
+ }
37
+ if (!detail)
38
+ return undefined;
39
+ const kept = {};
40
+ for (const [key, value] of Object.entries(detail)) {
41
+ if (!keys.includes(key)) {
42
+ throw new Error(`Audit kind "${kind}" does not allow detail "${key}". Allowed: ${keys.join(', ') || 'none'}.`);
43
+ }
44
+ kept[key] = typeof value === 'string' ? value.slice(0, 64) : value;
45
+ }
46
+ return Object.keys(kept).length ? kept : undefined;
47
+ }
48
+ /* Doors */
49
+ registerAuditKind('door.check');
50
+ registerAuditKind('door.result');
51
+ /* Signing in and out (ADR-0141 §3.3). The reason of a failure is the error code column. */
52
+ registerAuditKind('auth.signin');
53
+ registerAuditKind('auth.checkin');
54
+ registerAuditKind('auth.signout');
55
+ registerAuditKind('password.change');
56
+ registerAuditKind('password.reset-by-admin');
57
+ registerAuditKind('credential.issue');
58
+ registerAuditKind('webauthn.register');
59
+ /* Who may do what */
60
+ registerAuditKind('role.grant');
61
+ registerAuditKind('role.revoke');
62
+ registerAuditKind('role.privilege.add');
63
+ registerAuditKind('role.privilege.remove');
64
+ registerAuditKind('role.delete');
65
+ registerAuditKind('user.status', ['from', 'to']);
66
+ registerAuditKind('domain.owner.add');
67
+ registerAuditKind('domain.owner.remove');
68
+ registerAuditKind('invitation.create');
69
+ registerAuditKind('invitation.status', ['from', 'to']);
70
+ //# sourceMappingURL=audit-kinds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-kinds.js","sourceRoot":"","sources":["../../../server/service/audit/audit-kinds.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The kinds of audit action, and the `detail` keys each may carry (ADR-0141 §3.4).\n *\n * The audit stores only fields it allows. The fixed columns of `AuditAction` hold no business\n * value; `detail` is the one open place, so it takes only the keys its kind registered here, and\n * a kind nobody registered is refused - a module that wants a new event says so here, at import,\n * and a typo does not become a new kind quietly.\n *\n * A key holds a security state or a count, never a business value: a user's status (`activated` →\n * `locked`) is the account's security state; a salary is not, and has no key to go in.\n */\n\nexport type AuditDetailValue = string | number | boolean | null\n\nconst KINDS = new Map<string, readonly string[]>()\n\nexport function registerAuditKind(kind: string, detail: readonly string[] = []) {\n const known = KINDS.get(kind)\n if (known && known.join() !== detail.join()) {\n throw new Error(`Audit kind \"${kind}\" is registered twice with different detail keys.`)\n }\n KINDS.set(kind, detail)\n}\n\nexport function isAuditKind(kind: string): boolean {\n return KINDS.has(kind)\n}\n\n/**\n * The detail as it may be stored: unknown kinds throw, keys the kind did not register throw, and\n * values are cut to 64 characters. Throwing is right - this is a program writing the wrong thing.\n */\nexport function allowedDetail(\n kind: string,\n detail?: Record<string, AuditDetailValue>\n): Record<string, AuditDetailValue> | undefined {\n const keys = KINDS.get(kind)\n if (!keys) {\n throw new Error(`Audit kind \"${kind}\" is not registered. Register it with registerAuditKind() first.`)\n }\n if (!detail) return undefined\n\n const kept: Record<string, AuditDetailValue> = {}\n for (const [key, value] of Object.entries(detail)) {\n if (!keys.includes(key)) {\n throw new Error(`Audit kind \"${kind}\" does not allow detail \"${key}\". Allowed: ${keys.join(', ') || 'none'}.`)\n }\n kept[key] = typeof value === 'string' ? value.slice(0, 64) : value\n }\n return Object.keys(kept).length ? kept : undefined\n}\n\n/* Doors */\nregisterAuditKind('door.check')\nregisterAuditKind('door.result')\n\n/* Signing in and out (ADR-0141 §3.3). The reason of a failure is the error code column. */\nregisterAuditKind('auth.signin')\nregisterAuditKind('auth.checkin')\nregisterAuditKind('auth.signout')\nregisterAuditKind('password.change')\nregisterAuditKind('password.reset-by-admin')\nregisterAuditKind('credential.issue')\nregisterAuditKind('webauthn.register')\n\n/* Who may do what */\nregisterAuditKind('role.grant')\nregisterAuditKind('role.revoke')\nregisterAuditKind('role.privilege.add')\nregisterAuditKind('role.privilege.remove')\nregisterAuditKind('role.delete')\nregisterAuditKind('user.status', ['from', 'to'])\nregisterAuditKind('domain.owner.add')\nregisterAuditKind('domain.owner.remove')\nregisterAuditKind('invitation.create')\nregisterAuditKind('invitation.status', ['from', 'to'])\n"]}
@@ -0,0 +1,11 @@
1
+ import { AuditAction } from './audit-action.js';
2
+ import { AuditRequest } from './audit-request.js';
3
+ import { AuditDoor, AuditHealth, AuditPerson, AuditRecordFilter, AuditRecordList } from './audit-types.js';
4
+ export declare class AuditQuery {
5
+ auditRecords(filter: AuditRecordFilter, context: ResolverContext): Promise<AuditRecordList>;
6
+ auditPeople(from: Date, to: Date, context: ResolverContext): Promise<AuditPerson[]>;
7
+ auditDoors(from: Date, to: Date, context: ResolverContext): Promise<AuditDoor[]>;
8
+ auditHealth(): Promise<AuditHealth>;
9
+ myAuditActions(kind: string, limit: number, context: ResolverContext): Promise<AuditAction[]>;
10
+ request(action: AuditAction): Promise<AuditRequest | null>;
11
+ }
@@ -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"]}