poc2_worked

This commit is contained in:
Mateusz Gruszczyński
2026-08-15 18:29:36 +02:00
parent fc3a2944b2
commit 71b6c0d86f
62 changed files with 9112 additions and 375 deletions
+103
View File
@@ -0,0 +1,103 @@
from __future__ import annotations
import hashlib
import hmac
import secrets
from datetime import datetime, timedelta, timezone
from http.cookies import SimpleCookie
from typing import Any
from .config import Config
from .store import AlertStore
SESSION_COOKIE = "mikrosuricata_session"
class SessionAuth:
"""Small dependency-free username/password session manager backed by SQLite."""
def __init__(self, config: Config, store: AlertStore) -> None:
self.config = config
self.store = store
@property
def enabled(self) -> bool:
return bool(self._password())
def authenticate(self, username: str, password: str) -> bool:
expected_password = self._password()
if not expected_password:
return False
return hmac.compare_digest(username, self.config.admin_username) and hmac.compare_digest(
password, expected_password
)
def create_session(self, username: str) -> tuple[str, dict[str, Any]]:
token = secrets.token_urlsafe(36)
csrf = secrets.token_urlsafe(24)
expires_at = datetime.now(timezone.utc) + timedelta(hours=self.config.session_hours)
self.store.create_web_session(self._hash(token), username, csrf, expires_at)
session = self.store.get_web_session(self._hash(token), touch=False)
if session is None:
raise RuntimeError("could not create web session")
return token, session
def session_from_cookie(self, cookie_header: str, *, touch: bool = True) -> dict[str, Any] | None:
token = self.cookie_token(cookie_header)
if not token:
return None
session = self.store.get_web_session(self._hash(token), touch=touch)
if session is not None:
session["token_hash"] = self._hash(token)
return session
def delete_session_from_cookie(self, cookie_header: str) -> None:
token = self.cookie_token(cookie_header)
if token:
self.store.delete_web_session(self._hash(token))
def cookie_header(self, token: str) -> str:
max_age = self.config.session_hours * 3600
parts = [
f"{SESSION_COOKIE}={token}",
"Path=/",
f"Max-Age={max_age}",
"HttpOnly",
"SameSite=Strict",
]
if self.config.session_cookie_secure:
parts.append("Secure")
return "; ".join(parts)
def clear_cookie_header(self) -> str:
parts = [
f"{SESSION_COOKIE}=",
"Path=/",
"Max-Age=0",
"HttpOnly",
"SameSite=Strict",
]
if self.config.session_cookie_secure:
parts.append("Secure")
return "; ".join(parts)
@staticmethod
def cookie_token(cookie_header: str) -> str:
if not cookie_header:
return ""
cookie = SimpleCookie()
try:
cookie.load(cookie_header)
except Exception:
return ""
morsel = cookie.get(SESSION_COOKIE)
return morsel.value if morsel else ""
@staticmethod
def _hash(token: str) -> str:
return hashlib.sha256(token.encode("utf-8")).hexdigest()
def _password(self) -> str:
# ADMIN_TOKEN remains a migration fallback only; the UI no longer stores or sends it.
return self.config.admin_password or self.config.admin_token