Enhance user roles to limit camera access (#20024)

* update config for roles and add validator

* ensure admin and viewer are never overridden

* add class method to user to retrieve all allowed cameras

* enforce config roles in auth api endpoints

* add camera access api dependency functions

* protect review endpoints

* protect preview endpoints

* rename param name for better fastapi injection matching

* remove unneeded

* protect export endpoints

* protect event endpoints

* protect media endpoints

* update auth hook for allowed cameras

* update default app view

* ensure anonymous user always returns all cameras

* limit cameras in explore

* cameras is already a list

* limit cameras in review/history

* limit cameras in live view

* limit cameras in camera groups

* only show face library and classification in sidebar for admin

* remove check in delete reviews

since admin role is required, no need to check camera access. fixes failing test

* pass request with camera access for tests

* more async

* camera access tests

* fix proxy auth tests

* allowed cameras for review tests

* combine event tests and refactor for camera access

* fix post validation for roles

* don't limit roles in create user dialog

* fix triggers endpoints

no need to run require camera access dep since the required role is admin

* fix type

* create and edit role dialogs

* delete role dialog

* fix role change dialog

* update settings view for roles

* i18n changes

* minor spacing tweaks

* docs

* use badges and camera name label component

* clarify docs

* display all cameras badge for admin and viewer

* i18n fix

* use validator to prevent reserved and empty roles from being assigned

* split users and roles into separate tabs in settings

* tweak docs

* clarify docs

* change icon

* don't memoize roles

always recalculate on component render
This commit is contained in:
Josh Hawkins
2025-09-12 05:19:29 -06:00
committed by GitHub
parent ba650af6f2
commit ed1e3a7c9a
41 changed files with 2286 additions and 739 deletions
+21 -15
View File
@@ -65,6 +65,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
type RecordingViewProps = {
startCamera: string;
@@ -97,11 +98,17 @@ export function RecordingView({
const timezone = useTimezone(config);
const allowedCameras = useAllowedCameras();
const effectiveCameras = useMemo(
() => allCameras.filter((camera) => allowedCameras.includes(camera)),
[allCameras, allowedCameras],
);
const { data: recordingsSummary } = useSWR<RecordingsSummary>([
"recordings/summary",
{
timezone: timezone,
cameras: allCameras.join(",") ?? null,
cameras: effectiveCameras.join(",") ?? null,
},
]);
@@ -276,14 +283,16 @@ export function RecordingView({
const onSelectCamera = useCallback(
(newCam: string) => {
setMainCamera(newCam);
setFullResolution({
width: 0,
height: 0,
});
setPlaybackStart(currentTime);
if (allowedCameras.includes(newCam)) {
setMainCamera(newCam);
setFullResolution({
width: 0,
height: 0,
});
setPlaybackStart(currentTime);
}
},
[currentTime],
[currentTime, allowedCameras],
);
// fullscreen
@@ -488,12 +497,9 @@ export function RecordingView({
</div>
<div className="flex items-center justify-end gap-2">
<MobileCameraDrawer
allCameras={allCameras}
allCameras={effectiveCameras}
selected={mainCamera}
onSelectCamera={(cam) => {
setPlaybackStart(currentTime);
setMainCamera(cam);
}}
onSelectCamera={onSelectCamera}
/>
{isDesktop && (
<ExportDialog
@@ -674,7 +680,7 @@ export function RecordingView({
containerRef={mainLayoutRef}
/>
</div>
{isDesktop && allCameras.length > 1 && (
{isDesktop && effectiveCameras.length > 1 && (
<div
ref={previewRowRef}
className={cn(
@@ -686,7 +692,7 @@ export function RecordingView({
)}
>
<div className="w-2" />
{allCameras.map((cam) => {
{effectiveCameras.map((cam) => {
if (cam == mainCamera || cam == "birdseye") {
return;
}