@jini-ai/user-management 0.2.0

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 (190) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/LICENSE +201 -0
  3. package/README.md +104 -0
  4. package/dist/admin/adapters/http.d.ts +29 -0
  5. package/dist/admin/adapters/http.js +69 -0
  6. package/dist/admin/adapters/memory.d.ts +61 -0
  7. package/dist/admin/adapters/memory.js +217 -0
  8. package/dist/admin/auth.module.d.ts +30 -0
  9. package/dist/admin/auth.module.js +12 -0
  10. package/dist/admin/conformance/index.d.ts +56 -0
  11. package/dist/admin/conformance/index.js +156 -0
  12. package/dist/admin/controllers/auth.controller.d.ts +23 -0
  13. package/dist/admin/controllers/auth.controller.js +33 -0
  14. package/dist/admin/controllers/controller-store.d.ts +18 -0
  15. package/dist/admin/controllers/controller-store.js +23 -0
  16. package/dist/admin/controllers/members.controller.d.ts +33 -0
  17. package/dist/admin/controllers/members.controller.js +119 -0
  18. package/dist/admin/controllers/roles.controller.d.ts +47 -0
  19. package/dist/admin/controllers/roles.controller.js +282 -0
  20. package/dist/admin/controllers/users.controller.d.ts +53 -0
  21. package/dist/admin/controllers/users.controller.js +220 -0
  22. package/dist/admin/index.d.ts +21 -0
  23. package/dist/admin/index.js +15 -0
  24. package/dist/admin/members.module.d.ts +34 -0
  25. package/dist/admin/members.module.js +13 -0
  26. package/dist/admin/messages.en.d.ts +37 -0
  27. package/dist/admin/messages.en.js +21 -0
  28. package/dist/admin/models.d.ts +133 -0
  29. package/dist/admin/models.js +2 -0
  30. package/dist/admin/people.rules.d.ts +29 -0
  31. package/dist/admin/people.rules.js +51 -0
  32. package/dist/admin/ports.d.ts +187 -0
  33. package/dist/admin/ports.js +17 -0
  34. package/dist/admin/react/components/DestructiveDialog.d.ts +5 -0
  35. package/dist/admin/react/components/DestructiveDialog.js +6 -0
  36. package/dist/admin/react/components/MemberRow.d.ts +5 -0
  37. package/dist/admin/react/components/MemberRow.js +8 -0
  38. package/dist/admin/react/components/MembersConfirmation.d.ts +2 -0
  39. package/dist/admin/react/components/MembersConfirmation.js +8 -0
  40. package/dist/admin/react/components/PermissionPanel.d.ts +5 -0
  41. package/dist/admin/react/components/PermissionPanel.js +9 -0
  42. package/dist/admin/react/components/PolicyRow.d.ts +5 -0
  43. package/dist/admin/react/components/PolicyRow.js +11 -0
  44. package/dist/admin/react/components/RoleRow.d.ts +5 -0
  45. package/dist/admin/react/components/RoleRow.js +10 -0
  46. package/dist/admin/react/components/UserRow.d.ts +5 -0
  47. package/dist/admin/react/components/UserRow.js +8 -0
  48. package/dist/admin/react/components/UsersConfirmation.d.ts +3 -0
  49. package/dist/admin/react/components/UsersConfirmation.js +9 -0
  50. package/dist/admin/react/hooks/AuthBinding.hooks.d.ts +39 -0
  51. package/dist/admin/react/hooks/AuthBinding.hooks.js +21 -0
  52. package/dist/admin/react/hooks/LoginPage.hooks.d.ts +54 -0
  53. package/dist/admin/react/hooks/LoginPage.hooks.js +23 -0
  54. package/dist/admin/react/hooks/MembersBinding.hooks.d.ts +35 -0
  55. package/dist/admin/react/hooks/MembersBinding.hooks.js +21 -0
  56. package/dist/admin/react/hooks/MembersPage.hooks.d.ts +99 -0
  57. package/dist/admin/react/hooks/MembersPage.hooks.js +38 -0
  58. package/dist/admin/react/hooks/PeopleController.hooks.d.ts +18 -0
  59. package/dist/admin/react/hooks/PeopleController.hooks.js +20 -0
  60. package/dist/admin/react/hooks/PermissionPanel.hooks.d.ts +30 -0
  61. package/dist/admin/react/hooks/PermissionPanel.hooks.js +15 -0
  62. package/dist/admin/react/hooks/PoliciesTab.hooks.d.ts +47 -0
  63. package/dist/admin/react/hooks/PoliciesTab.hooks.js +26 -0
  64. package/dist/admin/react/hooks/RolesBinding.hooks.d.ts +39 -0
  65. package/dist/admin/react/hooks/RolesBinding.hooks.js +23 -0
  66. package/dist/admin/react/hooks/RolesController.hooks.d.ts +43 -0
  67. package/dist/admin/react/hooks/RolesController.hooks.js +25 -0
  68. package/dist/admin/react/hooks/RolesPage.hooks.d.ts +105 -0
  69. package/dist/admin/react/hooks/RolesPage.hooks.js +33 -0
  70. package/dist/admin/react/hooks/RolesTab.hooks.d.ts +35 -0
  71. package/dist/admin/react/hooks/RolesTab.hooks.js +19 -0
  72. package/dist/admin/react/hooks/UsersBinding.hooks.d.ts +36 -0
  73. package/dist/admin/react/hooks/UsersBinding.hooks.js +21 -0
  74. package/dist/admin/react/hooks/UsersPage.hooks.d.ts +106 -0
  75. package/dist/admin/react/hooks/UsersPage.hooks.js +22 -0
  76. package/dist/admin/react/hooks/UsersTab.hooks.d.ts +101 -0
  77. package/dist/admin/react/hooks/UsersTab.hooks.js +53 -0
  78. package/dist/admin/react/index.d.ts +203 -0
  79. package/dist/admin/react/index.js +32 -0
  80. package/dist/admin/react/pages/LoginPage.d.ts +3 -0
  81. package/dist/admin/react/pages/LoginPage.js +13 -0
  82. package/dist/admin/react/pages/MembersPage.d.ts +3 -0
  83. package/dist/admin/react/pages/MembersPage.js +19 -0
  84. package/dist/admin/react/pages/RolesPage.d.ts +3 -0
  85. package/dist/admin/react/pages/RolesPage.js +19 -0
  86. package/dist/admin/react/pages/UsersPage.d.ts +3 -0
  87. package/dist/admin/react/pages/UsersPage.js +19 -0
  88. package/dist/admin/react/tabs/AuthTab.d.ts +4 -0
  89. package/dist/admin/react/tabs/AuthTab.js +8 -0
  90. package/dist/admin/react/tabs/MembersTab.d.ts +4 -0
  91. package/dist/admin/react/tabs/MembersTab.js +9 -0
  92. package/dist/admin/react/tabs/PoliciesTab.d.ts +4 -0
  93. package/dist/admin/react/tabs/PoliciesTab.js +9 -0
  94. package/dist/admin/react/tabs/RolesTab.d.ts +4 -0
  95. package/dist/admin/react/tabs/RolesTab.js +9 -0
  96. package/dist/admin/react/tabs/UsersTab.d.ts +4 -0
  97. package/dist/admin/react/tabs/UsersTab.js +9 -0
  98. package/dist/admin/roles.module.d.ts +48 -0
  99. package/dist/admin/roles.module.js +15 -0
  100. package/dist/admin/rules.d.ts +32 -0
  101. package/dist/admin/rules.js +32 -0
  102. package/dist/admin/users.module.d.ts +39 -0
  103. package/dist/admin/users.module.js +14 -0
  104. package/dist/core/builtin-permissions.d.ts +10 -0
  105. package/dist/core/builtin-permissions.js +402 -0
  106. package/dist/core/index.d.ts +6 -0
  107. package/dist/core/index.js +9 -0
  108. package/dist/core/permissions.d.ts +40 -0
  109. package/dist/core/permissions.js +52 -0
  110. package/dist/core/ports.d.ts +237 -0
  111. package/dist/core/ports.js +2 -0
  112. package/dist/core/runtime-ports.d.ts +8 -0
  113. package/dist/core/runtime-ports.js +2 -0
  114. package/dist/core/types.d.ts +224 -0
  115. package/dist/core/types.js +93 -0
  116. package/dist/react/auth/Login.d.ts +9 -0
  117. package/dist/react/auth/Login.js +8 -0
  118. package/dist/react/auth/hooks/use-login.hooks.d.ts +22 -0
  119. package/dist/react/auth/hooks/use-login.hooks.js +24 -0
  120. package/dist/react/errors.d.ts +10 -0
  121. package/dist/react/errors.js +13 -0
  122. package/dist/react/index.d.ts +18 -0
  123. package/dist/react/index.js +9 -0
  124. package/dist/react/members/Members.d.ts +6 -0
  125. package/dist/react/members/Members.js +38 -0
  126. package/dist/react/members/hooks/use-members.hooks.d.ts +32 -0
  127. package/dist/react/members/hooks/use-members.hooks.js +120 -0
  128. package/dist/react/members/rules.d.ts +26 -0
  129. package/dist/react/members/rules.js +35 -0
  130. package/dist/react/models.d.ts +50 -0
  131. package/dist/react/models.js +2 -0
  132. package/dist/react/ports.d.ts +157 -0
  133. package/dist/react/ports.js +2 -0
  134. package/dist/react/roles/Roles.d.ts +100 -0
  135. package/dist/react/roles/Roles.hooks.d.ts +17 -0
  136. package/dist/react/roles/Roles.hooks.js +40 -0
  137. package/dist/react/roles/Roles.js +150 -0
  138. package/dist/react/roles/hooks/use-roles.hooks.d.ts +80 -0
  139. package/dist/react/roles/hooks/use-roles.hooks.js +303 -0
  140. package/dist/react/roles/rules.d.ts +35 -0
  141. package/dist/react/roles/rules.js +31 -0
  142. package/dist/react/roles/text.d.ts +27 -0
  143. package/dist/react/roles/text.js +13 -0
  144. package/dist/react/testing.d.ts +51 -0
  145. package/dist/react/testing.js +275 -0
  146. package/dist/react/users/Users.d.ts +72 -0
  147. package/dist/react/users/Users.js +129 -0
  148. package/dist/react/users/hooks/use-reset-password-fields.hooks.d.ts +16 -0
  149. package/dist/react/users/hooks/use-reset-password-fields.hooks.js +21 -0
  150. package/dist/react/users/hooks/use-users.hooks.d.ts +77 -0
  151. package/dist/react/users/hooks/use-users.hooks.js +286 -0
  152. package/dist/react/users/rules.d.ts +33 -0
  153. package/dist/react/users/rules.js +61 -0
  154. package/dist/server/admin-crud-service.d.ts +295 -0
  155. package/dist/server/admin-crud-service.js +561 -0
  156. package/dist/server/agent-tool-input.d.ts +58 -0
  157. package/dist/server/agent-tool-input.js +158 -0
  158. package/dist/server/agent-tool-registrations.d.ts +51 -0
  159. package/dist/server/agent-tool-registrations.js +417 -0
  160. package/dist/server/agent-tools.d.ts +88 -0
  161. package/dist/server/agent-tools.js +229 -0
  162. package/dist/server/auth-service.d.ts +107 -0
  163. package/dist/server/auth-service.js +170 -0
  164. package/dist/server/authorize.d.ts +92 -0
  165. package/dist/server/authorize.js +96 -0
  166. package/dist/server/grant-service.d.ts +219 -0
  167. package/dist/server/grant-service.js +419 -0
  168. package/dist/server/hasher.d.ts +64 -0
  169. package/dist/server/hasher.js +53 -0
  170. package/dist/server/identity-transaction.d.ts +9 -0
  171. package/dist/server/identity-transaction.js +8 -0
  172. package/dist/server/index.d.ts +18 -0
  173. package/dist/server/index.js +20 -0
  174. package/dist/server/messages.d.ts +7 -0
  175. package/dist/server/messages.js +5 -0
  176. package/dist/server/password-policy.d.ts +59 -0
  177. package/dist/server/password-policy.js +66 -0
  178. package/dist/server/permission-migrations.d.ts +84 -0
  179. package/dist/server/permission-migrations.js +77 -0
  180. package/dist/server/repo.memory-transactions.d.ts +16 -0
  181. package/dist/server/repo.memory-transactions.js +79 -0
  182. package/dist/server/repo.memory.d.ts +186 -0
  183. package/dist/server/repo.memory.js +230 -0
  184. package/dist/server/seed.d.ts +56 -0
  185. package/dist/server/seed.js +435 -0
  186. package/dist/server/session-tokens.d.ts +10 -0
  187. package/dist/server/session-tokens.js +12 -0
  188. package/dist/server/username.d.ts +14 -0
  189. package/dist/server/username.js +14 -0
  190. package/package.json +144 -0
@@ -0,0 +1,92 @@
1
+ import type { UUID } from "@jini-ai/core/primitives";
2
+ import type { PolicyPermissionRepoPort, PrincipalPolicyRepoPort, PrincipalRepoPort, PrincipalRoleRepoPort, RolePolicyRepoPort } from "../core/ports.js";
3
+ import type { PolicyPermissionRecord } from "../core/types.js";
4
+ /**
5
+ * @file `authorize` — the RBAC evaluator.
6
+ *
7
+ * Purpose:
8
+ * Ordinary core function, deliberately NOT a port (one evaluator —
9
+ * "no PolicyPort"). The command gateway calls this before every
10
+ * mutation; it is deterministic and side-effect free given the
11
+ * repo snapshot (state.spec §4).
12
+ *
13
+ * How it relates to the project:
14
+ * - `command.ts`'s `executeCommand` calls a bound closure of this shape
15
+ * (typed there as `AuthorizeFn`, kept generic so `core/commands` never
16
+ * imports this feature-level library — see that file's header).
17
+ * - Route handlers call it directly for mutations that do not flow through
18
+ * the command gateway (this pass: `members` disable — see the handoff).
19
+ *
20
+ * Architectural role:
21
+ * Implements the matcher precedence in behavior.spec §1.1 exactly:
22
+ * disabled-principal short-circuit > owner wildcard > exact matching row >
23
+ * fail-closed default. Fail-closed extensibility: a non-null
24
+ * `constraintJson` is never treated as unconstrained, and a `resourceType`
25
+ * mismatch/absence is never treated as a global grant.
26
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
27
+ */
28
+ /** The context an authorization decision is evaluated against. See docs/decisions/DR-001-identity-and-session-boundary.md. */
29
+ export interface AuthorizeContext {
30
+ workspaceId: UUID;
31
+ entityType?: string;
32
+ entityId?: UUID;
33
+ }
34
+ /** `authorize`'s result — always fail-closed on `allowed: false`. See docs/decisions/DR-001-identity-and-session-boundary.md. */
35
+ export interface AuthorizeResult {
36
+ allowed: boolean;
37
+ /** Machine-readable reason: `principal_disabled | no_grant | resource_scope_mismatch | unconstrained_deny | owner_wildcard | matched`. */
38
+ reason: string;
39
+ }
40
+ /** Repos `authorize()`/`resolveEffectivePermissions()` read. Not a port bag with alternate adapters — see file header. */
41
+ export interface AuthorizeDeps {
42
+ principals: PrincipalRepoPort;
43
+ principalRoles: PrincipalRoleRepoPort;
44
+ rolePolicies: RolePolicyRepoPort;
45
+ principalPolicies: PrincipalPolicyRepoPort;
46
+ policyPermissions: PolicyPermissionRepoPort;
47
+ }
48
+ /**
49
+ * Resolve a principal's effective permission rows: the union of (a) its
50
+ * roles' policies' permissions and (b) policies attached directly via
51
+ * `principal_policies` (behavior.spec §1.2). Scoped to `workspaceId` by
52
+ * construction (every repo call below is workspace-scoped).
53
+ *
54
+ * @complexity O(r*p + d) round trips, r = roles held, p = policies per role,
55
+ * d = direct policy attachments — all expected small (a handful of rows per
56
+ * principal). No caller-unbounded collection is walked.
57
+ * @overallScore 100
58
+ */
59
+ export declare function resolveEffectivePermissions(required: {
60
+ deps: AuthorizeDeps;
61
+ principalId: UUID;
62
+ workspaceId: UUID;
63
+ }): Promise<PolicyPermissionRecord[]>;
64
+ /**
65
+ * The RBAC decision. Fail-closed: `allowed` defaults to `false` and
66
+ * only ever flips to `true` on an explicit disabled-short-circuit-cleared,
67
+ * matching row.
68
+ *
69
+ * Precedence (behavior.spec §1.1, highest to lowest):
70
+ * 1. Disabled principal -> `false`, `principal_disabled` (overrides all grants).
71
+ * 2. An unconstrained owner `"*"` row -> `true`, `owner_wildcard`.
72
+ * 3. An exact matching row (permission + workspace + resource-scope + no
73
+ * constraint) -> `true`, `matched`.
74
+ * 4. Fail-closed default -> `false`, with the most specific diagnostic reason
75
+ * available among same-permission candidates (`unconstrained_deny` over
76
+ * `resource_scope_mismatch` over `no_grant` — an inferred, undocumented-but-
77
+ * reasonable tie-break; the spec only pins each reason in isolation, see
78
+ * ).
79
+ *
80
+ * @complexity O(resolveEffectivePermissions) + O(k) local filtering, k = the
81
+ * principal's effective row count (small, bounded by grants a workspace
82
+ * administrator creates by hand).
83
+ * @overallScore 100
84
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
85
+ */
86
+ export declare function authorize(required: {
87
+ deps: AuthorizeDeps;
88
+ principalId: UUID;
89
+ permission: string;
90
+ context: Pick<AuthorizeContext, "workspaceId">;
91
+ }, optional?: Omit<AuthorizeContext, "workspaceId">): Promise<AuthorizeResult>;
92
+ //# sourceMappingURL=authorize.d.ts.map
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Resolve a principal's effective permission rows: the union of (a) its
3
+ * roles' policies' permissions and (b) policies attached directly via
4
+ * `principal_policies` (behavior.spec §1.2). Scoped to `workspaceId` by
5
+ * construction (every repo call below is workspace-scoped).
6
+ *
7
+ * @complexity O(r*p + d) round trips, r = roles held, p = policies per role,
8
+ * d = direct policy attachments — all expected small (a handful of rows per
9
+ * principal). No caller-unbounded collection is walked.
10
+ * @overallScore 100
11
+ */
12
+ export async function resolveEffectivePermissions(required) {
13
+ const { deps, principalId, workspaceId } = required;
14
+ const roleLinks = await deps.principalRoles.listByPrincipalId({ workspaceId, principalId });
15
+ const rolePermissionGroups = await Promise.all(roleLinks.map(async (roleLink) => {
16
+ const rolePolicyLinks = await deps.rolePolicies.listByRoleId({
17
+ workspaceId,
18
+ roleId: roleLink.roleId,
19
+ });
20
+ const policyPermissionGroups = await Promise.all(rolePolicyLinks.map((rolePolicyLink) => deps.policyPermissions.listByPolicyId({ workspaceId, policyId: rolePolicyLink.policyId })));
21
+ return policyPermissionGroups.flat();
22
+ }));
23
+ const directLinks = await deps.principalPolicies.listByPrincipalId({ workspaceId, principalId });
24
+ const directPermissionGroups = await Promise.all(directLinks.map((directLink) => deps.policyPermissions.listByPolicyId({ workspaceId, policyId: directLink.policyId })));
25
+ return [...rolePermissionGroups.flat(), ...directPermissionGroups.flat()];
26
+ }
27
+ /**
28
+ * True iff `row` is an exact, unconstrained-or-scope-matching grant of
29
+ * `permission` in `context` (state.spec §4 `matchesRow`). Assumes `row` has
30
+ * already passed the owner-wildcard check upstream — this never special-cases
31
+ * `"*"`.
32
+ */
33
+ function matchesRow(row, permission, context) {
34
+ if (row.permission !== permission)
35
+ return false;
36
+ if (row.constraintJson != null)
37
+ return false;
38
+ if (row.resourceType != null && row.resourceType !== context.entityType)
39
+ return false;
40
+ return true;
41
+ }
42
+ /**
43
+ * The RBAC decision. Fail-closed: `allowed` defaults to `false` and
44
+ * only ever flips to `true` on an explicit disabled-short-circuit-cleared,
45
+ * matching row.
46
+ *
47
+ * Precedence (behavior.spec §1.1, highest to lowest):
48
+ * 1. Disabled principal -> `false`, `principal_disabled` (overrides all grants).
49
+ * 2. An unconstrained owner `"*"` row -> `true`, `owner_wildcard`.
50
+ * 3. An exact matching row (permission + workspace + resource-scope + no
51
+ * constraint) -> `true`, `matched`.
52
+ * 4. Fail-closed default -> `false`, with the most specific diagnostic reason
53
+ * available among same-permission candidates (`unconstrained_deny` over
54
+ * `resource_scope_mismatch` over `no_grant` — an inferred, undocumented-but-
55
+ * reasonable tie-break; the spec only pins each reason in isolation, see
56
+ * ).
57
+ *
58
+ * @complexity O(resolveEffectivePermissions) + O(k) local filtering, k = the
59
+ * principal's effective row count (small, bounded by grants a workspace
60
+ * administrator creates by hand).
61
+ * @overallScore 100
62
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
63
+ */
64
+ export async function authorize(required, optional = {}) {
65
+ const { deps, principalId, permission } = required;
66
+ const context = { ...required.context, ...optional };
67
+ const principal = await deps.principals.findById({ workspaceId: context.workspaceId, id: principalId });
68
+ if (!principal || principal.status === "disabled") {
69
+ return { allowed: false, reason: "principal_disabled" };
70
+ }
71
+ const effectiveRows = await resolveEffectivePermissions({
72
+ deps,
73
+ principalId,
74
+ workspaceId: context.workspaceId,
75
+ });
76
+ const hasUnconstrainedWildcard = effectiveRows.some((row) => row.permission === "*" && row.resourceType == null && row.constraintJson == null);
77
+ if (hasUnconstrainedWildcard) {
78
+ return { allowed: true, reason: "owner_wildcard" };
79
+ }
80
+ const candidates = effectiveRows.filter((row) => row.permission === permission);
81
+ if (candidates.length === 0) {
82
+ return { allowed: false, reason: "no_grant" };
83
+ }
84
+ const matched = candidates.some((row) => matchesRow(row, permission, context));
85
+ if (matched) {
86
+ return { allowed: true, reason: "matched" };
87
+ }
88
+ if (candidates.some((row) => row.constraintJson != null)) {
89
+ return { allowed: false, reason: "unconstrained_deny" };
90
+ }
91
+ if (candidates.some((row) => row.resourceType != null)) {
92
+ return { allowed: false, reason: "resource_scope_mismatch" };
93
+ }
94
+ return { allowed: false, reason: "no_grant" };
95
+ }
96
+ //# sourceMappingURL=authorize.js.map
@@ -0,0 +1,219 @@
1
+ import type { UUID } from "@jini-ai/core/primitives";
2
+ import { type AuthorizeDeps } from "./authorize.js";
3
+ import type { AuthServiceDeps } from "./auth-service.js";
4
+ import type { IdentityRepos } from "../core/ports.js";
5
+ import { type PolicyRecord, type PrincipalPolicyRecord, type PrincipalRecord, type PrincipalRoleRecord, type RoleRecord, type UserRecord } from "../core/types.js";
6
+ /**
7
+ * @file Human grant-writing transitions (state.spec.md §3): `CREATE_USER`,
8
+ * `CREATE_ROLE`, `CREATE_POLICY`, `ASSIGN_ROLE`, `ATTACH_POLICY`.
9
+ *
10
+ * Purpose:
11
+ * The only usable identity principal before this file was the seeded owner —
12
+ * there was no API path to create a second user or grant/change permissions.
13
+ * These five functions are that path, following `auth-service.ts`'s exact
14
+ * shape (`{ deps: AuthServiceDeps, input: {...} }`, one exported async
15
+ * function per transition, plain `Error` subclasses for control flow).
16
+ *
17
+ * Out of scope (deferred, see the dispatch directive): `ISSUE_API_KEY`,
18
+ * `CREATE_PRINCIPAL` (api_key/agent principal creation), agent/api_key
19
+ * delegation, `WRITE_POLICY_PERMISSION`, `DISABLE_PRINCIPAL`.
20
+ *
21
+ * Architectural role:
22
+ * Ordinary core functions, not ports (mirrors `auth-service.ts`'s reasoning —
23
+ * grant-writing logic has one implementation). Each function calls
24
+ * `authorize`/`resolveEffectivePermissions` from `authorize.ts` itself
25
+ * (rather than pushing the gate into the route layer, the way
26
+ * `members/disable.ts`'s route does) so the caller-permission gate and the
27
+ * clamp are exercised directly by unit tests, no HTTP harness needed.
28
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
29
+ */
30
+ /**
31
+ * Assemble the `AuthorizeDeps` bag `authorize()`/`resolveEffectivePermissions()` expect from the
32
+ * flat `IdentityRepos` bag. Exported so `admin-crud-service.ts`'s new transitions
33
+ * (`ENABLE_PRINCIPAL`/`UPDATE_USER`/`RESET_USER_PASSWORD`/`UPDATE_ROLE`/`UPDATE_POLICY`/
34
+ * `DELETE_ROLE`/`DELETE_POLICY`/`WRITE_POLICY_PERMISSION`/`DISABLE_PRINCIPAL`) reuse the identical
35
+ * gate/clamp logic rather than duplicating it — same file-split reasoning `auth-service.ts` and
36
+ * `grant-service.ts` already use (one shared helper set, two transition files).
37
+ */
38
+ export declare function authorizeDepsFrom(repos: IdentityRepos): AuthorizeDeps;
39
+ /** True iff `principalId` holds the owner wildcard `*` (unconstrained) in its effective set. */
40
+ export declare function principalHoldsOwnerWildcard(required: {
41
+ deps: AuthServiceDeps;
42
+ workspaceId: UUID;
43
+ principalId: UUID;
44
+ }): Promise<boolean>;
45
+ /** Owner-account writes reuse the same unconstrained wildcard test as the active-owner floor. */
46
+ export declare function assertOwnerTargetMayBeModified(required: {
47
+ deps: AuthServiceDeps;
48
+ workspaceId: UUID;
49
+ callerPrincipalId: UUID;
50
+ principalId: UUID;
51
+ }): Promise<void>;
52
+ /**
53
+ * Fail-closed caller-permission gate shared by every transition in this file.
54
+ * Throws `IdentityForbiddenError` (403 `FORBIDDEN`) unless the caller holds
55
+ * at least one of `permissions` (an OR gate where the transition permits it;
56
+ * operator creation requires `user.manage`, while self-profile edits retain their existing OR).
57
+ *
58
+ * @complexity O(p) `authorize()` calls, p = `permissions.length` (1 or 2 in
59
+ * every caller this pass) — each itself O(resolveEffectivePermissions), see
60
+ * that function's doc.
61
+ * @overallScore 100
62
+ */
63
+ export declare function assertCallerHasAnyPermission(required: {
64
+ deps: AuthServiceDeps;
65
+ workspaceId: UUID;
66
+ callerPrincipalId: UUID;
67
+ permissions: readonly string[];
68
+ }): Promise<void>;
69
+ /**
70
+ * The grant-authority clamp (feature.spec.md, state.spec §3
71
+ * `ASSIGN_ROLE`/`ATTACH_POLICY` rows): every permission in `conferredPermissions`
72
+ * must be a matching effective row the caller holds unconstrained
73
+ * (`resourceType`/`constraintJson` both null; owner's `*` qualifies for all).
74
+ * A permission held only conditionally (resource-scoped or constraint-bound)
75
+ * cannot be delegated. Throws `GrantExceedsIssuerError` (403
76
+ * `GRANT_EXCEEDS_ISSUER`) naming every offending permission, and writes no
77
+ * grant row (caller does the write only after this resolves).
78
+ *
79
+ * @complexity O(resolveEffectivePermissions) + O(k) local filtering, k =
80
+ * `conferredPermissions.length` (bounded by one role's/policy's permission
81
+ * rows — a handful, per `identity/INFO.md`'s scale assumptions).
82
+ * @overallScore 100
83
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
84
+ */
85
+ export declare function assertGrantClamp(required: {
86
+ deps: AuthServiceDeps;
87
+ workspaceId: UUID;
88
+ callerPrincipalId: UUID;
89
+ conferredPermissions: readonly string[];
90
+ }): Promise<void>;
91
+ /**
92
+ * `CREATE_USER` (state.spec §3). Atomically mints a NEW
93
+ * `kind='user'` principal and its `users` row — the `principalId` is always
94
+ * generated here, never caller-supplied, so this transition can never attach
95
+ * a credential to a pre-existing principal (the privilege-escalation
96
+ * class: an earlier draft let an admin bind a password to the seeded owner
97
+ * principal and log in as owner). Gated by `user.manage`; front-end
98
+ * `member.manage` never authorizes minting an operator credential.
99
+ *
100
+ * "Atomically" here means structurally, not via a DB transaction — the
101
+ * in-memory adapters have no cross-repo transaction (same disclosed gap as
102
+ * `seed.ts`/`executeCommand`'s compensating-rollback path). The invariant
103
+ * that actually matters (no credential ever attaches to an existing
104
+ * principal) holds regardless, because `principalId` is generated inline and
105
+ * the password is hashed before either row is written, so a hashing failure
106
+ * leaves no orphan principal. A future SQLite adapter wraps both writes in
107
+ * one real transaction (port/adapter rule-of-two) without changing this contract.
108
+ *
109
+ * @complexity O(1) — one permission check, one username lookup, one hash,
110
+ * two saves.
111
+ * @overallScore 100
112
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
113
+ */
114
+ export declare function createUser(required: {
115
+ deps: AuthServiceDeps;
116
+ input: {
117
+ workspaceId: UUID;
118
+ callerPrincipalId: UUID;
119
+ username: string;
120
+ password: string;
121
+ };
122
+ }, optional?: {
123
+ email?: string | undefined;
124
+ }): Promise<{
125
+ principal: PrincipalRecord;
126
+ user: UserRecord;
127
+ }>;
128
+ /**
129
+ * `CREATE_ROLE` (state.spec §3). Gated by `role.manage`. Always mints
130
+ * `isBuiltin=false` — built-ins are SEED-only and immutable.
131
+ *
132
+ * Deviation from the state.spec §3 table (disclosed): that row lists
133
+ * `name`, `description?` jointly for `CREATE_ROLE`/`CREATE_POLICY`, but
134
+ * `RoleRecord` (types.ts, frozen before this task) has no `description`
135
+ * field — only `PolicyRecord` does. `createRole` therefore takes `name`
136
+ * only; `createPolicy` below takes both.
137
+ *
138
+ * @complexity O(1) — one permission check, one save.
139
+ * @overallScore 100
140
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
141
+ */
142
+ export declare function createRole(required: {
143
+ deps: AuthServiceDeps;
144
+ input: {
145
+ workspaceId: UUID;
146
+ callerPrincipalId: UUID;
147
+ name: string;
148
+ };
149
+ }): Promise<{
150
+ role: RoleRecord;
151
+ }>;
152
+ /**
153
+ * `CREATE_POLICY` (state.spec §3). Gated by `role.manage`. Always mints
154
+ * `isBuiltin=false` and `isFrozen=false` — only the (out-of-scope this pass)
155
+ * `ISSUE_API_KEY` transition ever mints `isFrozen=true` policies.
156
+ *
157
+ * @complexity O(1) — one permission check, one save.
158
+ * @overallScore 100
159
+ */
160
+ export declare function createPolicy(required: {
161
+ deps: AuthServiceDeps;
162
+ input: {
163
+ workspaceId: UUID;
164
+ callerPrincipalId: UUID;
165
+ name: string;
166
+ };
167
+ }, optional?: {
168
+ description?: string | undefined;
169
+ }): Promise<{
170
+ policy: PolicyRecord;
171
+ }>;
172
+ /**
173
+ * `ASSIGN_ROLE` (state.spec §3). Gated by `role.manage`;
174
+ * target must be a `kind='user'` principal; the clamp must
175
+ * pass over every permission carried by the role's policies — so
176
+ * assigning the built-in `owner` role requires the caller to already hold
177
+ * the wildcard `*` (owner-only). No row is written on any rejection.
178
+ *
179
+ * @complexity O(resolveEffectivePermissions) + O(role's rolePolicy count)
180
+ * round trips to gather conferred permissions — both bounded by the small,
181
+ * hand-authored grant graph this system assumes (see `identity/INFO.md`).
182
+ * @overallScore 100
183
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
184
+ */
185
+ export declare function assignRole(required: {
186
+ deps: AuthServiceDeps;
187
+ input: {
188
+ workspaceId: UUID;
189
+ callerPrincipalId: UUID;
190
+ principalId: UUID;
191
+ roleId: UUID;
192
+ };
193
+ }): Promise<{
194
+ assignment: PrincipalRoleRecord;
195
+ }>;
196
+ /**
197
+ * `ATTACH_POLICY` (state.spec §3). Gated by
198
+ * `role.manage`; target must be a `kind='user'` principal; the
199
+ * clamp must pass over every permission the policy carries —
200
+ * attaching the built-in `owner` policy requires the caller to already hold
201
+ * `*` (owner-only). No row is written on any rejection.
202
+ *
203
+ * @complexity O(resolveEffectivePermissions) + O(policy's permission row
204
+ * count) — both bounded, see `assignRole`'s doc.
205
+ * @overallScore 100
206
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
207
+ */
208
+ export declare function attachPolicy(required: {
209
+ deps: AuthServiceDeps;
210
+ input: {
211
+ workspaceId: UUID;
212
+ callerPrincipalId: UUID;
213
+ principalId: UUID;
214
+ policyId: UUID;
215
+ };
216
+ }): Promise<{
217
+ attachment: PrincipalPolicyRecord;
218
+ }>;
219
+ //# sourceMappingURL=grant-service.d.ts.map