mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-30 23:59:02 +03:00
UI viewer role (#16978)
* db migration * db model * assign admin role on password reset * add role to jwt and api responses * don't restrict api access for admins yet * use json response * frontend auth context * update auth form for profile endpoint * add access denied page * add protected routes * auth hook * dialogs * user settings view * restrict viewer access to settings * restrict camera functions for viewer role * add password dialog to account menu * spacing tweak * migrator default to admin * escape quotes in migrator * ui tweaks * tweaks * colors * colors * fix merge conflict * fix icons * add api layer enforcement * ui tweaks * fix error message * debug * clean up * remove print * guard apis for admin only * fix tests * fix review tests * use correct error responses from api in toasts * add role to account menu
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import axios from "axios";
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
interface AuthState {
|
||||
user: { username: string; role: "admin" | "viewer" | null } | null;
|
||||
isLoading: boolean;
|
||||
isAuthenticated: boolean; // true if auth is required
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
auth: AuthState;
|
||||
login: (user: AuthState["user"]) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
export const AuthContext = createContext<AuthContextType>({
|
||||
auth: { user: null, isLoading: true, isAuthenticated: false },
|
||||
login: () => {},
|
||||
logout: () => {},
|
||||
});
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [auth, setAuth] = useState<AuthState>({
|
||||
user: null,
|
||||
isLoading: true,
|
||||
isAuthenticated: false,
|
||||
});
|
||||
|
||||
const { data: profile, error } = useSWR("/profile", {
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: true,
|
||||
fetcher: (url) =>
|
||||
axios.get(url, { withCredentials: true }).then((res) => res.data),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
||||
// auth required but not logged in
|
||||
setAuth({ user: null, isLoading: false, isAuthenticated: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile) {
|
||||
if (profile.username && profile.username !== "anonymous") {
|
||||
const newUser = {
|
||||
username: profile.username,
|
||||
role: profile.role || "viewer",
|
||||
};
|
||||
setAuth({ user: newUser, isLoading: false, isAuthenticated: true });
|
||||
} else {
|
||||
// Unauthenticated mode (anonymous)
|
||||
setAuth({ user: null, isLoading: false, isAuthenticated: false });
|
||||
}
|
||||
}
|
||||
}, [profile, error]);
|
||||
|
||||
const login = (user: AuthState["user"]) => {
|
||||
setAuth({ user, isLoading: false, isAuthenticated: true });
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
setAuth({ user: null, isLoading: false, isAuthenticated: true });
|
||||
axios.get("/logout", { withCredentials: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ auth, login, logout }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { IconContext } from "react-icons";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { StatusBarMessagesProvider } from "@/context/statusbar-provider";
|
||||
import { StreamingSettingsProvider } from "./streaming-settings-provider";
|
||||
import { AuthProvider } from "./auth-context";
|
||||
|
||||
type TProvidersProps = {
|
||||
children: ReactNode;
|
||||
@@ -14,19 +15,21 @@ type TProvidersProps = {
|
||||
function providers({ children }: TProvidersProps) {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<ApiProvider>
|
||||
<ThemeProvider defaultTheme="system" storageKey="frigate-ui-theme">
|
||||
<TooltipProvider>
|
||||
<IconContext.Provider value={{ size: "20" }}>
|
||||
<StatusBarMessagesProvider>
|
||||
<StreamingSettingsProvider>
|
||||
{children}
|
||||
</StreamingSettingsProvider>
|
||||
</StatusBarMessagesProvider>
|
||||
</IconContext.Provider>
|
||||
</TooltipProvider>
|
||||
</ThemeProvider>
|
||||
</ApiProvider>
|
||||
<AuthProvider>
|
||||
<ApiProvider>
|
||||
<ThemeProvider defaultTheme="system" storageKey="frigate-ui-theme">
|
||||
<TooltipProvider>
|
||||
<IconContext.Provider value={{ size: "20" }}>
|
||||
<StatusBarMessagesProvider>
|
||||
<StreamingSettingsProvider>
|
||||
{children}
|
||||
</StreamingSettingsProvider>
|
||||
</StatusBarMessagesProvider>
|
||||
</IconContext.Provider>
|
||||
</TooltipProvider>
|
||||
</ThemeProvider>
|
||||
</ApiProvider>
|
||||
</AuthProvider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user