mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-31 08:09:02 +03:00
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:
@@ -3,7 +3,8 @@ import { createContext, useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
interface AuthState {
|
||||
user: { username: string; role: "admin" | "viewer" | null } | null;
|
||||
user: { username: string; role: string | null } | null;
|
||||
allowedCameras: string[];
|
||||
isLoading: boolean;
|
||||
isAuthenticated: boolean; // true if auth is required
|
||||
}
|
||||
@@ -15,7 +16,12 @@ interface AuthContextType {
|
||||
}
|
||||
|
||||
export const AuthContext = createContext<AuthContextType>({
|
||||
auth: { user: null, isLoading: true, isAuthenticated: false },
|
||||
auth: {
|
||||
user: null,
|
||||
allowedCameras: [],
|
||||
isLoading: true,
|
||||
isAuthenticated: false,
|
||||
},
|
||||
login: () => {},
|
||||
logout: () => {},
|
||||
});
|
||||
@@ -23,6 +29,7 @@ export const AuthContext = createContext<AuthContextType>({
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [auth, setAuth] = useState<AuthState>({
|
||||
user: null,
|
||||
allowedCameras: [],
|
||||
isLoading: true,
|
||||
isAuthenticated: false,
|
||||
});
|
||||
@@ -38,7 +45,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
if (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
||||
// auth required but not logged in
|
||||
setAuth({ user: null, isLoading: false, isAuthenticated: true });
|
||||
setAuth({
|
||||
user: null,
|
||||
allowedCameras: [],
|
||||
isLoading: false,
|
||||
isAuthenticated: true,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -49,20 +61,44 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
username: profile.username,
|
||||
role: profile.role || "viewer",
|
||||
};
|
||||
setAuth({ user: newUser, isLoading: false, isAuthenticated: true });
|
||||
|
||||
const allowedCameras = Array.isArray(profile.allowed_cameras)
|
||||
? profile.allowed_cameras
|
||||
: [];
|
||||
setAuth({
|
||||
user: newUser,
|
||||
allowedCameras,
|
||||
isLoading: false,
|
||||
isAuthenticated: true,
|
||||
});
|
||||
} else {
|
||||
// Unauthenticated mode (anonymous)
|
||||
setAuth({ user: null, isLoading: false, isAuthenticated: false });
|
||||
setAuth({
|
||||
user: null,
|
||||
allowedCameras: [],
|
||||
isLoading: false,
|
||||
isAuthenticated: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [profile, error]);
|
||||
|
||||
const login = (user: AuthState["user"]) => {
|
||||
setAuth({ user, isLoading: false, isAuthenticated: true });
|
||||
setAuth((current) => ({
|
||||
...current,
|
||||
user,
|
||||
isLoading: false,
|
||||
isAuthenticated: true,
|
||||
}));
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
setAuth({ user: null, isLoading: false, isAuthenticated: true });
|
||||
setAuth({
|
||||
user: null,
|
||||
allowedCameras: [],
|
||||
isLoading: false,
|
||||
isAuthenticated: true,
|
||||
});
|
||||
axios.get("/logout", { withCredentials: true });
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user