fix(sessions): hash tokens at rest and revoke on ban/kick #95

Merged
icub3d merged 1 commit from feature/78-session-hashing into main 2026-07-08 03:36:35 +00:00
Owner

Summary

Fixes #78 (follow-up to the closed #65). Two problems: session tokens were stored/compared in plaintext (a DB or log leak = usable bearer tokens), and ban/kick never invalidated the target's sessions or live connections.

Changes

Hash at rest

  • Store only SHA-256(token); look up and delete by hash. The raw token is returned to the client at creation and never persisted.
  • Migration 010_hash_session_tokens.sql: clears existing rows (every user re-authenticates once) and renames sessions.token -> token_hash.

Revoke on ban/kick

  • New SessionStore::delete_user_sessions(user_id), called from both ban_member and kick_member.
  • New ConnectionRegistry::disconnect_user(user_id): each connection holds an Arc<Notify> close signal; ban/kick fires it and the gateway loop closes the socket (close code 4003) and unregisters.

Behavior change

A kicked/banned user's existing token is now immediately invalid (401) and their live WebSocket is dropped. Re-authentication still works (auth isn't blocked by a ban), but a ban still blocks the actual rejoin. Updated the two membership tests to reflect this.

Tests

  • token_is_stored_hashed_not_in_plaintext - stored column is the hash, raw token still resolves.
  • delete_user_sessions_removes_all_of_a_users_sessions - scoped to the user.
  • disconnect_user_fires_close_signal_for_each_connection.
  • Updated kick_* / ban_* membership tests: old token -> 401, re-auth required to rejoin.

Full server suite green (118 tests); clippy -D warnings clean.

Closes #78

## Summary Fixes #78 (follow-up to the closed #65). Two problems: session tokens were stored/compared in plaintext (a DB or log leak = usable bearer tokens), and ban/kick never invalidated the target's sessions or live connections. ## Changes **Hash at rest** - Store only `SHA-256(token)`; look up and delete by hash. The raw token is returned to the client at creation and never persisted. - Migration `010_hash_session_tokens.sql`: clears existing rows (every user re-authenticates once) and renames `sessions.token` -> `token_hash`. **Revoke on ban/kick** - New `SessionStore::delete_user_sessions(user_id)`, called from both `ban_member` and `kick_member`. - New `ConnectionRegistry::disconnect_user(user_id)`: each connection holds an `Arc<Notify>` close signal; ban/kick fires it and the gateway loop closes the socket (close code 4003) and unregisters. ## Behavior change A kicked/banned user's existing token is now immediately invalid (`401`) and their live WebSocket is dropped. Re-authentication still works (auth isn't blocked by a ban), but a ban still blocks the actual rejoin. Updated the two membership tests to reflect this. ## Tests - `token_is_stored_hashed_not_in_plaintext` - stored column is the hash, raw token still resolves. - `delete_user_sessions_removes_all_of_a_users_sessions` - scoped to the user. - `disconnect_user_fires_close_signal_for_each_connection`. - Updated `kick_*` / `ban_*` membership tests: old token -> 401, re-auth required to rejoin. Full server suite green (118 tests); `clippy -D warnings` clean. Closes #78
fix(sessions): hash tokens at rest and revoke on ban/kick
Some checks failed
Security Audit / Gitleaks (Secrets) (pull_request) Successful in 9s
CI / Client — test & lint (pull_request) Successful in 39s
Security Audit / PNPM Audit (JS SCA) (pull_request) Failing after 42s
Security Audit / Trivy (SCA & IaC) (pull_request) Failing after 48s
Security Audit / Semgrep (SAST) (pull_request) Failing after 1m31s
Security Audit / Cargo Audit (Rust SCA) (pull_request) Failing after 4m19s
CI / Server — test & lint (pull_request) Successful in 4m52s
de78434491
Session tokens were stored and looked up in plaintext, so a database or
log leak yielded directly usable bearer tokens. Ban/kick also left the
target's sessions valid until natural expiry (default 24h).

- Store only SHA-256(token); look up and delete by hash. The raw token is
  returned to the client on creation and never persisted. Migration 010
  clears existing rows (all users re-authenticate once) and renames the
  column token -> token_hash.
- Add SessionStore::delete_user_sessions and call it from ban/kick.
- Add gateway ConnectionRegistry::disconnect_user (per-connection Notify
  close signal) and drop the target's live WebSocket connections on
  ban/kick with close code 4003.

Tests: token stored hashed (not raw); delete_user_sessions scoping;
disconnect_user fires the close signal; ban/kick now revoke the old token
(401) and require re-auth to rejoin.

Closes #78

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
icub3d merged commit 999f97a2ef into main 2026-07-08 03:36:35 +00:00
icub3d deleted branch feature/78-session-hashing 2026-07-08 03:36:36 +00:00
Sign in to join this conversation.
No reviewers
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
icub3d/decentcom!95
No description provided.