diff --git a/docs/docs/configuration/authentication.md b/docs/docs/configuration/authentication.md index 1d1581b2c..17718c405 100644 --- a/docs/docs/configuration/authentication.md +++ b/docs/docs/configuration/authentication.md @@ -270,3 +270,42 @@ To use role-based access control, you must connect to Frigate via the **authenti 1. Log in as an **admin** user via port `8971`. 2. Navigate to **Settings > Users**. 3. Edit a user’s role by selecting **admin** or **viewer**. + +## API Authentication Guide + +### Getting a Bearer Token + +To use the Frigate API, you need to authenticate first. Follow these steps to obtain a Bearer token: + +#### 1. Login + +Make a POST request to `/login` with your credentials: + +```bash +curl -i -X POST https://frigate_ip:8971/api/login \ + -H "Content-Type: application/json" \ + -d '{"user": "admin", "password": "your_password"}' +``` + +:::note + +You may need to include `-k` in the argument list in these steps (eg: `curl -k -i -X POST ...`) if your Frigate instance is using a self-signed certificate. + +::: + +The response will contain a cookie with the JWT token. + +#### 2. Using the Bearer Token + +Once you have the token, include it in the Authorization header for subsequent requests: + +```bash +curl -H "Authorization: Bearer " https://frigate_ip:8971/api/profile +``` + +#### 3. Token Lifecycle + +- Tokens are valid for the configured session length +- Tokens are automatically refreshed when you visit the `/auth` endpoint +- Tokens are invalidated when the user's password is changed +- Use `/logout` to clear your session cookie diff --git a/docs/docs/configuration/custom_classification/state_classification.md b/docs/docs/configuration/custom_classification/state_classification.md index 927fe91af..801d5d905 100644 --- a/docs/docs/configuration/custom_classification/state_classification.md +++ b/docs/docs/configuration/custom_classification/state_classification.md @@ -60,11 +60,9 @@ Choose one or more cameras and draw a rectangle over the area of interest for ea ### Step 3: Assign Training Examples -The system will automatically generate example images from your camera feeds. You'll be guided through each class one at a time to select which images represent that state. +The system will automatically generate example images from your camera feeds. You'll be guided through each class one at a time to select which images represent that state. It's not strictly required to select all images you see. If a state is missing from the samples, you can train it from the Recent tab later. -**Important**: All images must be assigned to a state before training can begin. This includes images that may not be optimal, such as when people temporarily block the view, sun glare is present, or other distractions occur. Assign these images to the state that is actually present (based on what you know the state to be), not based on the distraction. This training helps the model correctly identify the state even when such conditions occur during inference. - -Once all images are assigned, training will begin automatically. +Once some images are assigned, training will begin automatically. ### Improving the Model diff --git a/docs/static/frigate-api.yaml b/docs/static/frigate-api.yaml index f9981f8de..1bd5e95b8 100644 --- a/docs/static/frigate-api.yaml +++ b/docs/static/frigate-api.yaml @@ -14,7 +14,11 @@ paths: get: tags: - Auth - summary: Auth + summary: Authenticate request + description: |- + Authenticates the current request based on proxy headers or JWT token. + Returns user role and permissions for camera access. + This endpoint verifies authentication credentials and manages JWT token refresh. operationId: auth_auth_get responses: "200": @@ -22,11 +26,21 @@ paths: content: application/json: schema: {} + "202": + description: Authentication Accepted + content: + application/json: + schema: {} + "401": + description: Authentication Failed /profile: get: tags: - Auth - summary: Profile + summary: Get user profile + description: |- + Returns the current authenticated user's profile including username, role, and allowed cameras. + This endpoint requires authentication and returns information about the user's permissions. operationId: profile_profile_get responses: "200": @@ -34,11 +48,16 @@ paths: content: application/json: schema: {} + "401": + description: Unauthorized /logout: get: tags: - Auth - summary: Logout + summary: Logout user + description: |- + Logs out the current user by clearing the session cookie. + After logout, subsequent requests will require re-authentication. operationId: logout_logout_get responses: "200": @@ -46,11 +65,22 @@ paths: content: application/json: schema: {} + "303": + description: See Other (redirects to login page) /login: post: tags: - Auth - summary: Login + summary: Login with credentials + description: |- + Authenticates a user with username and password. + Returns a JWT token as a secure HTTP-only cookie that can be used for subsequent API requests. + The JWT token can also be retrieved from the response and used as a Bearer token in the Authorization header. + + Example using Bearer token: + ``` + curl -H "Authorization: Bearer " https://frigate_ip:8971/api/profile + ``` operationId: login_login_post requestBody: required: true @@ -64,6 +94,11 @@ paths: content: application/json: schema: {} + "401": + description: Login Failed - Invalid credentials + content: + application/json: + schema: {} "422": description: Validation Error content: @@ -74,7 +109,10 @@ paths: get: tags: - Auth - summary: Get Users + summary: Get all users + description: |- + Returns a list of all users with their usernames and roles. + Requires admin role. Each user object contains the username and assigned role. operationId: get_users_users_get responses: "200": @@ -82,10 +120,19 @@ paths: content: application/json: schema: {} + "403": + description: Forbidden - Admin role required post: tags: - Auth - summary: Create User + summary: Create new user + description: |- + Creates a new user with the specified username, password, and role. + Requires admin role. Password must meet strength requirements: + - Minimum 8 characters + - At least one uppercase letter + - At least one digit + - At least one special character (!@#$%^&*(),.?":{}\|<>) operationId: create_user_users_post requestBody: required: true @@ -99,6 +146,13 @@ paths: content: application/json: schema: {} + "400": + description: Bad Request - Invalid username or role + content: + application/json: + schema: {} + "403": + description: Forbidden - Admin role required "422": description: Validation Error content: @@ -109,7 +163,10 @@ paths: delete: tags: - Auth - summary: Delete User + summary: Delete user + description: |- + Deletes a user by username. The built-in admin user cannot be deleted. + Requires admin role. Returns success message or error if user not found. operationId: delete_user_users__username__delete parameters: - name: username @@ -118,12 +175,15 @@ paths: schema: type: string title: Username + description: The username of the user to delete responses: "200": description: Successful Response content: application/json: schema: {} + "403": + description: Forbidden - Cannot delete admin user or admin role required "422": description: Validation Error content: @@ -134,7 +194,17 @@ paths: put: tags: - Auth - summary: Update Password + summary: Update user password + description: |- + Updates a user's password. Users can only change their own password unless they have admin role. + Requires the current password to verify identity for non-admin users. + Password must meet strength requirements: + - Minimum 8 characters + - At least one uppercase letter + - At least one digit + - At least one special character (!@#$%^&*(),.?":{}\|<>) + + If user changes their own password, a new JWT cookie is automatically issued. operationId: update_password_users__username__password_put parameters: - name: username @@ -143,6 +213,7 @@ paths: schema: type: string title: Username + description: The username of the user whose password to update requestBody: required: true content: @@ -155,6 +226,14 @@ paths: content: application/json: schema: {} + "400": + description: Bad Request - Current password required or password doesn't meet requirements + "401": + description: Unauthorized - Current password is incorrect + "403": + description: Forbidden - Viewers can only update their own password + "404": + description: Not Found - User not found "422": description: Validation Error content: @@ -165,7 +244,10 @@ paths: put: tags: - Auth - summary: Update Role + summary: Update user role + description: |- + Updates a user's role. The built-in admin user's role cannot be modified. + Requires admin role. Valid roles are defined in the configuration. operationId: update_role_users__username__role_put parameters: - name: username @@ -174,6 +256,7 @@ paths: schema: type: string title: Username + description: The username of the user whose role to update requestBody: required: true content: @@ -186,6 +269,10 @@ paths: content: application/json: schema: {} + "400": + description: Bad Request - Invalid role + "403": + description: Forbidden - Cannot modify admin user's role or admin role required "422": description: Validation Error content: diff --git a/frigate/api/auth.py b/frigate/api/auth.py index d913173d0..cae80dc5b 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -549,7 +549,12 @@ def resolve_role( # Endpoints -@router.get("/auth", dependencies=[Depends(allow_public())]) +@router.get( + "/auth", + dependencies=[Depends(allow_public())], + summary="Authenticate request", + description="Authenticates the current request based on proxy headers or JWT token. Returns user role and permissions for camera access.", +) def auth(request: Request): auth_config: AuthConfig = request.app.frigate_config.auth proxy_config: ProxyConfig = request.app.frigate_config.proxy @@ -689,7 +694,12 @@ def auth(request: Request): return fail_response -@router.get("/profile", dependencies=[Depends(allow_any_authenticated())]) +@router.get( + "/profile", + dependencies=[Depends(allow_any_authenticated())], + summary="Get user profile", + description="Returns the current authenticated user's profile including username, role, and allowed cameras.", +) def profile(request: Request): username = request.headers.get("remote-user", "anonymous") role = request.headers.get("remote-role", "viewer") @@ -703,7 +713,12 @@ def profile(request: Request): ) -@router.get("/logout", dependencies=[Depends(allow_public())]) +@router.get( + "/logout", + dependencies=[Depends(allow_public())], + summary="Logout user", + description="Logs out the current user by clearing the session cookie.", +) def logout(request: Request): auth_config: AuthConfig = request.app.frigate_config.auth response = RedirectResponse("/login", status_code=303) @@ -714,7 +729,12 @@ def logout(request: Request): limiter = Limiter(key_func=get_remote_addr) -@router.post("/login", dependencies=[Depends(allow_public())]) +@router.post( + "/login", + dependencies=[Depends(allow_public())], + summary="Login with credentials", + description="Authenticates a user with username and password. Returns a JWT token as a secure HTTP-only cookie that can be used for subsequent API requests. The token can also be retrieved and used as a Bearer token in the Authorization header.", +) @limiter.limit(limit_value=rateLimiter.get_limit) def login(request: Request, body: AppPostLoginBody): JWT_COOKIE_NAME = request.app.frigate_config.auth.cookie_name @@ -752,7 +772,12 @@ def login(request: Request, body: AppPostLoginBody): return JSONResponse(content={"message": "Login failed"}, status_code=401) -@router.get("/users", dependencies=[Depends(require_role(["admin"]))]) +@router.get( + "/users", + dependencies=[Depends(require_role(["admin"]))], + summary="Get all users", + description="Returns a list of all users with their usernames and roles. Requires admin role.", +) def get_users(): exports = ( User.select(User.username, User.role).order_by(User.username).dicts().iterator() @@ -760,7 +785,12 @@ def get_users(): return JSONResponse([e for e in exports]) -@router.post("/users", dependencies=[Depends(require_role(["admin"]))]) +@router.post( + "/users", + dependencies=[Depends(require_role(["admin"]))], + summary="Create new user", + description="Creates a new user with the specified username, password, and role. Requires admin role. Password must meet strength requirements.", +) def create_user( request: Request, body: AppPostUsersBody, @@ -789,7 +819,12 @@ def create_user( return JSONResponse(content={"username": body.username}) -@router.delete("/users/{username}", dependencies=[Depends(require_role(["admin"]))]) +@router.delete( + "/users/{username}", + dependencies=[Depends(require_role(["admin"]))], + summary="Delete user", + description="Deletes a user by username. The built-in admin user cannot be deleted. Requires admin role.", +) def delete_user(request: Request, username: str): # Prevent deletion of the built-in admin user if username == "admin": @@ -802,7 +837,10 @@ def delete_user(request: Request, username: str): @router.put( - "/users/{username}/password", dependencies=[Depends(allow_any_authenticated())] + "/users/{username}/password", + dependencies=[Depends(allow_any_authenticated())], + summary="Update user password", + description="Updates a user's password. Users can only change their own password unless they have admin role. Requires the current password to verify identity. Password must meet strength requirements (minimum 8 characters, uppercase letter, digit, and special character).", ) async def update_password( request: Request, @@ -887,6 +925,8 @@ async def update_password( @router.put( "/users/{username}/role", dependencies=[Depends(require_role(["admin"]))], + summary="Update user role", + description="Updates a user's role. The built-in admin user's role cannot be modified. Requires admin role.", ) async def update_role( request: Request, diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index d7724c648..36f7828fc 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -153,7 +153,7 @@ PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = { FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {2}", "preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {2}", "preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v main -level:v 4.1 -async_depth:v 1 {2}", - FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -hwaccel device {3} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", + FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -hwaccel cuda -hwaccel_device {3} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", "preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}", "preset-jetson-h265": "{0} -hide_banner {1} -c:v h264_nvmpi -profile main {2}", FFMPEG_HWACCEL_RKMPP: "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}", diff --git a/web/src/components/Statusbar.tsx b/web/src/components/Statusbar.tsx index 0ac6d10a4..ab22a1143 100644 --- a/web/src/components/Statusbar.tsx +++ b/web/src/components/Statusbar.tsx @@ -116,10 +116,10 @@ export default function Statusbar() { } return ( - + {" "}
{t("wizard.step1.classificationType")} - +