feat(web): token-authenticated control.ashx upgrade (VS-32) #47

Merged
David merged 2 commits from feat/control-token-upgrade-vs-32 into main 2026-06-07 00:02:36 +02:00
Owner

VS-32: Token-authenticated control channel upgrade (browser cross-origin)

Problem

/control.ashx authenticated the WebSocket upgrade exclusively from the mc_session cookie. A browser that logged in through vervain-apps holds its session as an HttpOnly vervain_session cookie on its own origin and can never present mc_session cross-origin, which blocked a direct persistent control connection (VAPP-14).

Change

A single-use, 60 s control-upgrade token bound to the session's userid:

  • New control_token::ControlTokenStore (in-memory, auto-GC, mirrors OtpStore). mint issues an opaque base64-url token; consume resolves and removes it (single-use), rejecting expired entries.
  • The authcookie control action mints one and returns it as a new controlToken field alongside the existing cookie / rcookie.
  • control_ws_handler: when mc_session is absent, accept ?auth=<token>, validate + consume it, and proceed with the resolved userid. The cookie path is unchanged; the token authenticates the upgrade only and the socket keeps today's session semantics.

Acceptance criteria

  • authcookie returns a control token alongside the existing cookies (controlToken field)
  • Upgrade with a valid ?auth= token succeeds with the correct user identity; reused or expired tokens get 401
  • Cookie-based upgrades behave exactly as before
  • Integration test covering mint, connect, reuse-rejection, expiry (tests/control_token.rs) plus store unit tests
  • VAPP-14 link present (this issue is its dependency)

Tests

cargo test -p meshcentral-web green: 5 new control_token store unit tests, updated authcookie_wire_shape, and a 4-case integration suite (token connect + identity, reuse 401, expiry 401, unknown-token 401, unchanged cookie path). Clippy clean.

#VS-32

## VS-32: Token-authenticated control channel upgrade (browser cross-origin) ### Problem `/control.ashx` authenticated the WebSocket upgrade exclusively from the `mc_session` cookie. A browser that logged in through vervain-apps holds its session as an HttpOnly `vervain_session` cookie on its own origin and can never present `mc_session` cross-origin, which blocked a direct persistent control connection (VAPP-14). ### Change A single-use, 60 s control-upgrade token bound to the session's userid: - New `control_token::ControlTokenStore` (in-memory, auto-GC, mirrors `OtpStore`). `mint` issues an opaque base64-url token; `consume` resolves and removes it (single-use), rejecting expired entries. - The `authcookie` control action mints one and returns it as a new `controlToken` field alongside the existing `cookie` / `rcookie`. - `control_ws_handler`: when `mc_session` is absent, accept `?auth=<token>`, validate + consume it, and proceed with the resolved userid. The cookie path is unchanged; the token authenticates the upgrade only and the socket keeps today's session semantics. ### Acceptance criteria - [x] `authcookie` returns a control token alongside the existing cookies (`controlToken` field) - [x] Upgrade with a valid `?auth=` token succeeds with the correct user identity; reused or expired tokens get 401 - [x] Cookie-based upgrades behave exactly as before - [x] Integration test covering mint, connect, reuse-rejection, expiry (`tests/control_token.rs`) plus store unit tests - [x] VAPP-14 link present (this issue is its dependency) ### Tests `cargo test -p meshcentral-web` green: 5 new `control_token` store unit tests, updated `authcookie_wire_shape`, and a 4-case integration suite (token connect + identity, reuse 401, expiry 401, unknown-token 401, unchanged cookie path). Clippy clean. #VS-32
feat(web): token-authenticated control.ashx upgrade for cross-origin browsers
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 1m34s
8519be91ed
The /control.ashx WebSocket upgrade authenticated exclusively from the mc_session cookie. A browser that logged in through vervain-apps holds its session as an HttpOnly cookie on a different origin and can never present mc_session, which blocked it from opening a direct persistent control connection (VAPP-14).

Add a single-use, 60 s control-upgrade token: the authenticated SPA control channel mints one via the authcookie action (returned as the new controlToken field alongside cookie/rcookie), and a cross-origin browser passes it as ?auth=<token> on the upgrade. When mc_session is absent the handler validates and consumes the token, resolving the bound userid. Tokens live in a new in-memory ControlTokenStore (mirrors OtpStore) and are consumed on first use, so replays and expired tokens are rejected with 401. The cookie path is unchanged; the token authenticates the upgrade only and the socket keeps today's session semantics.

Covered by control_token store unit tests plus a control_token integration suite exercising mint, connect-with-correct-identity, reuse-rejection, expiry, unknown-token, and the unchanged cookie path.

#VS-32
Merge branch 'main' into feat/control-token-upgrade-vs-32
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt + clippy + build + tests (pull_request) Successful in 1m48s
ae735cf14e
David merged commit 739b8a6f1d into main 2026-06-07 00:02:36 +02:00
David deleted branch feat/control-token-upgrade-vs-32 2026-06-07 00:02:36 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-server!47
No description provided.