Publish LumaOps source

This commit is contained in:
LumaOps release export committed 2026-09-03 01:18:36 +02:00
commit 7f1c0e5f71
2363 files changed
+501543

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
"""Optional local-admin authentication with signed cookies and CSRF checks."""
from __future__ import annotations
import base64
import hashlib
import hmac
import json
import secrets
import time
from typing import Any
from fastapi import Request, Response
from .config import Settings
from .errors import LumaOpsError
SESSION_COOKIE = "lumaops_session"
CSRF_COOKIE = "lumaops_csrf"
SESSION_MAX_AGE = 12 * 60 * 60
class AuthManager:
def __init__(self, settings: Settings) -> None:
self.settings = settings
self._key = (settings.admin_token or "auth-disabled").encode("utf-8")
def require(self, request: Request) -> str:
if not self.settings.auth_enabled:
return "local-admin"
authorization = request.headers.get("authorization", "")
if authorization.startswith("Bearer ") and self._valid_token(authorization[7:]):
return "api-token"
session = request.cookies.get(SESSION_COOKIE)
if not session or not self._verify_session(session):
raise LumaOpsError("authentication_required", "Aanmelding is vereist.", 401)
if request.method not in {"GET", "HEAD", "OPTIONS"}:
csrf_cookie = request.cookies.get(CSRF_COOKIE, "")
csrf_header = request.headers.get("x-csrf-token", "")
if not csrf_cookie or not hmac.compare_digest(csrf_cookie, csrf_header):
raise LumaOpsError("csrf_failed", "CSRF-validatie is mislukt.", 403)
return "local-admin"
def login(self, token: str, response: Response) -> dict[str, Any]:
if not self.settings.auth_enabled:
return {"authenticated": True, "auth_enabled": False}
if not self._valid_token(token):
raise LumaOpsError("invalid_credentials", "Ongeldige beheertoken.", 401)
csrf = secrets.token_urlsafe(32)
session = self._create_session()
response.set_cookie(
SESSION_COOKIE,
session,
max_age=SESSION_MAX_AGE,
httponly=True,
secure=self.settings.secure_cookies,
samesite="strict",
path="/",
)
response.set_cookie(
CSRF_COOKIE,
csrf,
max_age=SESSION_MAX_AGE,
httponly=False,
secure=self.settings.secure_cookies,
samesite="strict",
path="/",
)
return {"authenticated": True, "auth_enabled": True, "csrf_token": csrf}
@staticmethod
def logout(response: Response) -> None:
response.delete_cookie(SESSION_COOKIE, path="/")
response.delete_cookie(CSRF_COOKIE, path="/")
def _valid_token(self, candidate: str) -> bool:
configured = self.settings.admin_token
return configured is not None and hmac.compare_digest(
candidate.encode("utf-8"), configured.encode("utf-8")
)
def _create_session(self) -> str:
payload = json.dumps(
{"sub": "local-admin", "iat": int(time.time()), "nonce": secrets.token_hex(16)},
separators=(",", ":"),
).encode("utf-8")
encoded = base64.urlsafe_b64encode(payload).rstrip(b"=")
signature = hmac.new(self._key, encoded, hashlib.sha256).digest()
return f"{encoded.decode()}.{base64.urlsafe_b64encode(signature).decode().rstrip('=')}"
def _verify_session(self, value: str) -> bool:
try:
encoded, signature = value.split(".", 1)
expected = hmac.new(self._key, encoded.encode("ascii"), hashlib.sha256).digest()
actual = base64.urlsafe_b64decode(signature + "=" * (-len(signature) % 4))
if not hmac.compare_digest(expected, actual):
return False
payload = json.loads(
base64.urlsafe_b64decode(encoded + "=" * (-len(encoded) % 4)).decode("utf-8")
)
return (
payload.get("sub") == "local-admin"
and int(payload["iat"]) + SESSION_MAX_AGE > time.time()
)
except (ValueError, KeyError, TypeError, json.JSONDecodeError):
return False