@things-factory/auth-base 10.1.106 โ 10.1.107
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-assistant.d.ts +50 -0
- package/dist-server/ai-entry/access-assistant.js +145 -0
- package/dist-server/ai-entry/access-assistant.js.map +1 -0
- package/dist-server/ai-entry/access-entry-targets.d.ts +32 -0
- package/dist-server/ai-entry/access-entry-targets.js +196 -0
- package/dist-server/ai-entry/access-entry-targets.js.map +1 -0
- package/dist-server/ai-entry/entry-target-shape.d.ts +82 -0
- package/dist-server/ai-entry/entry-target-shape.js +3 -0
- package/dist-server/ai-entry/entry-target-shape.js.map +1 -0
- package/dist-server/controllers/access-request.d.ts +4 -0
- package/dist-server/controllers/access-request.js +49 -9
- package/dist-server/controllers/access-request.js.map +1 -1
- package/dist-server/index.d.ts +3 -0
- package/dist-server/index.js +3 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/access-request/access-request-query.d.ts +5 -0
- package/dist-server/service/access-request/access-request-query.js +15 -0
- 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/privilege/privilege-directive.js +2 -2
- package/dist-server/service/privilege/privilege-directive.js.map +1 -1
- package/dist-server/service/privilege/privilege-rejection.d.ts +3 -1
- package/dist-server/service/privilege/privilege-rejection.js +2 -10
- package/dist-server/service/privilege/privilege-rejection.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/tests/access-assistant-db.test.ts +155 -0
- package/tests/access-entry-targets-db.test.ts +138 -0
- package/tests/access-request-db.test.ts +40 -3
- package/tests/access-request-pg.test.ts +72 -0
- package/tests/privilege-directive.test.ts +18 -0
- package/translations/en.json +3 -1
- package/translations/ja.json +3 -1
- package/translations/ko.json +3 -1
- package/translations/ms.json +3 -1
- package/translations/zh.json +3 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AccessDecisionRefused = void 0;
|
|
3
|
+
exports.ACCESS_REQUEST_DENIAL_DAYS = exports.AccessDecisionRefused = void 0;
|
|
4
4
|
exports.requestAccess = requestAccess;
|
|
5
|
+
exports.latestDenial = latestDenial;
|
|
5
6
|
exports.withdrawAccessRequest = withdrawAccessRequest;
|
|
6
7
|
exports.findDeciderIds = findDeciderIds;
|
|
7
8
|
exports.rolesGranting = rolesGranting;
|
|
@@ -45,6 +46,11 @@ async function requestAccess(input, context) {
|
|
|
45
46
|
if (!known) {
|
|
46
47
|
throw new shell_1.Refusal('access-request-unknown-privilege', { privilege: `${category}:${privilege}` }, `There is no privilege ${category}:${privilege}.`);
|
|
47
48
|
}
|
|
49
|
+
/* Asking is not a reflex - the reason is what the person deciding weighs, so there is no request without one. */
|
|
50
|
+
const reason = input.reason?.trim();
|
|
51
|
+
if (!reason) {
|
|
52
|
+
throw new shell_1.Refusal('access-request-needs-reason', {}, 'Write why you need it.');
|
|
53
|
+
}
|
|
48
54
|
const judgement = await (0, check_permission_js_1.judgePermission)({ category, privilege }, user, domain, unsafeIP, prohibitedPrivileges);
|
|
49
55
|
if (judgement.permitted === true) {
|
|
50
56
|
throw new shell_1.Refusal('access-request-already-holds', { privilege: `${category}:${privilege}` }, `You already have ${category}:${privilege}.`);
|
|
@@ -52,6 +58,15 @@ async function requestAccess(input, context) {
|
|
|
52
58
|
if (judgement.because !== 'role') {
|
|
53
59
|
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
60
|
}
|
|
61
|
+
await holdRequester(user.id, tx);
|
|
62
|
+
/*
|
|
63
|
+
* Denied lately: asking again is allowed, the same words are not - the person deciding has already answered them.
|
|
64
|
+
* The screen shows that answer before the person writes (ACCESS_REQUEST_DENIAL_DAYS).
|
|
65
|
+
*/
|
|
66
|
+
const denied = await latestDenial(domain, user.id, category, privilege, now, tx);
|
|
67
|
+
if (denied && (denied.reason || '').trim() === reason) {
|
|
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
|
+
}
|
|
55
70
|
const repository = (0, shell_1.getRepository)(access_request_js_1.AccessRequest, tx);
|
|
56
71
|
const pending = await repository.findOne({
|
|
57
72
|
where: {
|
|
@@ -66,8 +81,7 @@ async function requestAccess(input, context) {
|
|
|
66
81
|
if (pending && (0, access_request_review_js_1.effectiveState)(pending.state, pending.expiresAt, now) === access_request_js_1.AccessRequestStatus.PENDING) {
|
|
67
82
|
pending.count = (pending.count || 1) + 1;
|
|
68
83
|
pending.lastRequestedAt = now;
|
|
69
|
-
|
|
70
|
-
pending.reason = input.reason.trim();
|
|
84
|
+
pending.reason = reason;
|
|
71
85
|
if (input.operationName)
|
|
72
86
|
pending.operationName = input.operationName;
|
|
73
87
|
/* Merged - the people deciding were told once, when it was first asked. */
|
|
@@ -83,7 +97,7 @@ async function requestAccess(input, context) {
|
|
|
83
97
|
category,
|
|
84
98
|
privilege,
|
|
85
99
|
operationName: input.operationName,
|
|
86
|
-
reason
|
|
100
|
+
reason,
|
|
87
101
|
count: 1,
|
|
88
102
|
lastRequestedAt: now,
|
|
89
103
|
state: access_request_js_1.AccessRequestStatus.PENDING,
|
|
@@ -93,9 +107,33 @@ async function requestAccess(input, context) {
|
|
|
93
107
|
tellLater(context, 'notify the people who decide access requests', async () => notifyEach(context, deciders, 'access-request.notify.asked', {
|
|
94
108
|
name: user.name || user.email || user.username,
|
|
95
109
|
privilege: `${category}:${privilege}`
|
|
96
|
-
}, created));
|
|
110
|
+
}, `access-requests?id=${created.id}`));
|
|
97
111
|
return created;
|
|
98
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* At most one pending request per person and privilege, under concurrency. A partial unique index would say it and
|
|
115
|
+
* is not portable, so the person's own row is locked for the rest of the transaction: a second ask of the same
|
|
116
|
+
* moment waits, then finds the first and adds to it (measured on postgres - access-request-pg.test.ts).
|
|
117
|
+
*
|
|
118
|
+
* sqlite has no row locks and one connection, so its transactions already run one after another.
|
|
119
|
+
*/
|
|
120
|
+
async function holdRequester(userId, tx) {
|
|
121
|
+
if (!tx || tx.connection?.options?.type?.includes('sqlite'))
|
|
122
|
+
return;
|
|
123
|
+
await tx.getRepository(user_js_1.User).createQueryBuilder('HELD').setLock('pessimistic_write').where('HELD.id = :id', { id: userId }).getOne();
|
|
124
|
+
}
|
|
125
|
+
/** How long a denial is shown back to the person and holds against the same words (questions Q9 ยท 5). */
|
|
126
|
+
exports.ACCESS_REQUEST_DENIAL_DAYS = 7;
|
|
127
|
+
/** The latest denial of this person and privilege within ACCESS_REQUEST_DENIAL_DAYS, if any. */
|
|
128
|
+
async function latestDenial(domain, requesterId, category, privilege, now, tx) {
|
|
129
|
+
const denied = await (0, shell_1.getRepository)(access_request_js_1.AccessRequest, tx).findOne({
|
|
130
|
+
where: { domain: { id: domain.id }, requester: { id: requesterId }, category, privilege, state: access_request_js_1.AccessRequestStatus.DENIED },
|
|
131
|
+
order: { decidedAt: 'DESC' }
|
|
132
|
+
});
|
|
133
|
+
if (!denied?.decidedAt)
|
|
134
|
+
return null;
|
|
135
|
+
return now.getTime() - new Date(denied.decidedAt).getTime() <= exports.ACCESS_REQUEST_DENIAL_DAYS * 24 * 60 * 60 * 1000 ? denied : null;
|
|
136
|
+
}
|
|
99
137
|
/** The requester takes back their own pending request. */
|
|
100
138
|
async function withdrawAccessRequest(id, context) {
|
|
101
139
|
const { domain, user, tx } = context.state;
|
|
@@ -273,7 +311,9 @@ async function decideAccessRequest(input, context) {
|
|
|
273
311
|
await (0, assignable_roles_js_1.assignRoles)(requester, { add: [input.roleId] }, domain, tx);
|
|
274
312
|
await closeWhatTheGrantOpened(request, context);
|
|
275
313
|
}
|
|
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 ?? '' },
|
|
314
|
+
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 ?? '' },
|
|
315
|
+
/* The requester cannot read the list of everyone's requests - their own is where the answer is. */
|
|
316
|
+
'my-access-requests'));
|
|
277
317
|
return request;
|
|
278
318
|
}
|
|
279
319
|
/** Other pending requests of the same person that the role just given now answers - closed as granted with it. */
|
|
@@ -298,10 +338,10 @@ function tellLater(context, name, run) {
|
|
|
298
338
|
void run().catch(() => { });
|
|
299
339
|
}
|
|
300
340
|
/**
|
|
301
|
-
* One notification per person, in that person's language (`User.locale`). `notify` is optional: it is there when
|
|
341
|
+
* 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
|
|
302
342
|
* the notification module is loaded, and nothing happens otherwise - the request list is the record.
|
|
303
343
|
*/
|
|
304
|
-
async function notifyEach(context, userIds, key, params,
|
|
344
|
+
async function notifyEach(context, userIds, key, params, url) {
|
|
305
345
|
const notify = context.state.notify;
|
|
306
346
|
if (typeof notify !== 'function' || userIds.length === 0)
|
|
307
347
|
return;
|
|
@@ -319,7 +359,7 @@ async function notifyEach(context, userIds, key, params, request) {
|
|
|
319
359
|
subject: 'access-request',
|
|
320
360
|
title: say('title'),
|
|
321
361
|
body: say('body'),
|
|
322
|
-
url
|
|
362
|
+
url,
|
|
323
363
|
mode: 'background'
|
|
324
364
|
});
|
|
325
365
|
}
|
|
@@ -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 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"]}
|
|
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"]}
|
package/dist-server/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import './routes.js';
|
|
2
2
|
export * from './service/index.js';
|
|
3
|
+
export * from './ai-entry/access-entry-targets.js';
|
|
4
|
+
export * from './ai-entry/access-assistant.js';
|
|
5
|
+
export type { EntryTargetDeclaration as AuthEntryTarget } from './ai-entry/entry-target-shape.js';
|
|
3
6
|
export * from './migrations/index.js';
|
|
4
7
|
export * from './middlewares/index.js';
|
|
5
8
|
export * from './router/index.js';
|
package/dist-server/index.js
CHANGED
|
@@ -7,6 +7,9 @@ const privilege_axis_js_1 = require("./service/privilege/privilege-axis.js");
|
|
|
7
7
|
const role_template_js_1 = require("./service/role-template/role-template.js");
|
|
8
8
|
require("./routes.js");
|
|
9
9
|
tslib_1.__exportStar(require("./service/index.js"), exports);
|
|
10
|
+
/* AI ์
๋ ฅ declarations - plain data, registered by an application that runs the assistant (ADR-0103). */
|
|
11
|
+
tslib_1.__exportStar(require("./ai-entry/access-entry-targets.js"), exports);
|
|
12
|
+
tslib_1.__exportStar(require("./ai-entry/access-assistant.js"), exports);
|
|
10
13
|
tslib_1.__exportStar(require("./migrations/index.js"), exports);
|
|
11
14
|
tslib_1.__exportStar(require("./middlewares/index.js"), exports);
|
|
12
15
|
tslib_1.__exportStar(require("./router/index.js"), exports);
|
package/dist-server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,iDAAqD;AACrD,mEAA4D;AAC5D,6EAA0E;AAC1E,+EAAsF;AAEtF,uBAAoB;AAEpB,6DAAkC;AAClC,gEAAqC;AAErC,iEAAsC;AACtC,4DAAiC;AACjC,sDAA2B;AAE3B,sEAA2C;AAC3C,sEAA2C;AAC3C,yEAA8C;AAC9C,6EAAkD;AAClD,sEAA2C;AAC3C,gEAAqC;AACrC,+EAAoD;AACpD,yEAA8C;AAC9C,mEAAwC;AACxC,sEAA2C;AAC3C,yEAA8C;AAC9C,oEAAyC;AAEzC,4DAAiC;AAEjC,qDAA0B;AAE1B,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAO,EAAE,EAAE;IACjF,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IACxC,MAAM,mBAAmB,GAAG,IAAA,qBAAa,EAAC,wBAAS,CAAC,CAAA;IAEpD,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,UAA8B,CAAC,EAAE,CAAC;QAC7E,IAAI,CAAC,IAAI,CAAC,MAAM,mBAAmB,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;YAC1E,MAAM,mBAAmB,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACpD,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAA;IAExF;;;;;OAKG;IACH,IAAA,8CAA2B,GAAE,CAAA;IAE7B;;;;;OAKG;IACH,IAAA,sCAAkB,GAAE,CAAA;AACtB,CAAC,CAAC,CAAA","sourcesContent":["import { getRepository } from '@things-factory/shell'\nimport { Privilege } from './service/privilege/privilege.js'\nimport { reportDeclaredAxes } from './service/privilege/privilege-axis.js'\nimport { reportUnknownTemplateGrants } from './service/role-template/role-template.js'\n\nimport './routes.js'\n\nexport * from './service/index.js'\nexport * from './migrations/index.js'\n\nexport * from './middlewares/index.js'\nexport * from './router/index.js'\nexport * from './routes.js'\n\nexport * from './utils/get-domain-users.js'\nexport * from './utils/get-user-domains.js'\nexport * from './utils/checkin-domain-rule.js'\nexport * from './utils/can-user-checkin-domain.js'\nexport * from './utils/domain-ownership.js'\nexport * from './utils/get-secret.js'\nexport * from './utils/check-user-belongs-domain.js'\nexport * from './utils/access-token-cookie.js'\nexport * from './utils/encrypt-state.js'\nexport * from './utils/check-permission.js'\nexport * from './utils/check-user-has-role.js'\nexport * from './utils/build-auth-url.js'\n\nexport * from './errors/index.js'\n\nexport * from './types.js'\n\nprocess.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {\n const privileges = process['PRIVILEGES']\n const privilegeRepository = getRepository(Privilege)\n\n for (const [category, name] of Object.values(privileges as [string, string])) {\n if (0 == (await privilegeRepository.count({ where: { category, name } }))) {\n await privilegeRepository.save({ category, name })\n }\n }\n\n console.log('[auth-base:bootstrap] Synchronization for privilege master has just done.')\n\n /*\n * Templates name privileges by (category, axis), and a category can move. A template\n * that survives a rename becomes a role that grants nothing โ the administrator picks\n * \"quality inspector\", gets a role, and no door opens. Check the names once, here, where\n * the privilege registry has just been filled.\n */\n reportUnknownTemplateGrants()\n\n /*\n * Say which axis vocabulary this installation ended up with. It cannot go in the SDL โ\n * it differs per installation, which is the whole point โ so a boot line is the honest\n * substitute, and it separates \"nobody declared, these are the defaults\" from \"someone\n * declared exactly these\".\n */\n reportDeclaredAxes()\n})\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,iDAAqD;AACrD,mEAA4D;AAC5D,6EAA0E;AAC1E,+EAAsF;AAEtF,uBAAoB;AAEpB,6DAAkC;AAClC,uGAAuG;AACvG,6EAAkD;AAClD,yEAA8C;AAE9C,gEAAqC;AAErC,iEAAsC;AACtC,4DAAiC;AACjC,sDAA2B;AAE3B,sEAA2C;AAC3C,sEAA2C;AAC3C,yEAA8C;AAC9C,6EAAkD;AAClD,sEAA2C;AAC3C,gEAAqC;AACrC,+EAAoD;AACpD,yEAA8C;AAC9C,mEAAwC;AACxC,sEAA2C;AAC3C,yEAA8C;AAC9C,oEAAyC;AAEzC,4DAAiC;AAEjC,qDAA0B;AAE1B,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAO,EAAE,EAAE;IACjF,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IACxC,MAAM,mBAAmB,GAAG,IAAA,qBAAa,EAAC,wBAAS,CAAC,CAAA;IAEpD,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,UAA8B,CAAC,EAAE,CAAC;QAC7E,IAAI,CAAC,IAAI,CAAC,MAAM,mBAAmB,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;YAC1E,MAAM,mBAAmB,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACpD,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAA;IAExF;;;;;OAKG;IACH,IAAA,8CAA2B,GAAE,CAAA;IAE7B;;;;;OAKG;IACH,IAAA,sCAAkB,GAAE,CAAA;AACtB,CAAC,CAAC,CAAA","sourcesContent":["import { getRepository } from '@things-factory/shell'\nimport { Privilege } from './service/privilege/privilege.js'\nimport { reportDeclaredAxes } from './service/privilege/privilege-axis.js'\nimport { reportUnknownTemplateGrants } from './service/role-template/role-template.js'\n\nimport './routes.js'\n\nexport * from './service/index.js'\n/* AI ์
๋ ฅ declarations - plain data, registered by an application that runs the assistant (ADR-0103). */\nexport * from './ai-entry/access-entry-targets.js'\nexport * from './ai-entry/access-assistant.js'\nexport type { EntryTargetDeclaration as AuthEntryTarget } from './ai-entry/entry-target-shape.js'\nexport * from './migrations/index.js'\n\nexport * from './middlewares/index.js'\nexport * from './router/index.js'\nexport * from './routes.js'\n\nexport * from './utils/get-domain-users.js'\nexport * from './utils/get-user-domains.js'\nexport * from './utils/checkin-domain-rule.js'\nexport * from './utils/can-user-checkin-domain.js'\nexport * from './utils/domain-ownership.js'\nexport * from './utils/get-secret.js'\nexport * from './utils/check-user-belongs-domain.js'\nexport * from './utils/access-token-cookie.js'\nexport * from './utils/encrypt-state.js'\nexport * from './utils/check-permission.js'\nexport * from './utils/check-user-has-role.js'\nexport * from './utils/build-auth-url.js'\n\nexport * from './errors/index.js'\n\nexport * from './types.js'\n\nprocess.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {\n const privileges = process['PRIVILEGES']\n const privilegeRepository = getRepository(Privilege)\n\n for (const [category, name] of Object.values(privileges as [string, string])) {\n if (0 == (await privilegeRepository.count({ where: { category, name } }))) {\n await privilegeRepository.save({ category, name })\n }\n }\n\n console.log('[auth-base:bootstrap] Synchronization for privilege master has just done.')\n\n /*\n * Templates name privileges by (category, axis), and a category can move. A template\n * that survives a rename becomes a role that grants nothing โ the administrator picks\n * \"quality inspector\", gets a role, and no door opens. Check the names once, here, where\n * the privilege registry has just been filled.\n */\n reportUnknownTemplateGrants()\n\n /*\n * Say which axis vocabulary this installation ended up with. It cannot go in the SDL โ\n * it differs per installation, which is the whole point โ so a boot line is the honest\n * substitute, and it separates \"nobody declared, these are the defaults\" from \"someone\n * declared exactly these\".\n */\n reportDeclaredAxes()\n})\n"]}
|
|
@@ -5,6 +5,11 @@ export declare class AccessRequestQuery {
|
|
|
5
5
|
accessRequests(params: ListParam, context: ResolverContext): Promise<AccessRequestList>;
|
|
6
6
|
/** Open by design: the caller's own requests - where they see what became of them. */
|
|
7
7
|
myAccessRequests(context: ResolverContext): Promise<AccessRequest[]>;
|
|
8
|
+
/**
|
|
9
|
+
* Open by design: how many people a request would reach - the popup says it before the person sends, so asking reads
|
|
10
|
+
* as what it is. A number, never who.
|
|
11
|
+
*/
|
|
12
|
+
accessRequestDeciderCount(context: ResolverContext): Promise<number>;
|
|
8
13
|
rolesGranting(category: string, privilege: string, context: ResolverContext): Promise<AccessRoleCandidate[]>;
|
|
9
14
|
/** The same review the decision runs - the list screen and the AI draft card show this (ADR-0103 decision 4). */
|
|
10
15
|
reviewAccessDecision(decision: AccessDecisionInput, context: ResolverContext): Promise<AccessDecisionIssue[]>;
|
|
@@ -35,6 +35,14 @@ let AccessRequestQuery = class AccessRequestQuery {
|
|
|
35
35
|
take: 100
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Open by design: how many people a request would reach - the popup says it before the person sends, so asking reads
|
|
40
|
+
* as what it is. A number, never who.
|
|
41
|
+
*/
|
|
42
|
+
async accessRequestDeciderCount(context) {
|
|
43
|
+
const { domain, user } = context.state;
|
|
44
|
+
return (await (0, access_request_js_1.findDeciderIds)(domain, user.id)).length;
|
|
45
|
+
}
|
|
38
46
|
async rolesGranting(category, privilege, context) {
|
|
39
47
|
return (await (0, access_request_js_1.rolesGranting)(category, privilege, context.state.domain)).map(({ id, name, privilegeCount }) => ({ id, name, privilegeCount }));
|
|
40
48
|
}
|
|
@@ -60,6 +68,13 @@ tslib_1.__decorate([
|
|
|
60
68
|
tslib_1.__metadata("design:paramtypes", [Object]),
|
|
61
69
|
tslib_1.__metadata("design:returntype", Promise)
|
|
62
70
|
], AccessRequestQuery.prototype, "myAccessRequests", null);
|
|
71
|
+
tslib_1.__decorate([
|
|
72
|
+
(0, type_graphql_1.Query)(returns => type_graphql_1.Int, { description: 'To count the people who would be told of an access request from the caller in this domain.' }),
|
|
73
|
+
tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
|
|
74
|
+
tslib_1.__metadata("design:type", Function),
|
|
75
|
+
tslib_1.__metadata("design:paramtypes", [Object]),
|
|
76
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
77
|
+
], AccessRequestQuery.prototype, "accessRequestDeciderCount", null);
|
|
63
78
|
tslib_1.__decorate([
|
|
64
79
|
(0, type_graphql_1.Directive)(DECIDE),
|
|
65
80
|
(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.' }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-request-query.js","sourceRoot":"","sources":["../../../server/service/access-request/access-request-query.ts"],"names":[],"mappings":";;;;AAAA,+
|
|
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, ListParam } from '@things-factory/shell'\n\nimport { findDeciderIds, 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 /**\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 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("./
|
|
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("./granted-role/granted-role.js").GrantedRole | 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.accessRequestDeciderCount': 'how many people an access request from the caller would reach - a number, never who',
|
|
23
24
|
'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)',
|
|
24
25
|
'Mutation.withdrawAccessRequest': "withdraws the caller's own pending request and nobody else's"
|
|
25
26
|
}
|
|
@@ -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,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 '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,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"]}
|
|
@@ -185,7 +185,7 @@ const privilegeDirectiveResolver = (schema, options = {}) => {
|
|
|
185
185
|
*
|
|
186
186
|
* ๋ฌธ์ฅ ๋ง๋ค๊ธฐ๋ `privilege-rejection.ts` ๊ฐ ์์ ํ๋ค.
|
|
187
187
|
*/
|
|
188
|
-
throw (0, privilege_rejection_js_1.privilegeRejection)(context, category, privilege, judgement.because);
|
|
188
|
+
throw (0, privilege_rejection_js_1.privilegeRejection)(context, category, privilege, judgement.because, info?.operation?.operation);
|
|
189
189
|
};
|
|
190
190
|
/*
|
|
191
191
|
* โโ A subscription is opened by `subscribe`, not `resolve` โโโโโโโโโโโโ
|
|
@@ -199,7 +199,7 @@ const privilegeDirectiveResolver = (schema, options = {}) => {
|
|
|
199
199
|
const { domain, user, unsafeIP, prohibitedPrivileges } = context.state;
|
|
200
200
|
const judgement = await (0, check_permission_js_1.judgePermission)({ category, privilege, owner: domainOwnerGranted, super: superUserGranted }, user, domain, unsafeIP, prohibitedPrivileges);
|
|
201
201
|
if (judgement.permitted === false) {
|
|
202
|
-
throw (0, privilege_rejection_js_1.privilegeRejection)(context, category, privilege, judgement.because);
|
|
202
|
+
throw (0, privilege_rejection_js_1.privilegeRejection)(context, category, privilege, judgement.because, 'subscription');
|
|
203
203
|
}
|
|
204
204
|
return await subscribe.call(this, source, args, context, info);
|
|
205
205
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"privilege-directive.js","sourceRoot":"","sources":["../../../server/service/privilege/privilege-directive.ts"],"names":[],"mappings":"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","sourcesContent":["import { defaultFieldResolver, GraphQLError, GraphQLSchema } from 'graphql'\nimport gql from 'graphql-tag'\n\nimport { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'\nimport { logger } from '@things-factory/env'\n\nimport { judgePermission } from '../../utils/check-permission.js'\nimport { declaredAxes, isDeclaredAxis } from './privilege-axis.js'\nimport { privilegeRejection } from './privilege-rejection.js'\nimport {\n coverageOf,\n DeclaringModule,\n enforcementMode,\n EnforcementMode,\n judgeUndeclared,\n modulesFromLoader\n} from './open-field-rule.js'\n\nprocess['PRIVILEGES'] = {}\n\n/*\n * โโ What each field asks for, by `Type.field` โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n * `PRIVILEGES` above is the list of pairs this installation declares; it cannot answer \"what\n * does `Query.jobOrders` ask for\". Something that reaches a door by another entrance has to ask\n * exactly what the door asks โ an AI tool is one (operato-twin ADR-0071 decision 2, ADR-0072\n * decision 5). Without this map each caller copied the pair by hand, and a copy drifts the day\n * the door's declaration changes.\n *\n * Three answers, and they are not the same:\n * - not in the map the schema has no such root field. A caller that named it has a typo,\n * and reading that as \"open\" is how a gate opens without an error.\n * - `null` the field exists and declares nothing. It is open.\n * - a gate what `checkPermission` is asked at that field.\n *\n * Every root field is recorded (Query / Mutation / Subscription) so the first two can be told\n * apart; a field of any other type is recorded only when it carries the directive.\n * Kept on `process` for the same reason `PRIVILEGES` is.\n */\nprocess['PRIVILEGE_OF_FIELD'] = {}\n\n/** What `checkPermission` is asked at a field โ the same four things the directive passes it. */\nexport interface FieldGate {\n category?: string\n privilege?: string\n owner: boolean\n super: boolean\n}\n\n/**\n * What the field `Type.field` asks for. Valid once the schema has been built.\n *\n * `known: false` means the schema has no such root field โ treat it as a mistake, never as open.\n */\nexport function privilegeOfField(typeName: string, fieldName: string): { known: false } | { known: true; gate: FieldGate | null } {\n const map = process['PRIVILEGE_OF_FIELD'] as Record<string, FieldGate | null>\n const where = `${typeName}.${fieldName}`\n return where in map ? { known: true, gate: map[where] } : { known: false }\n}\n\nconst DIRECTIVE = 'privilege'\n\n/**\n * โ **๋ค์์ ๋ชฉ๋ก ์ธ์๋ฅผ ๋ถ์ด๋ ์ฌ๋์๊ฒ.**\n *\n * ์ฌ๊ธฐ `legacy` ์ธ์๊ฐ ์์๋ค โ ๊ถํ ์ด๋ฆ์ ์ฎ๊ธฐ๋ ๋์ ์ ์ด๋ฆ๋ ๋ฐ์ ์ฃผ๋ ๋ณ์นญ์ด์๋ค.\n * **๊ฑท์ด๋๋ค.** ์ด ์ ์ฅ์๋ ๋๊ฐ์กฐ ์ค์ด๊ณ **ํธํ ๊ณ์ธต์ ๋ผ์ฐ์ง ์๋๋ค**: ์ด๋ฆ์ ๋ฐ๊พธ๋ฉด ๋ฐ๊พผ\n * ๊ฒ์ด๊ณ , ๋ถ์ฌ๋ ๊ด๋ฆฌ์๊ฐ ๋ค์ ๋๋๋ค. ๋ณ์นญ์ ใํ๋์ใ์ด ์์์ด ๋๋ ๊ธธ์ด์๋ค.\n *\n * ๊ณง ํ์ํด์ง๋ ๋ชฉ๋ก ์ธ์๋ **AND** ๋ค(`docs/design/auth-rebuild.md` ยง1-(3)): ์ธ๋ถ์์\n * ๋น๊ฒจ์ค๋ฉด์ ๋จ์ ๋ฐ์ดํฐ๋ฅผ ์ํ๋ ํธ์ถ์ `execute` **์** ๊ทธ ๋ชจ๋์ `mutation` ์ **๋ ๋ค**\n * ์๊ตฌํด์ผ ํ๋ค.\n *\n * ๊ทธ๊ฒ์ `privileges: [...]` ์ฒ๋ผ ์ค๋ฆฝ์ ์ผ๋ก ์ด๋ฆ ์ง์ผ๋ฉด ๋ค์ ์ฌ๋์ด ใ์ด ์ค ํ๋ใ๋ก ์ฝ๊ณ ,\n * ๊ทธ๋ ๊ฒ ์ฐ๋ ๋ ๊ฒ์ดํธ๊ฐ **์ค๋ฅ ์์ด** ์ด๋ฆฐ๋ค. `requiresAll: [...]` ์ฒ๋ผ **์ด๋ฆ์ด AND ๋ผ๊ณ \n * ๋งํ๊ฒ** ํ๋ค.\n */\nexport const privilegeDirectiveTypeDefs = gql`\n directive @privilege(\n category: String\n privilege: String\n domainOwnerGranted: Boolean\n superUserGranted: Boolean\n ) on FIELD_DEFINITION\n`\n/**\n * The server calls this with the merged schema only. `modules` and `mode` are for tests, which\n * have no module walk of their own (`orderedModuleNames` is empty under jest) โ see `open-fields.ts`.\n */\nexport const privilegeDirectiveResolver = (\n schema: GraphQLSchema,\n options: { modules?: DeclaringModule[]; mode?: EnforcementMode } = {}\n) => {\n const coverage = coverageOf(options.modules ?? modulesFromLoader())\n const mode = options.mode ?? enforcementMode()\n const unlisted: string[] = []\n\n const mapped = mapSchema(schema, {\n [MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {\n const privilegeDirective = getDirective(schema, fieldConfig, DIRECTIVE)?.[0]\n\n /* A root field with no directive is recorded as open, so \"open\" and \"no such field\" differ. */\n if (!privilegeDirective && isRootType(schema, typeName)) {\n const where = `${typeName}.${fieldName}`\n\n process['PRIVILEGE_OF_FIELD'][where] = null\n\n /*\n * โโ The field that arrived without saying anything โโโโโโโโโโโโโโโโโโโ\n * Its module opted in (declared `openFields`) and names it in neither list. That is how\n * `deleteAppBinding` went in, and the whole point of ยง8 step 1 is that it cannot happen\n * quietly any more. A field of a module that has not opted in is left exactly as before.\n */\n if (judgeUndeclared(where, coverage) === 'unlisted') {\n if (mode === 'boot') {\n unlisted.push(where)\n } else {\n refuseUnlisted(fieldConfig, where)\n }\n }\n }\n\n if (privilegeDirective) {\n const { resolve = defaultFieldResolver, args } = fieldConfig\n\n if (!args) {\n throw new Error(`Unexpected Error. args should be defined in @privilege directive for field ${fieldName}.`)\n }\n\n const { domainOwnerGranted, superUserGranted, category, privilege } = privilegeDirective\n const where = `${typeName}.${fieldName}`\n\n /*\n * A declaration has to name something.\n *\n * `category` and `privilege` are one thing, not two: together they name a row in the\n * privilege table, and either alone names no row, so `User.hasPrivilege` is never\n * reached and the field falls back on ownership flags it did not set.\n */\n if (!category !== !privilege) {\n throw new Error(\n `@privilege on ${where} gives ${category ? 'a category with no privilege' : 'a privilege with no category'}. ` +\n `The pair names the privilege to look for; one half names none.`\n )\n }\n\n /*\n * `@privilege()` with nothing in it reads like a gate and guards nothing. Until\n * 2026-09-15 what it did depended on where the caller sat: `checkPermission` answered\n * false from a safe address and refused everyone, true from an unsafe one and let\n * everyone through. Both are false now, so the shape is merely useless rather than\n * dangerous โ and a field that is open should say so by carrying no directive, not by\n * carrying an empty one.\n *\n * It is also the shape someone reaches for wanting \"being signed in is enough\". That\n * is a real thing to want and this directive does not express it; the answer settled\n * on 2026-09-15 is a category for the published side of a module, `<module>-published`.\n *\n * Throwing stops the boot, which is what ยง6-2 of docs/design/auth-rebuild.md settled\n * for a resolver that declares nothing. A decorator that declares nothing is that\n * same resolver wearing one.\n */\n if (!category && !privilege && !domainOwnerGranted && !superUserGranted) {\n throw new Error(\n `@privilege on ${where} declares nothing. ` +\n `Name (category, privilege) for a privilege gate, or domainOwnerGranted / superUserGranted for an ` +\n `ownership gate. A field open to everyone carries no directive at all.`\n )\n }\n\n /*\n * The axis has to be one this installation declared.\n *\n * Without this, `privilege: \"mutaiton\"` registered a pair nobody would ever be granted\n * โ a gate shut forever, with no error to read. The list is not the framework's: see\n * `privilege-axis.ts` for why, and for the fact that it must be declared at module\n * import time rather than on a bootstrap hook.\n */\n if (privilege && !isDeclaredAxis(privilege)) {\n throw new Error(\n `@privilege on ${where} uses axis \"${privilege}\", which this installation does not declare. ` +\n `Declared axes: ${[...declaredAxes()].sort().join(', ')}. ` +\n `Fix the spelling, or call declareAxes(['${privilege}']) at the top level of the module's ` +\n `server/index.ts โ a bootstrap hook runs after the schema is built and is too late.`\n )\n }\n\n if (category && privilege) {\n process['PRIVILEGES'][`${category} ${privilege}`] = [category, privilege]\n }\n\n /* After every refusal above, so a declaration that names nothing never lands here. */\n process['PRIVILEGE_OF_FIELD'][where] = {\n category: category || undefined,\n privilege: privilege || undefined,\n owner: !!domainOwnerGranted,\n super: !!superUserGranted\n } satisfies FieldGate\n\n // ํ๋์ ๊ธฐ์กด description ๊ฐ์ ธ์ค๊ธฐ\n const existingDescription = fieldConfig.description || ''\n\n // ๊ถํ ์ ๋ณด๋ฅผ ํฌํจํ ์๋ก์ด description ์์ฑ\n const privilegeDescription =\n `\\n\\n๐ Requires privilege: ${category}:${privilege}` +\n (domainOwnerGranted ? ', Domain ownership' : '') +\n (superUserGranted ? ', System ownership' : '')\n\n // ๊ธฐ์กด description๊ณผ ๊ฒฐํฉ\n fieldConfig.description = `${existingDescription} ${privilegeDescription}`.trim()\n\n fieldConfig.resolve = async function (source, args, context, info) {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n const judgement = await judgePermission(\n {\n category,\n privilege,\n owner: domainOwnerGranted,\n super: superUserGranted\n },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (judgement.permitted === true) {\n return await resolve.call(this, source, args, context, info)\n }\n\n /*\n * ์ ์ด๋ ธ์ผ๋ฉด **๊ฑฐ๊ธฐ์ ๋์ด๋ค.**\n *\n * ์ฌ๊ธฐ ์ ์ด๋ฆ์ผ๋ก ํ ๋ฒ ๋ ๋ณด๋ ๊ธธ์ด ์์๋ค. ๊ทธ๊ฒ์ ๊ฑท์ด๋๋ค โ ํธํ ๊ณ์ธต์ ๋ผ์ฐ์ง\n * ์๋๋ค. ์ด๋ฆ์ ์ฎ๊ธฐ๋ฉด ๋ถ์ฌ๋ ์ฎ๊ธฐ๋ ๊ฒ์ด๊ณ , ๊ทธ ์ผ์ ๊ด๋ฆฌ์๊ฐ ์ญํ ํ๋ฉด์์ ํ๋ค.\n *\n * ๋ฌธ์ฅ ๋ง๋ค๊ธฐ๋ `privilege-rejection.ts` ๊ฐ ์์ ํ๋ค.\n */\n throw privilegeRejection(context, category, privilege, judgement.because)\n }\n\n /*\n * โโ A subscription is opened by `subscribe`, not `resolve` โโโโโโโโโโโโ\n * Wrapping only `resolve` left a declared subscription connectable by anyone: the stream\n * opened and every event then failed. It leaked nothing, and it refused nothing either.\n * The same question is asked where the stream starts.\n */\n if (fieldConfig.subscribe) {\n const subscribe = fieldConfig.subscribe\n\n fieldConfig.subscribe = async function (source, args, context, info) {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n const judgement = await judgePermission(\n { category, privilege, owner: domainOwnerGranted, super: superUserGranted },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (judgement.permitted === false) {\n throw privilegeRejection(context, category, privilege, judgement.because)\n }\n\n return await subscribe.call(this, source, args, context, info)\n }\n }\n\n return fieldConfig\n }\n }\n })\n\n /*\n * Every unlisted field at once, rather than the first one. The person who meets this has usually\n * added several resolvers together, and fixing them one boot at a time is how a list gets\n * abandoned half done.\n */\n if (unlisted.length) {\n throw new Error(\n `${unlisted.length} field(s) declare no @privilege and are not listed as open by their module:\\n` +\n unlisted.map(where => ` ${where}`).join('\\n') +\n `\\n\\nFor each, either give it @privilege(category: โฆ, privilege: โฆ), or name it in the module's ` +\n `schema.openFields โ under byDesign with the reason it is open, or under notYetDeclared if it ` +\n `is waiting to be closed. See docs/design/auth-rebuild.md ยง8.`\n )\n }\n\n return mapped\n}\n\n/**\n * Production's answer to an unlisted field: that field refuses, and says so in the log once.\n *\n * The server stays up. What cannot happen is the field answering โ it arrived without saying who\n * may call it, and the answer to that is nobody until somebody says.\n */\nconst reported = new Set<string>()\n\nfunction refuseUnlisted(fieldConfig: any, where: string) {\n const refuse = () => {\n if (!reported.has(where)) {\n reported.add(where)\n logger.error(\n `${where} declares no @privilege and is not listed in its module's schema.openFields โ refusing it.`\n )\n }\n\n return new GraphQLError(`${where} is not available: it declares no privilege.`, {\n extensions: { code: 'FORBIDDEN', refusal: { code: 'field-declares-no-privilege', params: { field: where } } }\n })\n }\n\n fieldConfig.resolve = async () => {\n throw refuse()\n }\n\n if (fieldConfig.subscribe) {\n fieldConfig.subscribe = async () => {\n throw refuse()\n }\n }\n}\n\nfunction isRootType(schema: GraphQLSchema, typeName: string): boolean {\n return [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()].some(\n root => root?.name === typeName\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"privilege-directive.js","sourceRoot":"","sources":["../../../server/service/privilege/privilege-directive.ts"],"names":[],"mappings":"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","sourcesContent":["import { defaultFieldResolver, GraphQLError, GraphQLSchema } from 'graphql'\nimport gql from 'graphql-tag'\n\nimport { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'\nimport { logger } from '@things-factory/env'\n\nimport { judgePermission } from '../../utils/check-permission.js'\nimport { declaredAxes, isDeclaredAxis } from './privilege-axis.js'\nimport { privilegeRejection } from './privilege-rejection.js'\nimport {\n coverageOf,\n DeclaringModule,\n enforcementMode,\n EnforcementMode,\n judgeUndeclared,\n modulesFromLoader\n} from './open-field-rule.js'\n\nprocess['PRIVILEGES'] = {}\n\n/*\n * โโ What each field asks for, by `Type.field` โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n * `PRIVILEGES` above is the list of pairs this installation declares; it cannot answer \"what\n * does `Query.jobOrders` ask for\". Something that reaches a door by another entrance has to ask\n * exactly what the door asks โ an AI tool is one (operato-twin ADR-0071 decision 2, ADR-0072\n * decision 5). Without this map each caller copied the pair by hand, and a copy drifts the day\n * the door's declaration changes.\n *\n * Three answers, and they are not the same:\n * - not in the map the schema has no such root field. A caller that named it has a typo,\n * and reading that as \"open\" is how a gate opens without an error.\n * - `null` the field exists and declares nothing. It is open.\n * - a gate what `checkPermission` is asked at that field.\n *\n * Every root field is recorded (Query / Mutation / Subscription) so the first two can be told\n * apart; a field of any other type is recorded only when it carries the directive.\n * Kept on `process` for the same reason `PRIVILEGES` is.\n */\nprocess['PRIVILEGE_OF_FIELD'] = {}\n\n/** What `checkPermission` is asked at a field โ the same four things the directive passes it. */\nexport interface FieldGate {\n category?: string\n privilege?: string\n owner: boolean\n super: boolean\n}\n\n/**\n * What the field `Type.field` asks for. Valid once the schema has been built.\n *\n * `known: false` means the schema has no such root field โ treat it as a mistake, never as open.\n */\nexport function privilegeOfField(typeName: string, fieldName: string): { known: false } | { known: true; gate: FieldGate | null } {\n const map = process['PRIVILEGE_OF_FIELD'] as Record<string, FieldGate | null>\n const where = `${typeName}.${fieldName}`\n return where in map ? { known: true, gate: map[where] } : { known: false }\n}\n\nconst DIRECTIVE = 'privilege'\n\n/**\n * โ **๋ค์์ ๋ชฉ๋ก ์ธ์๋ฅผ ๋ถ์ด๋ ์ฌ๋์๊ฒ.**\n *\n * ์ฌ๊ธฐ `legacy` ์ธ์๊ฐ ์์๋ค โ ๊ถํ ์ด๋ฆ์ ์ฎ๊ธฐ๋ ๋์ ์ ์ด๋ฆ๋ ๋ฐ์ ์ฃผ๋ ๋ณ์นญ์ด์๋ค.\n * **๊ฑท์ด๋๋ค.** ์ด ์ ์ฅ์๋ ๋๊ฐ์กฐ ์ค์ด๊ณ **ํธํ ๊ณ์ธต์ ๋ผ์ฐ์ง ์๋๋ค**: ์ด๋ฆ์ ๋ฐ๊พธ๋ฉด ๋ฐ๊พผ\n * ๊ฒ์ด๊ณ , ๋ถ์ฌ๋ ๊ด๋ฆฌ์๊ฐ ๋ค์ ๋๋๋ค. ๋ณ์นญ์ ใํ๋์ใ์ด ์์์ด ๋๋ ๊ธธ์ด์๋ค.\n *\n * ๊ณง ํ์ํด์ง๋ ๋ชฉ๋ก ์ธ์๋ **AND** ๋ค(`docs/design/auth-rebuild.md` ยง1-(3)): ์ธ๋ถ์์\n * ๋น๊ฒจ์ค๋ฉด์ ๋จ์ ๋ฐ์ดํฐ๋ฅผ ์ํ๋ ํธ์ถ์ `execute` **์** ๊ทธ ๋ชจ๋์ `mutation` ์ **๋ ๋ค**\n * ์๊ตฌํด์ผ ํ๋ค.\n *\n * ๊ทธ๊ฒ์ `privileges: [...]` ์ฒ๋ผ ์ค๋ฆฝ์ ์ผ๋ก ์ด๋ฆ ์ง์ผ๋ฉด ๋ค์ ์ฌ๋์ด ใ์ด ์ค ํ๋ใ๋ก ์ฝ๊ณ ,\n * ๊ทธ๋ ๊ฒ ์ฐ๋ ๋ ๊ฒ์ดํธ๊ฐ **์ค๋ฅ ์์ด** ์ด๋ฆฐ๋ค. `requiresAll: [...]` ์ฒ๋ผ **์ด๋ฆ์ด AND ๋ผ๊ณ \n * ๋งํ๊ฒ** ํ๋ค.\n */\nexport const privilegeDirectiveTypeDefs = gql`\n directive @privilege(\n category: String\n privilege: String\n domainOwnerGranted: Boolean\n superUserGranted: Boolean\n ) on FIELD_DEFINITION\n`\n/**\n * The server calls this with the merged schema only. `modules` and `mode` are for tests, which\n * have no module walk of their own (`orderedModuleNames` is empty under jest) โ see `open-fields.ts`.\n */\nexport const privilegeDirectiveResolver = (\n schema: GraphQLSchema,\n options: { modules?: DeclaringModule[]; mode?: EnforcementMode } = {}\n) => {\n const coverage = coverageOf(options.modules ?? modulesFromLoader())\n const mode = options.mode ?? enforcementMode()\n const unlisted: string[] = []\n\n const mapped = mapSchema(schema, {\n [MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {\n const privilegeDirective = getDirective(schema, fieldConfig, DIRECTIVE)?.[0]\n\n /* A root field with no directive is recorded as open, so \"open\" and \"no such field\" differ. */\n if (!privilegeDirective && isRootType(schema, typeName)) {\n const where = `${typeName}.${fieldName}`\n\n process['PRIVILEGE_OF_FIELD'][where] = null\n\n /*\n * โโ The field that arrived without saying anything โโโโโโโโโโโโโโโโโโโ\n * Its module opted in (declared `openFields`) and names it in neither list. That is how\n * `deleteAppBinding` went in, and the whole point of ยง8 step 1 is that it cannot happen\n * quietly any more. A field of a module that has not opted in is left exactly as before.\n */\n if (judgeUndeclared(where, coverage) === 'unlisted') {\n if (mode === 'boot') {\n unlisted.push(where)\n } else {\n refuseUnlisted(fieldConfig, where)\n }\n }\n }\n\n if (privilegeDirective) {\n const { resolve = defaultFieldResolver, args } = fieldConfig\n\n if (!args) {\n throw new Error(`Unexpected Error. args should be defined in @privilege directive for field ${fieldName}.`)\n }\n\n const { domainOwnerGranted, superUserGranted, category, privilege } = privilegeDirective\n const where = `${typeName}.${fieldName}`\n\n /*\n * A declaration has to name something.\n *\n * `category` and `privilege` are one thing, not two: together they name a row in the\n * privilege table, and either alone names no row, so `User.hasPrivilege` is never\n * reached and the field falls back on ownership flags it did not set.\n */\n if (!category !== !privilege) {\n throw new Error(\n `@privilege on ${where} gives ${category ? 'a category with no privilege' : 'a privilege with no category'}. ` +\n `The pair names the privilege to look for; one half names none.`\n )\n }\n\n /*\n * `@privilege()` with nothing in it reads like a gate and guards nothing. Until\n * 2026-09-15 what it did depended on where the caller sat: `checkPermission` answered\n * false from a safe address and refused everyone, true from an unsafe one and let\n * everyone through. Both are false now, so the shape is merely useless rather than\n * dangerous โ and a field that is open should say so by carrying no directive, not by\n * carrying an empty one.\n *\n * It is also the shape someone reaches for wanting \"being signed in is enough\". That\n * is a real thing to want and this directive does not express it; the answer settled\n * on 2026-09-15 is a category for the published side of a module, `<module>-published`.\n *\n * Throwing stops the boot, which is what ยง6-2 of docs/design/auth-rebuild.md settled\n * for a resolver that declares nothing. A decorator that declares nothing is that\n * same resolver wearing one.\n */\n if (!category && !privilege && !domainOwnerGranted && !superUserGranted) {\n throw new Error(\n `@privilege on ${where} declares nothing. ` +\n `Name (category, privilege) for a privilege gate, or domainOwnerGranted / superUserGranted for an ` +\n `ownership gate. A field open to everyone carries no directive at all.`\n )\n }\n\n /*\n * The axis has to be one this installation declared.\n *\n * Without this, `privilege: \"mutaiton\"` registered a pair nobody would ever be granted\n * โ a gate shut forever, with no error to read. The list is not the framework's: see\n * `privilege-axis.ts` for why, and for the fact that it must be declared at module\n * import time rather than on a bootstrap hook.\n */\n if (privilege && !isDeclaredAxis(privilege)) {\n throw new Error(\n `@privilege on ${where} uses axis \"${privilege}\", which this installation does not declare. ` +\n `Declared axes: ${[...declaredAxes()].sort().join(', ')}. ` +\n `Fix the spelling, or call declareAxes(['${privilege}']) at the top level of the module's ` +\n `server/index.ts โ a bootstrap hook runs after the schema is built and is too late.`\n )\n }\n\n if (category && privilege) {\n process['PRIVILEGES'][`${category} ${privilege}`] = [category, privilege]\n }\n\n /* After every refusal above, so a declaration that names nothing never lands here. */\n process['PRIVILEGE_OF_FIELD'][where] = {\n category: category || undefined,\n privilege: privilege || undefined,\n owner: !!domainOwnerGranted,\n super: !!superUserGranted\n } satisfies FieldGate\n\n // ํ๋์ ๊ธฐ์กด description ๊ฐ์ ธ์ค๊ธฐ\n const existingDescription = fieldConfig.description || ''\n\n // ๊ถํ ์ ๋ณด๋ฅผ ํฌํจํ ์๋ก์ด description ์์ฑ\n const privilegeDescription =\n `\\n\\n๐ Requires privilege: ${category}:${privilege}` +\n (domainOwnerGranted ? ', Domain ownership' : '') +\n (superUserGranted ? ', System ownership' : '')\n\n // ๊ธฐ์กด description๊ณผ ๊ฒฐํฉ\n fieldConfig.description = `${existingDescription} ${privilegeDescription}`.trim()\n\n fieldConfig.resolve = async function (source, args, context, info) {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n const judgement = await judgePermission(\n {\n category,\n privilege,\n owner: domainOwnerGranted,\n super: superUserGranted\n },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (judgement.permitted === true) {\n return await resolve.call(this, source, args, context, info)\n }\n\n /*\n * ์ ์ด๋ ธ์ผ๋ฉด **๊ฑฐ๊ธฐ์ ๋์ด๋ค.**\n *\n * ์ฌ๊ธฐ ์ ์ด๋ฆ์ผ๋ก ํ ๋ฒ ๋ ๋ณด๋ ๊ธธ์ด ์์๋ค. ๊ทธ๊ฒ์ ๊ฑท์ด๋๋ค โ ํธํ ๊ณ์ธต์ ๋ผ์ฐ์ง\n * ์๋๋ค. ์ด๋ฆ์ ์ฎ๊ธฐ๋ฉด ๋ถ์ฌ๋ ์ฎ๊ธฐ๋ ๊ฒ์ด๊ณ , ๊ทธ ์ผ์ ๊ด๋ฆฌ์๊ฐ ์ญํ ํ๋ฉด์์ ํ๋ค.\n *\n * ๋ฌธ์ฅ ๋ง๋ค๊ธฐ๋ `privilege-rejection.ts` ๊ฐ ์์ ํ๋ค.\n */\n throw privilegeRejection(context, category, privilege, judgement.because, info?.operation?.operation)\n }\n\n /*\n * โโ A subscription is opened by `subscribe`, not `resolve` โโโโโโโโโโโโ\n * Wrapping only `resolve` left a declared subscription connectable by anyone: the stream\n * opened and every event then failed. It leaked nothing, and it refused nothing either.\n * The same question is asked where the stream starts.\n */\n if (fieldConfig.subscribe) {\n const subscribe = fieldConfig.subscribe\n\n fieldConfig.subscribe = async function (source, args, context, info) {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n const judgement = await judgePermission(\n { category, privilege, owner: domainOwnerGranted, super: superUserGranted },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (judgement.permitted === false) {\n throw privilegeRejection(context, category, privilege, judgement.because, 'subscription')\n }\n\n return await subscribe.call(this, source, args, context, info)\n }\n }\n\n return fieldConfig\n }\n }\n })\n\n /*\n * Every unlisted field at once, rather than the first one. The person who meets this has usually\n * added several resolvers together, and fixing them one boot at a time is how a list gets\n * abandoned half done.\n */\n if (unlisted.length) {\n throw new Error(\n `${unlisted.length} field(s) declare no @privilege and are not listed as open by their module:\\n` +\n unlisted.map(where => ` ${where}`).join('\\n') +\n `\\n\\nFor each, either give it @privilege(category: โฆ, privilege: โฆ), or name it in the module's ` +\n `schema.openFields โ under byDesign with the reason it is open, or under notYetDeclared if it ` +\n `is waiting to be closed. See docs/design/auth-rebuild.md ยง8.`\n )\n }\n\n return mapped\n}\n\n/**\n * Production's answer to an unlisted field: that field refuses, and says so in the log once.\n *\n * The server stays up. What cannot happen is the field answering โ it arrived without saying who\n * may call it, and the answer to that is nobody until somebody says.\n */\nconst reported = new Set<string>()\n\nfunction refuseUnlisted(fieldConfig: any, where: string) {\n const refuse = () => {\n if (!reported.has(where)) {\n reported.add(where)\n logger.error(\n `${where} declares no @privilege and is not listed in its module's schema.openFields โ refusing it.`\n )\n }\n\n return new GraphQLError(`${where} is not available: it declares no privilege.`, {\n extensions: { code: 'FORBIDDEN', refusal: { code: 'field-declares-no-privilege', params: { field: where } } }\n })\n }\n\n fieldConfig.resolve = async () => {\n throw refuse()\n }\n\n if (fieldConfig.subscribe) {\n fieldConfig.subscribe = async () => {\n throw refuse()\n }\n }\n}\n\nfunction isRootType(schema: GraphQLSchema, typeName: string): boolean {\n return [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()].some(\n root => root?.name === typeName\n )\n}\n"]}
|
|
@@ -18,4 +18,6 @@ import type { RefusalCause } from '../../utils/check-permission.js';
|
|
|
18
18
|
*
|
|
19
19
|
* @param context koa ์ปจํ
์คํธ(์์ฒญ์ ์ธ์ด๋ฅผ ์๋ `t` ๋ฅผ ๊ฐ์ง๋ค). ์์ด๋ ๋์ํ๋ค.
|
|
20
20
|
*/
|
|
21
|
-
|
|
21
|
+
/** Which kind of operation was refused - a screen offers an access request only for a change the person made. */
|
|
22
|
+
export type RefusedOperation = 'query' | 'mutation' | 'subscription';
|
|
23
|
+
export declare function privilegeRejection(context: any, category?: string, privilege?: string, because?: RefusalCause, operation?: RefusedOperation): GraphQLError;
|