@things-factory/auth-base 10.1.47 → 10.1.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist-client/tsconfig.tsbuildinfo +1 -1
  2. package/dist-server/controllers/access-request.d.ts +53 -0
  3. package/dist-server/controllers/access-request.js +333 -0
  4. package/dist-server/controllers/access-request.js.map +1 -0
  5. package/dist-server/service/access-request/access-request-mutation.d.ts +17 -0
  6. package/dist-server/service/access-request/access-request-mutation.js +65 -0
  7. package/dist-server/service/access-request/access-request-mutation.js.map +1 -0
  8. package/dist-server/service/access-request/access-request-query.d.ts +11 -0
  9. package/dist-server/service/access-request/access-request-query.js +85 -0
  10. package/dist-server/service/access-request/access-request-query.js.map +1 -0
  11. package/dist-server/service/access-request/access-request-types.d.ts +32 -0
  12. package/dist-server/service/access-request/access-request-types.js +115 -0
  13. package/dist-server/service/access-request/access-request-types.js.map +1 -0
  14. package/dist-server/service/access-request/access-request.d.ts +33 -0
  15. package/dist-server/service/access-request/access-request.js +160 -0
  16. package/dist-server/service/access-request/access-request.js.map +1 -0
  17. package/dist-server/service/access-request/index.d.ts +5 -0
  18. package/dist-server/service/access-request/index.js +9 -0
  19. package/dist-server/service/access-request/index.js.map +1 -0
  20. package/dist-server/service/index.d.ts +3 -1
  21. package/dist-server/service/index.js +42 -37
  22. package/dist-server/service/index.js.map +1 -1
  23. package/dist-server/service/open-fields.js +4 -1
  24. package/dist-server/service/open-fields.js.map +1 -1
  25. package/dist-server/service/privilege/privilege-directive.js +6 -6
  26. package/dist-server/service/privilege/privilege-directive.js.map +1 -1
  27. package/dist-server/service/privilege/privilege-rejection.d.ts +8 -2
  28. package/dist-server/service/privilege/privilege-rejection.js +27 -6
  29. package/dist-server/service/privilege/privilege-rejection.js.map +1 -1
  30. package/dist-server/service/user/user-mutation.js +5 -4
  31. package/dist-server/service/user/user-mutation.js.map +1 -1
  32. package/dist-server/service/user/user-query.js +5 -17
  33. package/dist-server/service/user/user-query.js.map +1 -1
  34. package/dist-server/tsconfig.tsbuildinfo +1 -1
  35. package/dist-server/utils/access-request-review.d.ts +69 -0
  36. package/dist-server/utils/access-request-review.js +119 -0
  37. package/dist-server/utils/access-request-review.js.map +1 -0
  38. package/dist-server/utils/assignable-roles.d.ts +23 -0
  39. package/dist-server/utils/assignable-roles.js +89 -0
  40. package/dist-server/utils/assignable-roles.js.map +1 -0
  41. package/dist-server/utils/check-permission.d.ts +29 -2
  42. package/dist-server/utils/check-permission.js +48 -14
  43. package/dist-server/utils/check-permission.js.map +1 -1
  44. package/package.json +4 -4
  45. package/tests/access-request-db.test.ts +236 -0
  46. package/tests/access-request-review.test.ts +155 -0
  47. package/tests/assignable-roles-db.test.ts +141 -0
  48. package/tests/permission-gate.test.ts +5 -7
  49. package/tests/privilege-directive.test.ts +21 -0
  50. package/tests/privilege-rejection.test.ts +32 -0
  51. package/tests/refusal-cause.test.ts +80 -0
  52. package/translations/en.json +26 -1
  53. package/translations/ja.json +26 -1
  54. package/translations/ko.json +26 -1
  55. package/translations/ms.json +26 -1
  56. package/translations/zh.json +26 -1
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AccessDecisionRefused = void 0;
4
+ exports.requestAccess = requestAccess;
5
+ exports.withdrawAccessRequest = withdrawAccessRequest;
6
+ exports.findDeciderIds = findDeciderIds;
7
+ exports.rolesGranting = rolesGranting;
8
+ exports.collectDecisionFacts = collectDecisionFacts;
9
+ exports.reviewDecision = reviewDecision;
10
+ exports.decideAccessRequest = decideAccessRequest;
11
+ const typeorm_1 = require("typeorm");
12
+ const shell_1 = require("@things-factory/shell");
13
+ const access_request_js_1 = require("../service/access-request/access-request.js");
14
+ const privilege_js_1 = require("../service/privilege/privilege.js");
15
+ const role_js_1 = require("../service/role/role.js");
16
+ const user_js_1 = require("../service/user/user.js");
17
+ const access_request_review_js_1 = require("../utils/access-request-review.js");
18
+ const assignable_roles_js_1 = require("../utils/assignable-roles.js");
19
+ const check_permission_js_1 = require("../utils/check-permission.js");
20
+ const domain_ownership_js_1 = require("../utils/domain-ownership.js");
21
+ /** Refuses a decision whose review blocks it - the first blocking issue names the refusal, all codes go along. */
22
+ class AccessDecisionRefused extends shell_1.Refusal {
23
+ constructor(issues) {
24
+ const first = issues.find(issue => issue.blocking);
25
+ super(`access-decision-${first.code}`, { codes: issues.filter(i => i.blocking).map(i => i.code).join(',') }, `The decision cannot be made: ${first.code}.`);
26
+ this.issues = issues;
27
+ }
28
+ }
29
+ exports.AccessDecisionRefused = AccessDecisionRefused;
30
+ /* ── asking ──────────────────────────────────────────────────────────────── */
31
+ /**
32
+ * The caller asks for `category:privilege` in this domain.
33
+ *
34
+ * The server judges again whether a role would open it - the `because` a screen got with its refusal is not
35
+ * taken on trust (ADR-0103 decision 2). Asking again while one is pending adds to that one.
36
+ */
37
+ async function requestAccess(input, context) {
38
+ const { domain, user, tx, unsafeIP, prohibitedPrivileges } = context.state;
39
+ const { category, privilege } = input;
40
+ const now = new Date();
41
+ if (!category || !privilege) {
42
+ throw new shell_1.Refusal('access-request-names-nothing', {}, 'An access request names a category and a privilege.');
43
+ }
44
+ const known = await (0, shell_1.getRepository)(privilege_js_1.Privilege, tx).findOneBy({ category, name: privilege });
45
+ if (!known) {
46
+ throw new shell_1.Refusal('access-request-unknown-privilege', { privilege: `${category}:${privilege}` }, `There is no privilege ${category}:${privilege}.`);
47
+ }
48
+ const judgement = await (0, check_permission_js_1.judgePermission)({ category, privilege }, user, domain, unsafeIP, prohibitedPrivileges);
49
+ if (judgement.permitted === true) {
50
+ throw new shell_1.Refusal('access-request-already-holds', { privilege: `${category}:${privilege}` }, `You already have ${category}:${privilege}.`);
51
+ }
52
+ if (judgement.because !== 'role') {
53
+ throw new shell_1.Refusal('access-request-not-by-role', { privilege: `${category}:${privilege}`, because: judgement.because }, `A role would not open ${category}:${privilege} here (${judgement.because}).`);
54
+ }
55
+ const repository = (0, shell_1.getRepository)(access_request_js_1.AccessRequest, tx);
56
+ const pending = await repository.findOne({
57
+ where: {
58
+ domain: { id: domain.id },
59
+ requester: { id: user.id },
60
+ category,
61
+ privilege,
62
+ state: access_request_js_1.AccessRequestStatus.PENDING
63
+ },
64
+ relations: ['domain', 'requester']
65
+ });
66
+ if (pending && (0, access_request_review_js_1.effectiveState)(pending.state, pending.expiresAt, now) === access_request_js_1.AccessRequestStatus.PENDING) {
67
+ pending.count = (pending.count || 1) + 1;
68
+ pending.lastRequestedAt = now;
69
+ if (input.reason?.trim())
70
+ pending.reason = input.reason.trim();
71
+ if (input.operationName)
72
+ pending.operationName = input.operationName;
73
+ /* Merged - the people deciding were told once, when it was first asked. */
74
+ return await repository.save(pending);
75
+ }
76
+ if (pending) {
77
+ pending.state = access_request_js_1.AccessRequestStatus.EXPIRED;
78
+ await repository.save(pending);
79
+ }
80
+ const created = await repository.save({
81
+ domain,
82
+ requester: user,
83
+ category,
84
+ privilege,
85
+ operationName: input.operationName,
86
+ reason: input.reason?.trim() || null,
87
+ count: 1,
88
+ lastRequestedAt: now,
89
+ state: access_request_js_1.AccessRequestStatus.PENDING,
90
+ expiresAt: (0, access_request_review_js_1.expiryFrom)(now)
91
+ });
92
+ const deciders = await findDeciderIds(domain, user.id, tx);
93
+ tellLater(context, 'notify the people who decide access requests', async () => notifyEach(context, deciders, 'access-request.notify.asked', {
94
+ name: user.name || user.email || user.username,
95
+ privilege: `${category}:${privilege}`
96
+ }, created));
97
+ return created;
98
+ }
99
+ /** The requester takes back their own pending request. */
100
+ async function withdrawAccessRequest(id, context) {
101
+ const { domain, user, tx } = context.state;
102
+ const repository = (0, shell_1.getRepository)(access_request_js_1.AccessRequest, tx);
103
+ const updated = await repository
104
+ .createQueryBuilder()
105
+ .update(access_request_js_1.AccessRequest)
106
+ .set({ state: access_request_js_1.AccessRequestStatus.WITHDRAWN })
107
+ .where('id = :id AND domain_id = :domainId AND requester_id = :userId AND state = :pending', {
108
+ id,
109
+ domainId: domain.id,
110
+ userId: user.id,
111
+ pending: access_request_js_1.AccessRequestStatus.PENDING
112
+ })
113
+ .execute();
114
+ if (!updated.affected) {
115
+ throw new shell_1.Refusal('access-request-not-withdrawable', { id }, 'There is no pending request of yours to withdraw.');
116
+ }
117
+ return await repository.findOne({ where: { id }, relations: ['domain', 'requester'] });
118
+ }
119
+ /* ── who decides ─────────────────────────────────────────────────────────── */
120
+ /**
121
+ * The people who can decide: domain owners and holders of the role-granting privilege - the same pair the
122
+ * decision door declares (`@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)`).
123
+ * The requester is never one of them.
124
+ */
125
+ async function findDeciderIds(domain, requesterId, tx) {
126
+ const owners = await (0, domain_ownership_js_1.findDomainOwnerUserIds)(domain, tx);
127
+ const qb = (0, shell_1.getRepository)(user_js_1.User, tx)
128
+ .createQueryBuilder('USER')
129
+ .select('USER.id', 'id')
130
+ .innerJoin('USER.domains', 'MEMBER', 'MEMBER.id = :memberOf', { memberOf: domain.id })
131
+ .innerJoin('USER.roles', 'role')
132
+ .innerJoin('role.privileges', 'PRIVILEGE')
133
+ .where('PRIVILEGE.category = :grantCategory', { grantCategory: access_request_review_js_1.ROLE_GRANTING_PRIVILEGE.category })
134
+ .andWhere('PRIVILEGE.name = :grantName', { grantName: access_request_review_js_1.ROLE_GRANTING_PRIVILEGE.privilege })
135
+ .andWhere('USER.status = :active', { active: user_js_1.UserStatus.ACTIVATED });
136
+ (0, shell_1.applyDomainInheritanceFilter)(qb, 'role', domain, shell_1.InheritedValueType.Include);
137
+ const holders = (await qb.getRawMany()).map(row => row.id);
138
+ return [...new Set([...owners, ...holders])].filter(id => id && id !== requesterId).sort();
139
+ }
140
+ /* ── reviewing ───────────────────────────────────────────────────────────── */
141
+ /** Every assignable role that grants `category:privilege`, smallest first. */
142
+ async function rolesGranting(category, privilege, domain, tx) {
143
+ const qb = (0, shell_1.getRepository)(role_js_1.Role, tx)
144
+ .createQueryBuilder('ROLE')
145
+ .innerJoin('ROLE.privileges', 'ASKED', 'ASKED.category = :askedCategory AND ASKED.name = :askedName', {
146
+ askedCategory: category,
147
+ askedName: privilege
148
+ });
149
+ const roles = await (0, assignable_roles_js_1.whereAssignableBy)(qb, 'ROLE', domain).getMany();
150
+ if (roles.length === 0)
151
+ return [];
152
+ const withPrivileges = await (0, shell_1.getRepository)(role_js_1.Role, tx).find({ where: { id: (0, typeorm_1.In)(roles.map(role => role.id)) }, relations: ['privileges'] });
153
+ return withPrivileges
154
+ .map(role => ({ id: role.id, name: role.name, privilegeCount: role.privileges?.length ?? 0, role }))
155
+ .sort((a, b) => a.privilegeCount - b.privilegeCount || a.name.localeCompare(b.name));
156
+ }
157
+ /** The facts `reviewAccessDecision` reads - collected as the person deciding, inside this domain. */
158
+ async function collectDecisionFacts(input, context) {
159
+ const { domain, user: decider, tx } = context.state;
160
+ const now = new Date();
161
+ const request = await (0, shell_1.getRepository)(access_request_js_1.AccessRequest, tx).findOne({
162
+ where: { id: input.requestId, domain: { id: domain.id } },
163
+ relations: ['requester', 'requester.domains']
164
+ });
165
+ const base = {
166
+ request: null,
167
+ deciderId: decider.id,
168
+ decision: input.decision,
169
+ reason: input.reason,
170
+ requester: null,
171
+ requesterHoldsIt: false,
172
+ candidates: [],
173
+ deciderPrivileges: [],
174
+ deciderIsOwner: false,
175
+ now
176
+ };
177
+ if (!request)
178
+ return base;
179
+ const requester = request.requester;
180
+ const [candidates, chosen, deciderIsOwner, deciderPrivileges, requesterHoldsIt, deniedBefore] = await Promise.all([
181
+ rolesGranting(request.category, request.privilege, domain, tx),
182
+ input.roleId ? (0, assignable_roles_js_1.findAssignableRoles)([input.roleId], domain, tx) : Promise.resolve([]),
183
+ (0, domain_ownership_js_1.isDomainOwner)(domain, decider, tx),
184
+ user_js_1.User.getPrivilegesByDomain(decider, domain),
185
+ requester ? user_js_1.User.hasPrivilege(request.privilege, request.category, domain, requester) : Promise.resolve(false),
186
+ (0, shell_1.getRepository)(access_request_js_1.AccessRequest, tx).findOne({
187
+ where: {
188
+ domain: { id: domain.id },
189
+ requester: { id: requester?.id },
190
+ category: request.category,
191
+ privilege: request.privilege,
192
+ state: access_request_js_1.AccessRequestStatus.DENIED
193
+ },
194
+ relations: ['decidedBy'],
195
+ order: { decidedAt: 'DESC' }
196
+ })
197
+ ]);
198
+ const role = chosen[0]
199
+ ? await (0, shell_1.getRepository)(role_js_1.Role, tx).findOne({ where: { id: chosen[0].id }, relations: ['privileges'] })
200
+ : null;
201
+ return {
202
+ ...base,
203
+ request: {
204
+ state: request.state,
205
+ requesterId: requester?.id,
206
+ category: request.category,
207
+ privilege: request.privilege,
208
+ expiresAt: request.expiresAt
209
+ },
210
+ role: role
211
+ ? { id: role.id, name: role.name, privileges: (role.privileges || []).map(p => ({ category: p.category, privilege: p.name })) }
212
+ : null,
213
+ roleNamedButNotAssignable: !!input.roleId && !role,
214
+ requester: requester
215
+ ? {
216
+ active: requester.status === user_js_1.UserStatus.ACTIVATED,
217
+ inDomain: (requester.domains || []).some(d => d.id === domain.id)
218
+ }
219
+ : null,
220
+ requesterHoldsIt,
221
+ candidates: candidates.map(({ id, name, privilegeCount }) => ({ id, name, privilegeCount })),
222
+ deciderIsOwner,
223
+ deciderPrivileges: (deciderPrivileges || []).map((p) => ({ category: p.category, privilege: p.privilege })),
224
+ deniedBefore: deniedBefore && deniedBefore.id !== request.id
225
+ ? {
226
+ by: deniedBefore.decidedBy?.name || deniedBefore.decidedBy?.email || '',
227
+ at: deniedBefore.decidedAt,
228
+ reason: deniedBefore.decisionReason
229
+ }
230
+ : null
231
+ };
232
+ }
233
+ async function reviewDecision(input, context) {
234
+ return (0, access_request_review_js_1.reviewAccessDecision)(await collectDecisionFacts(input, context));
235
+ }
236
+ /* ── deciding ────────────────────────────────────────────────────────────── */
237
+ /**
238
+ * Grants or denies. The review runs again here - the card and the screen were shown a moment ago, and another
239
+ * person may have decided since. The state moves PENDING → decided in one conditional update, so two people
240
+ * deciding at once cannot both win.
241
+ */
242
+ async function decideAccessRequest(input, context) {
243
+ const { domain, user: decider, tx } = context.state;
244
+ const now = new Date();
245
+ const issues = await reviewDecision(input, context);
246
+ if ((0, access_request_review_js_1.isBlocked)(issues)) {
247
+ throw new AccessDecisionRefused(issues);
248
+ }
249
+ const repository = (0, shell_1.getRepository)(access_request_js_1.AccessRequest, tx);
250
+ const granting = input.decision === 'GRANT';
251
+ const moved = await repository
252
+ .createQueryBuilder()
253
+ .update(access_request_js_1.AccessRequest)
254
+ .set({
255
+ state: granting ? access_request_js_1.AccessRequestStatus.GRANTED : access_request_js_1.AccessRequestStatus.DENIED,
256
+ decidedBy: { id: decider.id },
257
+ decidedAt: now,
258
+ decisionReason: input.reason?.trim() || null,
259
+ grantedRole: granting ? { id: input.roleId } : null
260
+ })
261
+ .where('id = :id AND domain_id = :domainId AND state = :pending', {
262
+ id: input.requestId,
263
+ domainId: domain.id,
264
+ pending: access_request_js_1.AccessRequestStatus.PENDING
265
+ })
266
+ .execute();
267
+ if (!moved.affected) {
268
+ throw new AccessDecisionRefused([{ code: 'request-not-pending', blocking: true, field: 'request' }]);
269
+ }
270
+ const request = await repository.findOne({ where: { id: input.requestId }, relations: ['requester', 'requester.roles', 'grantedRole'] });
271
+ const requester = request.requester;
272
+ if (granting) {
273
+ await (0, assignable_roles_js_1.assignRoles)(requester, { add: [input.roleId] }, domain, tx);
274
+ await closeWhatTheGrantOpened(request, context);
275
+ }
276
+ tellLater(context, 'notify the requester of the decision', async () => notifyEach(context, [requester.id], granting ? 'access-request.notify.granted' : 'access-request.notify.denied', { privilege: `${request.category}:${request.privilege}`, role: request.grantedRole?.name ?? '', reason: request.decisionReason ?? '' }, request));
277
+ return request;
278
+ }
279
+ /** Other pending requests of the same person that the role just given now answers - closed as granted with it. */
280
+ async function closeWhatTheGrantOpened(granted, context) {
281
+ const { domain, user: decider, tx } = context.state;
282
+ const repository = (0, shell_1.getRepository)(access_request_js_1.AccessRequest, tx);
283
+ const others = await repository.find({
284
+ where: { domain: { id: domain.id }, requester: { id: granted.requester.id }, state: access_request_js_1.AccessRequestStatus.PENDING },
285
+ relations: ['requester']
286
+ });
287
+ for (const other of others) {
288
+ if (!(await user_js_1.User.hasPrivilege(other.privilege, other.category, domain, granted.requester)))
289
+ continue;
290
+ await repository.update({ id: other.id, state: access_request_js_1.AccessRequestStatus.PENDING }, { state: access_request_js_1.AccessRequestStatus.GRANTED, decidedBy: { id: decider.id }, decidedAt: new Date(), grantedRole: granted.grantedRole });
291
+ }
292
+ }
293
+ /* ── telling ─────────────────────────────────────────────────────────────── */
294
+ /** After the commit when there is a transaction, at once otherwise - a failed notification never fails the decision. */
295
+ function tellLater(context, name, run) {
296
+ const deferred = (0, shell_1.whenCommitted)(context.state.afterCommit, name, run);
297
+ if (!deferred)
298
+ void run().catch(() => { });
299
+ }
300
+ /**
301
+ * One notification per person, in that person's language (`User.locale`). `notify` is optional: it is there when
302
+ * the notification module is loaded, and nothing happens otherwise - the request list is the record.
303
+ */
304
+ async function notifyEach(context, userIds, key, params, request) {
305
+ const notify = context.state.notify;
306
+ if (typeof notify !== 'function' || userIds.length === 0)
307
+ return;
308
+ const people = await (0, shell_1.getRepository)(user_js_1.User).find({ where: { id: (0, typeorm_1.In)(userIds) } });
309
+ const byLocale = new Map();
310
+ for (const person of people) {
311
+ const locale = person.locale || '';
312
+ byLocale.set(locale, [...(byLocale.get(locale) || []), person.id]);
313
+ }
314
+ for (const [locale, receivers] of byLocale) {
315
+ const say = (suffix) => translate(context, `${key}.${suffix}`, params, locale);
316
+ await notify({
317
+ receivers,
318
+ type: 'info',
319
+ subject: 'access-request',
320
+ title: say('title'),
321
+ body: say('body'),
322
+ url: `access-requests?id=${request.id}`,
323
+ mode: 'background'
324
+ });
325
+ }
326
+ }
327
+ function translate(context, key, params, locale) {
328
+ const i18next = context?.i18next;
329
+ if (locale && i18next?.t)
330
+ return i18next.t(key, { ...params, lng: locale });
331
+ return typeof context?.t === 'function' ? context.t(key, { ...params }) : key;
332
+ }
333
+ //# sourceMappingURL=access-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-request.js","sourceRoot":"","sources":["../../server/controllers/access-request.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\n\nimport { applyDomainInheritanceFilter, Domain, getRepository, InheritedValueType, Refusal, whenCommitted } from '@things-factory/shell'\n\nimport { AccessRequest, AccessRequestStatus } from '../service/access-request/access-request.js'\nimport { Privilege } from '../service/privilege/privilege.js'\nimport { Role } from '../service/role/role.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport {\n AccessDecisionKind,\n DecisionFacts,\n DecisionIssue,\n effectiveState,\n expiryFrom,\n isBlocked,\n reviewAccessDecision,\n ROLE_GRANTING_PRIVILEGE\n} from '../utils/access-request-review.js'\nimport { assignRoles, findAssignableRoles, whereAssignableBy } from '../utils/assignable-roles.js'\nimport { judgePermission } from '../utils/check-permission.js'\nimport { findDomainOwnerUserIds, isDomainOwner } from '../utils/domain-ownership.js'\n\n/*\n * Access requests - asking, withdrawing, reviewing and deciding (ADR-0103, docs/design/access-request.md).\n *\n * Every door reads the domain and the person from the request, never from an argument.\n */\n\nexport type NewAccessRequestInput = { category: string; privilege: string; operationName?: string; reason?: string }\nexport type AccessDecisionInput = { requestId: string; decision: AccessDecisionKind; roleId?: string; reason?: string }\n\n/** Refuses a decision whose review blocks it - the first blocking issue names the refusal, all codes go along. */\nexport class AccessDecisionRefused extends Refusal {\n constructor(readonly issues: DecisionIssue[]) {\n const first = issues.find(issue => issue.blocking)\n super(`access-decision-${first.code}`, { codes: issues.filter(i => i.blocking).map(i => i.code).join(',') }, `The decision cannot be made: ${first.code}.`)\n }\n}\n\n/* ── asking ──────────────────────────────────────────────────────────────── */\n\n/**\n * The caller asks for `category:privilege` in this domain.\n *\n * The server judges again whether a role would open it - the `because` a screen got with its refusal is not\n * taken on trust (ADR-0103 decision 2). Asking again while one is pending adds to that one.\n */\nexport async function requestAccess(input: NewAccessRequestInput, context: ResolverContext): Promise<AccessRequest> {\n const { domain, user, tx, unsafeIP, prohibitedPrivileges } = context.state\n const { category, privilege } = input\n const now = new Date()\n\n if (!category || !privilege) {\n throw new Refusal('access-request-names-nothing', {}, 'An access request names a category and a privilege.')\n }\n\n const known = await getRepository(Privilege, tx).findOneBy({ category, name: privilege })\n if (!known) {\n throw new Refusal('access-request-unknown-privilege', { privilege: `${category}:${privilege}` }, `There is no privilege ${category}:${privilege}.`)\n }\n\n const judgement = await judgePermission({ category, privilege }, user, domain, unsafeIP, prohibitedPrivileges)\n if (judgement.permitted === true) {\n throw new Refusal('access-request-already-holds', { privilege: `${category}:${privilege}` }, `You already have ${category}:${privilege}.`)\n }\n if (judgement.because !== 'role') {\n throw new Refusal(\n 'access-request-not-by-role',\n { privilege: `${category}:${privilege}`, because: judgement.because },\n `A role would not open ${category}:${privilege} here (${judgement.because}).`\n )\n }\n\n const repository = getRepository(AccessRequest, tx)\n const pending = await repository.findOne({\n where: {\n domain: { id: domain.id },\n requester: { id: user.id },\n category,\n privilege,\n state: AccessRequestStatus.PENDING\n },\n relations: ['domain', 'requester']\n })\n\n if (pending && effectiveState(pending.state, pending.expiresAt, now) === AccessRequestStatus.PENDING) {\n pending.count = (pending.count || 1) + 1\n pending.lastRequestedAt = now\n if (input.reason?.trim()) pending.reason = input.reason.trim()\n if (input.operationName) pending.operationName = input.operationName\n /* Merged - the people deciding were told once, when it was first asked. */\n return await repository.save(pending)\n }\n\n if (pending) {\n pending.state = AccessRequestStatus.EXPIRED\n await repository.save(pending)\n }\n\n const created = await repository.save({\n domain,\n requester: user,\n category,\n privilege,\n operationName: input.operationName,\n reason: input.reason?.trim() || null,\n count: 1,\n lastRequestedAt: now,\n state: AccessRequestStatus.PENDING,\n expiresAt: expiryFrom(now)\n } as Partial<AccessRequest>)\n\n const deciders = await findDeciderIds(domain, user.id, tx)\n tellLater(context, 'notify the people who decide access requests', async () =>\n notifyEach(context, deciders, 'access-request.notify.asked', {\n name: user.name || user.email || user.username,\n privilege: `${category}:${privilege}`\n }, created)\n )\n\n return created\n}\n\n/** The requester takes back their own pending request. */\nexport async function withdrawAccessRequest(id: string, context: ResolverContext): Promise<AccessRequest> {\n const { domain, user, tx } = context.state\n const repository = getRepository(AccessRequest, tx)\n\n const updated = await repository\n .createQueryBuilder()\n .update(AccessRequest)\n .set({ state: AccessRequestStatus.WITHDRAWN })\n .where('id = :id AND domain_id = :domainId AND requester_id = :userId AND state = :pending', {\n id,\n domainId: domain.id,\n userId: user.id,\n pending: AccessRequestStatus.PENDING\n })\n .execute()\n\n if (!updated.affected) {\n throw new Refusal('access-request-not-withdrawable', { id }, 'There is no pending request of yours to withdraw.')\n }\n return await repository.findOne({ where: { id }, relations: ['domain', 'requester'] })\n}\n\n/* ── who decides ─────────────────────────────────────────────────────────── */\n\n/**\n * The people who can decide: domain owners and holders of the role-granting privilege - the same pair the\n * decision door declares (`@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)`).\n * The requester is never one of them.\n */\nexport async function findDeciderIds(domain: Domain, requesterId: string, tx?: EntityManager): Promise<string[]> {\n const owners = await findDomainOwnerUserIds(domain, tx)\n\n const qb = getRepository(User, tx)\n .createQueryBuilder('USER')\n .select('USER.id', 'id')\n .innerJoin('USER.domains', 'MEMBER', 'MEMBER.id = :memberOf', { memberOf: domain.id })\n .innerJoin('USER.roles', 'role')\n .innerJoin('role.privileges', 'PRIVILEGE')\n .where('PRIVILEGE.category = :grantCategory', { grantCategory: ROLE_GRANTING_PRIVILEGE.category })\n .andWhere('PRIVILEGE.name = :grantName', { grantName: ROLE_GRANTING_PRIVILEGE.privilege })\n .andWhere('USER.status = :active', { active: UserStatus.ACTIVATED })\n applyDomainInheritanceFilter(qb, 'role', domain, InheritedValueType.Include)\n const holders = (await qb.getRawMany()).map(row => row.id)\n\n return [...new Set([...owners, ...holders])].filter(id => id && id !== requesterId).sort()\n}\n\n/* ── reviewing ───────────────────────────────────────────────────────────── */\n\n/** Every assignable role that grants `category:privilege`, smallest first. */\nexport async function rolesGranting(category: string, privilege: string, domain: Domain, tx?: EntityManager) {\n const qb = getRepository(Role, tx)\n .createQueryBuilder('ROLE')\n .innerJoin('ROLE.privileges', 'ASKED', 'ASKED.category = :askedCategory AND ASKED.name = :askedName', {\n askedCategory: category,\n askedName: privilege\n })\n const roles = await whereAssignableBy(qb, 'ROLE', domain).getMany()\n if (roles.length === 0) return []\n\n const withPrivileges = await getRepository(Role, tx).find({ where: { id: In(roles.map(role => role.id)) }, relations: ['privileges'] })\n return withPrivileges\n .map(role => ({ id: role.id, name: role.name, privilegeCount: role.privileges?.length ?? 0, role }))\n .sort((a, b) => a.privilegeCount - b.privilegeCount || a.name.localeCompare(b.name))\n}\n\n/** The facts `reviewAccessDecision` reads - collected as the person deciding, inside this domain. */\nexport async function collectDecisionFacts(input: AccessDecisionInput, context: ResolverContext): Promise<DecisionFacts> {\n const { domain, user: decider, tx } = context.state\n const now = new Date()\n\n const request = await getRepository(AccessRequest, tx).findOne({\n where: { id: input.requestId, domain: { id: domain.id } },\n relations: ['requester', 'requester.domains']\n })\n\n const base: DecisionFacts = {\n request: null,\n deciderId: decider.id,\n decision: input.decision,\n reason: input.reason,\n requester: null,\n requesterHoldsIt: false,\n candidates: [],\n deciderPrivileges: [],\n deciderIsOwner: false,\n now\n }\n if (!request) return base\n\n const requester = request.requester\n const [candidates, chosen, deciderIsOwner, deciderPrivileges, requesterHoldsIt, deniedBefore] = await Promise.all([\n rolesGranting(request.category, request.privilege, domain, tx),\n input.roleId ? findAssignableRoles([input.roleId], domain, tx) : Promise.resolve([]),\n isDomainOwner(domain, decider, tx),\n User.getPrivilegesByDomain(decider, domain),\n requester ? User.hasPrivilege(request.privilege, request.category, domain, requester) : Promise.resolve(false),\n getRepository(AccessRequest, tx).findOne({\n where: {\n domain: { id: domain.id },\n requester: { id: requester?.id },\n category: request.category,\n privilege: request.privilege,\n state: AccessRequestStatus.DENIED\n },\n relations: ['decidedBy'],\n order: { decidedAt: 'DESC' }\n })\n ])\n\n const role = chosen[0]\n ? await getRepository(Role, tx).findOne({ where: { id: chosen[0].id }, relations: ['privileges'] })\n : null\n\n return {\n ...base,\n request: {\n state: request.state as any,\n requesterId: requester?.id,\n category: request.category,\n privilege: request.privilege,\n expiresAt: request.expiresAt\n },\n role: role\n ? { id: role.id, name: role.name, privileges: (role.privileges || []).map(p => ({ category: p.category, privilege: p.name })) }\n : null,\n roleNamedButNotAssignable: !!input.roleId && !role,\n requester: requester\n ? {\n active: requester.status === UserStatus.ACTIVATED,\n inDomain: (requester.domains || []).some(d => d.id === domain.id)\n }\n : null,\n requesterHoldsIt,\n candidates: candidates.map(({ id, name, privilegeCount }) => ({ id, name, privilegeCount })),\n deciderIsOwner,\n deciderPrivileges: (deciderPrivileges || []).map((p: any) => ({ category: p.category, privilege: p.privilege })),\n deniedBefore:\n deniedBefore && deniedBefore.id !== request.id\n ? {\n by: deniedBefore.decidedBy?.name || deniedBefore.decidedBy?.email || '',\n at: deniedBefore.decidedAt,\n reason: deniedBefore.decisionReason\n }\n : null\n }\n}\n\nexport async function reviewDecision(input: AccessDecisionInput, context: ResolverContext): Promise<DecisionIssue[]> {\n return reviewAccessDecision(await collectDecisionFacts(input, context))\n}\n\n/* ── deciding ────────────────────────────────────────────────────────────── */\n\n/**\n * Grants or denies. The review runs again here - the card and the screen were shown a moment ago, and another\n * person may have decided since. The state moves PENDING → decided in one conditional update, so two people\n * deciding at once cannot both win.\n */\nexport async function decideAccessRequest(input: AccessDecisionInput, context: ResolverContext): Promise<AccessRequest> {\n const { domain, user: decider, tx } = context.state\n const now = new Date()\n\n const issues = await reviewDecision(input, context)\n if (isBlocked(issues)) {\n throw new AccessDecisionRefused(issues)\n }\n\n const repository = getRepository(AccessRequest, tx)\n const granting = input.decision === 'GRANT'\n\n const moved = await repository\n .createQueryBuilder()\n .update(AccessRequest)\n .set({\n state: granting ? AccessRequestStatus.GRANTED : AccessRequestStatus.DENIED,\n decidedBy: { id: decider.id } as User,\n decidedAt: now,\n decisionReason: input.reason?.trim() || null,\n grantedRole: granting ? ({ id: input.roleId } as Role) : null\n })\n .where('id = :id AND domain_id = :domainId AND state = :pending', {\n id: input.requestId,\n domainId: domain.id,\n pending: AccessRequestStatus.PENDING\n })\n .execute()\n\n if (!moved.affected) {\n throw new AccessDecisionRefused([{ code: 'request-not-pending', blocking: true, field: 'request' }])\n }\n\n const request = await repository.findOne({ where: { id: input.requestId }, relations: ['requester', 'requester.roles', 'grantedRole'] })\n const requester = request.requester\n\n if (granting) {\n await assignRoles(requester, { add: [input.roleId] }, domain, tx)\n await closeWhatTheGrantOpened(request, context)\n }\n\n tellLater(context, 'notify the requester of the decision', async () =>\n notifyEach(\n context,\n [requester.id],\n granting ? 'access-request.notify.granted' : 'access-request.notify.denied',\n { privilege: `${request.category}:${request.privilege}`, role: request.grantedRole?.name ?? '', reason: request.decisionReason ?? '' },\n request\n )\n )\n\n return request\n}\n\n/** Other pending requests of the same person that the role just given now answers - closed as granted with it. */\nasync function closeWhatTheGrantOpened(granted: AccessRequest, context: ResolverContext) {\n const { domain, user: decider, tx } = context.state\n const repository = getRepository(AccessRequest, tx)\n const others = await repository.find({\n where: { domain: { id: domain.id }, requester: { id: granted.requester.id }, state: AccessRequestStatus.PENDING },\n relations: ['requester']\n })\n\n for (const other of others) {\n if (!(await User.hasPrivilege(other.privilege, other.category, domain, granted.requester))) continue\n await repository.update(\n { id: other.id, state: AccessRequestStatus.PENDING },\n { state: AccessRequestStatus.GRANTED, decidedBy: { id: decider.id } as User, decidedAt: new Date(), grantedRole: granted.grantedRole }\n )\n }\n}\n\n/* ── telling ─────────────────────────────────────────────────────────────── */\n\n/** After the commit when there is a transaction, at once otherwise - a failed notification never fails the decision. */\nfunction tellLater(context: ResolverContext, name: string, run: () => Promise<void>) {\n const deferred = whenCommitted(context.state.afterCommit, name, run)\n if (!deferred) void run().catch(() => {})\n}\n\n/**\n * One notification per person, in that person's language (`User.locale`). `notify` is optional: it is there when\n * the notification module is loaded, and nothing happens otherwise - the request list is the record.\n */\nasync function notifyEach(\n context: ResolverContext,\n userIds: string[],\n key: string,\n params: Record<string, string>,\n request: AccessRequest\n) {\n const notify = (context.state as any).notify\n if (typeof notify !== 'function' || userIds.length === 0) return\n\n const people = await getRepository(User).find({ where: { id: In(userIds) } })\n const byLocale = new Map<string, string[]>()\n for (const person of people) {\n const locale = person.locale || ''\n byLocale.set(locale, [...(byLocale.get(locale) || []), person.id])\n }\n\n for (const [locale, receivers] of byLocale) {\n const say = (suffix: string) => translate(context, `${key}.${suffix}`, params, locale)\n await notify({\n receivers,\n type: 'info',\n subject: 'access-request',\n title: say('title'),\n body: say('body'),\n url: `access-requests?id=${request.id}`,\n mode: 'background'\n })\n }\n}\n\nfunction translate(context: any, key: string, params: Record<string, string>, locale: string): string {\n const i18next = context?.i18next\n if (locale && i18next?.t) return i18next.t(key, { ...params, lng: locale })\n return typeof context?.t === 'function' ? context.t(key, { ...params }) : key\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { AccessRequest } from './access-request.js';
2
+ import { AccessDecisionInput, NewAccessRequest } from './access-request-types.js';
3
+ export declare const DECIDE = "@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)";
4
+ export declare class AccessRequestMutation {
5
+ /**
6
+ * Open by design (open-fields.ts): anyone in the domain who was refused for want of a role asks here. The
7
+ * door holds the rest - the caller asks only for themselves, the server judges again that a role would open it.
8
+ */
9
+ requestAccess(request: NewAccessRequest, context: ResolverContext): Promise<AccessRequest>;
10
+ /** Open by design: only the caller's own pending request can be withdrawn. */
11
+ withdrawAccessRequest(id: string, context: ResolverContext): Promise<AccessRequest>;
12
+ /**
13
+ * A person presses grant or deny - on the list screen, or on the AI 입력 draft card. The model never calls this
14
+ * (ADR-0103 decision 4); the review runs again inside.
15
+ */
16
+ decideAccessRequest(decision: AccessDecisionInput, context: ResolverContext): Promise<AccessRequest>;
17
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AccessRequestMutation = exports.DECIDE = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const access_request_js_1 = require("../../controllers/access-request.js");
8
+ const access_request_js_2 = require("./access-request.js");
9
+ const access_request_types_js_1 = require("./access-request-types.js");
10
+ /* Deciding is putting somebody into a role - the same declaration as `updateUserRoles`, so who is told and who may decide are one set. */
11
+ exports.DECIDE = '@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)';
12
+ let AccessRequestMutation = class AccessRequestMutation {
13
+ /**
14
+ * Open by design (open-fields.ts): anyone in the domain who was refused for want of a role asks here. The
15
+ * door holds the rest - the caller asks only for themselves, the server judges again that a role would open it.
16
+ */
17
+ async requestAccess(request, context) {
18
+ return await (0, access_request_js_1.requestAccess)(request, context);
19
+ }
20
+ /** Open by design: only the caller's own pending request can be withdrawn. */
21
+ async withdrawAccessRequest(id, context) {
22
+ return await (0, access_request_js_1.withdrawAccessRequest)(id, context);
23
+ }
24
+ /**
25
+ * A person presses grant or deny - on the list screen, or on the AI 입력 draft card. The model never calls this
26
+ * (ADR-0103 decision 4); the review runs again inside.
27
+ */
28
+ async decideAccessRequest(decision, context) {
29
+ return await (0, access_request_js_1.decideAccessRequest)(decision, context);
30
+ }
31
+ };
32
+ exports.AccessRequestMutation = AccessRequestMutation;
33
+ tslib_1.__decorate([
34
+ (0, type_graphql_1.Directive)('@transaction'),
35
+ (0, type_graphql_1.Mutation)(returns => access_request_js_2.AccessRequest, { description: 'To ask for a privilege the caller was refused for want of a role. Asking again while one is pending adds to it.' }),
36
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('request', type => access_request_types_js_1.NewAccessRequest)),
37
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
38
+ tslib_1.__metadata("design:type", Function),
39
+ tslib_1.__metadata("design:paramtypes", [access_request_types_js_1.NewAccessRequest, Object]),
40
+ tslib_1.__metadata("design:returntype", Promise)
41
+ ], AccessRequestMutation.prototype, "requestAccess", null);
42
+ tslib_1.__decorate([
43
+ (0, type_graphql_1.Directive)('@transaction'),
44
+ (0, type_graphql_1.Mutation)(returns => access_request_js_2.AccessRequest, { description: "To withdraw the caller's own pending access request." }),
45
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
46
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
47
+ tslib_1.__metadata("design:type", Function),
48
+ tslib_1.__metadata("design:paramtypes", [String, Object]),
49
+ tslib_1.__metadata("design:returntype", Promise)
50
+ ], AccessRequestMutation.prototype, "withdrawAccessRequest", null);
51
+ tslib_1.__decorate([
52
+ (0, type_graphql_1.Directive)(exports.DECIDE),
53
+ (0, type_graphql_1.Directive)('@transaction'),
54
+ (0, type_graphql_1.Mutation)(returns => access_request_js_2.AccessRequest, { description: 'To grant an access request by putting the requester into a role, or to deny it with a reason.' }),
55
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('decision', type => access_request_types_js_1.AccessDecisionInput)),
56
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
57
+ tslib_1.__metadata("design:type", Function),
58
+ tslib_1.__metadata("design:paramtypes", [access_request_types_js_1.AccessDecisionInput, Object]),
59
+ tslib_1.__metadata("design:returntype", Promise)
60
+ ], AccessRequestMutation.prototype, "decideAccessRequest", null);
61
+ exports.AccessRequestMutation = AccessRequestMutation = tslib_1.__decorate([
62
+ (0, type_graphql_1.Resolver)(access_request_js_2.AccessRequest),
63
+ (0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
64
+ ], AccessRequestMutation);
65
+ //# sourceMappingURL=access-request-mutation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-request-mutation.js","sourceRoot":"","sources":["../../../server/service/access-request/access-request-mutation.ts"],"names":[],"mappings":";;;;AAAA,+CAAqF;AAErF,iDAAqD;AAErD,2EAA+G;AAC/G,2DAAmD;AACnD,uEAAiF;AAEjF,0IAA0I;AAC7H,QAAA,MAAM,GAAG,+EAA+E,CAAA;AAI9F,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC;;;OAGG;IAGG,AAAN,KAAK,CAAC,aAAa,CAA2C,OAAyB,EAAS,OAAwB;QACtH,OAAO,MAAM,IAAA,iCAAa,EAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAC9C,CAAC;IAED,8EAA8E;IAGxE,AAAN,KAAK,CAAC,qBAAqB,CAAY,EAAU,EAAS,OAAwB;QAChF,OAAO,MAAM,IAAA,yCAAqB,EAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IACjD,CAAC;IAED;;;OAGG;IAIG,AAAN,KAAK,CAAC,mBAAmB,CAA+C,QAA6B,EAAS,OAAwB;QACpI,OAAO,MAAM,IAAA,uCAAmB,EAAC,QAAe,EAAE,OAAO,CAAC,CAAA;IAC5D,CAAC;CACF,CAAA;AA5BY,sDAAqB;AAO1B;IAFL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,iCAAa,EAAE,EAAE,WAAW,EAAE,iHAAiH,EAAE,CAAC;IAClJ,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,0CAAgB,CAAC,CAAA;IAA6B,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAAxB,0CAAgB;;0DAEtF;AAKK;IAFL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,iCAAa,EAAE,EAAE,WAAW,EAAE,sDAAsD,EAAE,CAAC;IAC/E,mBAAA,IAAA,kBAAG,EAAC,IAAI,CAAC,CAAA;IAAc,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;kEAExD;AASK;IAHL,IAAA,wBAAS,EAAC,cAAM,CAAC;IACjB,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,iCAAa,EAAE,EAAE,WAAW,EAAE,+FAA+F,EAAE,CAAC;IAC1H,mBAAA,IAAA,kBAAG,EAAC,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,6CAAmB,CAAC,CAAA;IAAiC,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAA3B,6CAAmB;;gEAEpG;gCA3BU,qBAAqB;IAFjC,IAAA,uBAAQ,EAAC,iCAAa,CAAC;IACvB,IAAA,4BAAa,EAAC,qBAAa,CAAC;GAChB,qBAAqB,CA4BjC","sourcesContent":["import { Arg, Ctx, Directive, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { SpeakRefusals } from '@things-factory/shell'\n\nimport { decideAccessRequest, requestAccess, withdrawAccessRequest } from '../../controllers/access-request.js'\nimport { AccessRequest } from './access-request.js'\nimport { AccessDecisionInput, NewAccessRequest } from './access-request-types.js'\n\n/* Deciding is putting somebody into a role - the same declaration as `updateUserRoles`, so who is told and who may decide are one set. */\nexport const DECIDE = '@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)'\n\n@Resolver(AccessRequest)\n@UseMiddleware(SpeakRefusals)\nexport class AccessRequestMutation {\n /**\n * Open by design (open-fields.ts): anyone in the domain who was refused for want of a role asks here. The\n * door holds the rest - the caller asks only for themselves, the server judges again that a role would open it.\n */\n @Directive('@transaction')\n @Mutation(returns => AccessRequest, { description: 'To ask for a privilege the caller was refused for want of a role. Asking again while one is pending adds to it.' })\n async requestAccess(@Arg('request', type => NewAccessRequest) request: NewAccessRequest, @Ctx() context: ResolverContext): Promise<AccessRequest> {\n return await requestAccess(request, context)\n }\n\n /** Open by design: only the caller's own pending request can be withdrawn. */\n @Directive('@transaction')\n @Mutation(returns => AccessRequest, { description: \"To withdraw the caller's own pending access request.\" })\n async withdrawAccessRequest(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<AccessRequest> {\n return await withdrawAccessRequest(id, context)\n }\n\n /**\n * A person presses grant or deny - on the list screen, or on the AI 입력 draft card. The model never calls this\n * (ADR-0103 decision 4); the review runs again inside.\n */\n @Directive(DECIDE)\n @Directive('@transaction')\n @Mutation(returns => AccessRequest, { description: 'To grant an access request by putting the requester into a role, or to deny it with a reason.' })\n async decideAccessRequest(@Arg('decision', type => AccessDecisionInput) decision: AccessDecisionInput, @Ctx() context: ResolverContext): Promise<AccessRequest> {\n return await decideAccessRequest(decision as any, context)\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import { ListParam } from '@things-factory/shell';
2
+ import { AccessRequest } from './access-request.js';
3
+ import { AccessDecisionInput, AccessDecisionIssue, AccessRequestList, AccessRoleCandidate } from './access-request-types.js';
4
+ export declare class AccessRequestQuery {
5
+ accessRequests(params: ListParam, context: ResolverContext): Promise<AccessRequestList>;
6
+ /** Open by design: the caller's own requests - where they see what became of them. */
7
+ myAccessRequests(context: ResolverContext): Promise<AccessRequest[]>;
8
+ rolesGranting(category: string, privilege: string, context: ResolverContext): Promise<AccessRoleCandidate[]>;
9
+ /** The same review the decision runs - the list screen and the AI draft card show this (ADR-0103 decision 4). */
10
+ reviewAccessDecision(decision: AccessDecisionInput, context: ResolverContext): Promise<AccessDecisionIssue[]>;
11
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AccessRequestQuery = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const access_request_js_1 = require("../../controllers/access-request.js");
8
+ const access_request_js_2 = require("./access-request.js");
9
+ const access_request_types_js_1 = require("./access-request-types.js");
10
+ /* Reading the requests is reading who asks for what in this domain - the user screens' read privilege. */
11
+ const READ = '@privilege(category: "user", privilege: "query", domainOwnerGranted: true)';
12
+ /* The review and the candidates serve a decision - the deciding declaration. */
13
+ const DECIDE = '@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)';
14
+ let AccessRequestQuery = class AccessRequestQuery {
15
+ async accessRequests(params, context) {
16
+ const { domain } = context.state;
17
+ const queryBuilder = (0, shell_1.getQueryBuilderFromListParams)({
18
+ domain,
19
+ params,
20
+ repository: (0, shell_1.getRepository)(access_request_js_2.AccessRequest),
21
+ alias: 'accessRequest',
22
+ searchables: ['category', 'privilege', 'reason']
23
+ });
24
+ queryBuilder.leftJoinAndSelect('accessRequest.requester', 'requester').leftJoinAndSelect('accessRequest.decidedBy', 'decidedBy').leftJoinAndSelect('accessRequest.grantedRole', 'grantedRole');
25
+ const [items, total] = await queryBuilder.getManyAndCount();
26
+ return { items, total };
27
+ }
28
+ /** Open by design: the caller's own requests - where they see what became of them. */
29
+ async myAccessRequests(context) {
30
+ const { domain, user } = context.state;
31
+ return await (0, shell_1.getRepository)(access_request_js_2.AccessRequest).find({
32
+ where: { domain: { id: domain.id }, requester: { id: user.id } },
33
+ relations: ['grantedRole'],
34
+ order: { createdAt: 'DESC' },
35
+ take: 100
36
+ });
37
+ }
38
+ async rolesGranting(category, privilege, context) {
39
+ return (await (0, access_request_js_1.rolesGranting)(category, privilege, context.state.domain)).map(({ id, name, privilegeCount }) => ({ id, name, privilegeCount }));
40
+ }
41
+ /** The same review the decision runs - the list screen and the AI draft card show this (ADR-0103 decision 4). */
42
+ async reviewAccessDecision(decision, context) {
43
+ return (await (0, access_request_js_1.reviewDecision)(decision, context));
44
+ }
45
+ };
46
+ exports.AccessRequestQuery = AccessRequestQuery;
47
+ tslib_1.__decorate([
48
+ (0, type_graphql_1.Directive)(READ),
49
+ (0, type_graphql_1.Query)(returns => access_request_types_js_1.AccessRequestList, { description: "To fetch this domain's access requests." }),
50
+ tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
51
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
52
+ tslib_1.__metadata("design:type", Function),
53
+ tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
54
+ tslib_1.__metadata("design:returntype", Promise)
55
+ ], AccessRequestQuery.prototype, "accessRequests", null);
56
+ tslib_1.__decorate([
57
+ (0, type_graphql_1.Query)(returns => [access_request_js_2.AccessRequest], { description: "To fetch the caller's own access requests in this domain, newest first." }),
58
+ tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
59
+ tslib_1.__metadata("design:type", Function),
60
+ tslib_1.__metadata("design:paramtypes", [Object]),
61
+ tslib_1.__metadata("design:returntype", Promise)
62
+ ], AccessRequestQuery.prototype, "myAccessRequests", null);
63
+ tslib_1.__decorate([
64
+ (0, type_graphql_1.Directive)(DECIDE),
65
+ (0, type_graphql_1.Query)(returns => [access_request_types_js_1.AccessRoleCandidate], { description: 'To list the roles this domain can assign that grant a privilege, fewest privileges first.' }),
66
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('category')),
67
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('privilege')),
68
+ tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
69
+ tslib_1.__metadata("design:type", Function),
70
+ tslib_1.__metadata("design:paramtypes", [String, String, Object]),
71
+ tslib_1.__metadata("design:returntype", Promise)
72
+ ], AccessRequestQuery.prototype, "rolesGranting", null);
73
+ tslib_1.__decorate([
74
+ (0, type_graphql_1.Directive)(DECIDE),
75
+ (0, type_graphql_1.Query)(returns => [access_request_types_js_1.AccessDecisionIssue], { description: 'To review a decision before it is made: what blocks it and what the person deciding should know.' }),
76
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('decision', type => access_request_types_js_1.AccessDecisionInput)),
77
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
78
+ tslib_1.__metadata("design:type", Function),
79
+ tslib_1.__metadata("design:paramtypes", [access_request_types_js_1.AccessDecisionInput, Object]),
80
+ tslib_1.__metadata("design:returntype", Promise)
81
+ ], AccessRequestQuery.prototype, "reviewAccessDecision", null);
82
+ exports.AccessRequestQuery = AccessRequestQuery = tslib_1.__decorate([
83
+ (0, type_graphql_1.Resolver)(access_request_js_2.AccessRequest)
84
+ ], AccessRequestQuery);
85
+ //# sourceMappingURL=access-request-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-request-query.js","sourceRoot":"","sources":["../../../server/service/access-request/access-request-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Directive, Query, Resolver } from 'type-graphql'\n\nimport { getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'\n\nimport { reviewDecision, rolesGranting } from '../../controllers/access-request.js'\nimport { AccessRequest } from './access-request.js'\nimport { AccessDecisionInput, AccessDecisionIssue, AccessRequestList, AccessRoleCandidate } from './access-request-types.js'\n\n/* Reading the requests is reading who asks for what in this domain - the user screens' read privilege. */\nconst READ = '@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true)'\n/* The review and the candidates serve a decision - the deciding declaration. */\nconst DECIDE = '@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)'\n\n@Resolver(AccessRequest)\nexport class AccessRequestQuery {\n @Directive(READ)\n @Query(returns => AccessRequestList, { description: \"To fetch this domain's access requests.\" })\n async accessRequests(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<AccessRequestList> {\n const { domain } = context.state\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: getRepository(AccessRequest),\n alias: 'accessRequest',\n searchables: ['category', 'privilege', 'reason']\n })\n queryBuilder.leftJoinAndSelect('accessRequest.requester', 'requester').leftJoinAndSelect('accessRequest.decidedBy', 'decidedBy').leftJoinAndSelect('accessRequest.grantedRole', 'grantedRole')\n const [items, total] = await queryBuilder.getManyAndCount()\n return { items, total }\n }\n\n /** Open by design: the caller's own requests - where they see what became of them. */\n @Query(returns => [AccessRequest], { description: \"To fetch the caller's own access requests in this domain, newest first.\" })\n async myAccessRequests(@Ctx() context: ResolverContext): Promise<AccessRequest[]> {\n const { domain, user } = context.state\n return await getRepository(AccessRequest).find({\n where: { domain: { id: domain.id }, requester: { id: user.id } },\n relations: ['grantedRole'],\n order: { createdAt: 'DESC' },\n take: 100\n })\n }\n\n @Directive(DECIDE)\n @Query(returns => [AccessRoleCandidate], { description: 'To list the roles this domain can assign that grant a privilege, fewest privileges first.' })\n async rolesGranting(@Arg('category') category: string, @Arg('privilege') privilege: string, @Ctx() context: ResolverContext): Promise<AccessRoleCandidate[]> {\n return (await rolesGranting(category, privilege, context.state.domain)).map(({ id, name, privilegeCount }) => ({ id, name, privilegeCount }))\n }\n\n /** The same review the decision runs - the list screen and the AI draft card show this (ADR-0103 decision 4). */\n @Directive(DECIDE)\n @Query(returns => [AccessDecisionIssue], { description: 'To review a decision before it is made: what blocks it and what the person deciding should know.' })\n async reviewAccessDecision(@Arg('decision', type => AccessDecisionInput) decision: AccessDecisionInput, @Ctx() context: ResolverContext): Promise<AccessDecisionIssue[]> {\n return (await reviewDecision(decision as any, context)) as AccessDecisionIssue[]\n }\n}\n"]}