@things-factory/auth-base 10.1.107 → 10.1.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/ai-entry/access-entry-targets.js +4 -3
- package/dist-server/ai-entry/access-entry-targets.js.map +1 -1
- package/dist-server/controllers/access-request.d.ts +2 -0
- package/dist-server/controllers/access-request.js +56 -18
- package/dist-server/controllers/access-request.js.map +1 -1
- package/dist-server/service/access-request/access-request-query.d.ts +2 -0
- package/dist-server/service/access-request/access-request-query.js +22 -5
- package/dist-server/service/access-request/access-request-query.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/service/open-fields.js +1 -0
- package/dist-server/service/open-fields.js.map +1 -1
- package/dist-server/service/role/role-query.d.ts +2 -0
- package/dist-server/service/role/role-query.js +12 -0
- package/dist-server/service/role/role-query.js.map +1 -1
- package/dist-server/service/user/index.d.ts +1 -1
- package/dist-server/service/user/user-mutation.js +1 -1
- package/dist-server/service/user/user-mutation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/access-request-review.d.ts +3 -0
- package/dist-server/utils/access-request-review.js +3 -0
- package/dist-server/utils/access-request-review.js.map +1 -1
- package/dist-server/utils/assignable-roles.d.ts +11 -1
- package/dist-server/utils/assignable-roles.js +73 -1
- package/dist-server/utils/assignable-roles.js.map +1 -1
- package/package.json +2 -2
- package/tests/access-request-db.test.ts +44 -0
- package/tests/access-request-pg.test.ts +11 -0
- package/tests/access-request-review.test.ts +8 -0
- package/tests/assignable-roles-db.test.ts +79 -1
- package/translations/en.json +2 -1
- package/translations/ja.json +2 -1
- package/translations/ko.json +2 -1
- package/translations/ms.json +2 -1
- package/translations/zh.json +2 -1
|
@@ -45,7 +45,8 @@ exports.accessEntryTargets = accessEntryTargets;
|
|
|
45
45
|
exports.AUTH_ACCESS_DECISION = 'auth.access-decision';
|
|
46
46
|
exports.AUTH_ACCESS_REQUEST = 'auth.access-request';
|
|
47
47
|
const PENDING_REQUESTS = 'query ($filters: [Filter!], $pagination: Pagination) { accessRequests(filters: $filters, pagination: $pagination) { items { id category privilege state requester { name email } } } }';
|
|
48
|
-
|
|
48
|
+
/* The decision's own scope - own, parent and granted roles - and the deciding declaration, not the role list's. */
|
|
49
|
+
const ROLES_NAMED = 'query ($name: String!) { assignableRolesNamed(name: $name) { id name } }';
|
|
49
50
|
/** "김○○ · warehouse:mutation" - how a request is named on the card. */
|
|
50
51
|
function requestLabel(request) {
|
|
51
52
|
const who = request?.requester?.name || request?.requester?.email || '';
|
|
@@ -100,8 +101,8 @@ const DECISION_FIELDS = [
|
|
|
100
101
|
description: 'the role to put the requester into, by its exact name - only for GRANT. Prefer the role with the fewest privileges that still holds the one asked for; the review names a smaller one when there is.',
|
|
101
102
|
reference: {
|
|
102
103
|
query: ROLES_NAMED,
|
|
103
|
-
variables: (said) => ({
|
|
104
|
-
recordsOf: (data, said) => rolesNamed(data?.
|
|
104
|
+
variables: (said) => ({ name: said.trim() }),
|
|
105
|
+
recordsOf: (data, said) => rolesNamed(data?.assignableRolesNamed ?? [], said)
|
|
105
106
|
}
|
|
106
107
|
},
|
|
107
108
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-entry-targets.js","sourceRoot":"","sources":["../../server/ai-entry/access-entry-targets.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * AI 입력 targets of the access request (ADR-0103 decision 4, docs/design/access-request.md §6).\n *\n * Plain declarations: this package does not depend on @things-factory/ai-assistant. An application that runs\n * the assistant passes them to `registerEntryTarget` and names their keys in a mode.\n *\n * ── The model drafts; a person decides ───────────────────────────────────\n * The model is given a tool that proposes a card. It is never given `decideAccessRequest`. The card's button\n * is pressed by the person, and the assistant then runs the mutation with that person's request state - the\n * door's `@privilege` and the review inside it apply as they do on the list screen.\n *\n * ── One review ────────────────────────────────────────────────────────────\n * `check` is `reviewDecision` - the function the list screen asks (`reviewAccessDecision`) and the decision\n * door runs again. The card cannot say something the screen does not.\n *\n * ⚠ The rows carry colleagues' names and the reasons they wrote, and proposing sends them to an external model.\n * Which product opens these targets is that product's decision.\n */\nimport type { EntryField, EntryIssue, EntryRow, EntryTargetDeclaration } from './entry-target-shape'\n\nexport const AUTH_ACCESS_DECISION = 'auth.access-decision'\nexport const AUTH_ACCESS_REQUEST = 'auth.access-request'\n\nconst PENDING_REQUESTS =\n 'query ($filters: [Filter!], $pagination: Pagination) { accessRequests(filters: $filters, pagination: $pagination) { items { id category privilege state requester { name email } } } }'\n\nconst ROLES_NAMED =\n 'query ($filters: [Filter!], $pagination: Pagination) { roles(filters: $filters, pagination: $pagination) { items { id name } } }'\n\n/** \"김○○ · warehouse:mutation\" - how a request is named on the card. */\nexport function requestLabel(request: any): string {\n const who = request?.requester?.name || request?.requester?.email || ''\n return [who, request?.category && request?.privilege ? `${request.category}:${request.privilege}` : ''].filter(Boolean).join(' · ')\n}\n\n/** The pending requests the said text names - by the requester's name or e-mail, or the privilege. Several stay several. */\nexport function requestsNamed(items: any[], said: string): { id: string; label: string }[] {\n const text = (said || '').trim().toLowerCase()\n if (!text) return []\n return (items || [])\n .filter(item => item?.state === 'PENDING')\n .filter(item => {\n if (item.id === said.trim()) return true\n const names = [item.requester?.name, item.requester?.email, `${item.category}:${item.privilege}`, requestLabel(item)]\n return names.some(name => typeof name === 'string' && name.toLowerCase().includes(text))\n })\n .map(item => ({ id: item.id, label: requestLabel(item) }))\n}\n\n/** A role named exactly - a near name is not taken. */\nexport function rolesNamed(items: any[], said: string): { id: string; label: string }[] {\n const text = (said || '').trim().toLowerCase()\n return (items || []).filter(item => typeof item?.name === 'string' && item.name.toLowerCase() === text).map(item => ({ id: item.id, label: item.name }))\n}\n\nconst DECISION_FIELDS: EntryField[] = [\n {\n name: 'request',\n type: 'reference',\n required: true,\n labelKey: 'access-request.field.request',\n description: \"the pending access request, by the requester's name and the privilege asked for\",\n reference: {\n query: PENDING_REQUESTS,\n variables: () => ({ filters: [{ name: 'state', operator: 'eq', value: 'PENDING' }], pagination: { page: 1, limit: 500 } }),\n recordsOf: (data, said) => requestsNamed(data?.accessRequests?.items ?? [], said)\n }\n },\n {\n name: 'decision',\n type: 'enum',\n required: true,\n values: ['GRANT', 'DENY'],\n labelKey: 'access-request.field.decision',\n valueKeyPrefix: 'access-request.decision.',\n description: 'GRANT puts the requester into a role; DENY refuses with a reason'\n },\n {\n name: 'role',\n type: 'reference',\n labelKey: 'access-request.field.granted role',\n description:\n 'the role to put the requester into, by its exact name - only for GRANT. Prefer the role with the fewest privileges that still holds the one asked for; the review names a smaller one when there is.',\n reference: {\n query: ROLES_NAMED,\n variables: (said: string) => ({ filters: [{ name: 'name', operator: 'eq', value: said.trim() }], pagination: { page: 1, limit: 20 } }),\n recordsOf: (data, said) => rolesNamed(data?.roles?.items ?? [], said)\n }\n },\n {\n name: 'reason',\n type: 'text',\n labelKey: 'access-request.field.decision reason',\n description: 'why - required for DENY. Never written by the model: ask the person.'\n }\n]\n\nconst idOf = (value: unknown) => (value && typeof value === 'object' && typeof (value as any).id === 'string' ? (value as any).id : undefined)\n\n/** A row as the decision door takes it. */\nexport function decisionOf(row: EntryRow) {\n const decision = row.values.decision === 'DENY' ? 'DENY' : 'GRANT'\n return {\n requestId: idOf(row.values.request),\n decision,\n roleId: decision === 'GRANT' ? idOf(row.values.role) : undefined,\n reason: typeof row.values.reason === 'string' ? row.values.reason : undefined\n }\n}\n\n/** The review's findings as the card's issues - the words are the screen's (`access-request.issue.<code>`). */\nexport function issuesOf(findings: { code: string; blocking: boolean; field?: string; params?: Record<string, unknown> }[]): EntryIssue[] {\n return findings.map(finding => ({\n ...(finding.field && finding.field !== 'decision' ? { field: finding.field } : {}),\n code: finding.code,\n messageKey: `access-request.issue.${finding.code}`,\n params: finding.params,\n blocking: finding.blocking\n }))\n}\n\nexport function accessDecisionEntryTarget(): EntryTargetDeclaration {\n return {\n key: AUTH_ACCESS_DECISION,\n labelKey: 'access-request.title.decision draft',\n description:\n 'one decision on a pending access request: which request, grant or deny, the role to grant with, and the reason to deny.',\n mutation: 'mutation ($decision: AccessDecisionInput!) { decideAccessRequest(decision: $decision) { id state } }',\n fields: DECISION_FIELDS,\n maxRows: 50,\n variables: row => ({ decision: decisionOf(row) }),\n appliedOf: (data, row) => ({ id: data?.decideAccessRequest?.id, label: (row.values.request as any)?.label }),\n\n check: async (rows, { state }) => {\n const { reviewDecision } = await import('../controllers/access-request.js')\n const out: EntryIssue[][] = []\n for (const row of rows) {\n const decision = decisionOf(row)\n out.push(decision.requestId ? issuesOf(await reviewDecision(decision as any, { state } as any)) : [])\n }\n return out\n }\n }\n}\n\nconst REQUEST_FIELDS: EntryField[] = [\n { name: 'category', type: 'string', required: true, labelKey: 'access-request.field.category', description: 'the category of the privilege, as the refusal named it' },\n { name: 'privilege', type: 'string', required: true, labelKey: 'access-request.field.privilege', description: 'the privilege, as the refusal named it - query, mutation, ...' },\n { name: 'reason', type: 'text', required: true, labelKey: 'access-request.field.reason', description: \"why the person needs it, in the person's words - ask them; never write it yourself\" }\n]\n\nexport function accessRequestEntryTarget(): EntryTargetDeclaration {\n return {\n key: AUTH_ACCESS_REQUEST,\n labelKey: 'access-request.title.request draft',\n description: 'one request for a privilege the person was refused for want of a role.',\n mutation: 'mutation ($request: NewAccessRequest!) { requestAccess(request: $request) { id count } }',\n fields: REQUEST_FIELDS,\n maxRows: 10,\n variables: row => ({ request: { category: row.values.category, privilege: row.values.privilege, reason: row.values.reason } }),\n appliedOf: data => ({ id: data?.requestAccess?.id }),\n\n /* What the door would refuse - asked before the card, so the person is not offered a request that cannot stand. */\n check: async (rows, { state }) => {\n const { judgePermission } = await import('../utils/check-permission.js')\n const out: EntryIssue[][] = []\n for (const row of rows) {\n const category = String(row.values.category ?? '')\n const privilege = String(row.values.privilege ?? '')\n if (!category || !privilege) {\n out.push([])\n continue\n }\n const judgement = await judgePermission({ category, privilege }, state?.user, state?.domain, state?.unsafeIP, state?.prohibitedPrivileges)\n out.push(\n judgement.permitted === true\n ? [{ code: 'already-holds', messageKey: 'access-request.issue.already-holds', blocking: true }]\n : judgement.because !== 'role'\n ? [{ code: 'not-by-role', messageKey: 'access-request.issue.not-by-role', params: { because: judgement.because }, blocking: true }]\n : []\n )\n }\n return out\n }\n }\n}\n\nexport function accessEntryTargets(): EntryTargetDeclaration[] {\n return [accessDecisionEntryTarget(), accessRequestEntryTarget()]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"access-entry-targets.js","sourceRoot":"","sources":["../../server/ai-entry/access-entry-targets.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * AI 입력 targets of the access request (ADR-0103 decision 4, docs/design/access-request.md §6).\n *\n * Plain declarations: this package does not depend on @things-factory/ai-assistant. An application that runs\n * the assistant passes them to `registerEntryTarget` and names their keys in a mode.\n *\n * ── The model drafts; a person decides ───────────────────────────────────\n * The model is given a tool that proposes a card. It is never given `decideAccessRequest`. The card's button\n * is pressed by the person, and the assistant then runs the mutation with that person's request state - the\n * door's `@privilege` and the review inside it apply as they do on the list screen.\n *\n * ── One review ────────────────────────────────────────────────────────────\n * `check` is `reviewDecision` - the function the list screen asks (`reviewAccessDecision`) and the decision\n * door runs again. The card cannot say something the screen does not.\n *\n * ⚠ The rows carry colleagues' names and the reasons they wrote, and proposing sends them to an external model.\n * Which product opens these targets is that product's decision.\n */\nimport type { EntryField, EntryIssue, EntryRow, EntryTargetDeclaration } from './entry-target-shape'\n\nexport const AUTH_ACCESS_DECISION = 'auth.access-decision'\nexport const AUTH_ACCESS_REQUEST = 'auth.access-request'\n\nconst PENDING_REQUESTS =\n 'query ($filters: [Filter!], $pagination: Pagination) { accessRequests(filters: $filters, pagination: $pagination) { items { id category privilege state requester { name email } } } }'\n\n/* The decision's own scope - own, parent and granted roles - and the deciding declaration, not the role list's. */\nconst ROLES_NAMED = 'query ($name: String!) { assignableRolesNamed(name: $name) { id name } }'\n\n/** \"김○○ · warehouse:mutation\" - how a request is named on the card. */\nexport function requestLabel(request: any): string {\n const who = request?.requester?.name || request?.requester?.email || ''\n return [who, request?.category && request?.privilege ? `${request.category}:${request.privilege}` : ''].filter(Boolean).join(' · ')\n}\n\n/** The pending requests the said text names - by the requester's name or e-mail, or the privilege. Several stay several. */\nexport function requestsNamed(items: any[], said: string): { id: string; label: string }[] {\n const text = (said || '').trim().toLowerCase()\n if (!text) return []\n return (items || [])\n .filter(item => item?.state === 'PENDING')\n .filter(item => {\n if (item.id === said.trim()) return true\n const names = [item.requester?.name, item.requester?.email, `${item.category}:${item.privilege}`, requestLabel(item)]\n return names.some(name => typeof name === 'string' && name.toLowerCase().includes(text))\n })\n .map(item => ({ id: item.id, label: requestLabel(item) }))\n}\n\n/** A role named exactly - a near name is not taken. */\nexport function rolesNamed(items: any[], said: string): { id: string; label: string }[] {\n const text = (said || '').trim().toLowerCase()\n return (items || []).filter(item => typeof item?.name === 'string' && item.name.toLowerCase() === text).map(item => ({ id: item.id, label: item.name }))\n}\n\nconst DECISION_FIELDS: EntryField[] = [\n {\n name: 'request',\n type: 'reference',\n required: true,\n labelKey: 'access-request.field.request',\n description: \"the pending access request, by the requester's name and the privilege asked for\",\n reference: {\n query: PENDING_REQUESTS,\n variables: () => ({ filters: [{ name: 'state', operator: 'eq', value: 'PENDING' }], pagination: { page: 1, limit: 500 } }),\n recordsOf: (data, said) => requestsNamed(data?.accessRequests?.items ?? [], said)\n }\n },\n {\n name: 'decision',\n type: 'enum',\n required: true,\n values: ['GRANT', 'DENY'],\n labelKey: 'access-request.field.decision',\n valueKeyPrefix: 'access-request.decision.',\n description: 'GRANT puts the requester into a role; DENY refuses with a reason'\n },\n {\n name: 'role',\n type: 'reference',\n labelKey: 'access-request.field.granted role',\n description:\n 'the role to put the requester into, by its exact name - only for GRANT. Prefer the role with the fewest privileges that still holds the one asked for; the review names a smaller one when there is.',\n reference: {\n query: ROLES_NAMED,\n variables: (said: string) => ({ name: said.trim() }),\n recordsOf: (data, said) => rolesNamed(data?.assignableRolesNamed ?? [], said)\n }\n },\n {\n name: 'reason',\n type: 'text',\n labelKey: 'access-request.field.decision reason',\n description: 'why - required for DENY. Never written by the model: ask the person.'\n }\n]\n\nconst idOf = (value: unknown) => (value && typeof value === 'object' && typeof (value as any).id === 'string' ? (value as any).id : undefined)\n\n/** A row as the decision door takes it. */\nexport function decisionOf(row: EntryRow) {\n const decision = row.values.decision === 'DENY' ? 'DENY' : 'GRANT'\n return {\n requestId: idOf(row.values.request),\n decision,\n roleId: decision === 'GRANT' ? idOf(row.values.role) : undefined,\n reason: typeof row.values.reason === 'string' ? row.values.reason : undefined\n }\n}\n\n/** The review's findings as the card's issues - the words are the screen's (`access-request.issue.<code>`). */\nexport function issuesOf(findings: { code: string; blocking: boolean; field?: string; params?: Record<string, unknown> }[]): EntryIssue[] {\n return findings.map(finding => ({\n ...(finding.field && finding.field !== 'decision' ? { field: finding.field } : {}),\n code: finding.code,\n messageKey: `access-request.issue.${finding.code}`,\n params: finding.params,\n blocking: finding.blocking\n }))\n}\n\nexport function accessDecisionEntryTarget(): EntryTargetDeclaration {\n return {\n key: AUTH_ACCESS_DECISION,\n labelKey: 'access-request.title.decision draft',\n description:\n 'one decision on a pending access request: which request, grant or deny, the role to grant with, and the reason to deny.',\n mutation: 'mutation ($decision: AccessDecisionInput!) { decideAccessRequest(decision: $decision) { id state } }',\n fields: DECISION_FIELDS,\n maxRows: 50,\n variables: row => ({ decision: decisionOf(row) }),\n appliedOf: (data, row) => ({ id: data?.decideAccessRequest?.id, label: (row.values.request as any)?.label }),\n\n check: async (rows, { state }) => {\n const { reviewDecision } = await import('../controllers/access-request.js')\n const out: EntryIssue[][] = []\n for (const row of rows) {\n const decision = decisionOf(row)\n out.push(decision.requestId ? issuesOf(await reviewDecision(decision as any, { state } as any)) : [])\n }\n return out\n }\n }\n}\n\nconst REQUEST_FIELDS: EntryField[] = [\n { name: 'category', type: 'string', required: true, labelKey: 'access-request.field.category', description: 'the category of the privilege, as the refusal named it' },\n { name: 'privilege', type: 'string', required: true, labelKey: 'access-request.field.privilege', description: 'the privilege, as the refusal named it - query, mutation, ...' },\n { name: 'reason', type: 'text', required: true, labelKey: 'access-request.field.reason', description: \"why the person needs it, in the person's words - ask them; never write it yourself\" }\n]\n\nexport function accessRequestEntryTarget(): EntryTargetDeclaration {\n return {\n key: AUTH_ACCESS_REQUEST,\n labelKey: 'access-request.title.request draft',\n description: 'one request for a privilege the person was refused for want of a role.',\n mutation: 'mutation ($request: NewAccessRequest!) { requestAccess(request: $request) { id count } }',\n fields: REQUEST_FIELDS,\n maxRows: 10,\n variables: row => ({ request: { category: row.values.category, privilege: row.values.privilege, reason: row.values.reason } }),\n appliedOf: data => ({ id: data?.requestAccess?.id }),\n\n /* What the door would refuse - asked before the card, so the person is not offered a request that cannot stand. */\n check: async (rows, { state }) => {\n const { judgePermission } = await import('../utils/check-permission.js')\n const out: EntryIssue[][] = []\n for (const row of rows) {\n const category = String(row.values.category ?? '')\n const privilege = String(row.values.privilege ?? '')\n if (!category || !privilege) {\n out.push([])\n continue\n }\n const judgement = await judgePermission({ category, privilege }, state?.user, state?.domain, state?.unsafeIP, state?.prohibitedPrivileges)\n out.push(\n judgement.permitted === true\n ? [{ code: 'already-holds', messageKey: 'access-request.issue.already-holds', blocking: true }]\n : judgement.because !== 'role'\n ? [{ code: 'not-by-role', messageKey: 'access-request.issue.not-by-role', params: { because: judgement.because }, blocking: true }]\n : []\n )\n }\n return out\n }\n }\n}\n\nexport function accessEntryTargets(): EntryTargetDeclaration[] {\n return [accessDecisionEntryTarget(), accessRequestEntryTarget()]\n}\n"]}
|
|
@@ -27,6 +27,8 @@ export declare class AccessDecisionRefused extends Refusal {
|
|
|
27
27
|
* taken on trust (ADR-0103 decision 2). Asking again while one is pending adds to that one.
|
|
28
28
|
*/
|
|
29
29
|
export declare function requestAccess(input: NewAccessRequestInput, context: ResolverContext): Promise<AccessRequest>;
|
|
30
|
+
/** A request withdrawn within this many hours comes back as the same row, without telling anyone again. */
|
|
31
|
+
export declare const ACCESS_REQUEST_QUIET_HOURS = 24;
|
|
30
32
|
/** How long a denial is shown back to the person and holds against the same words (questions Q9 · 5). */
|
|
31
33
|
export declare const ACCESS_REQUEST_DENIAL_DAYS = 7;
|
|
32
34
|
/** The latest denial of this person and privilege within ACCESS_REQUEST_DENIAL_DAYS, if any. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ACCESS_REQUEST_DENIAL_DAYS = exports.AccessDecisionRefused = void 0;
|
|
3
|
+
exports.ACCESS_REQUEST_DENIAL_DAYS = exports.ACCESS_REQUEST_QUIET_HOURS = exports.AccessDecisionRefused = void 0;
|
|
4
4
|
exports.requestAccess = requestAccess;
|
|
5
5
|
exports.latestDenial = latestDenial;
|
|
6
6
|
exports.withdrawAccessRequest = withdrawAccessRequest;
|
|
@@ -68,16 +68,7 @@ async function requestAccess(input, context) {
|
|
|
68
68
|
throw new shell_1.Refusal('access-request-same-reason-as-denied', { reason: denied.decisionReason || '' }, 'This was denied for the same reason lately - write what has changed.');
|
|
69
69
|
}
|
|
70
70
|
const repository = (0, shell_1.getRepository)(access_request_js_1.AccessRequest, tx);
|
|
71
|
-
const pending = await repository.
|
|
72
|
-
where: {
|
|
73
|
-
domain: { id: domain.id },
|
|
74
|
-
requester: { id: user.id },
|
|
75
|
-
category,
|
|
76
|
-
privilege,
|
|
77
|
-
state: access_request_js_1.AccessRequestStatus.PENDING
|
|
78
|
-
},
|
|
79
|
-
relations: ['domain', 'requester']
|
|
80
|
-
});
|
|
71
|
+
const pending = await findMine(repository, domain.id, user.id, category, privilege, access_request_js_1.AccessRequestStatus.PENDING, tx);
|
|
81
72
|
if (pending && (0, access_request_review_js_1.effectiveState)(pending.state, pending.expiresAt, now) === access_request_js_1.AccessRequestStatus.PENDING) {
|
|
82
73
|
pending.count = (pending.count || 1) + 1;
|
|
83
74
|
pending.lastRequestedAt = now;
|
|
@@ -91,6 +82,21 @@ async function requestAccess(input, context) {
|
|
|
91
82
|
pending.state = access_request_js_1.AccessRequestStatus.EXPIRED;
|
|
92
83
|
await repository.save(pending);
|
|
93
84
|
}
|
|
85
|
+
/*
|
|
86
|
+
* Withdrawn lately: the same request comes back - one row, and nobody is told again. Asking and withdrawing in a loop
|
|
87
|
+
* would otherwise make a row and a notification to every decider on each turn.
|
|
88
|
+
*/
|
|
89
|
+
const withdrawn = await findMine(repository, domain.id, user.id, category, privilege, access_request_js_1.AccessRequestStatus.WITHDRAWN, tx);
|
|
90
|
+
if (withdrawn && now.getTime() - new Date(withdrawn.updatedAt).getTime() <= exports.ACCESS_REQUEST_QUIET_HOURS * 60 * 60 * 1000) {
|
|
91
|
+
withdrawn.state = access_request_js_1.AccessRequestStatus.PENDING;
|
|
92
|
+
withdrawn.count = (withdrawn.count || 1) + 1;
|
|
93
|
+
withdrawn.lastRequestedAt = now;
|
|
94
|
+
withdrawn.reason = reason;
|
|
95
|
+
withdrawn.expiresAt = (0, access_request_review_js_1.expiryFrom)(now);
|
|
96
|
+
if (input.operationName)
|
|
97
|
+
withdrawn.operationName = input.operationName;
|
|
98
|
+
return await repository.save(withdrawn);
|
|
99
|
+
}
|
|
94
100
|
const created = await repository.save({
|
|
95
101
|
domain,
|
|
96
102
|
requester: user,
|
|
@@ -110,6 +116,26 @@ async function requestAccess(input, context) {
|
|
|
110
116
|
}, `access-requests?id=${created.id}`));
|
|
111
117
|
return created;
|
|
112
118
|
}
|
|
119
|
+
/** A request withdrawn within this many hours comes back as the same row, without telling anyone again. */
|
|
120
|
+
exports.ACCESS_REQUEST_QUIET_HOURS = 24;
|
|
121
|
+
/**
|
|
122
|
+
* The caller's latest request of one privilege in one state. On a database with row locks it is a locking read: after
|
|
123
|
+
* `holdRequester` waits, a plain read under REPEATABLE READ (MySQL's default) would still see the snapshot taken before
|
|
124
|
+
* the other ask committed, and insert a second pending row. A locking read sees the committed row.
|
|
125
|
+
*/
|
|
126
|
+
async function findMine(repository, domainId, requesterId, category, privilege, state, tx) {
|
|
127
|
+
const qb = repository
|
|
128
|
+
.createQueryBuilder('mine')
|
|
129
|
+
.leftJoinAndSelect('mine.domain', 'mine_domain')
|
|
130
|
+
.leftJoinAndSelect('mine.requester', 'mine_requester')
|
|
131
|
+
.where('mine.domain = :domainId AND mine.requester = :requesterId', { domainId, requesterId })
|
|
132
|
+
.andWhere('mine.category = :category AND mine.privilege = :privilege AND mine.state = :state', { category, privilege, state })
|
|
133
|
+
.orderBy('mine.updatedAt', 'DESC');
|
|
134
|
+
/* Lowercase alias: postgres folds the unquoted name in `FOR UPDATE OF`, and only the request row is locked - not the outer-joined ones. */
|
|
135
|
+
if (tx && !tx.connection?.options?.type?.includes('sqlite'))
|
|
136
|
+
qb.setLock('pessimistic_write', undefined, ['mine']);
|
|
137
|
+
return await qb.getOne();
|
|
138
|
+
}
|
|
113
139
|
/**
|
|
114
140
|
* At most one pending request per person and privilege, under concurrency. A partial unique index would say it and
|
|
115
141
|
* is not portable, so the person's own row is locked for the rest of the transaction: a second ask of the same
|
|
@@ -236,6 +262,8 @@ async function collectDecisionFacts(input, context) {
|
|
|
236
262
|
const role = chosen[0]
|
|
237
263
|
? await (0, shell_1.getRepository)(role_js_1.Role, tx).findOne({ where: { id: chosen[0].id }, relations: ['privileges'] })
|
|
238
264
|
: null;
|
|
265
|
+
const roleIsParents = !!role && !!domain.parentId && role.domainId === domain.parentId;
|
|
266
|
+
const deciderMayChangeParentRoles = roleIsParents ? await (0, assignable_roles_js_1.mayChangeParentRoles)(decider, domain, tx) : false;
|
|
239
267
|
return {
|
|
240
268
|
...base,
|
|
241
269
|
request: {
|
|
@@ -249,6 +277,8 @@ async function collectDecisionFacts(input, context) {
|
|
|
249
277
|
? { id: role.id, name: role.name, privileges: (role.privileges || []).map(p => ({ category: p.category, privilege: p.name })) }
|
|
250
278
|
: null,
|
|
251
279
|
roleNamedButNotAssignable: !!input.roleId && !role,
|
|
280
|
+
roleIsParents,
|
|
281
|
+
deciderMayChangeParentRoles,
|
|
252
282
|
requester: requester
|
|
253
283
|
? {
|
|
254
284
|
active: requester.status === user_js_1.UserStatus.ACTIVATED,
|
|
@@ -305,10 +335,10 @@ async function decideAccessRequest(input, context) {
|
|
|
305
335
|
if (!moved.affected) {
|
|
306
336
|
throw new AccessDecisionRefused([{ code: 'request-not-pending', blocking: true, field: 'request' }]);
|
|
307
337
|
}
|
|
308
|
-
const request = await repository.findOne({ where: { id: input.requestId }, relations: ['requester', 'requester.roles', 'grantedRole'] });
|
|
338
|
+
const request = await repository.findOne({ where: { id: input.requestId }, relations: ['domain', 'requester', 'requester.roles', 'grantedRole'] });
|
|
309
339
|
const requester = request.requester;
|
|
310
340
|
if (granting) {
|
|
311
|
-
await (0, assignable_roles_js_1.assignRoles)(requester, { add: [input.roleId] }, domain, tx);
|
|
341
|
+
await (0, assignable_roles_js_1.assignRoles)(requester, { add: [input.roleId] }, domain, tx, decider);
|
|
312
342
|
await closeWhatTheGrantOpened(request, context);
|
|
313
343
|
}
|
|
314
344
|
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 ?? '' },
|
|
@@ -316,18 +346,26 @@ async function decideAccessRequest(input, context) {
|
|
|
316
346
|
'my-access-requests'));
|
|
317
347
|
return request;
|
|
318
348
|
}
|
|
319
|
-
/**
|
|
349
|
+
/**
|
|
350
|
+
* Other pending requests of the same person that the role just given now answers - closed as granted with it.
|
|
351
|
+
*
|
|
352
|
+
* Judged by the granted role's own privileges, read in this transaction. `User.hasPrivilege` reads through the default
|
|
353
|
+
* connection, which on a pooled database does not see the role added a moment ago in this uncommitted transaction.
|
|
354
|
+
*/
|
|
320
355
|
async function closeWhatTheGrantOpened(granted, context) {
|
|
321
356
|
const { domain, user: decider, tx } = context.state;
|
|
322
357
|
const repository = (0, shell_1.getRepository)(access_request_js_1.AccessRequest, tx);
|
|
358
|
+
const role = await (0, shell_1.getRepository)(role_js_1.Role, tx).findOne({ where: { id: granted.grantedRole?.id }, relations: ['privileges'] });
|
|
359
|
+
const gives = new Set((role?.privileges || []).map(p => `${p.category}:${p.name}`));
|
|
360
|
+
if (gives.size === 0)
|
|
361
|
+
return;
|
|
323
362
|
const others = await repository.find({
|
|
324
|
-
where: { domain: { id: domain.id }, requester: { id: granted.requester.id }, state: access_request_js_1.AccessRequestStatus.PENDING }
|
|
325
|
-
relations: ['requester']
|
|
363
|
+
where: { domain: { id: domain.id }, requester: { id: granted.requester.id }, state: access_request_js_1.AccessRequestStatus.PENDING }
|
|
326
364
|
});
|
|
327
365
|
for (const other of others) {
|
|
328
|
-
if (
|
|
366
|
+
if (other.id === granted.id || !gives.has(`${other.category}:${other.privilege}`))
|
|
329
367
|
continue;
|
|
330
|
-
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:
|
|
368
|
+
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: { id: role.id } });
|
|
331
369
|
}
|
|
332
370
|
}
|
|
333
371
|
/* ── telling ─────────────────────────────────────────────────────────────── */
|
|
@@ -1 +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 /* Asking is not a reflex - the reason is what the person deciding weighs, so there is no request without one. */\n const reason = input.reason?.trim()\n if (!reason) {\n throw new Refusal('access-request-needs-reason', {}, 'Write why you need it.')\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 await holdRequester(user.id, tx)\n\n /*\n * Denied lately: asking again is allowed, the same words are not - the person deciding has already answered them.\n * The screen shows that answer before the person writes (ACCESS_REQUEST_DENIAL_DAYS).\n */\n const denied = await latestDenial(domain, user.id, category, privilege, now, tx)\n if (denied && (denied.reason || '').trim() === reason) {\n throw new Refusal(\n 'access-request-same-reason-as-denied',\n { reason: denied.decisionReason || '' },\n 'This was denied for the same reason lately - write what has changed.'\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 pending.reason = reason\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,\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 }, `access-requests?id=${created.id}`)\n )\n\n return created\n}\n\n/**\n * At most one pending request per person and privilege, under concurrency. A partial unique index would say it and\n * is not portable, so the person's own row is locked for the rest of the transaction: a second ask of the same\n * moment waits, then finds the first and adds to it (measured on postgres - access-request-pg.test.ts).\n *\n * sqlite has no row locks and one connection, so its transactions already run one after another.\n */\nasync function holdRequester(userId: string, tx?: EntityManager) {\n if (!tx || (tx.connection?.options?.type as string)?.includes('sqlite')) return\n await tx.getRepository(User).createQueryBuilder('HELD').setLock('pessimistic_write').where('HELD.id = :id', { id: userId }).getOne()\n}\n\n/** How long a denial is shown back to the person and holds against the same words (questions Q9 · 5). */\nexport const ACCESS_REQUEST_DENIAL_DAYS = 7\n\n/** The latest denial of this person and privilege within ACCESS_REQUEST_DENIAL_DAYS, if any. */\nexport async function latestDenial(domain: Domain, requesterId: string, category: string, privilege: string, now: Date, tx?: EntityManager) {\n const denied = await getRepository(AccessRequest, tx).findOne({\n where: { domain: { id: domain.id }, requester: { id: requesterId }, category, privilege, state: AccessRequestStatus.DENIED },\n order: { decidedAt: 'DESC' }\n })\n if (!denied?.decidedAt) return null\n return now.getTime() - new Date(denied.decidedAt).getTime() <= ACCESS_REQUEST_DENIAL_DAYS * 24 * 60 * 60 * 1000 ? denied : null\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 /* The requester cannot read the list of everyone's requests - their own is where the answer is. */\n 'my-access-requests'\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`), linking to where they can act on it. `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 url: string\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,\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"]}
|
|
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, mayChangeParentRoles, 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 /* Asking is not a reflex - the reason is what the person deciding weighs, so there is no request without one. */\n const reason = input.reason?.trim()\n if (!reason) {\n throw new Refusal('access-request-needs-reason', {}, 'Write why you need it.')\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 await holdRequester(user.id, tx)\n\n /*\n * Denied lately: asking again is allowed, the same words are not - the person deciding has already answered them.\n * The screen shows that answer before the person writes (ACCESS_REQUEST_DENIAL_DAYS).\n */\n const denied = await latestDenial(domain, user.id, category, privilege, now, tx)\n if (denied && (denied.reason || '').trim() === reason) {\n throw new Refusal(\n 'access-request-same-reason-as-denied',\n { reason: denied.decisionReason || '' },\n 'This was denied for the same reason lately - write what has changed.'\n )\n }\n\n const repository = getRepository(AccessRequest, tx)\n const pending = await findMine(repository, domain.id, user.id, category, privilege, AccessRequestStatus.PENDING, tx)\n\n if (pending && effectiveState(pending.state, pending.expiresAt, now) === AccessRequestStatus.PENDING) {\n pending.count = (pending.count || 1) + 1\n pending.lastRequestedAt = now\n pending.reason = reason\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 /*\n * Withdrawn lately: the same request comes back - one row, and nobody is told again. Asking and withdrawing in a loop\n * would otherwise make a row and a notification to every decider on each turn.\n */\n const withdrawn = await findMine(repository, domain.id, user.id, category, privilege, AccessRequestStatus.WITHDRAWN, tx)\n if (withdrawn && now.getTime() - new Date(withdrawn.updatedAt).getTime() <= ACCESS_REQUEST_QUIET_HOURS * 60 * 60 * 1000) {\n withdrawn.state = AccessRequestStatus.PENDING\n withdrawn.count = (withdrawn.count || 1) + 1\n withdrawn.lastRequestedAt = now\n withdrawn.reason = reason\n withdrawn.expiresAt = expiryFrom(now)\n if (input.operationName) withdrawn.operationName = input.operationName\n return await repository.save(withdrawn)\n }\n\n const created = await repository.save({\n domain,\n requester: user,\n category,\n privilege,\n operationName: input.operationName,\n reason,\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 }, `access-requests?id=${created.id}`)\n )\n\n return created\n}\n\n/** A request withdrawn within this many hours comes back as the same row, without telling anyone again. */\nexport const ACCESS_REQUEST_QUIET_HOURS = 24\n\n/**\n * The caller's latest request of one privilege in one state. On a database with row locks it is a locking read: after\n * `holdRequester` waits, a plain read under REPEATABLE READ (MySQL's default) would still see the snapshot taken before\n * the other ask committed, and insert a second pending row. A locking read sees the committed row.\n */\nasync function findMine(repository: any, domainId: string, requesterId: string, category: string, privilege: string, state: AccessRequestStatus, tx?: EntityManager) {\n const qb = repository\n .createQueryBuilder('mine')\n .leftJoinAndSelect('mine.domain', 'mine_domain')\n .leftJoinAndSelect('mine.requester', 'mine_requester')\n .where('mine.domain = :domainId AND mine.requester = :requesterId', { domainId, requesterId })\n .andWhere('mine.category = :category AND mine.privilege = :privilege AND mine.state = :state', { category, privilege, state })\n .orderBy('mine.updatedAt', 'DESC')\n /* Lowercase alias: postgres folds the unquoted name in `FOR UPDATE OF`, and only the request row is locked - not the outer-joined ones. */\n if (tx && !(tx.connection?.options?.type as string)?.includes('sqlite')) qb.setLock('pessimistic_write', undefined, ['mine'])\n return await qb.getOne()\n}\n\n/**\n * At most one pending request per person and privilege, under concurrency. A partial unique index would say it and\n * is not portable, so the person's own row is locked for the rest of the transaction: a second ask of the same\n * moment waits, then finds the first and adds to it (measured on postgres - access-request-pg.test.ts).\n *\n * sqlite has no row locks and one connection, so its transactions already run one after another.\n */\nasync function holdRequester(userId: string, tx?: EntityManager) {\n if (!tx || (tx.connection?.options?.type as string)?.includes('sqlite')) return\n await tx.getRepository(User).createQueryBuilder('HELD').setLock('pessimistic_write').where('HELD.id = :id', { id: userId }).getOne()\n}\n\n/** How long a denial is shown back to the person and holds against the same words (questions Q9 · 5). */\nexport const ACCESS_REQUEST_DENIAL_DAYS = 7\n\n/** The latest denial of this person and privilege within ACCESS_REQUEST_DENIAL_DAYS, if any. */\nexport async function latestDenial(domain: Domain, requesterId: string, category: string, privilege: string, now: Date, tx?: EntityManager) {\n const denied = await getRepository(AccessRequest, tx).findOne({\n where: { domain: { id: domain.id }, requester: { id: requesterId }, category, privilege, state: AccessRequestStatus.DENIED },\n order: { decidedAt: 'DESC' }\n })\n if (!denied?.decidedAt) return null\n return now.getTime() - new Date(denied.decidedAt).getTime() <= ACCESS_REQUEST_DENIAL_DAYS * 24 * 60 * 60 * 1000 ? denied : null\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 const roleIsParents = !!role && !!domain.parentId && role.domainId === domain.parentId\n const deciderMayChangeParentRoles = roleIsParents ? await mayChangeParentRoles(decider, domain, tx) : false\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 roleIsParents,\n deciderMayChangeParentRoles,\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: ['domain', 'requester', 'requester.roles', 'grantedRole'] })\n const requester = request.requester\n\n if (granting) {\n await assignRoles(requester, { add: [input.roleId] }, domain, tx, decider)\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 /* The requester cannot read the list of everyone's requests - their own is where the answer is. */\n 'my-access-requests'\n )\n )\n\n return request\n}\n\n/**\n * Other pending requests of the same person that the role just given now answers - closed as granted with it.\n *\n * Judged by the granted role's own privileges, read in this transaction. `User.hasPrivilege` reads through the default\n * connection, which on a pooled database does not see the role added a moment ago in this uncommitted transaction.\n */\nasync function closeWhatTheGrantOpened(granted: AccessRequest, context: ResolverContext) {\n const { domain, user: decider, tx } = context.state\n const repository = getRepository(AccessRequest, tx)\n const role = await getRepository(Role, tx).findOne({ where: { id: granted.grantedRole?.id }, relations: ['privileges'] })\n const gives = new Set((role?.privileges || []).map(p => `${p.category}:${p.name}`))\n if (gives.size === 0) return\n\n const others = await repository.find({\n where: { domain: { id: domain.id }, requester: { id: granted.requester.id }, state: AccessRequestStatus.PENDING }\n })\n\n for (const other of others) {\n if (other.id === granted.id || !gives.has(`${other.category}:${other.privilege}`)) 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: { id: role.id } as Role }\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`), linking to where they can act on it. `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 url: string\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,\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"]}
|
|
@@ -11,6 +11,8 @@ export declare class AccessRequestQuery {
|
|
|
11
11
|
*/
|
|
12
12
|
accessRequestDeciderCount(context: ResolverContext): Promise<number>;
|
|
13
13
|
rolesGranting(category: string, privilege: string, context: ResolverContext): Promise<AccessRoleCandidate[]>;
|
|
14
|
+
/** The roles of this domain's assignable scope (own, parent, granted) with exactly this name - what a decision may name. */
|
|
15
|
+
assignableRolesNamed(name: string, context: ResolverContext): Promise<AccessRoleCandidate[]>;
|
|
14
16
|
/** The same review the decision runs - the list screen and the AI draft card show this (ADR-0103 decision 4). */
|
|
15
17
|
reviewAccessDecision(decision: AccessDecisionInput, context: ResolverContext): Promise<AccessDecisionIssue[]>;
|
|
16
18
|
}
|
|
@@ -5,18 +5,21 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const type_graphql_1 = require("type-graphql");
|
|
6
6
|
const shell_1 = require("@things-factory/shell");
|
|
7
7
|
const access_request_js_1 = require("../../controllers/access-request.js");
|
|
8
|
+
const assignable_roles_js_1 = require("../../utils/assignable-roles.js");
|
|
8
9
|
const access_request_js_2 = require("./access-request.js");
|
|
9
10
|
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
11
|
/* The review and the candidates serve a decision - the deciding declaration. */
|
|
13
12
|
const DECIDE = '@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)';
|
|
14
13
|
let AccessRequestQuery = class AccessRequestQuery {
|
|
14
|
+
/*
|
|
15
|
+
* A decider's list - the same declaration as the decision, so the people told of a request can open it (ADR-0103).
|
|
16
|
+
* This domain's rows only: `inherited` is pinned, a parent domain's requests are not this domain's to read.
|
|
17
|
+
*/
|
|
15
18
|
async accessRequests(params, context) {
|
|
16
19
|
const { domain } = context.state;
|
|
17
20
|
const queryBuilder = (0, shell_1.getQueryBuilderFromListParams)({
|
|
18
21
|
domain,
|
|
19
|
-
params,
|
|
22
|
+
params: { ...params, inherited: shell_1.InheritedValueType.None },
|
|
20
23
|
repository: (0, shell_1.getRepository)(access_request_js_2.AccessRequest),
|
|
21
24
|
alias: 'accessRequest',
|
|
22
25
|
searchables: ['category', 'privilege', 'reason']
|
|
@@ -30,7 +33,7 @@ let AccessRequestQuery = class AccessRequestQuery {
|
|
|
30
33
|
const { domain, user } = context.state;
|
|
31
34
|
return await (0, shell_1.getRepository)(access_request_js_2.AccessRequest).find({
|
|
32
35
|
where: { domain: { id: domain.id }, requester: { id: user.id } },
|
|
33
|
-
relations: ['grantedRole'],
|
|
36
|
+
relations: ['domain', 'requester', 'grantedRole'],
|
|
34
37
|
order: { createdAt: 'DESC' },
|
|
35
38
|
take: 100
|
|
36
39
|
});
|
|
@@ -46,6 +49,11 @@ let AccessRequestQuery = class AccessRequestQuery {
|
|
|
46
49
|
async rolesGranting(category, privilege, context) {
|
|
47
50
|
return (await (0, access_request_js_1.rolesGranting)(category, privilege, context.state.domain)).map(({ id, name, privilegeCount }) => ({ id, name, privilegeCount }));
|
|
48
51
|
}
|
|
52
|
+
/** The roles of this domain's assignable scope (own, parent, granted) with exactly this name - what a decision may name. */
|
|
53
|
+
async assignableRolesNamed(name, context) {
|
|
54
|
+
const roles = await (0, assignable_roles_js_1.findAssignableRolesNamed)(name, context.state.domain);
|
|
55
|
+
return roles.map(role => ({ id: role.id, name: role.name, privilegeCount: role.privileges?.length ?? 0 }));
|
|
56
|
+
}
|
|
49
57
|
/** The same review the decision runs - the list screen and the AI draft card show this (ADR-0103 decision 4). */
|
|
50
58
|
async reviewAccessDecision(decision, context) {
|
|
51
59
|
return (await (0, access_request_js_1.reviewDecision)(decision, context));
|
|
@@ -53,7 +61,7 @@ let AccessRequestQuery = class AccessRequestQuery {
|
|
|
53
61
|
};
|
|
54
62
|
exports.AccessRequestQuery = AccessRequestQuery;
|
|
55
63
|
tslib_1.__decorate([
|
|
56
|
-
(0, type_graphql_1.Directive)(
|
|
64
|
+
(0, type_graphql_1.Directive)(DECIDE),
|
|
57
65
|
(0, type_graphql_1.Query)(returns => access_request_types_js_1.AccessRequestList, { description: "To fetch this domain's access requests." }),
|
|
58
66
|
tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
|
|
59
67
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
@@ -85,6 +93,15 @@ tslib_1.__decorate([
|
|
|
85
93
|
tslib_1.__metadata("design:paramtypes", [String, String, Object]),
|
|
86
94
|
tslib_1.__metadata("design:returntype", Promise)
|
|
87
95
|
], AccessRequestQuery.prototype, "rolesGranting", null);
|
|
96
|
+
tslib_1.__decorate([
|
|
97
|
+
(0, type_graphql_1.Directive)(DECIDE),
|
|
98
|
+
(0, type_graphql_1.Query)(returns => [access_request_types_js_1.AccessRoleCandidate], { description: 'To find, by exact name, the roles this domain can assign.' }),
|
|
99
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('name')),
|
|
100
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
101
|
+
tslib_1.__metadata("design:type", Function),
|
|
102
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
103
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
104
|
+
], AccessRequestQuery.prototype, "assignableRolesNamed", null);
|
|
88
105
|
tslib_1.__decorate([
|
|
89
106
|
(0, type_graphql_1.Directive)(DECIDE),
|
|
90
107
|
(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.' }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-request-query.js","sourceRoot":"","sources":["../../../server/service/access-request/access-request-query.ts"],"names":[],"mappings":";;;;AAAA,+CAA8E;AAE9E,
|
|
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, Int, Query, Resolver } from 'type-graphql'\n\nimport { getQueryBuilderFromListParams, getRepository, InheritedValueType, ListParam } from '@things-factory/shell'\n\nimport { findDeciderIds, reviewDecision, rolesGranting } from '../../controllers/access-request.js'\nimport { findAssignableRolesNamed } from '../../utils/assignable-roles.js'\nimport { AccessRequest } from './access-request.js'\nimport { AccessDecisionInput, AccessDecisionIssue, AccessRequestList, AccessRoleCandidate } from './access-request-types.js'\n\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 /*\n * A decider's list - the same declaration as the decision, so the people told of a request can open it (ADR-0103).\n * This domain's rows only: `inherited` is pinned, a parent domain's requests are not this domain's to read.\n */\n @Directive(DECIDE)\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: { ...params, inherited: InheritedValueType.None },\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: ['domain', 'requester', 'grantedRole'],\n order: { createdAt: 'DESC' },\n take: 100\n })\n }\n\n /**\n * Open by design: how many people a request would reach - the popup says it before the person sends, so asking reads\n * as what it is. A number, never who.\n */\n @Query(returns => Int, { description: 'To count the people who would be told of an access request from the caller in this domain.' })\n async accessRequestDeciderCount(@Ctx() context: ResolverContext): Promise<number> {\n const { domain, user } = context.state\n return (await findDeciderIds(domain, user.id)).length\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 roles of this domain's assignable scope (own, parent, granted) with exactly this name - what a decision may name. */\n @Directive(DECIDE)\n @Query(returns => [AccessRoleCandidate], { description: 'To find, by exact name, the roles this domain can assign.' })\n async assignableRolesNamed(@Arg('name') name: string, @Ctx() context: ResolverContext): Promise<AccessRoleCandidate[]> {\n const roles = await findAssignableRolesNamed(name, context.state.domain)\n return roles.map(role => ({ id: role.id, name: role.name, privilegeCount: role.privileges?.length ?? 0 }))\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"]}
|
|
@@ -32,7 +32,7 @@ export * from './role/role-types.js';
|
|
|
32
32
|
export * from './user/user-types.js';
|
|
33
33
|
export * from './domain-link/domain-link-types.js';
|
|
34
34
|
export * from './access-request/access-request-types.js';
|
|
35
|
-
export declare const entities: (typeof import("./access-request/access-request.js").AccessRequest | typeof import("./role/role.js").Role | typeof import("./privilege/privilege.js").Privilege | typeof import("./user/user.js").User | typeof import("./users-auth-providers/users-auth-providers.js").UsersAuthProviders | typeof import("./auth-provider/auth-provider.js").AuthProvider | typeof import("./application/application.js").Application | typeof import("./appliance/appliance.js").Appliance | typeof import("./partner/partner.js").Partner | typeof import("./
|
|
35
|
+
export declare const entities: (typeof import("./access-request/access-request.js").AccessRequest | typeof import("./role/role.js").Role | typeof import("./privilege/privilege.js").Privilege | typeof import("./user/user.js").User | typeof import("./granted-role/granted-role.js").GrantedRole | typeof import("./users-auth-providers/users-auth-providers.js").UsersAuthProviders | typeof import("./auth-provider/auth-provider.js").AuthProvider | typeof import("./application/application.js").Application | typeof import("./appliance/appliance.js").Appliance | typeof import("./partner/partner.js").Partner | typeof import("./invitation/invitation.js").Invitation | typeof import("./password-history/password-history.js").PasswordHistory | typeof import("./verification-token/verification-token.js").VerificationToken | typeof import("./verification-token/verification-token.js").VerificationTokenType | typeof import("./login-history/login-history.js").LoginHistory | typeof import("./web-auth-credential/web-auth-credential.js").WebAuthCredential | typeof import("./domain-link/domain-link.js").DomainLink | typeof import("./domain-owner/domain-owner.js").DomainOwner)[];
|
|
36
36
|
export declare const schema: {
|
|
37
37
|
typeDefs: {
|
|
38
38
|
privilegeDirectiveTypeDefs: import("graphql").DocumentNode;
|
|
@@ -20,6 +20,7 @@ exports.openFields = {
|
|
|
20
20
|
'Query.passwordRule': "the installation's password policy. The change-password screen needs it before the caller has any privilege — and after a forced reset they may have none",
|
|
21
21
|
'Query.authProviderTypes': "which provider types this build supports. A catalogue, not a tenant's configuration",
|
|
22
22
|
'Query.myAccessRequests': "the caller's own access requests",
|
|
23
|
+
'Query.mayChangeParentRoles': "whether the caller may give the parent domain's roles here - about the caller only",
|
|
23
24
|
'Query.accessRequestDeciderCount': 'how many people an access request from the caller would reach - a number, never who',
|
|
24
25
|
'Mutation.requestAccess': 'asked by someone refused for want of a role - a gate here would need the privilege being asked for. The door asks only for the caller and judges again that a role would open it (ADR-0103)',
|
|
25
26
|
'Mutation.withdrawAccessRequest': "withdraws the caller's own pending request and nobody else's"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-fields.js","sourceRoot":"","sources":["../../server/service/open-fields.ts"],"names":[],"mappings":";;;AAEA;;;;;;GAMG;AACU,QAAA,UAAU,GAA0B;IAC/C,QAAQ,EAAE;QACR,wBAAwB,EAAE,2BAA2B;QACrD,eAAe,EAAE,wBAAwB;QACzC,oBAAoB,EAAE,0EAA0E;QAChG,4BAA4B,EAAE,sCAAsC;QACpE,8BAA8B,EAAE,2CAA2C;QAC3E,wCAAwC,EAAE,iCAAiC;QAC3E,4BAA4B,EAAE,qDAAqD;QACnF,oBAAoB,EAClB,2JAA2J;QAC7J,yBAAyB,EAAE,qFAAqF;QAChH,wBAAwB,EAAE,kCAAkC;QAC5D,iCAAiC,EAAE,qFAAqF;QACxH,wBAAwB,EACtB,6LAA6L;QAC/L,gCAAgC,EAAE,8DAA8D;KACjG;CACF,CAAA","sourcesContent":["import type { OpenFieldsDeclaration } from './privilege/open-field-rule.js'\n\n/**\n * auth-base's root fields that declare no `@privilege` — every one of them, and why.\n *\n * There is no `notYetDeclared` here. The twelve that were open without a reason were closed on\n * 2026-09-20 (`c02ee9bdc1`); what is left answers about the caller, or about the installation\n * rather than anybody's data. An addition to this list needs its reason written first.\n */\nexport const openFields: OpenFieldsDeclaration = {\n byDesign: {\n 'Query.myLoginHistories': \"the caller's own sign-ins\",\n 'Query.myRoles': \"the caller's own roles\",\n 'Query.hasPrivilege': 'whether the caller has one — a gate here would need the answer to itself',\n 'Query.domainsWithPrivilege': 'the domains where the caller has one',\n 'Query.checkUserBelongsDomain': 'whether the caller belongs to this domain',\n 'Query.checkResettablePasswordToDefault': \"about the caller's own password\",\n 'Query.checkDefaultPassword': 'whether the caller is still on the default password',\n 'Query.passwordRule':\n \"the installation's password policy. The change-password screen needs it before the caller has any privilege — and after a forced reset they may have none\",\n 'Query.authProviderTypes': \"which provider types this build supports. A catalogue, not a tenant's configuration\",\n 'Query.myAccessRequests': \"the caller's own access requests\",\n 'Query.accessRequestDeciderCount': 'how many people an access request from the caller would reach - a number, never who',\n 'Mutation.requestAccess':\n 'asked by someone refused for want of a role - a gate here would need the privilege being asked for. The door asks only for the caller and judges again that a role would open it (ADR-0103)',\n 'Mutation.withdrawAccessRequest': \"withdraws the caller's own pending request and nobody else's\"\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"open-fields.js","sourceRoot":"","sources":["../../server/service/open-fields.ts"],"names":[],"mappings":";;;AAEA;;;;;;GAMG;AACU,QAAA,UAAU,GAA0B;IAC/C,QAAQ,EAAE;QACR,wBAAwB,EAAE,2BAA2B;QACrD,eAAe,EAAE,wBAAwB;QACzC,oBAAoB,EAAE,0EAA0E;QAChG,4BAA4B,EAAE,sCAAsC;QACpE,8BAA8B,EAAE,2CAA2C;QAC3E,wCAAwC,EAAE,iCAAiC;QAC3E,4BAA4B,EAAE,qDAAqD;QACnF,oBAAoB,EAClB,2JAA2J;QAC7J,yBAAyB,EAAE,qFAAqF;QAChH,wBAAwB,EAAE,kCAAkC;QAC5D,4BAA4B,EAAE,oFAAoF;QAClH,iCAAiC,EAAE,qFAAqF;QACxH,wBAAwB,EACtB,6LAA6L;QAC/L,gCAAgC,EAAE,8DAA8D;KACjG;CACF,CAAA","sourcesContent":["import type { OpenFieldsDeclaration } from './privilege/open-field-rule.js'\n\n/**\n * auth-base's root fields that declare no `@privilege` — every one of them, and why.\n *\n * There is no `notYetDeclared` here. The twelve that were open without a reason were closed on\n * 2026-09-20 (`c02ee9bdc1`); what is left answers about the caller, or about the installation\n * rather than anybody's data. An addition to this list needs its reason written first.\n */\nexport const openFields: OpenFieldsDeclaration = {\n byDesign: {\n 'Query.myLoginHistories': \"the caller's own sign-ins\",\n 'Query.myRoles': \"the caller's own roles\",\n 'Query.hasPrivilege': 'whether the caller has one — a gate here would need the answer to itself',\n 'Query.domainsWithPrivilege': 'the domains where the caller has one',\n 'Query.checkUserBelongsDomain': 'whether the caller belongs to this domain',\n 'Query.checkResettablePasswordToDefault': \"about the caller's own password\",\n 'Query.checkDefaultPassword': 'whether the caller is still on the default password',\n 'Query.passwordRule':\n \"the installation's password policy. The change-password screen needs it before the caller has any privilege — and after a forced reset they may have none\",\n 'Query.authProviderTypes': \"which provider types this build supports. A catalogue, not a tenant's configuration\",\n 'Query.myAccessRequests': \"the caller's own access requests\",\n 'Query.mayChangeParentRoles': \"whether the caller may give the parent domain's roles here - about the caller only\",\n 'Query.accessRequestDeciderCount': 'how many people an access request from the caller would reach - a number, never who',\n 'Mutation.requestAccess':\n 'asked by someone refused for want of a role - a gate here would need the privilege being asked for. The door asks only for the caller and judges again that a role would open it (ADR-0103)',\n 'Mutation.withdrawAccessRequest': \"withdraws the caller's own pending request and nobody else's\"\n }\n}\n"]}
|