fix(server): revoke viewer tokens on logout + stop logging chat content
Security follow-ups (audit 2026-05-30, both reviewed APPROVE): - MEDIUM: viewer tokens were never blacklisted on logout, so a minted session-scoped viewer token stayed valid up to its 5-min TTL after the user logged out. Add a per-user ViewerTokenRegistry (Arc<Mutex<HashMap<sub, Vec<(token, expires_at)>>>>, prune-on-insert) on AppState; mint_viewer_token registers each token under the user sub; logout drains take_for_user(sub) and blacklists each via the existing token_blacklist. The viewer WS already calls is_revoked, so no WS change. Key chain user.user_id == ViewerClaims.sub == registry key verified consistent. 8 new tests. - LOW: relay chat logs now emit content length, not the chat body (support-chat can carry secrets/PII). cargo fmt/clippy(-D warnings)/test green on GURU-5070 (37 agent + 61 server). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,10 @@ use tower_http::trace::TraceLayer;
|
||||
use tracing::{info, Level};
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
|
||||
use auth::{generate_random_password, hash_password, AuthenticatedUser, JwtConfig, TokenBlacklist};
|
||||
use auth::{
|
||||
generate_random_password, hash_password, AuthenticatedUser, JwtConfig, TokenBlacklist,
|
||||
ViewerTokenRegistry,
|
||||
};
|
||||
|
||||
/// Root of the static asset tree, relative to the server's working directory.
|
||||
/// Holds the agent `downloads/` tree AND the v2 SPA build under `app/`.
|
||||
@@ -66,6 +69,13 @@ pub struct AppState {
|
||||
db: Option<db::Database>,
|
||||
pub jwt_config: Arc<JwtConfig>,
|
||||
pub token_blacklist: TokenBlacklist,
|
||||
/// Per-user registry of outstanding session-scoped viewer tokens. Minting a
|
||||
/// viewer token registers it here under the minting user's `sub`; logout
|
||||
/// drains the user's registered viewer tokens into `token_blacklist` so a
|
||||
/// just-logged-out user cannot keep a live viewer/remote-control plane until
|
||||
/// the token's natural 5-minute expiry. The viewer WS already blacklist-
|
||||
/// checks the exact token string, so no WS change is needed.
|
||||
pub viewer_tokens: ViewerTokenRegistry,
|
||||
/// Optional API key for persistent agents (env: AGENT_API_KEY)
|
||||
pub agent_api_key: Option<String>,
|
||||
/// Prometheus metrics
|
||||
@@ -291,6 +301,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
// Create application state
|
||||
let token_blacklist = TokenBlacklist::new();
|
||||
let viewer_tokens = ViewerTokenRegistry::new();
|
||||
|
||||
let state = AppState {
|
||||
sessions,
|
||||
@@ -298,6 +309,7 @@ async fn main() -> Result<()> {
|
||||
db: database,
|
||||
jwt_config,
|
||||
token_blacklist,
|
||||
viewer_tokens,
|
||||
agent_api_key,
|
||||
metrics,
|
||||
registry,
|
||||
|
||||
Reference in New Issue
Block a user