230b542015
- Rename package fablednetmon → fabledscryer throughout - Multi-dashboard: ownership, per-user defaults, HTMX edit (add/remove/reorder) - Read-only share tokens scoped to individual dashboards - Dashboard edit is HTMX-driven (no page reloads) - Plugin management system: remote catalog, download/install, hot-reload, in-app restart - plugin_index.py: fetch/cache remote index.yaml; default URL → bvandeusen/fabledscryer-plugins - plugin_manager.py: download_and_install_plugin, hot_reload_plugin, restart_app - ZIP extraction handles GitHub archive formats (name-v1.0.0/, name-main/) - Settings split into tabbed sections: General, Notifications, Ansible, Plugins - Plugins tab: catalog browser (HTMX), install/activate/update/restart actions - UI/branding: dark palette (#07071a), crystal ball SVG logo, animated star field, Libertinus Serif applied to headings, nav, labels, and section titles - Widget registry (core/widgets.py) for dashboard plugin integration - UPS widget.html (dashboard card) and settings/_tabs.html include - Migrations 0005–0008: dashboards, is_default, ownership, share tokens - docs/plugins/: writing-a-plugin.md updated with publishing guide, index.yaml.example template for fabledscryer-plugins repo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
43 lines
1.4 KiB
Python
43 lines
1.4 KiB
Python
from __future__ import annotations
|
|
import functools
|
|
from quart import session, redirect, url_for, abort, g
|
|
from fabledscryer.models.users import UserRole
|
|
|
|
_ROLE_ORDER = [UserRole.viewer, UserRole.operator, UserRole.admin]
|
|
|
|
|
|
def require_role(minimum_role: UserRole):
|
|
"""Decorator: requires authenticated user with at least minimum_role.
|
|
Also allows access for validated share-token requests (viewer level only).
|
|
"""
|
|
def decorator(f):
|
|
@functools.wraps(f)
|
|
async def wrapper(*args, **kwargs):
|
|
# Share-token requests bypass session auth for viewer-level endpoints
|
|
if getattr(g, "share_viewer", False) and minimum_role == UserRole.viewer:
|
|
return await f(*args, **kwargs)
|
|
user_id = session.get("user_id")
|
|
if not user_id:
|
|
return redirect(url_for("auth.login"))
|
|
user_role = UserRole(session.get("user_role", "viewer"))
|
|
if _ROLE_ORDER.index(user_role) < _ROLE_ORDER.index(minimum_role):
|
|
abort(403)
|
|
return await f(*args, **kwargs)
|
|
return wrapper
|
|
return decorator
|
|
|
|
|
|
def login_user(user) -> None:
|
|
"""Write user identity into the session."""
|
|
session["user_id"] = user.id
|
|
session["user_role"] = user.role.value
|
|
session["username"] = user.username
|
|
|
|
|
|
def logout_user() -> None:
|
|
session.clear()
|
|
|
|
|
|
def current_user_id() -> str | None:
|
|
return session.get("user_id")
|