Repository navigation
ui: let operators manage a user from the user page - #1253
Conversation
|
Thanks for opening this pull request! A SPACE maintainer will take a look. CONTRIBUTING.md explains how we review outside contributions. |
zibi-pplx
left a comment
There was a problem hiding this comment.
The server enforces the role, delete and password rules, and the UI now adds confirmations for each. One note on how a refused delete is explained.
- [low] The 409 branch labels the wrong refusal as sandbox ownership.
ui/src/lib/user-delete.ts:21: today,delete_useranswers 409 only forSelfMutation("cannot modify your own role or delete yourself",crates/libs/api/src/routes/admin/identity.rs:35, mapped at:84). The foreign-key refusal comes back as a 500, because the handler runs a plainDELETE(identity.rs:453) and the error converts toInternal. So a 409 would be relabelled "owns sandboxes". It isn't reachable now, because Delete is hidden on your own row.- Suggestion: keep the API's message for a 409 until the server answers the ownership refusal with its own status.
- Suggestion: open a tracking issue for returning a 4xx on that foreign-key violation, so the 500 heuristic, which reads the org's sandboxes on a 500, can go once it lands.
Withdrawing this approval for now. Any feedback above still stands.
The user detail page showed five fields and a role select. Resetting a
user's password, which docs/installation.md documents through
PATCH /admin/api/v1/users/{id}, needed curl; so did correcting a name or
email. The page did not link to the user's organization, sandboxes or
keys, and role changes, including promotion to organization admin of
the managing organization, applied on a single click.
User detail now has Edit (name, email), Set password and Delete, links
the organization, and lists the user's sandboxes (among the org's newest
1000) and the API keys they created. Role changes on the user page and
in the Users table ask for confirmation first. The Users page opens on
the signed-in admin's own organization instead of the first one listed,
and organization detail shows role labels instead of raw role keys.
The user page's delete confirmation said a deleted user's sandboxes and API keys stay with the organization. Deleting a user removes the users row: sessions and credentials go with it, API keys they created stay without a creator, and the database refuses the delete while the user owns any sandbox or archived sandbox, which the admin API currently reports as a 500. Both delete confirmations (user page and Users table) now say that. On the user page Delete is disabled, with the reason, when the user's sandbox list is not empty; that list covers the organization's newest 1000, so a refused delete is still possible and now reads as an explanation of the ownership rule rather than "internal server error".
- Both delete confirmations say that API keys the user created are not revoked and keep authenticating for the organization. - A refused delete is explained as sandbox ownership only for a 409, or a 500 when the user's sandboxes are known to exist; any other failure, including a retryable 503, shows the API's message. The users list reads the org's sandboxes on a 500 to tell. - Edit opened with empty fields, because the values were set before the dialog's form existed; they are now its initial values. The email rule no longer rejects seeded accounts such as dev@local. - Your own page offers Edit and Set password, as the API allows; the role and Delete stay hidden. Set password is hidden in external sign-in. - The sandbox and API key lists each show their own error with Retry instead of loading forever, and Delete says when the sandbox list is unavailable. The sandbox list reads the org's newest 5000, as the org page does. - The password limit is 72 bytes, checked as bytes. The role-change text promises console access only for users in the managing org.
- Under external sign-in your own email is read-only: the provider's email is matched exactly against it, so saving a changed one would lock you out with no way back. The display name stays editable; password-mode self-edits and edits of other users are unchanged. - Setting a password ends every session of that user. The dialog says so; after changing your own, the console clears its sign-in and the login page asks you to sign in with the new password. Resetting another user's password leaves your session alone. - A save no longer puts its own copy of the user on screen: each save reloads the user, and only the newest reload applies, so a slower save cannot restore an outdated role. - When the cluster record did not load, the role-change text says whether the role gives console access could not be determined, instead of saying it gives none.
After a successful save the page reloads the user and drops the answer when that reload fails, so it kept showing the user as before the save. A later Edit then started from that copy and sent the replaced email back: save email B, the reload answers 503, the page still shows A, and the next display-name edit writes A again. When the reload fails, the page now applies the save's own answer, under the same newest-request check, and says "Saved; the page could not reload the user: <error>" with Retry. The login page took its one-time notice in an effect that set the notice to whatever it took, so a second run of the effect (React Strict Mode in development) would set it back to nothing. It now keeps what the first run took.
The edit dialog kept one form store across opens. Its form remounts on each open, but a remounted form's initial values do not replace what the store still holds, so after a save the dialog reopened with the values of the previous open: edit the email, save, open Edit again, and it showed the old email, which a second save wrote back. Development builds hid it, because React Strict Mode mounts the form twice. The dialog is now its own component, mounted only while open, with its own form, so each open starts from the user as the page shows it.
The delete failure explained every 409 as the user owning sandboxes, but the admin API answers 409 only when callers try to delete themselves; the database's ownership refusal comes back as a 500. A 409 now shows the API's message. The 500 explanation, used while the page shows the user owns sandboxes, stays until the API answers that refusal with its own status, as the comment says.
f755255 to
d9b00da
Compare
johnlouie-pplx
left a comment
There was a problem hiding this comment.
Approving.
Since 5d00c2d the only change is d9b00da: a 409 now keeps the API's message. That's right, because delete_user returns 409 only for SelfMutation, and the delete text no longer blames sandbox ownership for it. Mutating the 409 back to the ownership text fails the updated test. I also rechecked the parts main touched under the rebase. require_admin_auth, delete_user and the users/sessions/api-keys foreign keys are unchanged, and the role confirmation's unknown-cluster branch covers main's new null cluster. I ran npm test (151 pass) and tsc on d9b00da, plus next build on the stack head. I didn't run it in a browser or against a live API.
Non-blocking: the ownership refusal still comes back as a 500 from delete_user, so the 500-plus-visible-sandboxes heuristic, and the Users table's extra read, stay until the API maps the sandboxes_user_id_fkey violation to its own status. Worth a small API follow-up when you get to it.
|
Thanks. That API follow-up is #1303, which is in review. |
Summary
Operators can now manage a user from the user page instead of through curl.
Before this change:
docs/installation.mddocuments throughPATCH /admin/api/v1/users/{id}, needed curl, and so did correcting a name or email.Changes:
dev@local. Both delete confirmations, here and in the Users table, say: "Their sessions end. API keys they created are not revoked and keep authenticating for the organization; delete them on the API keys page first if this user's access should end. The delete is refused while they own sandboxes or archived sandboxes." (api_keys.created_byisON DELETE SET NULL; the sandbox refusal is a foreign key onsandboxes.user_id, which the admin API currently answers with a 500.) Delete is disabled, with that reason, while the page lists sandboxes the user owns. A refused delete is explained as sandbox ownership only for a 500 when the user's sandboxes are known to exist (the Users table reads the organization's sandboxes on a 500 to tell); a code comment says this stays until the server answers the foreign-key refusal with its own status. Any other failure shows the API's message, including a 409, which the admin API returns today only when an operator tries to delete themselves, and a retryable 503;?org=, which api, ui: show and filter sandboxes by organization #1248 adds;organization_admin).Stack
Stacked on #1247, which adds the shared load-failure handling this page uses. Merge #1247 first. The "organization's sandbox list" link filters with
?org=, which #1248 adds, so #1248 must merge first for that link to filter. #1262 (organization page) is stacked on this PR.Validation
npm ci,npm test(60 pass, includingsrc/lib/user-delete.test.mjsfor the delete text, the refusal rules by status and the byte-counted password limit, andsrc/lib/user-roles.test.mjsfor the role-change text, including an unknown cluster record) andnpm run buildinui/.space-uiimage):dev@localsaves;main.npm test(60 pass) now expects the API's message for a 409, with and without owned sandboxes, andnpx tsc --noEmit -p .reports no errors. In the server,delete_user(crates/libs/api/src/routes/admin/identity.rs) returns 409 only for a self-target ("cannot modify your own role or delete yourself"), and theDELETE FROM usersforeign-key refusal reaches the client as a 500.Visual confirmation
Setting a password:
Video: after.
A delete answered with a synthetic 409, on an earlier head of this branch and on this head: the page now shows the API's message instead of the ownership explanation:
Severity and risk
Severity: medium. Password resets are a routine support task.
Risk: low. Every action uses existing admin routes with the same permission checks. Roll back by reverting the commit.
AI assistance: Claude Code wrote the code and this description, and ran the validation; a maintainer reviews it.