feat: v2.7.6 — edit users in one form, Settings → Account, display names, copy over plain HTTP
- Users page: one Edit user form (display name, email, role, active, a new or generated password, workspaces given or taken back, sign out everywhere), validated as a whole before anything is written (PATCH /api/users/:id, GET /api/users/:id); users.display_name/email added migration-safe. - Settings → Account for every signed-in user: profile, own password (the current one required, this login kept), sign out other devices (/api/me/account, /api/me/password, /api/me/sign-out-others; login cookie only, never a pane token). - Display names wherever a person is named (sidebar, workspace owner groups, Users page and audit log); owner_display_name / reader_display additive. - Users page: admins add and remove Claude/Codex pool accounts. - Folder browser: an admin with their own Linux account opens in their home; the "Server folder" tick opens the host's (never remembered). - AI Proxy: "Config 9router" beside Add profile when 9router is installed, opened on the dashboard's own host (GET /api/ai-proxies/9router). - Terminal: a tmux selection reaches the clipboard over plain HTTP too (writeClipboard's textarea fallback, focus handed back). - Chat: a file the session touched outside the workspace previews (GET /api/sessions/:id/file). - install-system: `!requiretty` only when visudo accepts it (sudo-rs on Ubuntu 25.10+ rejects the setting). - openapi.yaml regenerated; docs (API, SERVER-MODE, LANES) updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+71
-3
@@ -115,7 +115,8 @@ their own workspaces (`useWorkLanes`); other users' work is read on Sessions,
|
||||
Analytics and Users, where Resume, Fork, Snapshot and "Open in workspace" are
|
||||
hidden for a session that is not theirs. The sidebar's workspace list shows an
|
||||
admin or analyst EVERY workspace, grouped under its owner (each lane carries
|
||||
`owner_name`; yours first, no owner last, each group folds): for an analyst
|
||||
`owner_name` and `owner_display_name`; the group shows the display name when
|
||||
the owner set one, else the username; yours first, no owner last, each group folds): for an analyst
|
||||
another user's workspace there is read only — no menu, rename or new session,
|
||||
no live tabs, and a session opens its analysis page (`/sessions/<id>`). An analyst's alert feed
|
||||
(`GET /api/alerts`) carries `can_ack` on each alert — true only for their own
|
||||
@@ -259,13 +260,36 @@ settings file that lacks the variable falls back to `USERS_ROOT` in
|
||||
## Accounts
|
||||
|
||||
An admin manages accounts on the **Users** page (sidebar; admins and analysts
|
||||
see it, an analyst only reads): add a user, change a role, disable or enable,
|
||||
reset a password, give a workspace. Every change asks for the admin's OWN
|
||||
see it, an analyst only reads): **Add user**, and one **Edit** form per account
|
||||
that changes, in a single save, its display name and email, role,
|
||||
active/disabled, password (typed or generated), the workspaces it owns (give
|
||||
one, or take one back — it is then left with no owner) and "sign out
|
||||
everywhere". Every field is checked before any is written, so a bad email or
|
||||
a workspace the user does not own changes nothing. Every change asks for the admin's OWN
|
||||
password again — the server refuses without it (`EREAUTH`, and `ETOOMANY`
|
||||
after 10 wrong tries in 15 minutes) — an admin can never demote or disable
|
||||
themselves (`ESELF`), and each change is written to the audit log. In local
|
||||
mode these routes refuse (`ELOCALMODE`): there is no login to check.
|
||||
|
||||
Wherever the dashboard names a person — the sidebar's sign-out tooltip, the
|
||||
workspace list's owner groups, the Users page and its audit log, the Edit user
|
||||
form — it shows their display name when one is set, else the username (the
|
||||
Users page keeps `@username` beside a display name). Logging in, the CLI and
|
||||
the Linux account always use the username.
|
||||
|
||||
Under S3 an admin's folder browser opens on their own home too; ticking
|
||||
**Server folder** opens the host account's home (`/home/<host>`), where a
|
||||
workspace runs as the host account that every admin shares. The tick is never
|
||||
remembered, so the default is always the admin's own account.
|
||||
|
||||
Every signed-in user has **Settings → Account**: their username, role, member
|
||||
since, workspace and session counts and active logins; their own display name
|
||||
and email; **Change password** (the current password is required, the login
|
||||
it is changed from stays and every other one ends); and **Sign out other
|
||||
devices**. These are taken from the login cookie only — an agent in a pane,
|
||||
which acts as its workspace owner through its pane token, cannot change its
|
||||
owner's password or logins.
|
||||
|
||||
The same from the host's shell:
|
||||
|
||||
```bash
|
||||
@@ -296,6 +320,43 @@ agenthub accounts list # live panes, $ in the last 5 h / 7 d, quota, stat
|
||||
agenthub accounts remove team-1
|
||||
```
|
||||
|
||||
In server mode an admin can do the same on the **Users** page, in the
|
||||
**Account pool** card: **Add account** and the bin icon to remove one. A Claude
|
||||
account comes from one of three sources:
|
||||
|
||||
- **A saved login**: a profile from Settings → Claude config → Switch account
|
||||
(log in with `claude auth login`, then save it). It is MOVED into the pool, so
|
||||
the profile leaves the switcher. The account this server's own Claude Code is
|
||||
logged into can be moved in too. It moves with the login the CLI holds right
|
||||
now, keeps working on the server, and the server's own `.credentials.json`
|
||||
becomes one more home the pool refreshes and writes into, keeping its other
|
||||
keys. Only a login kept in the macOS Keychain is refused (`EACTIVE`). Removing
|
||||
that account later leaves the server logged in.
|
||||
- **A pasted `.credentials.json`**: from `~/.claude/.credentials.json`, or on a
|
||||
Mac `security find-generic-password -s "Claude Code-credentials" -w`. Stop
|
||||
using that login where you copied it from.
|
||||
- **A `claude setup-token` token** (`sk-ant-…`), as with `agenthub accounts add`.
|
||||
|
||||
A Codex account is an OpenAI API key, or the whole `~/.codex/auth.json` after
|
||||
`codex login`.
|
||||
|
||||
A **login** (the first two sources) works like a Codex ChatGPT login. Claude
|
||||
Code reads it from no env var, so before a member's pane starts, AgentHub writes
|
||||
it as that member's `~/.claude/.credentials.json`, as the member (0600). It is
|
||||
never written into a host pane's home, never over a login the member made
|
||||
themselves, and never a second account into a home where a pane of theirs runs
|
||||
on one. Host panes therefore get only setup-token accounts. The refresh token of
|
||||
a login is single use, so the server is the one that refreshes it, before it
|
||||
expires (`claude-pool.refreshLogins`, checked every 60 s; `DASHBOARD_POOL_REFRESH_MS=0` turns it off). It then writes the new pair into every
|
||||
home it placed the login in, and Claude Code picks up tokens another process
|
||||
refreshed. A pair a member's Claude refreshed first is copied back only after
|
||||
Anthropic confirms it is the same account. Removing the account deletes it from
|
||||
the homes that still hold AgentHub's copy. Each
|
||||
change asks for the admin's own password again and is written to the audit log
|
||||
by account name. The token only goes in; the page, like `agenthub accounts list`,
|
||||
never shows it again. An analyst sees the card read-only; a developer does not
|
||||
see it. Every user shares the pool: a user created later needs no setup of their own.
|
||||
|
||||
How a new session picks its account (`server/lib/claude-pool.js`):
|
||||
|
||||
1. An account whose last quota reading is at or above 90% in the 5-hour or
|
||||
@@ -308,6 +369,13 @@ How a new session picks its account (`server/lib/claude-pool.js`):
|
||||
- The pane gets the token through tmux `-e CLAUDE_CODE_OAUTH_TOKEN=…`. That
|
||||
token outranks the machine's own `/login` for that process only.
|
||||
- A session on an AI Proxy keeps the proxy's key and does not use the pool.
|
||||
- With one Linux user per account (S3), a member's home starts empty, and a
|
||||
fresh Claude Code shows its "Select login method" screen even with the token
|
||||
set. So before a member's pane starts on a pool account or an AI Proxy,
|
||||
AgentHub adds `hasCompletedOnboarding: true` to that member's
|
||||
`~/.claude.json`, written as the member, with every other key kept
|
||||
(`run-as.markClaudeOnboarded`). With an empty pool and no proxy, a member's
|
||||
pane still needs that member's own `/login`.
|
||||
- A token more than a year old is skipped.
|
||||
- A resumed session picks an account again, like a new one.
|
||||
- Quota is read with each account's own token, in the background, at most every
|
||||
|
||||
Reference in New Issue
Block a user