From 743acd87fd337b217d16878999f5a2f37ca0fda3 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:35:55 -0600 Subject: [PATCH] update auth api endpoint descriptions and docs --- docs/docs/configuration/authentication.md | 39 ++++++++ docs/static/frigate-api.yaml | 105 ++++++++++++++++++++-- frigate/api/auth.py | 56 ++++++++++-- 3 files changed, 183 insertions(+), 17 deletions(-) diff --git a/docs/docs/configuration/authentication.md b/docs/docs/configuration/authentication.md index 1d1581b2c..bbcb7f32c 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/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,