@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,18 @@
1
+ export type * from './models.js';
2
+ export type * from './ports.js';
3
+ export { Login } from './auth/Login.js';
4
+ export type { LoginProps } from './auth/Login.js';
5
+ export { useLogin } from './auth/hooks/use-login.hooks.js';
6
+ export type { LoginController, LoginHookProps, LoginOptions } from './auth/hooks/use-login.hooks.js';
7
+ export { Users, UserManagePanel } from './users/Users.js';
8
+ export type { UsersProps, GrantOption, GrantSelectController, UserManageController, UserManagePanelProps, UserRowActionsController, UserRowProps, UsersTableProps } from './users/Users.js';
9
+ export { useUsers } from './users/hooks/use-users.hooks.js';
10
+ export type { UsersController, UsersDependencies, UsersOptions } from './users/hooks/use-users.hooks.js';
11
+ export { useResetPasswordFields } from './users/hooks/use-reset-password-fields.hooks.js';
12
+ export type { ResetPasswordFieldsInput, ResetPasswordFieldsController } from './users/hooks/use-reset-password-fields.hooks.js';
13
+ export { Members } from './members/Members.js';
14
+ export type { MembersProps } from './members/Members.js';
15
+ export { useMembers } from './members/hooks/use-members.hooks.js';
16
+ export type { MembersController, MembersDependencies, MembersOptions } from './members/hooks/use-members.hooks.js';
17
+ export { describeIdentityError } from './errors.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,9 @@
1
+ export { Login } from './auth/Login.js';
2
+ export { useLogin } from './auth/hooks/use-login.hooks.js';
3
+ export { Users, UserManagePanel } from './users/Users.js';
4
+ export { useUsers } from './users/hooks/use-users.hooks.js';
5
+ export { useResetPasswordFields } from './users/hooks/use-reset-password-fields.hooks.js';
6
+ export { Members } from './members/Members.js';
7
+ export { useMembers } from './members/hooks/use-members.hooks.js';
8
+ export { describeIdentityError } from './errors.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ import { useMembers, type MembersDependencies, type MembersOptions } from "./hooks/use-members.hooks.js";
2
+ export interface MembersProps extends MembersDependencies, MembersOptions {
3
+ useMembersHook?: typeof useMembers | undefined;
4
+ }
5
+ export declare function Members({ useMembersHook, port, translate: injectedTranslate, refresh }: MembersProps): import("react").JSX.Element;
6
+ //# sourceMappingURL=Members.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment } from "react";
3
+ import { RowMenu, ConfirmDialog } from "@jini-ai/admin/react";
4
+ import { agentHandle } from "@jini-ai/agentic";
5
+ import { formatTimestamp } from "@jini-ai/ui/panel-kit";
6
+ import { buildAgentListHandles } from "@jini-ai/ui/panel-kit";
7
+ import { memberRowMenuItems } from "./rules.js";
8
+ import { useMembers } from "./hooks/use-members.hooks.js";
9
+ function MemberDetailPanel({ memberId, detailLoadingId, detailError, detail, t }) {
10
+ if (detailLoadingId === memberId)
11
+ return _jsx("div", { className: "notice", children: t("Loading detail…") });
12
+ if (detailError)
13
+ return _jsx("div", { className: "notice error", children: detailError });
14
+ if (!detail)
15
+ return null;
16
+ return (_jsxs("dl", { className: "member-detail", children: [_jsx("dt", { children: t("ID") }), _jsx("dd", { children: detail.id }), _jsx("dt", { children: t("Email verified") }), _jsx("dd", { children: detail.emailVerifiedAt ?? t("not verified") }), _jsx("dt", { children: t("Updated") }), _jsx("dd", { children: detail.updatedAt }), _jsx("dt", { children: t("Version") }), _jsx("dd", { children: detail.version })] }));
17
+ }
18
+ function MemberRow({ member, rowState, isExpanded, detail, detailError, detailLoadingId, onToggleDetail, onResendSignInLink, setConfirmingDisable, agentBase, t, translate, }) {
19
+ return (_jsxs(Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { children: _jsx("button", { type: "button", className: "link-button", onClick: () => void onToggleDetail(member), "aria-expanded": isExpanded, ...agentHandle({ handle: `${agentBase}-toggle-detail` }, { role: "button", label: t(`Expand or collapse ${member.email}'s detail panel`) }), children: member.email }) }), _jsx("td", { children: member.name ?? t("—") }), _jsx("td", { children: _jsx("span", { className: `status status-${member.status}`, children: t(member.status) }) }), _jsx("td", { children: formatTimestamp({ iso: member.createdAt }) }), _jsxs("td", { children: [_jsx(RowMenu, { triggerLabel: `${t("Actions for member")} "${member.email}"`, agentHandle: `${agentBase}-menu`, items: memberRowMenuItems({
20
+ member,
21
+ rs: rowState,
22
+ handlers: {
23
+ onResendSignInLink: (m) => void onResendSignInLink(m),
24
+ onRequestDisable: setConfirmingDisable,
25
+ },
26
+ translate,
27
+ }) }), rowState.error ? (_jsx("div", { className: "notice error", role: "alert", children: rowState.error })) : null, rowState.notice ? _jsx("div", { className: "notice", children: rowState.notice }) : null] })] }), isExpanded ? (_jsx("tr", { children: _jsx("td", { colSpan: 5, children: _jsx(MemberDetailPanel, { memberId: member.id, detailLoadingId: detailLoadingId, detailError: detailError, detail: detail, t: t }) }) })) : null] }, member.id));
28
+ }
29
+ export function Members({ useMembersHook = useMembers, port, translate: injectedTranslate, refresh }) {
30
+ const { members, error, stateFor, onResendSignInLink, expandedId, detailById, detailError, detailLoadingId, onToggleDetail, confirmingDisable, setConfirmingDisable, confirmDisable, t, translate, } = useMembersHook({ port, translate: injectedTranslate }, { refresh });
31
+ if (error)
32
+ return _jsx("div", { className: "notice error", children: error });
33
+ if (!members)
34
+ return _jsx("div", { className: "notice", children: t("Loading members…") });
35
+ const memberMenuBases = buildAgentListHandles({ prefix: "members-row", ids: members.map((member) => member.id) });
36
+ return (_jsxs("div", { className: "page", children: [_jsx("div", { className: "page-header", children: _jsxs("div", { className: "page-header-text", children: [_jsx("p", { className: "page-kicker", children: t("People") }), _jsx("h1", { className: "page-title", children: t("Members") }), _jsx("p", { className: "page-description", children: t("People who have registered an account — review status, resend a sign-in link, or disable access.") })] }) }), members.length === 0 ? (_jsx("div", { className: "card", children: _jsxs("div", { className: "empty-state", children: [_jsx("p", { children: t("No members yet.") }), _jsx("p", { className: "page-description", children: t("Registered members will show up here.") })] }) })) : (_jsx("div", { className: "table-scroll", children: _jsxs("table", { className: "list-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t("Email") }), _jsx("th", { children: t("Name") }), _jsx("th", { children: t("Status") }), _jsx("th", { children: t("Created") }), _jsx("th", { children: t("Actions") })] }) }), _jsx("tbody", { children: members.map((member, index) => (_jsx(MemberRow, { member: member, rowState: stateFor({ id: member.id }), isExpanded: expandedId === member.id, detail: detailById[member.id], detailError: detailError, detailLoadingId: detailLoadingId, onToggleDetail: onToggleDetail, onResendSignInLink: onResendSignInLink, setConfirmingDisable: setConfirmingDisable, agentBase: memberMenuBases[index], t: t, translate: translate }, member.id))) })] }) })), _jsx(ConfirmDialog, { cancelLabel: t("Cancel"), open: confirmingDisable !== null, agentHandle: "members-disable", title: t("Disable this member?"), body: confirmingDisable ? (_jsxs("p", { children: [t("Disable"), " \"", confirmingDisable.email, "\"? ", t("They will no longer be able to sign in.")] })) : null, confirmLabel: t("Disable"), tone: "warning", pending: confirmingDisable !== null && stateFor({ id: confirmingDisable.id }).disabling, onConfirm: confirmDisable, onCancel: () => setConfirmingDisable(null) })] }));
37
+ }
38
+ //# sourceMappingURL=Members.js.map
@@ -0,0 +1,32 @@
1
+ import { type Dispatch, type SetStateAction } from "react";
2
+ import type { AdminMember } from "../../models.js";
3
+ import { type RowActionState } from "../rules.js";
4
+ import type { MembersPort, IdentityRefreshPort } from "../../ports.js";
5
+ import type { Translate } from "@jini-ai/ui/panel-kit";
6
+ export interface MembersDependencies {
7
+ port: MembersPort;
8
+ translate: Translate;
9
+ }
10
+ export interface MembersController {
11
+ members: AdminMember[] | null;
12
+ error: string | null;
13
+ stateFor: (required: {
14
+ id: string;
15
+ }) => RowActionState;
16
+ onResendSignInLink: (member: AdminMember) => Promise<void>;
17
+ expandedId: string | null;
18
+ detailById: Record<string, AdminMember>;
19
+ detailError: string | null;
20
+ detailLoadingId: string | null;
21
+ onToggleDetail: (member: AdminMember) => Promise<void>;
22
+ confirmingDisable: AdminMember | null;
23
+ setConfirmingDisable: Dispatch<SetStateAction<AdminMember | null>>;
24
+ confirmDisable: () => Promise<void>;
25
+ t: Translate;
26
+ translate: Translate;
27
+ }
28
+ export interface MembersOptions {
29
+ refresh?: IdentityRefreshPort | undefined;
30
+ }
31
+ export declare function useMembers({ port, translate }: MembersDependencies, { refresh }?: MembersOptions): MembersController;
32
+ //# sourceMappingURL=use-members.hooks.d.ts.map
@@ -0,0 +1,120 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { describeApiError as describeFeatureError, emptyRowState } from "../rules.js";
3
+ import { useSettlementGeneration } from "@jini-ai/ui/panel-kit";
4
+ export function useMembers({ port, translate }, { refresh } = {}) {
5
+ const describeApiError = (error, fallback) => describeFeatureError({ error, fallback, translate });
6
+ const t = translate;
7
+ const boundT = translate;
8
+ const [members, setMembers] = useState(null);
9
+ const [error, setError] = useState(null);
10
+ const [rowState, setRowState] = useState({});
11
+ const [expandedId, setExpandedId] = useState(null);
12
+ const [detailById, setDetailById] = useState({});
13
+ const [detailError, setDetailError] = useState(null);
14
+ const [detailLoadingId, setDetailLoadingId] = useState(null);
15
+ const [confirmingDisable, setConfirmingDisable] = useState(null);
16
+ const settlement = useSettlementGeneration();
17
+ const expandedIdRef = useRef(null);
18
+ const detailSettlement = useSettlementGeneration();
19
+ const load = useCallback(() => {
20
+ const generation = settlement.next();
21
+ port
22
+ .listMembers({})
23
+ .then((r) => {
24
+ if (!settlement.isCurrent({ generation }))
25
+ return;
26
+ setMembers(r.members);
27
+ setError(null);
28
+ })
29
+ .catch((e) => {
30
+ if (!settlement.isCurrent({ generation }))
31
+ return;
32
+ setError(e instanceof Error ? e.message : t("failed to load members"));
33
+ });
34
+ }, [port, settlement, translate]);
35
+ useEffect(() => {
36
+ load();
37
+ }, [load]);
38
+ useEffect(() => refresh?.subscribe({ onRefresh: load }), [refresh, load]);
39
+ function stateFor({ id }) {
40
+ return rowState[id] ?? emptyRowState({});
41
+ }
42
+ function patchRowState(id, patch) {
43
+ setRowState((current) => ({ ...current, [id]: { ...emptyRowState({}), ...current[id], ...patch } }));
44
+ }
45
+ async function onDisable(member) {
46
+ if (stateFor({ id: member.id }).disabling)
47
+ return;
48
+ patchRowState(member.id, { disabling: true, error: null, notice: null });
49
+ try {
50
+ const result = await port.disableMember({ id: member.id });
51
+ setMembers((current) => (current ? current.map((m) => (m.id === member.id ? result.member : m)) : current));
52
+ patchRowState(member.id, { disabling: false, notice: t("Member disabled.") });
53
+ }
54
+ catch (e) {
55
+ patchRowState(member.id, { disabling: false, error: describeApiError(e, t("Failed to disable member.")) });
56
+ }
57
+ }
58
+ async function onResendSignInLink(member) {
59
+ if (stateFor({ id: member.id }).resending)
60
+ return;
61
+ patchRowState(member.id, { resending: true, error: null, notice: null });
62
+ try {
63
+ await port.requestMemberMagicLink({ email: member.email });
64
+ patchRowState(member.id, { resending: false, notice: t("Sign-in link sent.") });
65
+ }
66
+ catch (e) {
67
+ patchRowState(member.id, { resending: false, error: describeApiError(e, t("Failed to send sign-in link.")) });
68
+ }
69
+ }
70
+ async function confirmDisable() {
71
+ if (!confirmingDisable)
72
+ return;
73
+ await onDisable(confirmingDisable);
74
+ setConfirmingDisable(null);
75
+ }
76
+ async function onToggleDetail(member) {
77
+ if (expandedIdRef.current === member.id) {
78
+ expandedIdRef.current = null;
79
+ setExpandedId(null);
80
+ return;
81
+ }
82
+ expandedIdRef.current = member.id;
83
+ setExpandedId(member.id);
84
+ setDetailError(null);
85
+ if (detailById[member.id])
86
+ return;
87
+ setDetailLoadingId(member.id);
88
+ const generation = detailSettlement.next();
89
+ try {
90
+ const result = await port.getMember({ id: member.id });
91
+ setDetailById((current) => ({ ...current, [member.id]: result.member }));
92
+ }
93
+ catch (e) {
94
+ if (detailSettlement.isCurrent({ generation }) && expandedIdRef.current === member.id) {
95
+ setDetailError(describeApiError(e, t("Failed to load member detail.")));
96
+ }
97
+ }
98
+ finally {
99
+ if (detailSettlement.isCurrent({ generation }))
100
+ setDetailLoadingId(null);
101
+ }
102
+ }
103
+ return {
104
+ members,
105
+ error,
106
+ stateFor,
107
+ onResendSignInLink,
108
+ expandedId,
109
+ detailById,
110
+ detailError,
111
+ detailLoadingId,
112
+ onToggleDetail,
113
+ confirmingDisable,
114
+ setConfirmingDisable,
115
+ confirmDisable,
116
+ t: boundT,
117
+ translate,
118
+ };
119
+ }
120
+ //# sourceMappingURL=use-members.hooks.js.map
@@ -0,0 +1,26 @@
1
+ import type { AdminMember } from "../models.js";
2
+ import type { Translate } from "@jini-ai/ui/panel-kit";
3
+ import type { RowMenuItem } from "@jini-ai/admin/react";
4
+ export interface RowActionState {
5
+ disabling: boolean;
6
+ resending: boolean;
7
+ error: string | null;
8
+ notice: string | null;
9
+ }
10
+ export declare function emptyRowState(_required: Record<string, never>): RowActionState;
11
+ export declare function describeApiError({ error, fallback, translate }: {
12
+ error: unknown;
13
+ fallback: string;
14
+ translate: Translate;
15
+ }): string;
16
+ export interface MemberRowMenuHandlers {
17
+ onResendSignInLink: (member: AdminMember) => void;
18
+ onRequestDisable: (member: AdminMember) => void;
19
+ }
20
+ export declare function memberRowMenuItems({ member, rs, handlers, translate }: {
21
+ member: AdminMember;
22
+ rs: RowActionState;
23
+ handlers: MemberRowMenuHandlers;
24
+ translate: Translate;
25
+ }): RowMenuItem[];
26
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1,35 @@
1
+ import { describeIdentityError } from "../errors.js";
2
+ const STATIC_ERROR_MESSAGES = { FORBIDDEN: "You do not have permission to do that." };
3
+ export function emptyRowState(_required) {
4
+ return { disabling: false, resending: false, error: null, notice: null };
5
+ }
6
+ export function describeApiError({ error, fallback, translate }) {
7
+ return describeIdentityError({ error, fallback, translate }, { messages: STATIC_ERROR_MESSAGES });
8
+ }
9
+ export function memberRowMenuItems({ member, rs, handlers, translate }) {
10
+ const items = [
11
+ {
12
+ key: "resend",
13
+ label: translate("Resend sign-in link"),
14
+ onSelect: () => {
15
+ if (rs.resending)
16
+ return;
17
+ handlers.onResendSignInLink(member);
18
+ },
19
+ },
20
+ ];
21
+ if (member.status !== "disabled") {
22
+ items.push({
23
+ key: "disable",
24
+ label: translate("Disable"),
25
+ tone: "warning",
26
+ onSelect: () => {
27
+ if (rs.disabling)
28
+ return;
29
+ handlers.onRequestDisable(member);
30
+ },
31
+ });
32
+ }
33
+ return items;
34
+ }
35
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1,50 @@
1
+ /** Browser-facing administration DTOs; no transport or server imports. */
2
+ export interface AdminUser {
3
+ id: string;
4
+ username: string;
5
+ }
6
+ export interface AdminIdentityUser {
7
+ principalId: string;
8
+ workspaceId: string;
9
+ username: string;
10
+ email?: string | undefined;
11
+ status: "active" | "disabled";
12
+ createdAt: string;
13
+ lastLoginAt?: string | undefined;
14
+ roleIds: string[];
15
+ policyIds: string[];
16
+ }
17
+ export interface AdminRole {
18
+ id: string;
19
+ workspaceId: string;
20
+ name: string;
21
+ isBuiltin: boolean;
22
+ }
23
+ export interface AdminPolicy {
24
+ id: string;
25
+ workspaceId: string;
26
+ name: string;
27
+ description?: string | undefined;
28
+ isBuiltin: boolean;
29
+ isFrozen: boolean;
30
+ }
31
+ export interface AdminPolicyPermission {
32
+ id: string;
33
+ workspaceId: string;
34
+ policyId: string;
35
+ permission: string;
36
+ resourceType?: string | null | undefined;
37
+ constraintJson?: string | null | undefined;
38
+ }
39
+ export interface AdminMember {
40
+ id: string;
41
+ workspaceId: string;
42
+ email: string;
43
+ name?: string | undefined;
44
+ status: "pending" | "active" | "disabled";
45
+ emailVerifiedAt?: string | undefined;
46
+ createdAt: string;
47
+ updatedAt: string;
48
+ version: number;
49
+ }
50
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1,157 @@
1
+ import type { AdminUser, AdminIdentityUser, AdminRole, AdminPolicy, AdminPolicyPermission, AdminMember } from "./models.js";
2
+ export interface LoginPort {
3
+ login(credentials: {
4
+ username: string;
5
+ password: string;
6
+ }): Promise<{
7
+ user: AdminUser;
8
+ }>;
9
+ }
10
+ export interface UsersPort {
11
+ listUsers(required: Record<string, never>): Promise<{
12
+ users: AdminIdentityUser[];
13
+ }>;
14
+ me(required: Record<string, never>): Promise<{
15
+ user: {
16
+ id: string;
17
+ };
18
+ canManageUserTrash: boolean;
19
+ }>;
20
+ listRoles(required: Record<string, never>): Promise<{
21
+ roles: AdminRole[];
22
+ }>;
23
+ listPolicies(required: Record<string, never>): Promise<{
24
+ policies: AdminPolicy[];
25
+ }>;
26
+ createUser(input: {
27
+ username: string;
28
+ password: string;
29
+ }, options?: {
30
+ email?: string | undefined;
31
+ }): Promise<{
32
+ user: AdminIdentityUser;
33
+ }>;
34
+ updateUser(target: {
35
+ principalId: string;
36
+ }, options?: {
37
+ email?: string | undefined;
38
+ }): Promise<{
39
+ user: AdminIdentityUser;
40
+ }>;
41
+ disableUser(required: {
42
+ principalId: string;
43
+ }): Promise<{
44
+ user: AdminIdentityUser;
45
+ }>;
46
+ enableUser(required: {
47
+ principalId: string;
48
+ }): Promise<{
49
+ user: AdminIdentityUser;
50
+ }>;
51
+ resetUserPassword(input: {
52
+ principalId: string;
53
+ password: string;
54
+ }): Promise<void>;
55
+ assignRole(input: {
56
+ principalId: string;
57
+ roleId: string;
58
+ }): Promise<{
59
+ assignment: unknown;
60
+ }>;
61
+ attachPolicy(input: {
62
+ principalId: string;
63
+ policyId: string;
64
+ }): Promise<{
65
+ attachment: unknown;
66
+ }>;
67
+ deleteUser(required: {
68
+ principalId: string;
69
+ }): Promise<void>;
70
+ }
71
+ export interface RolesPort {
72
+ listRoles(required: Record<string, never>): Promise<{
73
+ roles: AdminRole[];
74
+ }>;
75
+ listPolicies(required: Record<string, never>): Promise<{
76
+ policies: AdminPolicy[];
77
+ }>;
78
+ createRole(required: {
79
+ name: string;
80
+ }): Promise<{
81
+ role: AdminRole;
82
+ }>;
83
+ updateRole(input: {
84
+ roleId: string;
85
+ name: string;
86
+ }): Promise<{
87
+ role: AdminRole;
88
+ }>;
89
+ deleteRole(required: {
90
+ roleId: string;
91
+ }): Promise<void>;
92
+ createPolicy(input: {
93
+ name: string;
94
+ }, options?: {
95
+ description?: string | undefined;
96
+ }): Promise<{
97
+ policy: AdminPolicy;
98
+ }>;
99
+ updatePolicy(target: {
100
+ policyId: string;
101
+ }, options?: {
102
+ name?: string | undefined;
103
+ description?: string | undefined;
104
+ }): Promise<{
105
+ policy: AdminPolicy;
106
+ }>;
107
+ deletePolicy(required: {
108
+ policyId: string;
109
+ }): Promise<void>;
110
+ writePolicyPermission(input: {
111
+ policyId: string;
112
+ permission: string;
113
+ }, options?: {
114
+ resourceType?: string | undefined;
115
+ }): Promise<{
116
+ policyPermission: unknown;
117
+ }>;
118
+ listPolicyPermissions(required: {
119
+ policyId: string;
120
+ }): Promise<{
121
+ policyPermissions: AdminPolicyPermission[];
122
+ }>;
123
+ removePolicyPermission(input: {
124
+ policyId: string;
125
+ policyPermissionId: string;
126
+ }): Promise<void>;
127
+ }
128
+ export interface MembersPort {
129
+ listMembers(required: Record<string, never>): Promise<{
130
+ members: AdminMember[];
131
+ }>;
132
+ getMember(required: {
133
+ id: string;
134
+ }): Promise<{
135
+ member: AdminMember;
136
+ }>;
137
+ disableMember(required: {
138
+ id: string;
139
+ }): Promise<{
140
+ member: AdminMember;
141
+ }>;
142
+ requestMemberMagicLink(input: {
143
+ email: string;
144
+ }): Promise<{
145
+ delivered: true;
146
+ }>;
147
+ }
148
+ /** Compose once per host/workspace. Screens never choose an HTTP transport. */
149
+ export interface IdentityAdminPort extends LoginPort, UsersPort, RolesPort, MembersPort {
150
+ }
151
+ /** The host owns event topics; subscribe binds only the current members scope. */
152
+ export interface IdentityRefreshPort {
153
+ subscribe(required: {
154
+ onRefresh: () => void;
155
+ }): () => void;
156
+ }
157
+ //# sourceMappingURL=ports.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ports.js.map
@@ -0,0 +1,100 @@
1
+ import type { Translate } from "@jini-ai/ui/panel-kit";
2
+ import { type FormEvent } from "react";
3
+ import type { AdminPolicy, AdminPolicyPermission, AdminRole } from "../models.js";
4
+ import { useRoles, type RolesDependencies, type PendingPermissionRemove } from "./hooks/use-roles.hooks.js";
5
+ import { type RolesTabId } from "./Roles.hooks.js";
6
+ export interface RolesProps extends RolesDependencies {
7
+ onTabChange: (required: {
8
+ tabId: RolesTabId;
9
+ }) => void;
10
+ usersHref: string;
11
+ tabId?: string | null | undefined;
12
+ useRolesHook?: typeof useRoles | undefined;
13
+ }
14
+ export interface RoleCreateFormController {
15
+ name: string;
16
+ setName: (name: string) => void;
17
+ saving: boolean;
18
+ error: string | null;
19
+ submit: (e: FormEvent) => Promise<void>;
20
+ }
21
+ export interface RoleRowController {
22
+ editingId: string | null;
23
+ setEditingId: (roleId: string | null) => void;
24
+ draftName: string;
25
+ setDraftName: (name: string) => void;
26
+ startRename: (role: AdminRole) => void;
27
+ saveRename: (required: {
28
+ roleId: string;
29
+ }) => Promise<void>;
30
+ savingId: string | null;
31
+ requestDelete: (role: AdminRole | null) => void;
32
+ }
33
+ export interface RolesSectionProps {
34
+ roles: AdminRole[];
35
+ create: RoleCreateFormController;
36
+ row: RoleRowController;
37
+ t: (key: string) => string;
38
+ translate: Translate;
39
+ }
40
+ export declare function RolesSection({ roles, create, row, t, translate }: RolesSectionProps): import("react").JSX.Element;
41
+ export interface PolicyRowController {
42
+ editingId: string | null;
43
+ setEditingId: (policyId: string | null) => void;
44
+ draftName: string;
45
+ setDraftName: (name: string) => void;
46
+ draftDescription: string;
47
+ setDraftDescription: (description: string) => void;
48
+ startRename: (policy: AdminPolicy) => void;
49
+ saveRename: (required: {
50
+ policyId: string;
51
+ }) => Promise<void>;
52
+ savingId: string | null;
53
+ requestDelete: (policy: AdminPolicy | null) => void;
54
+ }
55
+ export interface PolicyPermissionController {
56
+ openForPolicyId: string | null;
57
+ permission: string;
58
+ setPermission: (permission: string) => void;
59
+ resourceType: string;
60
+ setResourceType: (resourceType: string) => void;
61
+ toggleForm: (required: {
62
+ policyId: string;
63
+ }) => void;
64
+ write: (required: {
65
+ policyId: string;
66
+ }) => Promise<void>;
67
+ rows: AdminPolicyPermission[];
68
+ loading: boolean;
69
+ removingId: string | null;
70
+ requestRemove: (target: PendingPermissionRemove) => void;
71
+ }
72
+ export interface PolicyRowProps {
73
+ policy: AdminPolicy;
74
+ row: PolicyRowController;
75
+ permission: PolicyPermissionController;
76
+ agentBase: string;
77
+ t: (key: string) => string;
78
+ translate: Translate;
79
+ }
80
+ export declare function PolicyRow({ policy, row, permission, agentBase, t, translate }: PolicyRowProps): import("react").JSX.Element;
81
+ export interface PolicyCreateFormController {
82
+ name: string;
83
+ setName: (name: string) => void;
84
+ description: string;
85
+ setDescription: (description: string) => void;
86
+ saving: boolean;
87
+ error: string | null;
88
+ submit: (e: FormEvent) => Promise<void>;
89
+ }
90
+ export interface PoliciesSectionProps {
91
+ policies: AdminPolicy[];
92
+ create: PolicyCreateFormController;
93
+ row: PolicyRowController;
94
+ permission: PolicyPermissionController;
95
+ t: (key: string) => string;
96
+ translate: Translate;
97
+ }
98
+ export declare function PoliciesSection({ policies, create, row, permission, t, translate }: PoliciesSectionProps): import("react").JSX.Element;
99
+ export declare function Roles({ tabId, useRolesHook, port, translate: injectedTranslate, queryScope, onTabChange, usersHref }: RolesProps): import("react").JSX.Element;
100
+ //# sourceMappingURL=Roles.d.ts.map
@@ -0,0 +1,17 @@
1
+ import type { TabBarTab } from "@jini-ai/ui/tab-strip";
2
+ import type { Translate } from "@jini-ai/ui/panel-kit";
3
+ export declare function RolesIcon({ size }: {
4
+ size?: number;
5
+ }): import("react").JSX.Element;
6
+ export declare function PoliciesIcon({ size }: {
7
+ size?: number;
8
+ }): import("react").JSX.Element;
9
+ export declare const ROLES_TAB_IDS: readonly ["roles", "policies"];
10
+ export type RolesTabId = (typeof ROLES_TAB_IDS)[number];
11
+ export declare function resolveRolesTabId({ tabId }: {
12
+ tabId: string | null | undefined;
13
+ }): RolesTabId;
14
+ export declare function resolveRolesTabs({ translate: t }: {
15
+ translate: Translate;
16
+ }): TabBarTab[];
17
+ //# sourceMappingURL=Roles.hooks.d.ts.map
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { resolveActiveTabId } from "@jini-ai/ui/panel-kit";
3
+ const LINE_ICON = {
4
+ viewBox: "0 0 24 24",
5
+ fill: "none",
6
+ stroke: "currentColor",
7
+ strokeWidth: 1.5,
8
+ strokeLinecap: "round",
9
+ strokeLinejoin: "round",
10
+ "aria-hidden": true,
11
+ };
12
+ export function RolesIcon({ size = 16 }) {
13
+ return (_jsxs("svg", { ...LINE_ICON, width: size, height: size, children: [_jsx("circle", { cx: "12", cy: "8", r: "3.5" }), _jsx("path", { d: "M5 20c0-3.3 3.1-6 7-6s7 2.7 7 6" })] }));
14
+ }
15
+ export function PoliciesIcon({ size = 16 }) {
16
+ return (_jsxs("svg", { ...LINE_ICON, width: size, height: size, children: [_jsx("path", { d: "M12 3l7 2.5v6c0 4-3 7.4-7 8.5-4-1.1-7-4.5-7-8.5v-6z" }), _jsx("path", { d: "M9.5 11.5h5M9.5 14.5h3" })] }));
17
+ }
18
+ export const ROLES_TAB_IDS = ["roles", "policies"];
19
+ export function resolveRolesTabId({ tabId }) {
20
+ return resolveActiveTabId({ tabId, validIds: ROLES_TAB_IDS, defaultId: "roles" });
21
+ }
22
+ export function resolveRolesTabs({ translate: t }) {
23
+ return [
24
+ {
25
+ id: "roles",
26
+ label: t("Roles"),
27
+ icon: _jsx(RolesIcon, {}),
28
+ handle: "roles-tab-roles",
29
+ handleLabel: t("Switch to the Roles tab — list, create, rename and delete the roles a person can be assigned"),
30
+ },
31
+ {
32
+ id: "policies",
33
+ label: t("Policies"),
34
+ icon: _jsx(PoliciesIcon, {}),
35
+ handle: "roles-tab-policies",
36
+ handleLabel: t("Switch to the Policies tab — list and create policies, and add or remove the individual permissions on each one"),
37
+ },
38
+ ];
39
+ }
40
+ //# sourceMappingURL=Roles.hooks.js.map