@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,237 @@
1
+ /**
2
+ * @file Port contracts for the `user-management` library.
3
+ *
4
+ * Purpose:
5
+ * Dependency-inversion seams (port/adapter rule-of-two) for the identity/RBAC
6
+ * tables, plus `PasswordHasherPort` — the hashing seam behind which argon2id
7
+ * lives (feature.spec.md Dependencies table: "behind a HasherPort, rule-of-two
8
+ * candidate"). Only an in-memory adapter for each repo port ships this pass
9
+ * (matches the disclosed precedent set by members/navigation/integrations/
10
+ * analytics — see `src/server/deps.ts` comments); a SQLite adapter is a later
11
+ * step, not a gap introduced here.
12
+ *
13
+ * `authorize()` itself is deliberately NOT a port (one
14
+ * evaluator) — see `authorize.ts`. `AuthorizeDeps` there is a plain repo bag,
15
+ * not declared here.
16
+ *
17
+ * Interfaces and types only — no feature logic.
18
+ */
19
+ import type { ISODateTime, UUID } from "@jini-ai/core/primitives";
20
+ import type { PolicyPermissionRecord, PolicyRecord, PrincipalPolicyRecord, PrincipalRecord, PrincipalRoleRecord, RolePolicyRecord, RoleRecord, SessionRecord, UserRecord } from "./types.js";
21
+ export interface PrincipalRepoPort {
22
+ findById(required: {
23
+ workspaceId: UUID;
24
+ id: UUID;
25
+ }): Promise<PrincipalRecord | null>;
26
+ list(required: {
27
+ workspaceId: UUID;
28
+ }): Promise<PrincipalRecord[]>;
29
+ save(record: PrincipalRecord): Promise<void>;
30
+ }
31
+ export interface UserRepoPort {
32
+ findByPrincipalId(required: {
33
+ workspaceId: UUID;
34
+ principalId: UUID;
35
+ }): Promise<UserRecord | null>;
36
+ /** `username` must already be normalized (NFC + lowercase) by the caller. */
37
+ findByUsername(required: {
38
+ workspaceId: UUID;
39
+ username: string;
40
+ }): Promise<UserRecord | null>;
41
+ /** All `users` rows in the workspace (Users admin screen listing). Not paginated in v1 — matches `PrincipalRepoPort.list`/`RoleRepoPort.list`/`PolicyRepoPort.list`. */
42
+ list(required: {
43
+ workspaceId: UUID;
44
+ }): Promise<UserRecord[]>;
45
+ save(record: UserRecord): Promise<void>;
46
+ }
47
+ export interface SessionRepoPort {
48
+ findById(required: {
49
+ workspaceId: UUID;
50
+ id: UUID;
51
+ }): Promise<SessionRecord | null>;
52
+ findByTokenHash(required: {
53
+ workspaceId: UUID;
54
+ tokenHash: string;
55
+ }): Promise<SessionRecord | null>;
56
+ save(record: SessionRecord): Promise<void>;
57
+ /** Server-side revocation (logout, disable-cascade). Idempotent. */
58
+ revoke(required: {
59
+ workspaceId: UUID;
60
+ id: UUID;
61
+ revokedAt: ISODateTime;
62
+ }): Promise<void>;
63
+ /** — every session bound to a principal, active or not (`RESET_USER_PASSWORD`
64
+ * revokes each active one; unlike `DISABLE_PRINCIPAL`, which needs no session enumeration because
65
+ * `validateSession`'s status check already invalidates every session for a disabled principal).
66
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
67
+ */
68
+ listByPrincipalId(required: {
69
+ workspaceId: UUID;
70
+ principalId: UUID;
71
+ }): Promise<SessionRecord[]>;
72
+ }
73
+ export interface RoleRepoPort {
74
+ findById(required: {
75
+ workspaceId: UUID;
76
+ id: UUID;
77
+ }): Promise<RoleRecord | null>;
78
+ findByName(required: {
79
+ workspaceId: UUID;
80
+ name: string;
81
+ }): Promise<RoleRecord | null>;
82
+ list(required: {
83
+ workspaceId: UUID;
84
+ }): Promise<RoleRecord[]>;
85
+ save(record: RoleRecord): Promise<void>;
86
+ /**. Callers must apply the is_builtin + zero-reference guards
87
+ * themselves (see `admin-crud-service.ts`'s `deleteRole`) — this method performs the row deletion
88
+ * only, no business rule (mirrors `WorkspaceRepoPort.delete`'s convention).
89
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
90
+ */
91
+ delete(required: {
92
+ workspaceId: UUID;
93
+ id: UUID;
94
+ }): Promise<void>;
95
+ }
96
+ export interface PolicyRepoPort {
97
+ findById(required: {
98
+ workspaceId: UUID;
99
+ id: UUID;
100
+ }): Promise<PolicyRecord | null>;
101
+ findByName(required: {
102
+ workspaceId: UUID;
103
+ name: string;
104
+ }): Promise<PolicyRecord | null>;
105
+ list(required: {
106
+ workspaceId: UUID;
107
+ }): Promise<PolicyRecord[]>;
108
+ save(record: PolicyRecord): Promise<void>;
109
+ /**. See `RoleRepoPort.delete`'s doc — same "no business rule here"
110
+ * contract.
111
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
112
+ */
113
+ delete(required: {
114
+ workspaceId: UUID;
115
+ id: UUID;
116
+ }): Promise<void>;
117
+ }
118
+ export interface PolicyPermissionRepoPort {
119
+ listByPolicyId(required: {
120
+ workspaceId: UUID;
121
+ policyId: UUID;
122
+ }): Promise<PolicyPermissionRecord[]>;
123
+ save(record: PolicyPermissionRecord): Promise<void>;
124
+ /** — cascades a `DELETE_POLICY` to the deleted policy's OWN
125
+ * `policy_permissions` rows only (never a different policy's rows, state.spec §3's `DELETE_POLICY`
126
+ * row).
127
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
128
+ */
129
+ deleteByPolicyId(required: {
130
+ workspaceId: UUID;
131
+ policyId: UUID;
132
+ }): Promise<void>;
133
+ /** — removes ONE permission row by its own id, the inverse of `save`. Deliberately keyed by
134
+ * `id` alone (plus the workspace scope every port call carries): `removePolicyPermission` proves
135
+ * the row belongs to the named policy via `listByPolicyId` BEFORE calling this, so a
136
+ * policy-scoped delete signature here would only duplicate a check the caller has already done
137
+ * against rows it is holding.
138
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
139
+ */
140
+ delete(required: {
141
+ workspaceId: UUID;
142
+ id: UUID;
143
+ }): Promise<void>;
144
+ }
145
+ export interface RolePolicyRepoPort {
146
+ listByRoleId(required: {
147
+ workspaceId: UUID;
148
+ roleId: UUID;
149
+ }): Promise<RolePolicyRecord[]>;
150
+ save(record: RolePolicyRecord): Promise<void>;
151
+ /** — the policy-side reference check `deletePolicy` needs (: no
152
+ * writer creates such a row for a non-built-in policy today, but the guard checks for real —
153
+ * defensive against that constraint ever being lifted, not a assumption baked into the guard).
154
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
155
+ */
156
+ listByPolicyId(required: {
157
+ workspaceId: UUID;
158
+ policyId: UUID;
159
+ }): Promise<RolePolicyRecord[]>;
160
+ }
161
+ export interface PrincipalRoleRepoPort {
162
+ listByPrincipalId(required: {
163
+ workspaceId: UUID;
164
+ principalId: UUID;
165
+ }): Promise<PrincipalRoleRecord[]>;
166
+ save(record: PrincipalRoleRecord): Promise<void>;
167
+ /** — the reference check `deleteRole` needs. See docs/decisions/DR-002-grant-authority-and-admin-safety.md. */
168
+ listByRoleId(required: {
169
+ workspaceId: UUID;
170
+ roleId: UUID;
171
+ }): Promise<PrincipalRoleRecord[]>;
172
+ }
173
+ export interface PrincipalPolicyRepoPort {
174
+ listByPrincipalId(required: {
175
+ workspaceId: UUID;
176
+ principalId: UUID;
177
+ }): Promise<PrincipalPolicyRecord[]>;
178
+ save(record: PrincipalPolicyRecord): Promise<void>;
179
+ /** — the reference check `deletePolicy` needs; this is the
180
+ * reference source that matters in practice (`ATTACH_POLICY` does write these rows for
181
+ * non-built-in policies, unlike `role_policies`).
182
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
183
+ */
184
+ listByPolicyId(required: {
185
+ workspaceId: UUID;
186
+ policyId: UUID;
187
+ }): Promise<PrincipalPolicyRecord[]>;
188
+ }
189
+ /**
190
+ * Atomic identity work over the repositories bound to this port. Commit on resolution;
191
+ * roll back every write on rejection. Guard reads and all competing grant/status/reference
192
+ * writers must be serialized within the workspace, including writers outside a transaction.
193
+ * SQL adapters must bind these repositories to the transaction connection and acquire locks
194
+ * sufficient to prevent phantom owner/reference rows; a bare read-committed transaction is
195
+ * insufficient. Nested runs join the current unit of work.
196
+ */
197
+ export interface IdentityTransactionPort {
198
+ run<T>(required: {
199
+ workspaceId: UUID;
200
+ execute: () => Promise<T>;
201
+ }): Promise<T>;
202
+ }
203
+ /**
204
+ * The nine identity repo ports, bagged for the functions in this library that
205
+ * need several of them at once (`authorize`, seeding, the auth service).
206
+ * Composition roots (`server/app.ts` / `server/deps.ts`) assemble this from
207
+ * individual `RouteDeps` fields.
208
+ */
209
+ export interface IdentityRepos {
210
+ /** Required by guarded mutations; omission fails closed rather than weakening isolation. */
211
+ transactions: IdentityTransactionPort;
212
+ principals: PrincipalRepoPort;
213
+ users: UserRepoPort;
214
+ sessions: SessionRepoPort;
215
+ roles: RoleRepoPort;
216
+ policies: PolicyRepoPort;
217
+ policyPermissions: PolicyPermissionRepoPort;
218
+ rolePolicies: RolePolicyRepoPort;
219
+ principalRoles: PrincipalRoleRepoPort;
220
+ principalPolicies: PrincipalPolicyRepoPort;
221
+ }
222
+ /**
223
+ * Password hashing seam. `argon2` (native argon2id bindings) is the
224
+ * v1 adapter — see `hasher.ts`. Declared as a port per the spec's own
225
+ * "rule-of-two candidate" language; only one real adapter exists this pass.
226
+ * See docs/decisions/DR-002-grant-authority-and-admin-safety.md.
227
+ */
228
+ export interface PasswordHasherPort {
229
+ hash(required: {
230
+ password: string;
231
+ }): Promise<string>;
232
+ verify(required: {
233
+ hash: string;
234
+ password: string;
235
+ }): Promise<boolean>;
236
+ }
237
+ //# sourceMappingURL=ports.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ports.js.map
@@ -0,0 +1,8 @@
1
+ /** Host-supplied session bearer generation and storage digest strategy. */
2
+ export interface SessionTokenPort {
3
+ newToken(required: Record<string, never>): string;
4
+ hashToken(required: {
5
+ rawToken: string;
6
+ }): string;
7
+ }
8
+ //# sourceMappingURL=runtime-ports.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runtime-ports.js.map
@@ -0,0 +1,224 @@
1
+ import type { ISODateTime, UUID } from "@jini-ai/core/primitives";
2
+ /**
3
+ * @file Core domain types for the `user-management` library.
4
+ *
5
+ * Purpose:
6
+ * Type-only definitions for the principal-centric identity & authorization
7
+ * model: every actor (human user, AI agent, API key, system) is rooted as a
8
+ * `PrincipalRecord`; humans get RBAC (`roles` -> `policies` -> permissions);
9
+ * machines get direct policy grants (`principal_policies`). Mirrors the
10
+ * `features/post` / `members` conventions: records + workspace-scoped ports
11
+ * live in `ports.ts`, no feature logic here.
12
+ *
13
+ * Scope note (core path, this pass):
14
+ * `agent`/`api_key` principal *lifecycle* transitions (CREATE_PRINCIPAL,
15
+ * ISSUE_API_KEY, ASSIGN_ROLE, ATTACH_POLICY, WRITE_POLICY_PERMISSION,
16
+ * DISABLE_PRINCIPAL) are OUT of scope for this pass (deferred — see the
17
+ * Programmer handoff). The `kind` enum and table shapes are still built to
18
+ * the full target schema so those transitions are additive later,
19
+ * not a repaint.
20
+ */
21
+ /** Every actor is one of these. Only `user`/`system` are populated this pass. */
22
+ export type PrincipalKind = "user" | "agent" | "api_key" | "system";
23
+ /** Disable-only lifecycle — there is no hard-delete state. See docs/decisions/DR-001-identity-and-session-boundary.md. */
24
+ export type PrincipalStatus = "active" | "disabled";
25
+ /**
26
+ * Root identity row for every actor. `change_set.actorId` references
27
+ * a principal by composite `(workspaceId, id)`.
28
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
29
+ */
30
+ export interface PrincipalRecord {
31
+ id: UUID;
32
+ workspaceId: UUID;
33
+ kind: PrincipalKind;
34
+ displayName: string;
35
+ status: PrincipalStatus;
36
+ disabledAt?: ISODateTime | undefined;
37
+ createdAt: ISODateTime;
38
+ }
39
+ /**
40
+ * Human auth credentials. A `UserRecord` is only ever created together with a
41
+ * NEW `kind='user'` principal in the same transaction — never
42
+ * attached to a pre-existing principal. `username` is unique per workspace,
43
+ * compared case-insensitively and Unicode-NFC-normalized (behavior.spec §5).
44
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
45
+ */
46
+ export interface UserRecord {
47
+ principalId: UUID;
48
+ workspaceId: UUID;
49
+ /** Stored pre-normalized (NFC + lowercase) so lookups are direct equality. */
50
+ username: string;
51
+ email?: string | undefined;
52
+ /** argon2id hash; the raw password is never stored. See docs/decisions/DR-001-identity-and-session-boundary.md. */
53
+ passwordHash: string;
54
+ lastLoginAt?: ISODateTime | undefined;
55
+ }
56
+ /**
57
+ * Revocable server-side session. `tokenHash` is a hash of the opaque
58
+ * bearer value carried by the `HttpOnly`/`SameSite=Strict` cookie; the raw
59
+ * token is never persisted. Lifetime is an **absolute** expiry fixed
60
+ * at creation (behavior.spec §4) — no sliding renewal in v1.
61
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
62
+ */
63
+ export interface SessionRecord {
64
+ id: UUID;
65
+ workspaceId: UUID;
66
+ principalId: UUID;
67
+ tokenHash: string;
68
+ createdAt: ISODateTime;
69
+ expiresAt: ISODateTime;
70
+ revokedAt?: ISODateTime;
71
+ ip?: string | undefined;
72
+ userAgent?: string | undefined;
73
+ }
74
+ /** Named RBAC role. Built-ins are seeded `isBuiltin=true`, immutable. See docs/decisions/DR-001-identity-and-session-boundary.md. */
75
+ export interface RoleRecord {
76
+ id: UUID;
77
+ workspaceId: UUID;
78
+ name: string;
79
+ isBuiltin: boolean;
80
+ }
81
+ /**
82
+ * A permission bundle. `isFrozen` is reserved for the API-key
83
+ * issuance-snapshot mechanism — out of scope this pass, so
84
+ * every policy built here is `isFrozen=false`; the field exists so the schema
85
+ * is additive-ready, not a later repaint.
86
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
87
+ */
88
+ export interface PolicyRecord {
89
+ id: UUID;
90
+ workspaceId: UUID;
91
+ name: string;
92
+ description?: string | undefined;
93
+ isBuiltin: boolean;
94
+ isFrozen: boolean;
95
+ }
96
+ /**
97
+ * A single permission carried by a policy. `resourceType`
98
+ * null = unscoped; non-null = matches only when it equals `context.entityType`
99
+ * `constraintJson` non-null and uninterpretable => fail-closed
100
+ * deny — the v1 evaluator interprets no constraint shape, so any
101
+ * non-null value here is treated as "cannot interpret."
102
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
103
+ */
104
+ export interface PolicyPermissionRecord {
105
+ id: UUID;
106
+ workspaceId: UUID;
107
+ policyId: UUID;
108
+ /** Dotted catalog permission string, or `"*"` (owner built-in policy only). */
109
+ permission: string;
110
+ resourceType?: string | null;
111
+ constraintJson?: string | null;
112
+ }
113
+ /** Role -> policy join. Not user-writable in v1 — seed-only rows. See docs/decisions/DR-001-identity-and-session-boundary.md. */
114
+ export interface RolePolicyRecord {
115
+ id: UUID;
116
+ workspaceId: UUID;
117
+ roleId: UUID;
118
+ policyId: UUID;
119
+ }
120
+ /** Principal -> role join, the human grant path. See docs/decisions/DR-001-identity-and-session-boundary.md. */
121
+ export interface PrincipalRoleRecord {
122
+ id: UUID;
123
+ workspaceId: UUID;
124
+ principalId: UUID;
125
+ roleId: UUID;
126
+ }
127
+ /** Principal -> policy direct grant, the machine grant path. See docs/decisions/DR-001-identity-and-session-boundary.md. */
128
+ export interface PrincipalPolicyRecord {
129
+ id: UUID;
130
+ workspaceId: UUID;
131
+ principalId: UUID;
132
+ policyId: UUID;
133
+ }
134
+ /** Raised on a malformed identity input (blank username, etc.). */
135
+ export declare class IdentityValidationError extends Error {
136
+ constructor({ message }: {
137
+ message: string;
138
+ }, optional?: ErrorOptions);
139
+ }
140
+ /** Raised when a referenced principal/user/role/policy is not found. */
141
+ export declare class IdentityNotFoundError extends Error {
142
+ constructor({ message }: {
143
+ message: string;
144
+ }, optional?: ErrorOptions);
145
+ }
146
+ /**
147
+ * Raised on a unique-constraint clash (duplicate `username` in a workspace). Also
148
+ * raised by `deleteRole`/`deletePolicy` (`admin-crud-service.ts`) when the target still has
149
+ * live references — the same 409 `RESOURCE_CONFLICT` shape, a `details.field`
150
+ * distinguishes the two cases at the route layer (errors.spec.md §3).
151
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
152
+ */
153
+ export declare class IdentityConflictError extends Error {
154
+ constructor({ message }: {
155
+ message: string;
156
+ }, optional?: ErrorOptions);
157
+ }
158
+ /** Raised by `login` on bad credentials, a disabled principal, or a blank field. See docs/decisions/DR-001-identity-and-session-boundary.md. */
159
+ export declare class AuthInvalidCredentialsError extends Error {
160
+ constructor({ message }: {
161
+ message: string;
162
+ }, optional?: ErrorOptions);
163
+ }
164
+ /**
165
+ * — raised by `disablePrincipal` when the target is the seeded owner
166
+ * principal, or when disabling would drop the workspace's active owner-`*` count to zero. Maps to
167
+ * 409 `OWNER_REQUIRED` (errors.spec.md §2) — a pre-existing registered code that had no HTTP
168
+ * emission site until this amendment gave `DISABLE_PRINCIPAL` a route.
169
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
170
+ */
171
+ export declare class OwnerRequiredError extends Error {
172
+ constructor({ message }: {
173
+ message: string;
174
+ }, optional?: ErrorOptions);
175
+ }
176
+ /**
177
+ * — raised by `writePolicyPermission` when the permission string is
178
+ * not in the registered catalog. Maps to 400 `PERMISSION_UNKNOWN` (errors.spec.md §2) — kept
179
+ * distinct from `IdentityValidationError` (plain `VALIDATION_ERROR`) so the route can emit the
180
+ * correct typed code without string-matching the message.
181
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
182
+ */
183
+ export declare class PermissionUnknownError extends Error {
184
+ constructor({ message }: {
185
+ message: string;
186
+ }, optional?: ErrorOptions);
187
+ }
188
+ /**
189
+ * Raised when a grant-writing transition's caller-permission gate fails
190
+ * (e.g. `ASSIGN_ROLE` called by a principal without `role.manage`). Maps to
191
+ * 403 `FORBIDDEN` (errors.spec.md §2) — distinct from `GrantExceedsIssuerError`,
192
+ * which is the clamp on the *grant's contents*, not the base gate.
193
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
194
+ */
195
+ export declare class IdentityForbiddenError extends Error {
196
+ readonly permission: string;
197
+ readonly reason: string;
198
+ constructor({ message, permission, reason }: {
199
+ message: string;
200
+ permission: string;
201
+ reason: string;
202
+ }, optional?: ErrorOptions);
203
+ }
204
+ /**
205
+ * Raised when a grant-writing transition (`ASSIGN_ROLE`, `ATTACH_POLICY`, and
206
+ * future `ISSUE_API_KEY`/`WRITE_POLICY_PERMISSION`) would confer a permission
207
+ * the caller does not itself hold unconstrained ( grant-authority
208
+ * clamp, feature.spec.md). Maps to 403 `GRANT_EXCEEDS_ISSUER`
209
+ * (errors.spec.md §2/§3) — `offendingPermissions` is the exact `details`
210
+ * payload shape that error code specifies.
211
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
212
+ */
213
+ export declare class GrantExceedsIssuerError extends Error {
214
+ readonly offendingPermissions: string[];
215
+ constructor({ message, offendingPermissions }: {
216
+ message: string;
217
+ offendingPermissions: string[];
218
+ }, optional?: ErrorOptions);
219
+ }
220
+ /** Raised before a guarded mutation when its host has not bound atomic identity storage. */
221
+ export declare class IdentityTransactionRequiredError extends Error {
222
+ constructor(required: Record<string, never>, optional?: ErrorOptions);
223
+ }
224
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,93 @@
1
+ /** Raised on a malformed identity input (blank username, etc.). */
2
+ export class IdentityValidationError extends Error {
3
+ constructor({ message }, optional = {}) {
4
+ super(message, optional);
5
+ }
6
+ }
7
+ /** Raised when a referenced principal/user/role/policy is not found. */
8
+ export class IdentityNotFoundError extends Error {
9
+ constructor({ message }, optional = {}) {
10
+ super(message, optional);
11
+ }
12
+ }
13
+ /**
14
+ * Raised on a unique-constraint clash (duplicate `username` in a workspace). Also
15
+ * raised by `deleteRole`/`deletePolicy` (`admin-crud-service.ts`) when the target still has
16
+ * live references — the same 409 `RESOURCE_CONFLICT` shape, a `details.field`
17
+ * distinguishes the two cases at the route layer (errors.spec.md §3).
18
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
19
+ */
20
+ export class IdentityConflictError extends Error {
21
+ constructor({ message }, optional = {}) {
22
+ super(message, optional);
23
+ }
24
+ }
25
+ /** Raised by `login` on bad credentials, a disabled principal, or a blank field. See docs/decisions/DR-001-identity-and-session-boundary.md. */
26
+ export class AuthInvalidCredentialsError extends Error {
27
+ constructor({ message }, optional = {}) {
28
+ super(message, optional);
29
+ }
30
+ }
31
+ /**
32
+ * — raised by `disablePrincipal` when the target is the seeded owner
33
+ * principal, or when disabling would drop the workspace's active owner-`*` count to zero. Maps to
34
+ * 409 `OWNER_REQUIRED` (errors.spec.md §2) — a pre-existing registered code that had no HTTP
35
+ * emission site until this amendment gave `DISABLE_PRINCIPAL` a route.
36
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
37
+ */
38
+ export class OwnerRequiredError extends Error {
39
+ constructor({ message }, optional = {}) {
40
+ super(message, optional);
41
+ }
42
+ }
43
+ /**
44
+ * — raised by `writePolicyPermission` when the permission string is
45
+ * not in the registered catalog. Maps to 400 `PERMISSION_UNKNOWN` (errors.spec.md §2) — kept
46
+ * distinct from `IdentityValidationError` (plain `VALIDATION_ERROR`) so the route can emit the
47
+ * correct typed code without string-matching the message.
48
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
49
+ */
50
+ export class PermissionUnknownError extends Error {
51
+ constructor({ message }, optional = {}) {
52
+ super(message, optional);
53
+ }
54
+ }
55
+ /**
56
+ * Raised when a grant-writing transition's caller-permission gate fails
57
+ * (e.g. `ASSIGN_ROLE` called by a principal without `role.manage`). Maps to
58
+ * 403 `FORBIDDEN` (errors.spec.md §2) — distinct from `GrantExceedsIssuerError`,
59
+ * which is the clamp on the *grant's contents*, not the base gate.
60
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
61
+ */
62
+ export class IdentityForbiddenError extends Error {
63
+ permission;
64
+ reason;
65
+ constructor({ message, permission, reason }, optional = {}) {
66
+ super(message, optional);
67
+ this.permission = permission;
68
+ this.reason = reason;
69
+ }
70
+ }
71
+ /**
72
+ * Raised when a grant-writing transition (`ASSIGN_ROLE`, `ATTACH_POLICY`, and
73
+ * future `ISSUE_API_KEY`/`WRITE_POLICY_PERMISSION`) would confer a permission
74
+ * the caller does not itself hold unconstrained ( grant-authority
75
+ * clamp, feature.spec.md). Maps to 403 `GRANT_EXCEEDS_ISSUER`
76
+ * (errors.spec.md §2/§3) — `offendingPermissions` is the exact `details`
77
+ * payload shape that error code specifies.
78
+ * See docs/decisions/DR-001-identity-and-session-boundary.md.
79
+ */
80
+ export class GrantExceedsIssuerError extends Error {
81
+ offendingPermissions;
82
+ constructor({ message, offendingPermissions }, optional = {}) {
83
+ super(message, optional);
84
+ this.offendingPermissions = offendingPermissions;
85
+ }
86
+ }
87
+ /** Raised before a guarded mutation when its host has not bound atomic identity storage. */
88
+ export class IdentityTransactionRequiredError extends Error {
89
+ constructor(required, optional = {}) {
90
+ super("identity mutation requires a transaction port", optional);
91
+ }
92
+ }
93
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,9 @@
1
+ import type { AdminUser } from "../models.js";
2
+ import { useLogin, type LoginHookProps, type LoginOptions } from "./hooks/use-login.hooks.js";
3
+ export interface LoginProps extends LoginHookProps, LoginOptions {
4
+ productName: string;
5
+ onLogin: (user: AdminUser) => void;
6
+ useLoginHook?: typeof useLogin | undefined;
7
+ }
8
+ export declare function Login({ onLogin, port, translate, productName, initialUsername, useLoginHook }: LoginProps): import("react").JSX.Element;
9
+ //# sourceMappingURL=Login.d.ts.map
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLogin } from "./hooks/use-login.hooks.js";
3
+ export function Login({ onLogin, port, translate, productName, initialUsername, useLoginHook = useLogin }) {
4
+ const { username, setUsername, password, setPassword, error, busy, submit } = useLoginHook({ onLogin, port, translate }, { initialUsername });
5
+ const t = translate;
6
+ return (_jsx("div", { className: "login-screen", children: _jsxs("form", { className: "login-card", onSubmit: submit, children: [_jsx("h1", { children: productName }), _jsx("p", { children: t("Sign in to your workspace") }), _jsxs("label", { children: [t("Username"), _jsx("input", { autoComplete: "username", value: username, onChange: (e) => setUsername(e.target.value), autoFocus: true })] }), _jsxs("label", { children: [t("Password"), _jsx("input", { type: "password", autoComplete: "current-password", value: password, onChange: (e) => setPassword(e.target.value) })] }), error ? _jsx("div", { className: "login-error", children: error }) : null, _jsx("button", { disabled: busy, children: busy ? t("Signing in…") : t("Sign in") })] }) }));
7
+ }
8
+ //# sourceMappingURL=Login.js.map
@@ -0,0 +1,22 @@
1
+ import type { Translate } from "@jini-ai/ui/panel-kit";
2
+ import type { AdminUser } from "../../models.js";
3
+ import type { LoginPort } from "../../ports.js";
4
+ export interface LoginHookProps {
5
+ onLogin: (user: AdminUser) => void;
6
+ port: LoginPort;
7
+ translate: Translate;
8
+ }
9
+ export interface LoginOptions {
10
+ initialUsername?: string | undefined;
11
+ }
12
+ export interface LoginController {
13
+ username: string;
14
+ setUsername: (username: string) => void;
15
+ password: string;
16
+ setPassword: (password: string) => void;
17
+ error: string | null;
18
+ busy: boolean;
19
+ submit: (e: React.FormEvent) => Promise<void>;
20
+ }
21
+ export declare function useLogin({ onLogin, port, translate }: LoginHookProps, { initialUsername }?: LoginOptions): LoginController;
22
+ //# sourceMappingURL=use-login.hooks.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { useState } from "react";
2
+ export function useLogin({ onLogin, port, translate }, { initialUsername = "" } = {}) {
3
+ const [username, setUsername] = useState(initialUsername);
4
+ const [password, setPassword] = useState("");
5
+ const [error, setError] = useState(null);
6
+ const [busy, setBusy] = useState(false);
7
+ async function submit(e) {
8
+ e.preventDefault();
9
+ setBusy(true);
10
+ setError(null);
11
+ try {
12
+ const { user } = await port.login({ username, password });
13
+ onLogin(user);
14
+ }
15
+ catch (err) {
16
+ setError(err instanceof Error ? err.message : translate("login failed"));
17
+ }
18
+ finally {
19
+ setBusy(false);
20
+ }
21
+ }
22
+ return { username, setUsername, password, setPassword, error, busy, submit };
23
+ }
24
+ //# sourceMappingURL=use-login.hooks.js.map
@@ -0,0 +1,10 @@
1
+ import type { Translate } from "@jini-ai/ui/panel-kit";
2
+ /** Error codes are data, so any host transport can supply them without a shared error class. */
3
+ export declare function describeIdentityError({ error, fallback, translate }: {
4
+ error: unknown;
5
+ fallback: string;
6
+ translate: Translate;
7
+ }, { messages }?: {
8
+ messages?: Readonly<Record<string, string>>;
9
+ }): string;
10
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1,13 @@
1
+ /** Error codes are data, so any host transport can supply them without a shared error class. */
2
+ export function describeIdentityError({ error, fallback, translate }, { messages = {} } = {}) {
3
+ if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
4
+ if (error.code === "VALIDATION_ERROR") {
5
+ return error instanceof Error && error.message ? error.message : translate("Please correct the highlighted fields.");
6
+ }
7
+ const message = messages[error.code];
8
+ if (message)
9
+ return translate(message);
10
+ }
11
+ return error instanceof Error && error.message ? error.message : fallback;
12
+ }
13
+ //# sourceMappingURL=errors.js.map