@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,220 @@
1
+ import { hasAdminGrant, userActionAllowed, describePeopleError } from '../people.rules.js';
2
+ import { createControllerStore } from './controller-store.js';
3
+ const initial = {
4
+ users: null, roles: null, policies: null, caller: null, error: null, rowError: null, formError: null,
5
+ grantError: null, passwordError: null, notice: null, formOpen: false, saving: false, grantSaving: false,
6
+ expandedId: null, rowSavingId: null, pending: null, confirming: false,
7
+ username: '', email: '', password: '', editEmail: '', pendingRoleId: '', pendingPolicyId: '',
8
+ newPassword: '', confirmPassword: '', showNewPassword: false, showConfirmPassword: false,
9
+ };
10
+ function snapshotUser(user) {
11
+ return Object.freeze({ ...user, roleIds: Object.freeze([...user.roleIds]), policyIds: Object.freeze([...user.policyIds]) });
12
+ }
13
+ /** Users, roles and policies refresh together. Only the caller read is best-effort: failure keeps
14
+ * all caller-dependent actions closed. Each panel transition invalidates both success and error
15
+ * settlements, including close/reopen of the same row. Password resets do not refresh the table. */
16
+ export function createUsersController({ api, safety }, { permissions = [], openOwnPasswordReset = false, onOwnPasswordResetClosed, } = {}) {
17
+ const grants = Object.freeze([...permissions]);
18
+ const store = createControllerStore({ initial });
19
+ const set = (patch) => store.set({ patch });
20
+ const read = store.getSnapshot, call = store.call;
21
+ let listGeneration = 0, panelGeneration = 0, draftGeneration = 0, deepLinkAttempted = false, openedViaDeepLink = false;
22
+ let callerPromise = null;
23
+ const row = (principalId) => read({}).users?.find(u => u.principalId === principalId);
24
+ const report = (error, fallback) => describePeopleError({ error, fallback });
25
+ function allowed({ principalId, action }, _optional = {}) {
26
+ const user = row(principalId);
27
+ return store.active({}) && !!user && userActionAllowed({ user, action, permissions: grants, caller: read({}).caller, safety });
28
+ }
29
+ function gate(principalId, action) {
30
+ if (read({}).confirming || !allowed({ principalId, action })) {
31
+ set({ rowError: 'You do not have permission to do that.' });
32
+ return false;
33
+ }
34
+ return true;
35
+ }
36
+ function requestDestructive({ principalId, kind }, _optional = {}) {
37
+ if (read({}).rowSavingId || !gate(principalId, kind))
38
+ return;
39
+ const user = row(principalId);
40
+ set({ pending: Object.freeze({ kind, user: snapshotUser(user) }), rowError: null, passwordError: null,
41
+ newPassword: '', confirmPassword: '', showNewPassword: false, showConfirmPassword: false });
42
+ }
43
+ function maybeDeepLink() {
44
+ const s = read({});
45
+ if (!openOwnPasswordReset || deepLinkAttempted || !s.users || !s.caller)
46
+ return;
47
+ const own = row(s.caller.user.id);
48
+ if (!own)
49
+ return;
50
+ deepLinkAttempted = true;
51
+ if (allowed({ principalId: own.principalId, action: 'reset' })) {
52
+ requestDestructive({ principalId: own.principalId, kind: 'reset' });
53
+ openedViaDeepLink = true;
54
+ }
55
+ }
56
+ function close() {
57
+ set({ pending: null, newPassword: '', confirmPassword: '', passwordError: null, showNewPassword: false, showConfirmPassword: false });
58
+ if (openedViaDeepLink) {
59
+ openedViaDeepLink = false;
60
+ onOwnPasswordResetClosed?.({});
61
+ }
62
+ }
63
+ async function load(_required, _optional = {}) {
64
+ if (!store.active({}))
65
+ return;
66
+ const generation = ++listGeneration;
67
+ set({ error: null });
68
+ try {
69
+ const [u, r, p] = await Promise.all([api.listUsers({}, call), api.listRoles({}, call), api.listPolicies({}, call)]);
70
+ if (generation !== listGeneration || !store.active({}))
71
+ return;
72
+ set({ users: Object.freeze(u.users.map(snapshotUser)), roles: Object.freeze(r.roles.map(r => Object.freeze({ ...r }))), policies: Object.freeze(p.policies.map(p => Object.freeze({ ...p }))) });
73
+ maybeDeepLink();
74
+ }
75
+ catch (error) {
76
+ if (generation === listGeneration)
77
+ set({ error: report(error, 'failed to load users') });
78
+ }
79
+ }
80
+ async function loadCaller(_required, _optional = {}) {
81
+ if (!store.active({}))
82
+ return;
83
+ if (!callerPromise)
84
+ callerPromise = (async () => {
85
+ try {
86
+ const caller = await api.me({}, call);
87
+ if (!store.active({}) || !caller.user || typeof caller.user.id !== 'string' || !caller.user.id)
88
+ return;
89
+ const safeCaller = Object.freeze({ user: Object.freeze({ ...caller.user }), canManageUserTrash: caller.canManageUserTrash === true,
90
+ ...(caller.effectivePermissions ? { effectivePermissions: Object.freeze([...caller.effectivePermissions]) } : {}) });
91
+ set({ caller: safeCaller });
92
+ maybeDeepLink();
93
+ }
94
+ catch { /* Best-effort identity: unknown defaults to no affordance, including delete. */ }
95
+ })();
96
+ await callerPromise;
97
+ }
98
+ async function createUser(_required, _optional = {}) {
99
+ const s = read({});
100
+ if (!store.active({}) || s.saving || s.confirming || !hasAdminGrant({ permissions: grants, permission: 'user.manage' }))
101
+ return false;
102
+ const draft = draftGeneration;
103
+ set({ saving: true, formError: null });
104
+ try {
105
+ await api.createUser({ username: s.username, password: s.password }, { ...call, ...(s.email ? { email: s.email } : {}) });
106
+ if (draft === draftGeneration)
107
+ set({ username: '', email: '', password: '', formOpen: false });
108
+ await load({});
109
+ return store.active({});
110
+ }
111
+ catch (error) {
112
+ set({ formError: report(error, 'failed to create user') });
113
+ return false;
114
+ }
115
+ finally {
116
+ set({ saving: false });
117
+ }
118
+ }
119
+ function toggleExpanded({ principalId }, _optional = {}) {
120
+ if (!store.active({}) || !row(principalId))
121
+ return;
122
+ ++panelGeneration;
123
+ // A stale grant cannot clear a newer panel's selection or busy flag. Reset on switching.
124
+ set({ expandedId: read({}).expandedId === principalId ? null : principalId, editEmail: row(principalId).email ?? '', pendingRoleId: '', pendingPolicyId: '', grantError: null, grantSaving: false });
125
+ }
126
+ async function savePanel({ principalId, kind }, _optional = {}) {
127
+ const s = read({}), action = kind === 'email' ? 'email' : 'grants';
128
+ if (s.grantSaving || s.expandedId !== principalId || !gate(principalId, action))
129
+ return false;
130
+ if ((kind === 'role' && !s.pendingRoleId) || (kind === 'policy' && !s.pendingPolicyId))
131
+ return false;
132
+ const generation = panelGeneration, draft = draftGeneration;
133
+ set({ grantSaving: true, grantError: null });
134
+ try {
135
+ if (kind === 'email')
136
+ await api.updateUser({ principalId }, { ...call, email: s.editEmail });
137
+ else if (kind === 'role')
138
+ await api.assignRole({ principalId, roleId: s.pendingRoleId }, call);
139
+ else
140
+ await api.attachPolicy({ principalId, policyId: s.pendingPolicyId }, call);
141
+ if (generation === panelGeneration && draft === draftGeneration)
142
+ set(kind === 'role' ? { pendingRoleId: '' } : kind === 'policy' ? { pendingPolicyId: '' } : {});
143
+ await load({});
144
+ return store.active({});
145
+ }
146
+ catch (error) {
147
+ if (generation === panelGeneration)
148
+ set({ grantError: report(error, `failed to ${kind === 'email' ? 'update email' : kind === 'role' ? 'assign role' : 'attach policy'}`) });
149
+ return false;
150
+ }
151
+ finally {
152
+ if (generation === panelGeneration)
153
+ set({ grantSaving: false });
154
+ }
155
+ }
156
+ async function enableUser({ principalId }, _optional = {}) {
157
+ if (read({}).rowSavingId || !gate(principalId, 'enable'))
158
+ return false;
159
+ set({ rowSavingId: principalId, rowError: null });
160
+ try {
161
+ await api.enableUser({ principalId }, call);
162
+ await load({});
163
+ return store.active({});
164
+ }
165
+ catch (error) {
166
+ set({ rowError: report(error, 'failed to change status') });
167
+ return false;
168
+ }
169
+ finally {
170
+ if (read({}).rowSavingId === principalId)
171
+ set({ rowSavingId: null });
172
+ }
173
+ }
174
+ async function confirmDestructive(_required, _optional = {}) {
175
+ const s = read({}), target = s.pending;
176
+ if (!target || s.confirming || s.rowSavingId || !gate(target.user.principalId, target.kind))
177
+ return false;
178
+ if (target.kind === 'reset' && (!s.newPassword || s.newPassword !== s.confirmPassword))
179
+ return false;
180
+ set({ confirming: true, rowError: null, passwordError: null });
181
+ let success = false;
182
+ try {
183
+ const principalId = target.user.principalId;
184
+ if (target.kind === 'reset') {
185
+ await api.resetUserPassword({ principalId, password: s.newPassword }, call);
186
+ set({ notice: principalId === s.caller?.user.id ? 'Password changed. Sign in again with your new password.' : `Password reset for "${target.user.username}" — every active session for this user was revoked.` });
187
+ }
188
+ else {
189
+ if (target.kind === 'disable')
190
+ await api.disableUser({ principalId }, call);
191
+ else
192
+ await api.deleteUser({ principalId }, call);
193
+ await load({});
194
+ }
195
+ success = true;
196
+ return store.active({});
197
+ }
198
+ catch (error) {
199
+ set(target.kind === 'reset' ? { passwordError: report(error, 'failed to reset password') } : { rowError: report(error, `failed to ${target.kind} user`) });
200
+ return false;
201
+ }
202
+ finally {
203
+ // Reset stays open on failure so the operator can retry the exact typed credential.
204
+ if (store.active({}) && read({}).pending === target && (success || target.kind !== 'reset'))
205
+ close();
206
+ set({ confirming: false });
207
+ }
208
+ }
209
+ return {
210
+ getSnapshot: store.getSnapshot, subscribe: store.subscribe, allowed,
211
+ load, loadCaller, createUser, toggleExpanded, savePanel, enableUser, requestDestructive, confirmDestructive,
212
+ cancelDestructive(_required, _optional = {}) { if (!read({}).confirming)
213
+ close(); },
214
+ setFormOpen({ open }, _optional = {}) { set({ formOpen: open }); },
215
+ setDraft({ patch }, _optional = {}) { ++draftGeneration; set(patch); },
216
+ togglePasswordVisible({ field }, _optional = {}) { set(field === 'new' ? { showNewPassword: !read({}).showNewPassword } : { showConfirmPassword: !read({}).showConfirmPassword }); },
217
+ dispose(_required = {}, _optional = {}) { set({ password: '', newPassword: '', confirmPassword: '', pending: null }); store.dispose({}); ++listGeneration; ++panelGeneration; },
218
+ };
219
+ }
220
+ //# sourceMappingURL=users.controller.js.map
@@ -0,0 +1,21 @@
1
+ export { rolesModule, rolesApiToken } from './roles.module.js';
2
+ export { createRolesController } from './controllers/roles.controller.js';
3
+ export type { RolesController } from './controllers/roles.controller.js';
4
+ export { canManageRoles, mutableRole, mutablePolicy, describeRolesError, destructiveCopy } from './rules.js';
5
+ export { rolesMessagesEn } from './messages.en.js';
6
+ export { RolesApiError } from './ports.js';
7
+ export type * from './ports.js';
8
+ export type * from './models.js';
9
+ export { usersModule, usersApiToken, usersSafetyToken } from './users.module.js';
10
+ export { membersModule, membersApiToken } from './members.module.js';
11
+ export { authModule, authApiToken } from './auth.module.js';
12
+ export { createUsersController } from './controllers/users.controller.js';
13
+ export type { UsersController } from './controllers/users.controller.js';
14
+ export { createMembersController } from './controllers/members.controller.js';
15
+ export type { MembersController } from './controllers/members.controller.js';
16
+ export { createAuthController } from './controllers/auth.controller.js';
17
+ export type { AuthController } from './controllers/auth.controller.js';
18
+ export { hasAdminGrant, userActionAllowed, describePeopleError, grantLabel } from './people.rules.js';
19
+ export { usersMessagesEn, membersMessagesEn, authMessagesEn } from './messages.en.js';
20
+ export { PeopleApiError } from './ports.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,15 @@
1
+ export { rolesModule, rolesApiToken } from './roles.module.js';
2
+ export { createRolesController } from './controllers/roles.controller.js';
3
+ export { canManageRoles, mutableRole, mutablePolicy, describeRolesError, destructiveCopy } from './rules.js';
4
+ export { rolesMessagesEn } from './messages.en.js';
5
+ export { RolesApiError } from './ports.js';
6
+ export { usersModule, usersApiToken, usersSafetyToken } from './users.module.js';
7
+ export { membersModule, membersApiToken } from './members.module.js';
8
+ export { authModule, authApiToken } from './auth.module.js';
9
+ export { createUsersController } from './controllers/users.controller.js';
10
+ export { createMembersController } from './controllers/members.controller.js';
11
+ export { createAuthController } from './controllers/auth.controller.js';
12
+ export { hasAdminGrant, userActionAllowed, describePeopleError, grantLabel } from './people.rules.js';
13
+ export { usersMessagesEn, membersMessagesEn, authMessagesEn } from './messages.en.js';
14
+ export { PeopleApiError } from './ports.js';
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,34 @@
1
+ import type { MembersApiPort } from './ports.js';
2
+ export declare const membersApiToken: import("@jini-ai/admin/core/module").AdminPortToken<MembersApiPort, "jini.identity.membersApi">;
3
+ /** Reads remain server-authorized; grants gate mutation affordances, denied by default.
4
+ * Login is mounted by the signed-out host, before the authenticated shell. */
5
+ export declare const membersModule: {
6
+ readonly id: "members";
7
+ readonly requires: {
8
+ readonly membersApi: import("@jini-ai/admin/core/module").AdminPortToken<MembersApiPort, "jini.identity.membersApi">;
9
+ };
10
+ readonly provides: {};
11
+ readonly messages: {
12
+ readonly title: "Members";
13
+ readonly description: "People who have registered an account — review status, resend a sign-in link, or disable access.";
14
+ readonly loading: "Loading members…";
15
+ readonly readOnly: "Read-only — member actions require member.manage.";
16
+ };
17
+ readonly pages: {
18
+ readonly members: {
19
+ readonly path: "/members";
20
+ readonly label: "Members";
21
+ readonly permissions: readonly [];
22
+ readonly nav: {
23
+ readonly group: "people";
24
+ readonly icon: "users";
25
+ };
26
+ readonly tabs: {
27
+ readonly members: {
28
+ readonly label: "Members";
29
+ };
30
+ };
31
+ };
32
+ };
33
+ };
34
+ //# sourceMappingURL=members.module.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { adminPort, defineAdminModule } from '@jini-ai/admin/core/module';
2
+ import { membersMessagesEn } from './messages.en.js';
3
+ export const membersApiToken = adminPort({ id: 'jini.identity.membersApi' });
4
+ /** Reads remain server-authorized; grants gate mutation affordances, denied by default.
5
+ * Login is mounted by the signed-out host, before the authenticated shell. */
6
+ export const membersModule = defineAdminModule({
7
+ id: 'members', requires: { membersApi: membersApiToken }, provides: {}, messages: membersMessagesEn,
8
+ pages: { members: { path: '/members', label: 'Members', permissions: [],
9
+ nav: { group: 'people', icon: 'users' },
10
+ tabs: { members: { label: 'Members' } },
11
+ } },
12
+ });
13
+ //# sourceMappingURL=members.module.js.map
@@ -0,0 +1,37 @@
1
+ export declare const rolesMessagesEn: Readonly<{
2
+ title: "Roles & Permissions";
3
+ roles: "Roles";
4
+ policies: "Policies";
5
+ people: "People";
6
+ description: "Define roles and policies here. Assign them to people in Users.";
7
+ loading: "Loading roles & permissions…";
8
+ readOnly: "Read-only: managing roles requires role.manage.";
9
+ forbidden: "You do not have permission to do that.";
10
+ conflict: "It is still in use — remove that assignment/attachment first.";
11
+ unknownPermission: "That permission is not recognized.";
12
+ exceedsIssuer: "You cannot grant a permission you do not hold.";
13
+ validation: "Please correct the highlighted fields.";
14
+ immutable: "Built-in roles and built-in or frozen policies cannot be changed.";
15
+ }>;
16
+ export declare const usersMessagesEn: {
17
+ readonly title: "Users";
18
+ readonly description: "Operator accounts with access to this admin — assign roles and policies, or disable access.";
19
+ readonly loading: "Loading users…";
20
+ readonly readOnly: "Read-only — user administration is unavailable with your current grants.";
21
+ readonly passwordHelp: "No 'forgot password' email yet. If someone is locked out, the owner can reset their password here.";
22
+ readonly protected: "Only an owner can modify an owner. Unresolved owner status prevents changes.";
23
+ readonly forbidden: "You do not have permission to do that.";
24
+ };
25
+ export declare const membersMessagesEn: {
26
+ readonly title: "Members";
27
+ readonly description: "People who have registered an account — review status, resend a sign-in link, or disable access.";
28
+ readonly loading: "Loading members…";
29
+ readonly readOnly: "Read-only — member actions require member.manage.";
30
+ };
31
+ export declare const authMessagesEn: {
32
+ readonly title: "Sign in to your workspace";
33
+ readonly submit: "Sign in";
34
+ readonly busy: "Signing in…";
35
+ readonly failed: "login failed";
36
+ };
37
+ //# sourceMappingURL=messages.en.d.ts.map
@@ -0,0 +1,21 @@
1
+ export const rolesMessagesEn = Object.freeze({
2
+ title: 'Roles & Permissions', roles: 'Roles', policies: 'Policies', people: 'People',
3
+ description: 'Define roles and policies here. Assign them to people in Users.',
4
+ loading: 'Loading roles & permissions…', readOnly: 'Read-only: managing roles requires role.manage.',
5
+ forbidden: 'You do not have permission to do that.', conflict: 'It is still in use — remove that assignment/attachment first.',
6
+ unknownPermission: 'That permission is not recognized.', exceedsIssuer: 'You cannot grant a permission you do not hold.',
7
+ validation: 'Please correct the highlighted fields.', immutable: 'Built-in roles and built-in or frozen policies cannot be changed.',
8
+ });
9
+ export const usersMessagesEn = {
10
+ title: 'Users', description: 'Operator accounts with access to this admin — assign roles and policies, or disable access.',
11
+ loading: 'Loading users…', readOnly: 'Read-only — user administration is unavailable with your current grants.',
12
+ passwordHelp: "No 'forgot password' email yet. If someone is locked out, the owner can reset their password here.",
13
+ protected: 'Only an owner can modify an owner. Unresolved owner status prevents changes.',
14
+ forbidden: 'You do not have permission to do that.',
15
+ };
16
+ export const membersMessagesEn = {
17
+ title: 'Members', description: 'People who have registered an account — review status, resend a sign-in link, or disable access.',
18
+ loading: 'Loading members…', readOnly: 'Read-only — member actions require member.manage.',
19
+ };
20
+ export const authMessagesEn = { title: 'Sign in to your workspace', submit: 'Sign in', busy: 'Signing in…', failed: 'login failed' };
21
+ //# sourceMappingURL=messages.en.js.map
@@ -0,0 +1,133 @@
1
+ import type { RoleRecord, PolicyRecord, PolicyPermissionRecord } from '../core/types.js';
2
+ /** Reuse domain records without importing browser or server DTO implementations. */
3
+ export interface RolesState {
4
+ readonly roles: readonly RoleRecord[] | null;
5
+ readonly policies: readonly PolicyRecord[] | null;
6
+ readonly error: string | null;
7
+ readonly rowError: string | null;
8
+ readonly roleError: string | null;
9
+ readonly policyError: string | null;
10
+ readonly roleName: string;
11
+ readonly policyName: string;
12
+ readonly policyDescription: string;
13
+ readonly roleSaving: boolean;
14
+ readonly policySaving: boolean;
15
+ readonly rowSavingId: string | null;
16
+ readonly editingRoleId: string | null;
17
+ readonly editingRoleName: string;
18
+ readonly editingPolicyId: string | null;
19
+ readonly editingPolicyName: string;
20
+ readonly editingPolicyDescription: string;
21
+ readonly permissionPolicyId: string | null;
22
+ readonly permissionInput: string;
23
+ readonly resourceTypeInput: string;
24
+ readonly permissionRows: readonly PolicyPermissionRecord[];
25
+ readonly permissionsLoading: boolean;
26
+ readonly removingPermissionId: string | null;
27
+ readonly pending: DestructiveTarget | null;
28
+ readonly confirming: boolean;
29
+ }
30
+ export type DestructiveTarget = {
31
+ readonly kind: 'role';
32
+ readonly role: RoleRecord;
33
+ } | {
34
+ readonly kind: 'policy';
35
+ readonly policy: PolicyRecord;
36
+ } | {
37
+ readonly kind: 'permission';
38
+ readonly policyId: string;
39
+ readonly row: PolicyPermissionRecord;
40
+ };
41
+ export type RolesDraft = Pick<RolesState, 'roleName' | 'policyName' | 'policyDescription' | 'editingRoleName' | 'editingPolicyName' | 'editingPolicyDescription' | 'permissionInput' | 'resourceTypeInput'>;
42
+ export type { RoleRecord, PolicyRecord, PolicyPermissionRecord } from '../core/types.js';
43
+ /** Safe administration projections: credentials and session tokens never enter snapshots. */
44
+ export interface AdminAccount {
45
+ id: string;
46
+ username: string;
47
+ }
48
+ export interface AdminOperator {
49
+ principalId: string;
50
+ workspaceId: string;
51
+ username: string;
52
+ email?: string | undefined;
53
+ status: 'active' | 'disabled';
54
+ createdAt: string;
55
+ lastLoginAt?: string | undefined;
56
+ roleIds: readonly string[];
57
+ policyIds: readonly string[];
58
+ }
59
+ export interface AdminMember {
60
+ id: string;
61
+ workspaceId: string;
62
+ email: string;
63
+ name?: string | undefined;
64
+ status: 'pending' | 'active' | 'disabled';
65
+ emailVerifiedAt?: string | undefined;
66
+ createdAt: string;
67
+ updatedAt: string;
68
+ version: number;
69
+ }
70
+ export interface CallerInfo {
71
+ user: AdminAccount;
72
+ effectivePermissions?: readonly string[];
73
+ canManageUserTrash?: boolean;
74
+ }
75
+ export type UserAction = 'email' | 'grants' | 'disable' | 'enable' | 'reset' | 'delete';
76
+ export interface UsersDraft {
77
+ username: string;
78
+ email: string;
79
+ password: string;
80
+ editEmail: string;
81
+ pendingRoleId: string;
82
+ pendingPolicyId: string;
83
+ newPassword: string;
84
+ confirmPassword: string;
85
+ }
86
+ export interface UsersState extends UsersDraft {
87
+ readonly users: readonly AdminOperator[] | null;
88
+ readonly roles: readonly RoleRecord[] | null;
89
+ readonly policies: readonly PolicyRecord[] | null;
90
+ readonly caller: CallerInfo | null;
91
+ readonly error: string | null;
92
+ readonly rowError: string | null;
93
+ readonly formError: string | null;
94
+ readonly grantError: string | null;
95
+ readonly passwordError: string | null;
96
+ readonly notice: string | null;
97
+ readonly formOpen: boolean;
98
+ readonly saving: boolean;
99
+ readonly grantSaving: boolean;
100
+ readonly expandedId: string | null;
101
+ readonly rowSavingId: string | null;
102
+ readonly pending: {
103
+ readonly kind: 'disable' | 'delete' | 'reset';
104
+ readonly user: AdminOperator;
105
+ } | null;
106
+ readonly confirming: boolean;
107
+ readonly showNewPassword: boolean;
108
+ readonly showConfirmPassword: boolean;
109
+ }
110
+ export interface MemberRowState {
111
+ readonly disabling: boolean;
112
+ readonly resending: boolean;
113
+ readonly error: string | null;
114
+ readonly notice: string | null;
115
+ }
116
+ export interface MembersState {
117
+ readonly members: readonly AdminMember[] | null;
118
+ readonly error: string | null;
119
+ readonly rows: Readonly<Record<string, MemberRowState>>;
120
+ readonly expandedId: string | null;
121
+ readonly detailById: Readonly<Record<string, AdminMember>>;
122
+ readonly detailError: string | null;
123
+ readonly detailLoadingId: string | null;
124
+ readonly pending: AdminMember | null;
125
+ readonly confirming: boolean;
126
+ }
127
+ export interface AuthState {
128
+ readonly username: string;
129
+ readonly password: string;
130
+ readonly error: string | null;
131
+ readonly busy: boolean;
132
+ }
133
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1,29 @@
1
+ import type { AdminOperator, CallerInfo, UserAction } from './models.js';
2
+ import type { UsersSafetyPort } from './ports.js';
3
+ export declare function hasAdminGrant({ permissions, permission }: {
4
+ permissions: readonly string[];
5
+ permission: string;
6
+ }, _optional?: Record<string, never>): boolean;
7
+ /** Server trash authority is a required additional check. This portable UI also requires
8
+ * user.manage: member.manage by itself must never reveal actions over operators.
9
+ * Unknown ownership never becomes permission. The member.manage self-email exception does not
10
+ * confer any credential, status or grant authority over other operators. */
11
+ export declare function userActionAllowed({ user, action, permissions, caller, safety }: {
12
+ user: AdminOperator;
13
+ action: UserAction;
14
+ permissions: readonly string[];
15
+ caller: CallerInfo | null;
16
+ safety: UsersSafetyPort;
17
+ }, _optional?: Record<string, never>): boolean;
18
+ export declare function describePeopleError({ error, fallback }: {
19
+ error: unknown;
20
+ fallback: string;
21
+ }, _optional?: Record<string, never>): string;
22
+ export declare function grantLabel({ ids, records }: {
23
+ ids: readonly string[];
24
+ records: readonly {
25
+ id: string;
26
+ name: string;
27
+ }[];
28
+ }, _optional?: Record<string, never>): string;
29
+ //# sourceMappingURL=people.rules.d.ts.map
@@ -0,0 +1,51 @@
1
+ export function hasAdminGrant({ permissions, permission }, _optional = {}) {
2
+ return permissions.includes('*') || permissions.includes(permission);
3
+ }
4
+ /** Server trash authority is a required additional check. This portable UI also requires
5
+ * user.manage: member.manage by itself must never reveal actions over operators.
6
+ * Unknown ownership never becomes permission. The member.manage self-email exception does not
7
+ * confer any credential, status or grant authority over other operators. */
8
+ export function userActionAllowed({ user, action, permissions, caller, safety }, _optional = {}) {
9
+ if (!caller)
10
+ return false;
11
+ let owner;
12
+ try {
13
+ owner = safety.ownerStatus({ principalId: user.principalId });
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ if ((owner !== 'owner' && owner !== 'operator') || (owner === 'owner' && !permissions.includes('*')))
19
+ return false;
20
+ const self = caller.user.id === user.principalId;
21
+ if (action === 'delete')
22
+ return hasAdminGrant({ permissions, permission: 'user.manage' }) && caller.canManageUserTrash === true && !self && safety.seededOwnerPrincipalId !== null && user.principalId !== safety.seededOwnerPrincipalId;
23
+ if (action === 'disable' && (user.status !== 'active' || safety.seededOwnerPrincipalId === null || user.principalId === safety.seededOwnerPrincipalId))
24
+ return false;
25
+ if (action === 'enable' && user.status !== 'disabled')
26
+ return false;
27
+ if (action === 'reset' && (safety.seededOwnerPrincipalId === null || (user.principalId === safety.seededOwnerPrincipalId && !self)))
28
+ return false;
29
+ if (action === 'grants')
30
+ return hasAdminGrant({ permissions, permission: 'role.manage' });
31
+ if (action === 'email' && self && hasAdminGrant({ permissions, permission: 'member.manage' }))
32
+ return true;
33
+ return hasAdminGrant({ permissions, permission: 'user.manage' });
34
+ }
35
+ export function describePeopleError({ error, fallback }, _optional = {}) {
36
+ const copy = {
37
+ FORBIDDEN: 'You do not have permission to do that.', GRANT_EXCEEDS_ISSUER: 'You cannot grant a permission you do not hold.',
38
+ RESOURCE_CONFLICT: 'That username is already in use.', OWNER_REQUIRED: 'The workspace must keep at least one active owner.',
39
+ SELF_DELETE: 'You cannot delete your own account.', USER_IN_TRASH: 'This user is in the Trash; restore them first.',
40
+ USERNAME_IN_TRASH: 'A user with this username is in the Trash; restore or delete them permanently first.',
41
+ };
42
+ if (error && typeof error === 'object' && 'code' in error && typeof error.code === 'string' && copy[error.code])
43
+ return copy[error.code];
44
+ return error instanceof Error && error.message ? error.message : fallback;
45
+ }
46
+ export function grantLabel({ ids, records }, _optional = {}) {
47
+ // Unknown grant ids stay visible: dropping them would conceal dangling references.
48
+ const byId = new Map(records.map(r => [r.id, r.name]));
49
+ return ids.map(id => byId.get(id) ?? id).join(', ') || 'none';
50
+ }
51
+ //# sourceMappingURL=people.rules.js.map