tiny_auth_support #6

Merged
gru merged 11 commits from tiny_auth_support into master 2026-05-26 08:04:52 +02:00
4 changed files with 31 additions and 6 deletions
Showing only changes of commit 0ee0f3424c - Show all commits

View File

@@ -117,6 +117,9 @@ def bypass_user_id() -> int:
def current_user_id() -> int: def current_user_id() -> int:
if not enabled(): if not enabled():
return default_user_id() return default_user_id()
if not has_request_context():
# Note: Background jobs and schedulers do not have Flask request/session state.
return 0
if auth_bypassed_request(): if auth_bypassed_request():
return bypass_user_id() return bypass_user_id()
api_user_id = getattr(g, "api_user_id", None) api_user_id = getattr(g, "api_user_id", None)

View File

@@ -6,6 +6,7 @@ import json
import psutil import psutil
from flask_socketio import emit, join_room, leave_room, disconnect from flask_socketio import emit, join_room, leave_room, disconnect
from .preferences import active_profile, get_profile from .preferences import active_profile, get_profile
from ..db import default_user_id
from .torrent_cache import torrent_cache from .torrent_cache import torrent_cache
from .torrent_summary import cached_summary from .torrent_summary import cached_summary
from . import rtorrent, smart_queue, traffic_history, automation_rules, torrent_stats, auth, speed_peaks, poller_control, download_planner from . import rtorrent, smart_queue, traffic_history, automation_rules, torrent_stats, auth, speed_peaks, poller_control, download_planner
@@ -38,13 +39,15 @@ def _emit_profile(socketio, event: str, payload: dict, profile_id: int) -> None:
def _run_slow_profile_tasks(socketio, profile: dict, profile_id: int) -> None: def _run_slow_profile_tasks(socketio, profile: dict, profile_id: int) -> None:
state = poller_control.state_for(profile_id) state = poller_control.state_for(profile_id)
# Note: Background checks keep the profile owner so bypass/admin profiles do not enqueue jobs as the fallback user.
profile_user_id = int(profile.get("user_id") or default_user_id())
try: try:
try: try:
torrent_stats.queue_refresh(socketio, profile, force=False, room=_profile_room(profile_id) if auth.enabled() else None) torrent_stats.queue_refresh(socketio, profile, force=False, room=_profile_room(profile_id) if auth.enabled() else None)
except Exception as exc: except Exception as exc:
_emit_profile(socketio, "torrent_stats_update", {"ok": False, "profile_id": profile_id, "error": str(exc)}, profile_id) _emit_profile(socketio, "torrent_stats_update", {"ok": False, "profile_id": profile_id, "error": str(exc)}, profile_id)
try: try:
result = smart_queue.check(profile, force=False) result = smart_queue.check(profile, user_id=profile_user_id, force=False)
if result.get("enabled"): if result.get("enabled"):
_emit_profile(socketio, "smart_queue_update", result, profile_id) _emit_profile(socketio, "smart_queue_update", result, profile_id)
if result.get("stopped") or result.get("started") or result.get("start_requested") or result.get("paused") or result.get("resumed"): if result.get("stopped") or result.get("started") or result.get("start_requested") or result.get("paused") or result.get("resumed"):
@@ -55,7 +58,7 @@ def _run_slow_profile_tasks(socketio, profile: dict, profile_id: int) -> None:
except Exception as exc: except Exception as exc:
_emit_profile(socketio, "smart_queue_update", {"ok": False, "profile_id": profile_id, "error": str(exc)}, profile_id) _emit_profile(socketio, "smart_queue_update", {"ok": False, "profile_id": profile_id, "error": str(exc)}, profile_id)
try: try:
auto_result = automation_rules.check(profile, force=False) auto_result = automation_rules.check(profile, user_id=profile_user_id, force=False)
if auto_result.get("applied"): if auto_result.get("applied"):
_emit_profile(socketio, "automation_update", auto_result, profile_id) _emit_profile(socketio, "automation_update", auto_result, profile_id)
except Exception as exc: except Exception as exc:

View File

@@ -9,6 +9,8 @@ from . import rtorrent, auth, disk_guard, operation_logs
from .preferences import get_profile from .preferences import get_profile
from ..config import WORKERS from ..config import WORKERS
from ..db import connect, utcnow, default_user_id from ..db import connect, utcnow, default_user_id
from .torrent_cache import torrent_cache
from .torrent_summary import cached_summary
LIGHT_ACTIONS = {"start", "stop", "pause", "resume", "unpause", "set_label", "set_ratio_group", "reannounce", "set_limits"} LIGHT_ACTIONS = {"start", "stop", "pause", "resume", "unpause", "set_label", "set_ratio_group", "reannounce", "set_limits"}
WATCHDOG_INTERVAL_SECONDS = 30 WATCHDOG_INTERVAL_SECONDS = 30
@@ -216,10 +218,11 @@ def _job_event_meta(payload: dict) -> dict:
return meta return meta
def _execute(profile: dict, action_name: str, payload: dict): def _execute(profile: dict, action_name: str, payload: dict, user_id: int | None = None):
if action_name == "smart_queue_check": if action_name == "smart_queue_check":
from . import smart_queue from . import smart_queue
return smart_queue.check(profile, user_id=auth.current_user_id() or default_user_id(), force=True) # Note: Worker execution uses the job owner instead of Flask session state.
return smart_queue.check(profile, user_id=user_id or default_user_id(), force=True)
if action_name == "add_magnet": if action_name == "add_magnet":
if bool(payload.get("start", True)): if bool(payload.get("start", True)):
disk_guard.assert_can_start_download(profile) disk_guard.assert_can_start_download(profile)
@@ -268,6 +271,22 @@ def _mark_running(job_id: str, attempts: int) -> bool:
return int(cur.rowcount or 0) == 1 return int(cur.rowcount or 0) == 1
def _emit_torrent_refresh(profile: dict, action_name: str) -> None:
if action_name not in {"add_magnet", "add_torrent_raw", "remove", "move", "start", "stop", "pause", "resume", "unpause", "set_label", "set_ratio_group", "recheck"}:
return
try:
diff = torrent_cache.refresh(profile)
profile_id = int(profile["id"])
if diff.get("ok"):
rows = torrent_cache.snapshot(profile_id)
_emit("torrent_patch", {**diff, "summary": cached_summary(profile_id, rows, force=True)})
else:
_emit("rtorrent_error", diff)
except Exception as exc:
# Note: A failed live refresh must not change the already completed job result.
_emit("rtorrent_error", {"profile_id": int(profile.get("id") or 0), "error": str(exc)})
def _run(job_id: str): def _run(job_id: str):
if not _claim_runner(job_id): if not _claim_runner(job_id):
return return
@@ -303,7 +322,7 @@ def _run(job_id: str):
operation_logs.record_job_event(profile["id"], job["action"], "started", payload, job_id=job_id, user_id=int(job.get("user_id") or 0)) operation_logs.record_job_event(profile["id"], job["action"], "started", payload, job_id=job_id, user_id=int(job.get("user_id") or 0))
_emit("operation_started", {"job_id": job_id, "action": job["action"], "profile_id": profile["id"], "hashes": payload.get("hashes") or [], "hash_count": len(payload.get("hashes") or []), "bulk": len(payload.get("hashes") or []) > 1, **event_meta}) _emit("operation_started", {"job_id": job_id, "action": job["action"], "profile_id": profile["id"], "hashes": payload.get("hashes") or [], "hash_count": len(payload.get("hashes") or []), "bulk": len(payload.get("hashes") or []) > 1, **event_meta})
_emit("job_update", {"id": job_id, "profile_id": profile["id"], "status": "running", "attempts": attempts}) _emit("job_update", {"id": job_id, "profile_id": profile["id"], "status": "running", "attempts": attempts})
result = _execute(profile, job["action"], payload) result = _execute(profile, job["action"], payload, user_id=int(job.get("user_id") or 0))
fresh = _job_row(job_id) fresh = _job_row(job_id)
# Note: Emergency cancel and watchdog timeout keep late work from overwriting a terminal state. # Note: Emergency cancel and watchdog timeout keep late work from overwriting a terminal state.
if fresh and fresh["status"] != "running": if fresh and fresh["status"] != "running":

File diff suppressed because one or more lines are too long