mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +08:00
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:
parent
ba33ab5c4f
commit
20c2fcd496
34 changed files with 1462 additions and 149 deletions
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue