Harden auth with revocable sessions, cookies, and single-login default.

Issue short-lived access JWTs backed by AuthSession rows, HttpOnly cookies with refresh rotation, idle timeout, session management UI, WebCRT ownership caps, and optional Redis login rate limits; new logins revoke other sessions by default.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-06 14:13:37 +08:00
parent ba33ab5c4f
commit 20c2fcd496
34 changed files with 1462 additions and 149 deletions

View file

@ -81,18 +81,29 @@ def auth_secret() -> str:
return ensure_auth_secret()
def issue_access_token(*, user_id: str, username: str, role: str) -> str:
def issue_access_token(
*,
user_id: str,
username: str,
role: str,
jti: str | None = None,
) -> tuple[str, str, int]:
"""Return (token, jti, ttl_sec). jti is required for server-side revocation."""
ttl = max(300, int(settings.auth_token_ttl_sec or 86400))
now = datetime.now(timezone.utc)
sid = str(jti or secrets.token_urlsafe(24)).strip()
if not sid:
sid = secrets.token_urlsafe(24)
payload = {
"sub": str(user_id),
"username": str(username),
"role": str(role),
"typ": "access",
"jti": sid,
"iat": int(now.timestamp()),
"exp": int((now + timedelta(seconds=ttl)).timestamp()),
}
return jwt.encode(payload, auth_secret(), algorithm="HS256")
return jwt.encode(payload, auth_secret(), algorithm="HS256"), sid, ttl
def decode_access_token(token: str) -> dict[str, Any]:
@ -100,7 +111,7 @@ def decode_access_token(token: str) -> dict[str, Any]:
str(token or ""),
auth_secret(),
algorithms=["HS256"],
options={"require": ["exp", "sub"]},
options={"require": ["exp", "sub", "jti"]},
)
@ -109,5 +120,10 @@ def new_api_token_plaintext() -> str:
return "nxt_" + secrets.token_urlsafe(32)
def new_refresh_token_plaintext() -> str:
"""Opaque refresh token (shown once). Prefix distinguishes from API tokens."""
return "nxr_" + secrets.token_urlsafe(32)
def hash_api_token(plaintext: str) -> str:
return hashlib.sha256(str(plaintext or "").encode("utf-8")).hexdigest()