Add host-based authentication, UTC timestamps, session management, and secure cookies; fix styling issues; refactor to remove module; update database schema for sessions and reset tokens.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import urlparse, urlsplit
|
||||
|
||||
from ..globals import passkey as global_passkey
|
||||
|
||||
@@ -70,3 +70,17 @@ def reset_link_url(
|
||||
|
||||
def reload_config() -> None:
|
||||
_load_config.cache_clear()
|
||||
|
||||
|
||||
def normalize_host(raw_host: str | None) -> str | None:
|
||||
"""Normalize a Host header or hostname by stripping port and lowercasing."""
|
||||
if not raw_host:
|
||||
return None
|
||||
candidate = raw_host.strip()
|
||||
if not candidate:
|
||||
return None
|
||||
# Ensure urlsplit can parse bare hosts (prepend //)
|
||||
parsed = urlsplit(candidate if "//" in candidate else f"//{candidate}")
|
||||
host = parsed.hostname or parsed.path or ""
|
||||
host = host.strip("[]") # Remove IPv6 brackets if present
|
||||
return host.lower() if host else None
|
||||
|
||||
@@ -4,6 +4,7 @@ from collections.abc import Sequence
|
||||
from fnmatch import fnmatchcase
|
||||
|
||||
from ..globals import db
|
||||
from .hostutil import normalize_host
|
||||
from .tokens import session_key
|
||||
|
||||
__all__ = ["has_any", "has_all", "session_context"]
|
||||
@@ -24,5 +25,8 @@ def has_all(ctx, patterns: Sequence[str]) -> bool:
|
||||
return all(_match(ctx.role.permissions, patterns)) if ctx else False
|
||||
|
||||
|
||||
async def session_context(auth: str | None):
|
||||
return await db.instance.get_session_context(session_key(auth)) if auth else None
|
||||
async def session_context(auth: str | None, host: str | None = None):
|
||||
if not auth:
|
||||
return None
|
||||
normalized_host = normalize_host(host) if host else None
|
||||
return await db.instance.get_session_context(session_key(auth), normalized_host)
|
||||
|
||||
@@ -15,6 +15,25 @@ def session_key(token: str) -> bytes:
|
||||
return b"sess" + base64.urlsafe_b64decode(token)
|
||||
|
||||
|
||||
def encode_session_key(key: bytes) -> str:
|
||||
"""Encode an opaque session key for external representation."""
|
||||
return base64.urlsafe_b64encode(key).decode().rstrip("=")
|
||||
|
||||
|
||||
def decode_session_key(encoded: str) -> bytes:
|
||||
"""Decode an opaque session key from its public representation."""
|
||||
if not encoded:
|
||||
raise ValueError("Invalid session identifier")
|
||||
padding = "=" * (-len(encoded) % 4)
|
||||
try:
|
||||
raw = base64.urlsafe_b64decode(encoded + padding)
|
||||
except Exception as exc: # pragma: no cover - defensive
|
||||
raise ValueError("Invalid session identifier") from exc
|
||||
if not raw.startswith(b"sess"):
|
||||
raise ValueError("Invalid session identifier")
|
||||
return raw
|
||||
|
||||
|
||||
def reset_key(passphrase: str) -> bytes:
|
||||
if not is_well_formed(passphrase):
|
||||
raise ValueError(
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import user_agents
|
||||
|
||||
|
||||
def compact_user_agent(ua: str | None) -> str:
|
||||
if not ua:
|
||||
return "-"
|
||||
u = user_agents.parse(ua)
|
||||
ver = u.browser.version_string.split(".")[0]
|
||||
dev = u.device.family if u.device.family not in ["Other", "Mac"] else ""
|
||||
return f"{u.browser.family}/{ver} {u.os.family} {dev}".strip()
|
||||
Reference in New Issue
Block a user