Skip to content

Admin users: a PATCH applies all of its fields or none; descriptions refuse bidi overrides - #701

Merged
schmitch merged 1 commit into
mainfrom
schmitch/admin-users-patch-585
Oct 30, 2026
Merged

schmitch merged 1 commit into
mainfrom
schmitch/admin-users-patch-585

Conversation

@schmitch

Copy link
Copy Markdown
Member

Fixes #585

The three defects of the user and service-account administration:

PATCH /admin/users/{id} in separate transactions

update committed description and realm (update_service_account), then applied active in a second transaction (set_active_guarded). Now the request is checked first (service_account_changes: the account exists and is a service account, the description parses, the realm is a trusted-header realm) and then applied in one transaction (apply_update): description, realm, then active under the last-admin guard. When active is absent the account is read in that transaction too, which is why user_dao::find_user_by_id now takes any executor (every caller passes a pool, so nothing else changes). The side effects of a deactivation (sessions, verdicts, invitation) still run after the commit, as before.

Test (service_accounts.rs): a test-only trigger makes the deactivation fail in the database. {"active": false, "description": …, "realm": …} then answers 500 and the account keeps its description and no realm; without the trigger all three apply at once. On main the test fails with the account still active in the new realm.

The service account page re-sent the realm

ServiceAccountSettings.save now sends only the fields that changed, so saving a description after the account's realm was renamed or deleted no longer fails with 400. The web tests' fake admin API now refuses an unknown realm like the server does; a new test saves a description with the realm gone (it fails on the old page), and the existing one now expects {realm: null} alone for a realm change.

Descriptions accepted format characters

parse_description refused Cc but accepted Cf, such as U+202E. It now also refuses Unicode format characters (is_format_character: bidi embeddings, overrides and isolates, zero-width and other invisible characters, tags), except U+200C/U+200D, which emoji and several scripts need. The 400 names the code point. This applies on create and update; docs/deployment.md states the rule.

Checks run

  • cargo test -p forge-server --test service_accounts --test admin (4 + 45), the new unit test admin::users::tests
  • web: pnpm run tsc, pnpm exec vitest run (523 passed)
  • cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --all --check, cargo xtask changelog --check

Changelog: docs/changes/2.2.0/585.fixed.md.


Generated by Claude Code

`PATCH /admin/users/{id}` committed a service account's `description`
and `realm` in one transaction and then applied `active` in another:
`{"active": false, "realm": "lb"}` left the account active in its new
realm for a moment, and a failure in the second step left the first
applied. The request is now checked first (the account is a service
account, the description and realm are accepted) and then applied in
one transaction: the description, the realm, and `active` under the
last-admin guard. `find_user_by_id` takes any executor, so the
unchanged account is read in that transaction too. The test makes the
deactivation fail in the database and checks that nothing else was
applied (on the old code the account kept its new realm).

The service account page sent `realm` with every save, so once the
realm was renamed or deleted after the page loaded, saving a new
description alone failed with 400. It now sends only the fields that
changed; the fake admin API of the web tests refuses an unknown realm
like the server.

`parse_description` refused control characters (Cc) but accepted
format characters (Cf) such as U+202E, which made a description read
otherwise in the admin list than it is. It now refuses them too, all
but the zero-width non-joiner and joiner that emoji and several scripts
need.

Fixes #585

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012e6DJZaXD2mBH9tURsdutM
@schmitch
schmitch added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 4621bac Oct 30, 2026
19 checks passed
@schmitch
schmitch deleted the schmitch/admin-users-patch-585 branch October 30, 2026 05:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Admin users: a PATCH applies its fields in separate transactions; the service account page re-sends the realm; descriptions accept bidi overrides

2 participants