fix: harden auth with refresh token session binding, logging, and router state
- Add session_id to refresh tokens and enforce it during validation, preventing stolen refresh tokens from being usable without the originating browser session - Set router.isAuthenticated via auth:login event after successful login (previously only set at page load) - Add console.warn logging to WS message parse/handler errors - Improve _refreshPromise error handling in token refresh flow - Document rate limiter in-memory limitation and CSP connect-src same-origin requirement - Add 3 tests for session-bound refresh token validation
This commit is contained in:
+11
-3
@@ -126,11 +126,15 @@ def generate_access_token(
|
||||
return jwt.encode(payload, secret, algorithm=algorithm)
|
||||
|
||||
|
||||
def generate_refresh_token(username: str) -> str:
|
||||
def generate_refresh_token(username: str, session_id: str | None = None) -> str:
|
||||
"""Generate a new refresh token for *username*.
|
||||
|
||||
Args:
|
||||
username: The authenticated username.
|
||||
session_id: Session binding ID included in the token payload.
|
||||
When present, the refresh endpoint requires a matching session_id,
|
||||
preventing a stolen refresh token from being usable without the
|
||||
originating browser session.
|
||||
|
||||
Returns:
|
||||
JWT refresh token string.
|
||||
@@ -150,6 +154,8 @@ def generate_refresh_token(username: str) -> str:
|
||||
"jti": str(uuid.uuid4()),
|
||||
"type": "refresh",
|
||||
}
|
||||
if session_id:
|
||||
payload["session_id"] = session_id
|
||||
return jwt.encode(payload, secret, algorithm=algorithm)
|
||||
|
||||
|
||||
@@ -165,7 +171,7 @@ def generate_tokens(username: str, permissions: dict[str, str]) -> dict[str, str
|
||||
"""
|
||||
session_id = secrets.token_urlsafe(16)
|
||||
access_token = generate_access_token(username, permissions, session_id)
|
||||
refresh_token = generate_refresh_token(username)
|
||||
refresh_token = generate_refresh_token(username, session_id)
|
||||
_persist_refresh_token(username, refresh_token)
|
||||
return {
|
||||
"access_token": access_token,
|
||||
@@ -397,7 +403,9 @@ class RateLimiter:
|
||||
del self.failures[key]
|
||||
|
||||
|
||||
# Global rate limiters
|
||||
# Global rate limiters — in-memory only. Counts reset on daemon restart
|
||||
# (SIGHUP reload, process restart). Acceptable for a single-user appliance
|
||||
# where restarts are rare; brute-force windows briefly reset post-restart.
|
||||
_login_limiter = RateLimiter(max_attempts=10, window_seconds=300)
|
||||
_webauthn_limiter = RateLimiter(max_attempts=5, window_seconds=600)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user