@things-factory/auth-base 10.1.198 → 10.1.203

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist-client/tsconfig.tsbuildinfo +1 -1
  2. package/dist-server/ai-entry/access-assistant.js +4 -3
  3. package/dist-server/ai-entry/access-assistant.js.map +1 -1
  4. package/dist-server/controllers/access-request.d.ts +8 -2
  5. package/dist-server/controllers/access-request.js +33 -6
  6. package/dist-server/controllers/access-request.js.map +1 -1
  7. package/dist-server/service/access-request/access-request-query.d.ts +7 -2
  8. package/dist-server/service/access-request/access-request-query.js +25 -4
  9. package/dist-server/service/access-request/access-request-query.js.map +1 -1
  10. package/dist-server/service/access-request/access-request-types.d.ts +7 -0
  11. package/dist-server/service/access-request/access-request-types.js +27 -1
  12. package/dist-server/service/access-request/access-request-types.js.map +1 -1
  13. package/dist-server/service/access-request/access-request.d.ts +1 -0
  14. package/dist-server/service/access-request/access-request.js +5 -0
  15. package/dist-server/service/access-request/access-request.js.map +1 -1
  16. package/dist-server/service/access-request/index.d.ts +1 -1
  17. package/dist-server/service/audit/index.d.ts +1 -1
  18. package/dist-server/service/index.d.ts +1 -1
  19. package/dist-server/service/open-fields.js +1 -0
  20. package/dist-server/service/open-fields.js.map +1 -1
  21. package/dist-server/service/role-template/index.d.ts +1 -1
  22. package/dist-server/service/role-template/role-task.d.ts +7 -0
  23. package/dist-server/service/role-template/role-task.js +16 -0
  24. package/dist-server/service/role-template/role-task.js.map +1 -1
  25. package/dist-server/tsconfig.tsbuildinfo +1 -1
  26. package/dist-server/utils/access-request-review.d.ts +12 -2
  27. package/dist-server/utils/access-request-review.js +13 -3
  28. package/dist-server/utils/access-request-review.js.map +1 -1
  29. package/package.json +2 -2
  30. package/tests/access-assistant-db.test.ts +1 -1
  31. package/tests/access-request-db.test.ts +71 -0
  32. package/tests/access-request-review.test.ts +30 -0
  33. package/translations/en.json +2 -1
  34. package/translations/es.json +2 -1
  35. package/translations/ja.json +2 -1
  36. package/translations/ko.json +2 -1
  37. package/translations/ms.json +2 -1
  38. package/translations/zh.json +2 -1
  39. package/dist-server/service/login-history/index.d.ts +0 -4
  40. package/dist-server/service/login-history/index.js +0 -8
  41. package/dist-server/service/login-history/index.js.map +0 -1
  42. package/dist-server/service/login-history/login-history-query.d.ts +0 -10
  43. package/dist-server/service/login-history/login-history-query.js +0 -80
  44. package/dist-server/service/login-history/login-history-query.js.map +0 -1
  45. package/dist-server/service/login-history/login-history-type.d.ts +0 -5
  46. package/dist-server/service/login-history/login-history-type.js +0 -21
  47. package/dist-server/service/login-history/login-history-type.js.map +0 -1
  48. package/dist-server/service/login-history/login-history.d.ts +0 -12
  49. package/dist-server/service/login-history/login-history.js +0 -58
  50. package/dist-server/service/login-history/login-history.js.map +0 -1
@@ -48,6 +48,13 @@ export declare function mergeGrants(lists: RoleTemplateGrant[][]): RoleTemplateG
48
48
  export declare function privilegeKey(category: string, axis: string): string;
49
49
  /** A task's privileges this installation does not declare, as `category:axis` — a definition error of the task. */
50
50
  export declare function undeclaredTaskGrants(task: RoleTask, declared?: Record<string, unknown>): string[];
51
+ /**
52
+ * The tasks that hold one privilege, fewest privileges first, ties by id - what a person refused that privilege may
53
+ * ask for (ADR-0103 decision 9). The same order wherever a task is offered for a privilege.
54
+ */
55
+ export declare function tasksHolding(category: string, axis: string, tasks?: RoleTask[]): RoleTask[];
56
+ /** A task's privileges this installation declares, as `category axis` keys. */
57
+ export declare function declaredTaskKeys(task: RoleTask, declared?: Record<string, unknown>): string[];
51
58
  export type RoleTaskStatus = 'all' | 'some' | 'none' | 'broken';
52
59
  export type RoleTaskCoverage = {
53
60
  taskId: string;
@@ -7,6 +7,8 @@ exports.roleTaskModule = roleTaskModule;
7
7
  exports.mergeGrants = mergeGrants;
8
8
  exports.privilegeKey = privilegeKey;
9
9
  exports.undeclaredTaskGrants = undeclaredTaskGrants;
10
+ exports.tasksHolding = tasksHolding;
11
+ exports.declaredTaskKeys = declaredTaskKeys;
10
12
  exports.taskCoverageOf = taskCoverageOf;
11
13
  const env_1 = require("@things-factory/env");
12
14
  const REGISTRY = 'ROLE_TASKS';
@@ -78,6 +80,20 @@ function privilegeKey(category, axis) {
78
80
  function undeclaredTaskGrants(task, declared = process['PRIVILEGES'] || {}) {
79
81
  return task.grants.flatMap(grant => grant.axes.filter(axis => !declared[privilegeKey(grant.category, axis)]).map(axis => `${grant.category}:${axis}`));
80
82
  }
83
+ /**
84
+ * The tasks that hold one privilege, fewest privileges first, ties by id - what a person refused that privilege may
85
+ * ask for (ADR-0103 decision 9). The same order wherever a task is offered for a privilege.
86
+ */
87
+ function tasksHolding(category, axis, tasks = roleTasks()) {
88
+ const size = (task) => task.grants.reduce((count, grant) => count + grant.axes.length, 0);
89
+ return tasks
90
+ .filter(task => task.grants.some(grant => grant.category === category && grant.axes.includes(axis)))
91
+ .sort((a, b) => size(a) - size(b) || a.id.localeCompare(b.id));
92
+ }
93
+ /** A task's privileges this installation declares, as `category axis` keys. */
94
+ function declaredTaskKeys(task, declared = process['PRIVILEGES'] || {}) {
95
+ return task.grants.flatMap(grant => grant.axes.map(axis => privilegeKey(grant.category, axis))).filter(key => declared[key]);
96
+ }
81
97
  /**
82
98
  * **A role's privileges laid against every task** — what the role screen groups by.
83
99
  *
@@ -1 +1 @@
1
- {"version":3,"file":"role-task.js","sourceRoot":"","sources":["../../../server/service/role-template/role-task.ts"],"names":[],"mappings":"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","sourcesContent":["import { logger } from '@things-factory/env'\n\nimport type { RoleTemplateGrant } from './role-template.js'\n\n/**\n * Role tasks — a piece of work named by the module that owns it, and the privileges it takes (ADR-0139 decision 11).\n *\n * ── Why a task and not a template ────────────────────────────────────────────\n * A template is a seat in one application: who does what in an office or a plant. A module does not know the seats\n * of the applications that install it, but it does know its own work — accounting knows that writing an expenditure\n * request takes one's own requests and picking a counterparty and an account subject. So the module names the work\n * and the application builds its seats from the work (`RoleTemplate.tasks`). An application names work of its own\n * in the same form, under its own prefix.\n *\n * ── What a task is not ───────────────────────────────────────────────────────\n * - Not a narrower door. A task holds whole privileges, and each privilege opens what its doors open. A task called\n * \"read the books\" that holds `accounting:query` opens everything `accounting:query` opens; the name and the\n * description say so rather than hide it.\n * - Not stored. Nothing on a role records which tasks it was built from — the role screen compares a role's\n * privileges with the tasks, computed, the same way a template's gap is (`templateGapOf`).\n * - Not nested. A task lists privileges only; seats are where tasks are put together.\n * - Not declared twice. One piece of work has one task. Two tasks may share a privilege — picking a counterparty is\n * part of more than one piece of work — and that is not a duplicate.\n *\n * Its words live in the declaring module's translations, `task.<id>.name` and `task.<id>.description`, so a module\n * that is not installed takes its words away with it. The screen draws the id when there are none.\n */\nexport type RoleTask = {\n /** `<module>.<work>` — the declaring module's name first: `accounting.expense-authoring`. */\n id: string\n /** Privileges by category, axes listed one by one — the shape a template's grants have. */\n grants: RoleTemplateGrant[]\n}\n\nconst REGISTRY = 'ROLE_TASKS'\n\nprocess[REGISTRY] = {}\n\nfunction registry(): Record<string, RoleTask> {\n return process[REGISTRY]\n}\n\n/**\n * Declares a task. Call it from the module's server entry, beside its `@privilege` doors.\n *\n * Privileges are not checked here, for the reason templates are not: modules load before the schema is built and\n * `process['PRIVILEGES']` is filled. Boot checks them (`reportRoleDefinitionErrors`).\n */\nexport function registerRoleTask(task: RoleTask): void {\n const prefix = task?.id?.split('.')[0]\n\n if (!task?.id || !prefix || prefix === task.id) {\n throw new Error(`A role task needs an id of the form <module>.<work>, the declaring module first — \"${task?.id ?? ''}\" is not.`)\n }\n\n if (!Array.isArray(task.grants) || task.grants.length === 0) {\n throw new Error(`Role task \"${task.id}\" holds no privileges. A task is the privileges a piece of work takes; one that takes none is not a task.`)\n }\n\n for (const grant of task.grants) {\n if (!grant?.category || !Array.isArray(grant.axes) || grant.axes.length === 0) {\n throw new Error(`Role task \"${task.id}\" has a grant without a category or with no axes. Axes are always listed one by one.`)\n }\n }\n\n if (registry()[task.id]) {\n throw new Error(`Role task \"${task.id}\" is declared twice.`)\n }\n\n const grants = mergeGrants([task.grants])\n const written = task.grants.reduce((count, grant) => count + grant.axes.length, 0)\n const kept = grants.reduce((count, grant) => count + grant.axes.length, 0)\n\n if (kept !== written) {\n logger.warn(`[role-task] \"${task.id}\" names the same privilege more than once; it is held once.`)\n }\n\n registry()[task.id] = { id: task.id, grants }\n}\n\nexport function roleTasks(): RoleTask[] {\n return Object.values(registry())\n}\n\nexport function roleTaskById(id: string): RoleTask | undefined {\n return registry()[id]\n}\n\n/** The module a task is declared by — the part of its id before the first dot. */\nexport function roleTaskModule(task: RoleTask): string {\n return task.id.split('.')[0]\n}\n\n/**\n * Several lists of grants as one: one entry per category, each axis once, in the order first written.\n * A privilege held through two tasks, or through a task and a direct grant, is held once.\n */\nexport function mergeGrants(lists: RoleTemplateGrant[][]): RoleTemplateGrant[] {\n const axesOf = new Map<string, string[]>()\n\n for (const grant of lists.flat()) {\n const axes = axesOf.get(grant.category) || []\n for (const axis of grant.axes) {\n if (!axes.includes(axis)) axes.push(axis)\n }\n axesOf.set(grant.category, axes)\n }\n\n return [...axesOf.entries()].map(([category, axes]) => ({ category, axes }))\n}\n\n/** `category axis` — the key `process['PRIVILEGES']` and a role's held set use. */\nexport function privilegeKey(category: string, axis: string): string {\n return `${category} ${axis}`\n}\n\n/** A task's privileges this installation does not declare, as `category:axis` — a definition error of the task. */\nexport function undeclaredTaskGrants(task: RoleTask, declared: Record<string, unknown> = process['PRIVILEGES'] || {}): string[] {\n return task.grants.flatMap(grant => grant.axes.filter(axis => !declared[privilegeKey(grant.category, axis)]).map(axis => `${grant.category}:${axis}`))\n}\n\nexport type RoleTaskStatus = 'all' | 'some' | 'none' | 'broken'\n\nexport type RoleTaskCoverage = {\n taskId: string\n module: string\n status: RoleTaskStatus\n held: RoleTemplateGrant[]\n missing: RoleTemplateGrant[]\n /** Privileges the task names that this installation does not declare. Any of them makes the task `broken`. */\n undeclared: RoleTemplateGrant[]\n}\n\n/**\n * **A role's privileges laid against every task** — what the role screen groups by.\n *\n * It says which of a task's privileges the role holds, nothing more. `all` is not \"this person can do the work\":\n * another role of theirs, the data's own scope, an approval line or a field's door decide that too. `some` is not a\n * fault: a privilege shared by several tasks makes each of them `some`. `none` is the common case.\n *\n * `unclassified` is what the role holds, declared here, that no task names. A privilege the role holds that nothing\n * declares any more is not here — the screen already strikes it through (`isDeprecatedPrivilege`).\n */\nexport function taskCoverageOf(\n held: ReadonlySet<string>,\n tasks: RoleTask[] = roleTasks(),\n declared: Record<string, unknown> = process['PRIVILEGES'] || {}\n): { tasks: RoleTaskCoverage[]; unclassified: RoleTemplateGrant[] } {\n const named = new Set<string>()\n\n const coverage = tasks.map(task => {\n const pick = (keep: (category: string, axis: string) => boolean) =>\n task.grants.map(grant => ({ category: grant.category, axes: grant.axes.filter(axis => keep(grant.category, axis)) })).filter(grant => grant.axes.length > 0)\n\n task.grants.forEach(grant => grant.axes.forEach(axis => named.add(privilegeKey(grant.category, axis))))\n\n const undeclared = pick((category, axis) => !declared[privilegeKey(category, axis)])\n const holds = pick((category, axis) => Boolean(declared[privilegeKey(category, axis)]) && held.has(privilegeKey(category, axis)))\n const missing = pick((category, axis) => Boolean(declared[privilegeKey(category, axis)]) && !held.has(privilegeKey(category, axis)))\n\n const status: RoleTaskStatus = undeclared.length ? 'broken' : !missing.length ? 'all' : holds.length ? 'some' : 'none'\n\n return { taskId: task.id, module: roleTaskModule(task), status, held: holds, missing, undeclared }\n })\n\n const unclassified = mergeGrants([\n [...held]\n .filter(key => declared[key] && !named.has(key))\n .sort()\n .map(key => {\n const [category, axis] = key.split(' ')\n return { category, axes: [axis] }\n })\n ])\n\n return { tasks: coverage, unclassified }\n}\n"]}
1
+ {"version":3,"file":"role-task.js","sourceRoot":"","sources":["../../../server/service/role-template/role-task.ts"],"names":[],"mappings":"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","sourcesContent":["import { logger } from '@things-factory/env'\n\nimport type { RoleTemplateGrant } from './role-template.js'\n\n/**\n * Role tasks — a piece of work named by the module that owns it, and the privileges it takes (ADR-0139 decision 11).\n *\n * ── Why a task and not a template ────────────────────────────────────────────\n * A template is a seat in one application: who does what in an office or a plant. A module does not know the seats\n * of the applications that install it, but it does know its own work — accounting knows that writing an expenditure\n * request takes one's own requests and picking a counterparty and an account subject. So the module names the work\n * and the application builds its seats from the work (`RoleTemplate.tasks`). An application names work of its own\n * in the same form, under its own prefix.\n *\n * ── What a task is not ───────────────────────────────────────────────────────\n * - Not a narrower door. A task holds whole privileges, and each privilege opens what its doors open. A task called\n * \"read the books\" that holds `accounting:query` opens everything `accounting:query` opens; the name and the\n * description say so rather than hide it.\n * - Not stored. Nothing on a role records which tasks it was built from — the role screen compares a role's\n * privileges with the tasks, computed, the same way a template's gap is (`templateGapOf`).\n * - Not nested. A task lists privileges only; seats are where tasks are put together.\n * - Not declared twice. One piece of work has one task. Two tasks may share a privilege — picking a counterparty is\n * part of more than one piece of work — and that is not a duplicate.\n *\n * Its words live in the declaring module's translations, `task.<id>.name` and `task.<id>.description`, so a module\n * that is not installed takes its words away with it. The screen draws the id when there are none.\n */\nexport type RoleTask = {\n /** `<module>.<work>` — the declaring module's name first: `accounting.expense-authoring`. */\n id: string\n /** Privileges by category, axes listed one by one — the shape a template's grants have. */\n grants: RoleTemplateGrant[]\n}\n\nconst REGISTRY = 'ROLE_TASKS'\n\nprocess[REGISTRY] = {}\n\nfunction registry(): Record<string, RoleTask> {\n return process[REGISTRY]\n}\n\n/**\n * Declares a task. Call it from the module's server entry, beside its `@privilege` doors.\n *\n * Privileges are not checked here, for the reason templates are not: modules load before the schema is built and\n * `process['PRIVILEGES']` is filled. Boot checks them (`reportRoleDefinitionErrors`).\n */\nexport function registerRoleTask(task: RoleTask): void {\n const prefix = task?.id?.split('.')[0]\n\n if (!task?.id || !prefix || prefix === task.id) {\n throw new Error(`A role task needs an id of the form <module>.<work>, the declaring module first — \"${task?.id ?? ''}\" is not.`)\n }\n\n if (!Array.isArray(task.grants) || task.grants.length === 0) {\n throw new Error(`Role task \"${task.id}\" holds no privileges. A task is the privileges a piece of work takes; one that takes none is not a task.`)\n }\n\n for (const grant of task.grants) {\n if (!grant?.category || !Array.isArray(grant.axes) || grant.axes.length === 0) {\n throw new Error(`Role task \"${task.id}\" has a grant without a category or with no axes. Axes are always listed one by one.`)\n }\n }\n\n if (registry()[task.id]) {\n throw new Error(`Role task \"${task.id}\" is declared twice.`)\n }\n\n const grants = mergeGrants([task.grants])\n const written = task.grants.reduce((count, grant) => count + grant.axes.length, 0)\n const kept = grants.reduce((count, grant) => count + grant.axes.length, 0)\n\n if (kept !== written) {\n logger.warn(`[role-task] \"${task.id}\" names the same privilege more than once; it is held once.`)\n }\n\n registry()[task.id] = { id: task.id, grants }\n}\n\nexport function roleTasks(): RoleTask[] {\n return Object.values(registry())\n}\n\nexport function roleTaskById(id: string): RoleTask | undefined {\n return registry()[id]\n}\n\n/** The module a task is declared by — the part of its id before the first dot. */\nexport function roleTaskModule(task: RoleTask): string {\n return task.id.split('.')[0]\n}\n\n/**\n * Several lists of grants as one: one entry per category, each axis once, in the order first written.\n * A privilege held through two tasks, or through a task and a direct grant, is held once.\n */\nexport function mergeGrants(lists: RoleTemplateGrant[][]): RoleTemplateGrant[] {\n const axesOf = new Map<string, string[]>()\n\n for (const grant of lists.flat()) {\n const axes = axesOf.get(grant.category) || []\n for (const axis of grant.axes) {\n if (!axes.includes(axis)) axes.push(axis)\n }\n axesOf.set(grant.category, axes)\n }\n\n return [...axesOf.entries()].map(([category, axes]) => ({ category, axes }))\n}\n\n/** `category axis` — the key `process['PRIVILEGES']` and a role's held set use. */\nexport function privilegeKey(category: string, axis: string): string {\n return `${category} ${axis}`\n}\n\n/** A task's privileges this installation does not declare, as `category:axis` — a definition error of the task. */\nexport function undeclaredTaskGrants(task: RoleTask, declared: Record<string, unknown> = process['PRIVILEGES'] || {}): string[] {\n return task.grants.flatMap(grant => grant.axes.filter(axis => !declared[privilegeKey(grant.category, axis)]).map(axis => `${grant.category}:${axis}`))\n}\n\n/**\n * The tasks that hold one privilege, fewest privileges first, ties by id - what a person refused that privilege may\n * ask for (ADR-0103 decision 9). The same order wherever a task is offered for a privilege.\n */\nexport function tasksHolding(category: string, axis: string, tasks: RoleTask[] = roleTasks()): RoleTask[] {\n const size = (task: RoleTask) => task.grants.reduce((count, grant) => count + grant.axes.length, 0)\n return tasks\n .filter(task => task.grants.some(grant => grant.category === category && grant.axes.includes(axis)))\n .sort((a, b) => size(a) - size(b) || a.id.localeCompare(b.id))\n}\n\n/** A task's privileges this installation declares, as `category axis` keys. */\nexport function declaredTaskKeys(task: RoleTask, declared: Record<string, unknown> = process['PRIVILEGES'] || {}): string[] {\n return task.grants.flatMap(grant => grant.axes.map(axis => privilegeKey(grant.category, axis))).filter(key => declared[key])\n}\n\nexport type RoleTaskStatus = 'all' | 'some' | 'none' | 'broken'\n\nexport type RoleTaskCoverage = {\n taskId: string\n module: string\n status: RoleTaskStatus\n held: RoleTemplateGrant[]\n missing: RoleTemplateGrant[]\n /** Privileges the task names that this installation does not declare. Any of them makes the task `broken`. */\n undeclared: RoleTemplateGrant[]\n}\n\n/**\n * **A role's privileges laid against every task** — what the role screen groups by.\n *\n * It says which of a task's privileges the role holds, nothing more. `all` is not \"this person can do the work\":\n * another role of theirs, the data's own scope, an approval line or a field's door decide that too. `some` is not a\n * fault: a privilege shared by several tasks makes each of them `some`. `none` is the common case.\n *\n * `unclassified` is what the role holds, declared here, that no task names. A privilege the role holds that nothing\n * declares any more is not here — the screen already strikes it through (`isDeprecatedPrivilege`).\n */\nexport function taskCoverageOf(\n held: ReadonlySet<string>,\n tasks: RoleTask[] = roleTasks(),\n declared: Record<string, unknown> = process['PRIVILEGES'] || {}\n): { tasks: RoleTaskCoverage[]; unclassified: RoleTemplateGrant[] } {\n const named = new Set<string>()\n\n const coverage = tasks.map(task => {\n const pick = (keep: (category: string, axis: string) => boolean) =>\n task.grants.map(grant => ({ category: grant.category, axes: grant.axes.filter(axis => keep(grant.category, axis)) })).filter(grant => grant.axes.length > 0)\n\n task.grants.forEach(grant => grant.axes.forEach(axis => named.add(privilegeKey(grant.category, axis))))\n\n const undeclared = pick((category, axis) => !declared[privilegeKey(category, axis)])\n const holds = pick((category, axis) => Boolean(declared[privilegeKey(category, axis)]) && held.has(privilegeKey(category, axis)))\n const missing = pick((category, axis) => Boolean(declared[privilegeKey(category, axis)]) && !held.has(privilegeKey(category, axis)))\n\n const status: RoleTaskStatus = undeclared.length ? 'broken' : !missing.length ? 'all' : holds.length ? 'some' : 'none'\n\n return { taskId: task.id, module: roleTaskModule(task), status, held: holds, missing, undeclared }\n })\n\n const unclassified = mergeGrants([\n [...held]\n .filter(key => declared[key] && !named.has(key))\n .sort()\n .map(key => {\n const [category, axis] = key.split(' ')\n return { category, axes: [axis] }\n })\n ])\n\n return { tasks: coverage, unclassified }\n}\n"]}