diff --git a/frigate/api/auth.py b/frigate/api/auth.py index d1c9688186..70446c8773 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -16,6 +16,7 @@ from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Request, Response from fastapi.responses import JSONResponse, RedirectResponse from joserfc import jwt +from joserfc.jwk import OctKey from peewee import DoesNotExist from slowapi import Limiter @@ -401,10 +402,14 @@ def validate_password_strength(password: str) -> tuple[bool, Optional[str]]: def create_encoded_jwt(user, role, expiration, secret): + # joserfc 1.x requires an OctKey for symmetric algorithms instead of a + # raw string; passing the string raises joserfc.errors.MissingKeyError + # (surfaces as a 500 on POST /api/login). See requirements pin + # `joserfc == 1.2.*`. return jwt.encode( {"alg": "HS256"}, {"sub": user, "role": role, "exp": expiration, "iat": int(time.time())}, - secret, + OctKey.import_key(secret), ) @@ -677,7 +682,7 @@ def auth(request: Request): return fail_response try: - token = jwt.decode(encoded_token, request.app.jwt_token) + token = jwt.decode(encoded_token, OctKey.import_key(request.app.jwt_token)) if "sub" not in token.claims: logger.debug("user not set in jwt token") return fail_response diff --git a/frigate/test/test_jwt_roundtrip.py b/frigate/test/test_jwt_roundtrip.py new file mode 100644 index 0000000000..8f82892239 --- /dev/null +++ b/frigate/test/test_jwt_roundtrip.py @@ -0,0 +1,31 @@ +"""Regression test for create_encoded_jwt + joserfc 1.x OctKey handling. + +joserfc 1.x requires an OctKey for symmetric algorithms; passing a raw +string raises MissingKeyError / ValueError inside jwt.encode. This test +pins the fix so a future refactor can't silently reintroduce the crash. +""" + +import time +import unittest + +from joserfc import jwt +from joserfc.jwk import OctKey + +from frigate.api.auth import create_encoded_jwt + + +class TestCreateEncodedJwtRoundTrip(unittest.TestCase): + def test_round_trip_with_string_secret(self): + secret = "unit-test-secret-string-abc123" + expiration = int(time.time()) + 60 + + token = create_encoded_jwt("alice", "admin", expiration, secret) + + decoded = jwt.decode(token, OctKey.import_key(secret)) + self.assertEqual(decoded.claims["sub"], "alice") + self.assertEqual(decoded.claims["role"], "admin") + self.assertEqual(decoded.claims["exp"], expiration) + + +if __name__ == "__main__": + unittest.main()