Skip to content

feat(reborn): minimal multi-user local auth — StaticUserTokenAuthenticator (#5272)#5286

Closed
zetyquickly wants to merge 1 commit into
feat/reborn-user-rolefrom
feat/local-user-token-auth
Closed

feat(reborn): minimal multi-user local auth — StaticUserTokenAuthenticator (#5272)#5286
zetyquickly wants to merge 1 commit into
feat/reborn-user-rolefrom
feat/local-user-token-auth

Conversation

@zetyquickly

Copy link
Copy Markdown
Member

What

Implements #5272 — the prerequisite that lets one operator act as several users on localhost, so per-user capability policy can be driven by hand.

StaticUserTokenAuthenticator (a WebuiAuthenticator, in ironclaw_reborn_webui_ingress) maps several bearer user-tokens to distinct (UserId, UserRole) pairs. Swap the bearer token → become director@ (Admin), Bob / Carol (Member), or shared engineering@ (Member). Unlike EnvBearerAuthenticator (single token → single operator), the resolved UserRole travels with the authentication, so the role-gated admin surface (#5268) and the per-(tenant, user) dispatch principal (#5267) both work per token.

  • from_json parses an IRONCLAW_REBORN_USER_TOKENS JSON array of {token, user_id, role} rows (role accepts owner/admin/member, defaults to member).
  • Constant-time, non-short-circuiting token comparison (no token-content or position timing leak), modeled on EnvBearerAuthenticator.
  • Rejects an empty table, an empty token, an invalid user_id, and malformed JSON.
  • mounts_operator_webui_config_routes() == false — deployment-wide operator config stays with the separate operator credential (per the composition guardrail: multi-user authenticators don't grant operator_webui_config). Admin-ness is carried by the role, consumed by [capability-policy] Admin REST surface (four dimensions) + REST action catalog #5268's is_admin() gate.

Multi-user is not blocked

The turn-runner already resolves thread scope per-turn from the actor (ThreadScopeResolver::resolve_for_turn; owner_scoped_thread_scope_isolates_each_caller locks that each caller gets its own owners/<user> subtree). So multiple users through one serve process are supported at the scope layer — this authenticator supplies the per-request identity + role.

Scope / follow-on

This PR ships the authenticator primitive (the issue's core). Wiring it into the ironclaw-reborn serve command — selecting it from IRONCLAW_REBORN_USER_TOKENS, and handling the base runtime owner + session-signing-secret (today both derived from the single env token) — is a separable integration step, deliberately kept out of this PR to avoid destabilizing the existing single-user serve path. That wiring lands with the manual-test enablement.

Tests

5 unit tests: token → user+role mapping, role-default, unknown/empty/prefix rejection, invalid-config rejection, no operator routes. Crate green under cargo clippy -p ironclaw_reborn_webui_ingress --all-features --tests -- -D warnings and the full --all-features test suite.

Stacking

Stacked on #5266 (PR #5270, base feat/reborn-user-role) — that PR put UserRole on the WebChat-v2 caller / WebuiAuthentication. Part of epic #5261.

🤖 Generated with Claude Code

…cator (#5272)

A `WebuiAuthenticator` that maps several bearer user-tokens to distinct
`(UserId, UserRole)` pairs, so one operator can act as several users —
`director@` (Admin), `Bob` / `Carol` (Member), shared `engineering@` (Member)
— by swapping the bearer token. Unlike `EnvBearerAuthenticator` (single token
-> single operator), the resolved `UserRole` travels with the authentication,
so the role-gated admin surface (#5268) and the per-(tenant, user) dispatch
principal (#5267) both work per token.

- `StaticUserTokenAuthenticator::from_json` parses an
  `IRONCLAW_REBORN_USER_TOKENS` JSON array of `{token, user_id, role}` rows
  (`role` defaults to least-privilege Member).
- Constant-time, non-short-circuiting token comparison (no content/position
  timing leak); rejects empty table / empty token / invalid user_id / bad JSON.
- `mounts_operator_webui_config_routes() == false` — deployment-wide operator
  config stays with the separate operator credential (per the composition
  guardrail); admin-ness is carried by the role, not operator capabilities.

Local-dev / standalone only; production multi-user uses Session/OIDC. The
turn-runner already isolates each caller's threads per-turn via
`ThreadScopeResolver::resolve_for_turn`, so multiple users through one process
are supported at the scope layer; wiring this authenticator into the `serve`
command (env selection + base-owner handling) is the follow-on integration.

Tests: 5 unit tests (token->user+role mapping, role default, unknown/empty/
prefix rejection, invalid-config rejection, no operator routes). Crate green
under `--all-features` clippy `-D warnings`.

Part of #5261. Depends on #5266 (UserRole on the caller).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@railway-app railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5286 June 26, 2026 02:55 Destroyed
@github-actions github-actions Bot added size: M 50-199 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jun 26, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • staging
  • reborn-integration

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cd3c3986-9b48-4f3a-b35b-08a72ff65a7a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@railway-app

railway-app Bot commented Jun 26, 2026

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-5286 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jun 26, 2026 at 3:01 am

@zetyquickly

Copy link
Copy Markdown
Member Author

Superseded by the REST-user rework (issue #5272, redesigned). The env-token StaticUserTokenAuthenticator + IRONCLAW_REBORN_USER_TOKENS path is dropped in favor of REST-created users (durable LocalUserDirectoryStore + admin POST/GET/DELETE /admin/users + PUT /role, returning a one-time bearer). The replacement lands on #5270 (control plane). Closing per epic #5261 de-nest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules size: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant