Address code review feedback: improve session secret generation, document rate-limit design
Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/afb996f6-f6f5-4d4a-9f99-e46e3f89b4d7 Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
02e40e6634
commit
56e1da93c1
@@ -499,7 +499,11 @@ class NoCacheMiddleware(BaseHTTPMiddleware):
|
|||||||
# ── Session / authentication helpers ─────────────────────────────
|
# ── Session / authentication helpers ─────────────────────────────
|
||||||
|
|
||||||
def _get_or_create_session_secret() -> bytes:
|
def _get_or_create_session_secret() -> bytes:
|
||||||
"""Return the Hub session secret, generating it on first boot."""
|
"""Return the Hub session secret, generating it on first boot.
|
||||||
|
|
||||||
|
The file is stored in /var/lib/secrets/ (mode 0600) so it is wiped
|
||||||
|
automatically during a security reset, which forces re-login after reset.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
with open(HUB_SESSION_SECRET_FILE, "rb") as f:
|
with open(HUB_SESSION_SECRET_FILE, "rb") as f:
|
||||||
data = f.read().strip()
|
data = f.read().strip()
|
||||||
@@ -507,15 +511,17 @@ def _get_or_create_session_secret() -> bytes:
|
|||||||
return data
|
return data
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
secret = secrets.token_hex(32).encode()
|
# Generate 32 random bytes and hex-encode for human readability
|
||||||
|
token_bytes = secrets.token_bytes(32)
|
||||||
|
token_hex = token_bytes.hex().encode()
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(HUB_SESSION_SECRET_FILE), exist_ok=True)
|
os.makedirs(os.path.dirname(HUB_SESSION_SECRET_FILE), exist_ok=True)
|
||||||
with open(HUB_SESSION_SECRET_FILE, "wb") as f:
|
with open(HUB_SESSION_SECRET_FILE, "wb") as f:
|
||||||
f.write(secret)
|
f.write(token_hex)
|
||||||
os.chmod(HUB_SESSION_SECRET_FILE, 0o600)
|
os.chmod(HUB_SESSION_SECRET_FILE, 0o600)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
return secret
|
return token_hex
|
||||||
|
|
||||||
|
|
||||||
def _create_session() -> str:
|
def _create_session() -> str:
|
||||||
@@ -571,13 +577,17 @@ def _check_password(submitted: str) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def _record_failure(client_ip: str) -> None:
|
def _record_failure(client_ip: str) -> None:
|
||||||
"""Record a failed login attempt and apply a rate-limit delay."""
|
"""Record a failed login attempt and apply a rate-limit delay.
|
||||||
|
|
||||||
|
Must always be called via loop.run_in_executor() so that the blocking
|
||||||
|
time.sleep() does not stall the asyncio event loop.
|
||||||
|
"""
|
||||||
now = time.time()
|
now = time.time()
|
||||||
failures = _login_failures.setdefault(client_ip, [])
|
failures = _login_failures.setdefault(client_ip, [])
|
||||||
# Prune old entries outside the window
|
# Prune old entries outside the window
|
||||||
_login_failures[client_ip] = [t for t in failures if now - t < LOGIN_FAIL_WINDOW]
|
_login_failures[client_ip] = [t for t in failures if now - t < LOGIN_FAIL_WINDOW]
|
||||||
_login_failures[client_ip].append(now)
|
_login_failures[client_ip].append(now)
|
||||||
# Always sleep a fixed delay to slow brute force
|
# Sleep in the thread-pool thread to slow brute-force without blocking the loop
|
||||||
time.sleep(LOGIN_FAIL_DELAY)
|
time.sleep(LOGIN_FAIL_DELAY)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user