poc2_worked
This commit is contained in:
+103
@@ -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
|
||||
Reference in New Issue
Block a user