feat(07.1): session revocation on privilege change — CR-01/CR-02/CR-03

- revoke_all_refresh_tokens: add skip_token_hash optional param (exclude
  current session while revoking others)
- change_password, enable_totp, disable_totp: call revoke with skip hash
  derived from refresh cookie; return sessions_revoked in response and
  write to audit log metadata_
- 3 new tests: test_{change_password,enable_totp,disable_totp}_revokes_other_sessions
  — all PASSED; 373 total passing, 0 regressions
- Frontend toasts: SettingsAccountTab + TotpEnrollment show
  "Other sessions have been terminated." when sessions_revoked > 0
- Companion fixes: rate_limiting get_client_ip refactor, deps/auth.py
  request.state.current_user, locustfile refresh-token task removal
- Version bump: 0.1.0 → 0.1.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-06-05 12:47:16 +02:00
co-authored by Claude Sonnet 4.6
parent 8d060a5da4
commit c38c6b1c01
14 changed files with 360 additions and 34 deletions
+5 -8
View File
@@ -114,6 +114,10 @@ class DocuVaultUser(HttpUser):
wait_time = between(0.5, 2.0)
access_token: str = ""
# NOTE: /api/auth/refresh requires an httpOnly cookie that Locust cannot
# obtain without a full browser session. Removed from the task mix to avoid
# guaranteed 401s that inflate the fail_ratio and mask real regressions.
def on_start(self) -> None:
global _TOKEN_IDX
with _TOKEN_LOCK:
@@ -128,7 +132,7 @@ class DocuVaultUser(HttpUser):
def _auth_headers(self) -> dict:
return {"Authorization": f"Bearer {self.access_token}"}
@task(5)
@task(6)
def list_documents(self) -> None:
self.client.get("/api/documents/", headers=self._auth_headers(), name="GET /api/documents/")
@@ -159,13 +163,6 @@ class DocuVaultUser(HttpUser):
name="POST /api/documents/upload",
)
@task(1)
def refresh_token(self) -> None:
self.client.post(
"/api/auth/refresh",
headers=self._auth_headers(),
name="POST /api/auth/refresh",
)
@events.quitting.add_listener