@jini-ai/user-management 0.6.11 → 0.6.15-rc.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.13
4
+
5
+ ### Patch Changes
6
+
7
+ - 0a2b903: Share accessible eye toggles across React credential fields, including multiline question answers,
8
+ CLI overrides, provider keys, login, new users, password resets, and source configuration. Preserve
9
+ autofill protection, translated toggle names, agent handles, and secret-answer redaction. Hidden
10
+ source environment blocks keep names visible and stored values masked until explicitly revealed.
11
+
12
+ Chat and user-management require `@jini-ai/ui ^0.5.7` for the new `SecretInput` and `SecretTextarea`
13
+ exports. Release UI 0.5.7 (or adjust those peer floors to the actual release containing these exports)
14
+ with these consumers; older UI versions cannot satisfy their imports. Hosts supply `.jini-secret`,
15
+ `.jini-secret-toggle`, and `.jini-secret--multiline` visual rules.
16
+
17
+ - Updated dependencies [7479159]
18
+ - Updated dependencies [c31264d]
19
+ - Updated dependencies [0728914]
20
+ - Updated dependencies [6ecbdb2]
21
+ - Updated dependencies [63bacc5]
22
+ - @jini-ai/core@0.6.13
23
+
24
+ ## 0.6.12
25
+
26
+ ### Patch Changes
27
+
28
+ - @jini-ai/core@0.6.12
29
+
3
30
  ## 0.6.11
4
31
 
5
32
  ### Patch Changes
@@ -145,9 +145,11 @@ export function createMemoryUsersApi({ workspaceId }, options = {}) {
145
145
  }
146
146
  const password = (value) => { if (!value || [...value].length > 512)
147
147
  fail('VALIDATION_ERROR', 'Password must be present and no more than 512 characters.'); };
148
- /** Disabling or deleting an owner must leave another active owner behind. */
149
- function keepAnotherActiveOwner(u) { if (owners.has(u.principalId) && [...users.values()].filter(v => v.status === 'active' && owners.has(v.principalId)).length <= 1)
150
- fail('OWNER_REQUIRED', 'The workspace must keep at least one active owner.'); }
148
+ /** Disabling or deleting an active owner must leave another active owner behind. */
149
+ function keepAnotherActiveOwner(u, _optional = {}) {
150
+ if (u.status === 'active' && owners.has(u.principalId) && [...users.values()].filter(v => v.status === 'active' && owners.has(v.principalId)).length <= 1)
151
+ fail('OWNER_REQUIRED', 'The workspace must keep at least one active owner.');
152
+ }
151
153
  function clamp(contents) { if (grants.includes('*'))
152
154
  return; if (!contents || contents.some(p => !grants.includes(p)))
153
155
  fail('GRANT_EXCEEDS_ISSUER', 'Grant exceeds issuer.'); }
@@ -1,9 +1,12 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ConfirmDialog, TextField, Button, Notice } from '@jini-ai/ui-kit/react';
2
+ import { ConfirmDialog, Notice } from '@jini-ai/ui-kit/react';
3
+ import { SecretInput } from '@jini-ai/ui';
4
+ import { useId } from 'react';
3
5
  import { useUsersConfirmation, newPasswordAttrs } from '../hooks/UsersTab.hooks.js';
4
6
  /** Kept mounted independently of row menus. Confirmation and cancellation share ui-kit's guards. */
5
7
  export function UsersConfirmation(_required = {}, _optional = {}) {
6
8
  const v = useUsersConfirmation({});
7
- return _jsx(ConfirmDialog, { ...v.confirm, body: _jsxs("div", { children: [_jsx("p", { children: v.s.pending?.user.username }), _jsx("p", { children: v.copy.body }), v.reset ? _jsxs(_Fragment, { children: [_jsx(TextField, { label: "New password", type: v.newType, attrs: newPasswordAttrs, value: v.s.newPassword, onValueChange: v.newPassword, disabled: v.s.confirming }), _jsx(Button, { onPress: v.toggleNew, attrs: { 'aria-pressed': v.s.showNewPassword }, children: v.newLabel }), _jsx(TextField, { label: "Confirm new password", type: v.confirmType, attrs: newPasswordAttrs, value: v.s.confirmPassword, onValueChange: v.confirmPassword, disabled: v.s.confirming }), _jsx(Button, { onPress: v.toggleConfirm, attrs: { 'aria-pressed': v.s.showConfirmPassword }, children: v.confirmLabel }), v.error ? _jsx(Notice, { tone: "danger", children: v.error }) : null] }) : null] }) });
9
+ const fieldId = useId();
10
+ return _jsx(ConfirmDialog, { ...v.confirm, body: _jsxs("div", { children: [_jsx("p", { children: v.s.pending?.user.username }), _jsx("p", { children: v.copy.body }), v.reset ? _jsxs(_Fragment, { children: [_jsx("label", { className: "jini-field-label", htmlFor: `${fieldId}-new`, children: "New password" }), _jsx(SecretInput, { id: `${fieldId}-new`, label: "New password", ...newPasswordAttrs, value: v.s.newPassword, onChange: event => v.newPassword({ value: event.target.value }), disabled: v.s.confirming, revealed: v.s.showNewPassword, onRevealedChange: v.toggleNew }), _jsx("label", { className: "jini-field-label", htmlFor: `${fieldId}-confirm`, children: "Confirm new password" }), _jsx(SecretInput, { id: `${fieldId}-confirm`, label: "Confirm new password", ...newPasswordAttrs, value: v.s.confirmPassword, onChange: event => v.confirmPassword({ value: event.target.value }), disabled: v.s.confirming, revealed: v.s.showConfirmPassword, onRevealedChange: v.toggleConfirm }), v.error ? _jsx(Notice, { tone: "danger", children: v.error }) : null] }) : null] }) });
8
11
  }
9
12
  //# sourceMappingURL=UsersConfirmation.js.map
@@ -97,11 +97,7 @@ export declare function useUsersConfirmation(_required?: Record<string, never>,
97
97
  confirmPassword: ({ value }: {
98
98
  value: string;
99
99
  }) => void;
100
- newType: "password" | "text";
101
- confirmType: "password" | "text";
102
100
  toggleNew: () => void;
103
101
  toggleConfirm: () => void;
104
- newLabel: string;
105
- confirmLabel: string;
106
102
  };
107
103
  //# sourceMappingURL=UsersTab.hooks.d.ts.map
@@ -45,21 +45,15 @@ const confirmationCopy = {
45
45
  reset: { title: 'Reset password?', label: 'Reset password', tone: 'warning', body: 'Every active session for this user will be signed out.', consequence: 'revokes every active session' },
46
46
  disable: { title: 'Disable this user?', label: 'Disable', tone: 'warning', body: 'They will not be able to sign in until re-enabled.', consequence: 'revokes sign-in access' },
47
47
  };
48
- /** A password field's reveal state: its input type and its toggle button's label. */
49
- function revealState({ shown, name }, _optional = {}) {
50
- return shown ? { type: 'text', label: `Hide ${name} password` } : { type: 'password', label: `Show ${name} password` };
51
- }
52
48
  export function useUsersConfirmation(_required = {}, _optional = {}) {
53
49
  const { state: s, controller: c } = useUsersView({});
54
50
  const kind = s.pending?.kind ?? 'disable', reset = kind === 'reset', mismatch = s.newPassword !== s.confirmPassword;
55
- const copy = confirmationCopy[kind], shownNew = revealState({ shown: s.showNewPassword, name: 'new' }), shownConfirm = revealState({ shown: s.showConfirmPassword, name: 'confirm' });
51
+ const copy = confirmationCopy[kind];
56
52
  return { s, reset, mismatch, error: mismatch ? 'Passwords do not match.' : s.passwordError, copy,
57
53
  confirm: { open: s.pending !== null, title: copy.title, confirmLabel: copy.label, tone: copy.tone, consequence: copy.consequence, pending: s.confirming, agentMayConfirm: false,
58
54
  onConfirm: async () => { await c.confirmDestructive({}); }, onCancel: () => c.cancelDestructive({}) },
59
55
  newPassword: ({ value }) => c.setDraft({ patch: { newPassword: value } }),
60
56
  confirmPassword: ({ value }) => c.setDraft({ patch: { confirmPassword: value } }),
61
- newType: shownNew.type, confirmType: shownConfirm.type,
62
- toggleNew: () => c.togglePasswordVisible({ field: 'new' }), toggleConfirm: () => c.togglePasswordVisible({ field: 'confirm' }),
63
- newLabel: shownNew.label, confirmLabel: shownConfirm.label, };
57
+ toggleNew: () => c.togglePasswordVisible({ field: 'new' }), toggleConfirm: () => c.togglePasswordVisible({ field: 'confirm' }), };
64
58
  }
65
59
  //# sourceMappingURL=UsersTab.hooks.js.map
@@ -1,8 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId } from "react";
3
+ import { SecretInput } from "@jini-ai/ui";
2
4
  import { useLogin } from "./hooks/use-login.hooks.js";
3
5
  export function Login({ onLogin, port, translate, productName, initialUsername, useLoginHook = useLogin }) {
4
6
  const { username, setUsername, password, setPassword, error, busy, submit } = useLoginHook({ onLogin, port, translate }, { initialUsername });
5
7
  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") })] }) }));
8
+ const passwordInputId = useId();
9
+ 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("div", { children: [_jsx("label", { htmlFor: passwordInputId, children: t("Password") }), _jsx(SecretInput, { id: passwordInputId, label: t("Password"), toggleLabels: { show: t("Show password"), hide: t("Hide 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
10
  }
8
11
  //# sourceMappingURL=Login.js.map
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import {} from "react";
2
+ import { useId } from "react";
3
3
  import { RowMenu, ConfirmDialog } from "@jini-ai/admin/react";
4
4
  import { agentHandle } from "@jini-ai/agentic";
5
5
  import { buildAgentListHandles } from "@jini-ai/ui/panel-kit";
@@ -7,11 +7,13 @@ import { grantOptionLabel } from "./rules.js";
7
7
  import { UserGrantCells, UserNameCell, UserStatusCell, userRowMenuItemsFor } from "./UserRowParts.js";
8
8
  import { useUsers } from "./hooks/use-users.hooks.js";
9
9
  import { InfoTip } from "@jini-ai/ui/admin-widgets";
10
+ import { SecretInput } from "@jini-ai/ui";
10
11
  import { useResetPasswordFields } from "./hooks/use-reset-password-fields.hooks.js";
11
12
  import { ChangeUsernameDialog } from "./ChangeUsernameDialog.js";
12
13
  /** The "New user" form — extracted from `Users` verbatim; own scope for `formError`/`saving`. */
13
14
  function NewUserForm({ username, setUsername, email, setEmail, password, setPassword, saving, formError, onCreate, t }) {
14
- return (_jsxs("form", { onSubmit: onCreate, className: "notice integrations-form form-measure", children: [formError ? _jsx("span", { className: "save-error", children: formError }) : null, _jsxs("label", { children: [t("Username"), _jsx("input", { value: username, onChange: (e) => setUsername(e.target.value), required: true, ...agentHandle({ handle: "users-new-username" }, { role: "field", label: "The new user's username" }) })] }), _jsxs("label", { children: [t("Email (optional)"), _jsx("input", { type: "email", value: email, onChange: (e) => setEmail(e.target.value), ...agentHandle({ handle: "users-new-email" }, { role: "field", label: "The new user's email address" }) })] }), _jsxs("label", { children: [t("Password"), _jsx("input", { type: "password", autoComplete: "new-password", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 1, ...agentHandle({ handle: "users-new-password" }, {
15
+ const passwordInputId = useId();
16
+ return (_jsxs("form", { onSubmit: onCreate, className: "notice integrations-form form-measure", children: [formError ? _jsx("span", { className: "save-error", children: formError }) : null, _jsxs("label", { children: [t("Username"), _jsx("input", { value: username, onChange: (e) => setUsername(e.target.value), required: true, ...agentHandle({ handle: "users-new-username" }, { role: "field", label: "The new user's username" }) })] }), _jsxs("label", { children: [t("Email (optional)"), _jsx("input", { type: "email", value: email, onChange: (e) => setEmail(e.target.value), ...agentHandle({ handle: "users-new-email" }, { role: "field", label: "The new user's email address" }) })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: passwordInputId, children: t("Password") }), _jsx(SecretInput, { label: t("Password"), toggleLabels: { show: t("Show password"), hide: t("Hide password") }, id: passwordInputId, autoComplete: "new-password", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 1, ...agentHandle({ handle: "users-new-password" }, {
15
17
  role: "field",
16
18
  label: "The new user's password — a credential field, so only a human can fill it",
17
19
  }) })] }), _jsx("button", { type: "submit", disabled: saving, ...agentHandle({ handle: "users-new-submit" }, { role: "button", label: "Create this operator account" }), children: saving ? t("Creating…") : t("Create user") })] }));
@@ -69,18 +71,6 @@ function UserDisableDialog({ confirmingDisable, setConfirmingDisable, toggleSavi
69
71
  function UserDeleteDialog({ confirmingDelete, setConfirmingDelete, deleteSaving, confirmDelete, t }) {
70
72
  return (_jsx(ConfirmDialog, { cancelLabel: t("Cancel"), open: confirmingDelete !== null, agentHandle: "users-delete", title: t("Delete this user?"), body: confirmingDelete ? (_jsxs("p", { children: [t("Delete"), " \"", confirmingDelete.username, "\"?", " ", t("They will be signed out and moved to the Trash. You can restore them there; they are deleted permanently after 60 days.")] })) : null, confirmLabel: t("Move to trash"), tone: "danger", pending: deleteSaving, onConfirm: confirmDelete, onCancel: () => setConfirmingDelete(null) }));
71
73
  }
72
- /** Open-eye glyph — standard "reveal" affordance, shown while a `RevealablePasswordField` is
73
- * hidden (click to show). House stroke-icon convention (`viewBox="0 0 18 18"`, `fill="none"`,
74
- * `stroke="currentColor"`, `strokeWidth={1.5}`, `aria-hidden`) — same shape `Media.tsx`'s
75
- * `PlaceholderIcon`/`ExpandIcon`/`CloseIcon` and `nav.ts`'s panel icons use throughout this app. */
76
- function EyeIcon() {
77
- return (_jsxs("svg", { viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", strokeWidth: 1.5, "aria-hidden": "true", children: [_jsx("path", { d: "M1.5 9S4.5 4 9 4s7.5 5 7.5 5-3 5-7.5 5-7.5-5-7.5-5Z", strokeLinejoin: "round" }), _jsx("circle", { cx: "9", cy: "9", r: "2" })] }));
78
- }
79
- /** Same glyph with a slash through it — standard "hide" affordance, shown while the field is
80
- * currently revealed (click to hide). */
81
- function EyeOffIcon() {
82
- return (_jsxs("svg", { viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", strokeWidth: 1.5, "aria-hidden": "true", children: [_jsx("path", { d: "M1.5 9S4.5 4 9 4s7.5 5 7.5 5-3 5-7.5 5-7.5-5-7.5-5Z", strokeLinejoin: "round" }), _jsx("circle", { cx: "9", cy: "9", r: "2" }), _jsx("path", { d: "M3 3 15 15", strokeLinecap: "round" })] }));
83
- }
84
74
  /** One password input plus its own independent show/hide toggle. `UserResetPasswordDialog` renders
85
75
  * this twice (new password, confirm new password) so each field can be revealed on its own — the
86
76
  * whole point of the confirm field is catching a typo by eye, which a single toggle driving both
@@ -89,22 +79,21 @@ function EyeOffIcon() {
89
79
  * twice.
90
80
  *
91
81
  * The toggle itself is icon-only (standard eye / eye-with-slash convention, per owner request —
92
- * not a text "Show"/"Hide" link): the accessible name still flips between "Show password"/"Hide
93
- * password" via `aria-label` (screen reader), and `aria-pressed` mirrors `visible` so assistive
94
- * tech gets the same on/off signal a text label would have given.
82
+ * not a text "Show"/"Hide" link): the accessible name flips between "Show <label>"/"Hide <label>"
83
+ * via `aria-label` (screen reader), which is the one on/off signal a text label would have given
84
+ * (no `aria-pressed` on top: that announced the state twice). The shared stroke icon keeps the
85
+ * open-eye/eye-with-slash convention without duplicating glyphs in this screen.
95
86
  *
96
- * The input's inline `flex`/`minWidth` override neutralizes `styles.css`'s `.field input { width:
97
- * 100% }` fighting the toggle button for room inside the `.editor-actions` row below — an inline
98
- * style wins on specificity without adding a new class to a stylesheet this feature doesn't own. */
87
+ * The shared `.jini-secret` wrapper gives the icon its own space: the host's `.field input {
88
+ * width: 100% }` must not make the input fight a sibling toggle for room. */
99
89
  function RevealablePasswordField({ id, label, value, onChange, visible, onToggleVisible, agentBase, t }) {
100
- const toggleLabel = visible ? t("Hide password") : t("Show password");
101
- return (_jsxs("div", { className: "field", children: [_jsx("label", { className: "field-label", htmlFor: id, children: label }), _jsxs("span", { className: "editor-actions", children: [_jsx("input", { id: id, type: visible ? "text" : "password",
102
- // `new-password`, not `off` (Chrome ignores `off`): without it Chrome filled a saved
103
- // login into "New password" only. Kept while revealed as `type="text"`, too.
104
- autoComplete: "new-password", value: value, onChange: (e) => onChange(e.target.value), style: { flex: "1 1 auto", minWidth: 0 }, ...agentHandle({ handle: agentBase }, {
105
- role: "field",
106
- label: `${label} — a credential field, so only a human can fill it`,
107
- }) }), _jsx("button", { type: "button", className: "link-button", "aria-label": toggleLabel, "aria-pressed": visible, title: toggleLabel, onClick: onToggleVisible, ...agentHandle({ handle: `${agentBase}-reveal` }, { role: "button", label: `Show or hide the ${label.toLowerCase()} field's characters` }), children: _jsx("span", { style: { display: "inline-flex", width: 16, height: 16 }, children: visible ? _jsx(EyeOffIcon, {}) : _jsx(EyeIcon, {}) }) })] })] }));
90
+ return (_jsxs("div", { className: "field", children: [_jsx("label", { className: "field-label", htmlFor: id, children: label }), _jsx(SecretInput, { id: id, label: label, toggleLabels: { show: `${t("Show")} ${label}`, hide: `${t("Hide")} ${label}` }, revealed: visible, onRevealedChange: onToggleVisible, toggleAttrs: agentHandle({ handle: `${agentBase}-reveal` }, { role: "button", label: `Show or hide the ${label.toLowerCase()} field's characters` }),
91
+ // `new-password`, not `off` (Chrome ignores `off`): without it Chrome filled a saved
92
+ // login into "New password" only. Kept while revealed as `type="text"`, too.
93
+ autoComplete: "new-password", value: value, onChange: (e) => onChange(e.target.value), style: { flex: "1 1 auto", minWidth: 0 }, ...agentHandle({ handle: agentBase }, {
94
+ role: "field",
95
+ label: `${label} — a credential field, so only a human can fill it`,
96
+ }) })] }));
108
97
  }
109
98
  /** Resolves `useFields` to the real hook when no override is passed, because ESLint's
110
99
  * cyclomatic-complexity rule counts a default parameter value evaluated inside a function's OWN
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/user-management",
3
- "version": "0.6.11",
3
+ "version": "0.6.15-rc.1",
4
4
  "description": "Runtime-separated user management contracts and Node authentication, authorization, and administration services.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -87,16 +87,16 @@
87
87
  "registry": "https://registry.npmjs.org"
88
88
  },
89
89
  "dependencies": {
90
- "@jini-ai/core": "^0.6.11"
90
+ "@jini-ai/core": "^0.6.15-rc.1"
91
91
  },
92
92
  "peerDependencies": {
93
93
  "argon2": "^0.44.0",
94
94
  "react": "^18.3.0 || ^19.0.0",
95
95
  "react-dom": "^18.3.0 || ^19.0.0",
96
- "@jini-ai/ui": "^0.6.10",
97
- "@jini-ai/admin": "^0.6.10",
98
- "@jini-ai/agentic": "^0.6.10",
99
- "@jini-ai/ui-kit": "^0.6.10"
96
+ "@jini-ai/ui": "^0.6.15-rc.1",
97
+ "@jini-ai/admin": "^0.6.15-rc.1",
98
+ "@jini-ai/agentic": "^0.6.15-rc.1",
99
+ "@jini-ai/ui-kit": "^0.6.15-rc.1"
100
100
  },
101
101
  "peerDependenciesMeta": {
102
102
  "argon2": {
@@ -134,12 +134,12 @@
134
134
  "@testing-library/user-event": "^14.5.0",
135
135
  "@testing-library/jest-dom": "^6.6.3",
136
136
  "jsdom": "^25.0.1",
137
- "@jini-ai/ui": "0.6.11",
138
- "@jini-ai/cms": "0.6.11",
139
- "@jini-ai/core": "^0.6.11",
140
- "@jini-ai/admin": "0.6.11",
141
- "@jini-ai/agentic": "0.6.11",
142
- "@jini-ai/ui-kit": "^0.6.11"
137
+ "@jini-ai/ui": "0.6.15-rc.1",
138
+ "@jini-ai/agentic": "0.6.15-rc.1",
139
+ "@jini-ai/cms": "0.6.15-rc.1",
140
+ "@jini-ai/core": "^0.6.15-rc.1",
141
+ "@jini-ai/admin": "0.6.15-rc.1",
142
+ "@jini-ai/ui-kit": "^0.6.15-rc.1"
143
143
  },
144
144
  "scripts": {
145
145
  "build": "tsc -p tsconfig.json",