Publish curated LumaOps source from c85ee9ef8f1454f5c305398902afbb9e6738de40
Managed validation / full (push) Failing after 8s
Managed validation / full (push) Failing after 8s
This commit is contained in:
1 parent
91f8b3e3c1
commit
def7920856
2271 files changed
+501225
-501178
No files matched your search
@@ -1,106 +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
|
||||
"""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
|
||||
Reference in new issue
Block a user