From 4fbde770d7950d59774bed46f6ece657a8a2d207 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 18 Oct 2026 12:16:01 -0500 Subject: [PATCH] apply runtime role changes to jsmpeg streams The output process never subscribed to config/auth, so role edits from the Roles UI (requires_restart 0) did not reach it and jsmpeg kept streaming cameras that had been removed from a role until restart. The output process now replaces its auth config on config/auth updates, which the camera and birdseye broadcast threads read through the shared config. --- frigate/output/output.py | 11 ++++++++++- frigate/test/test_output_ws_auth.py | 10 +++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/frigate/output/output.py b/frigate/output/output.py index c8df3c5638..866c4729d4 100644 --- a/frigate/output/output.py +++ b/frigate/output/output.py @@ -18,7 +18,7 @@ from ws4py.server.wsgirefserver import ( from frigate.comms.config_updater import ConfigSubscriber from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeEnum from frigate.comms.ws import OriginCheckedWebSocketApplication, WebSocket -from frigate.config import FrigateConfig +from frigate.config import AuthConfig, FrigateConfig from frigate.config.camera.updater import ( CameraConfigUpdateEnum, CameraConfigUpdateSubscriber, @@ -153,6 +153,7 @@ class OutputProcess(FrigateProcess): ], ) birdseye_config_subscriber = ConfigSubscriber("config/birdseye", exact=True) + auth_config_subscriber = ConfigSubscriber("config/auth", exact=True) jsmpeg_cameras: dict[str, JsmpegCamera] = {} birdseye: Birdseye | None = None @@ -193,6 +194,13 @@ class OutputProcess(FrigateProcess): self.config.birdseye = birdseye_config logger.debug("Applied dynamic birdseye config update") + # user changes publish None on this topic, role edits an AuthConfig + _, auth_config = auth_config_subscriber.check_for_update() + + if isinstance(auth_config, AuthConfig): + self.config.auth = auth_config + logger.debug("Applied dynamic auth config update") + # drain review updates every iteration, not just when birdseye is # being consumed, so a dropped end never strands a camera if birdseye is not None: @@ -329,6 +337,7 @@ class OutputProcess(FrigateProcess): config_subscriber.stop() birdseye_config_subscriber.stop() + auth_config_subscriber.stop() websocket_server.manager.close_all() websocket_server.manager.stop() websocket_server.manager.join() diff --git a/frigate/test/test_output_ws_auth.py b/frigate/test/test_output_ws_auth.py index ea4834ef13..7f5dce58d7 100644 --- a/frigate/test/test_output_ws_auth.py +++ b/frigate/test/test_output_ws_auth.py @@ -3,7 +3,7 @@ import unittest from types import SimpleNamespace -from frigate.config import FrigateConfig +from frigate.config import AuthConfig, FrigateConfig from frigate.output.ws_auth import ws_has_camera_access @@ -55,3 +55,11 @@ class TestWsHasCameraAccess(unittest.TestCase): self.assertFalse( ws_has_camera_access(self._make_ws("limited_user"), "birdseye", self.config) ) + + def test_replaced_auth_config_revokes_camera(self): + ws = self._make_ws("limited_user") + self.assertTrue(ws_has_camera_access(ws, "front_door", self.config)) + + self.config.auth = AuthConfig(roles={"limited_user": ["back_door"]}) + self.assertFalse(ws_has_camera_access(ws, "front_door", self.config)) + self.assertTrue(ws_has_camera_access(ws, "back_door", self.config))