mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-15 00:11:15 +03:00
fix(auth): wrap JWT secret in OctKey for joserfc 1.x compatibility
joserfc 1.x requires symmetric-algorithm keys to be `OctKey` instances
rather than raw strings. Since `requirements-wheels.txt` pins
`joserfc == 1.2.*`, every call to `create_encoded_jwt()` currently raises
`joserfc.errors.MissingKeyError` at `jwt.encode`, which nginx surfaces as
`500 Internal Server Error` on `POST /api/login`. The symmetric-verify
path in `is_logged_in_without_redirect` has the same problem on
`jwt.decode`.
Reproduction
docker exec <container> pip show joserfc # 1.2.2
curl -X POST -H 'Content-Type: application/json' \
-d '{"user":"<valid-user>","password":"<valid-pw>"}' \
http://<frigate>:5000/api/login
# -> 500 Internal Server Error
# log shows: joserfc.errors.MissingKeyError: missing_key
Minimal repro inside the container:
python3 -c "
from joserfc import jwt
jwt.encode({'alg':'HS256'}, {'sub':'t','exp':0}, 'some-secret')
"
# ValueError: Invalid key
python3 -c "
from joserfc import jwt
from joserfc.jwk import OctKey
print(jwt.encode({'alg':'HS256'}, {'sub':'t','exp':0},
OctKey.import_key('some-secret'))[:40])
"
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
The Home Assistant Frigate integration trips this path on every
configuration attempt because it always does `POST /api/login` with the
credentials supplied in the HA config flow, producing a confusing
"failed to connect" symptom rather than an auth error.
Fix
Wrap the secret with `OctKey.import_key(...)` at the two sites that call
into `joserfc.jwt`. The stored secret file format and everything else
stays the same; `get_jwt_secret()` continues to return a string.
AI disclosure
AI-assisted. I reviewed and tested the change end-to-end against a live
Frigate 0.17.1 container running joserfc 1.2.2: reproduced the 500 via
Home Assistant, identified the stack (auth.py line 366 in 0.17.1 / 404
on dev, and the decode call on 680), verified `OctKey.import_key(str)`
accepts the existing secret format, applied the patch in-container,
restarted, and confirmed `POST /api/login` now returns a clean
`401 {"message":"Login failed"}` for bad credentials and issues a valid
JWT cookie for good credentials. No new dependencies; `OctKey` is part
of `joserfc` which is already pinned.
This commit is contained in:
parent
cfb87f9744
commit
25b1db3118
@ -16,6 +16,7 @@ from typing import List, Optional
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response
|
from fastapi import APIRouter, Depends, HTTPException, Request, Response
|
||||||
from fastapi.responses import JSONResponse, RedirectResponse
|
from fastapi.responses import JSONResponse, RedirectResponse
|
||||||
from joserfc import jwt
|
from joserfc import jwt
|
||||||
|
from joserfc.jwk import OctKey
|
||||||
from peewee import DoesNotExist
|
from peewee import DoesNotExist
|
||||||
from slowapi import Limiter
|
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):
|
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(
|
return jwt.encode(
|
||||||
{"alg": "HS256"},
|
{"alg": "HS256"},
|
||||||
{"sub": user, "role": role, "exp": expiration, "iat": int(time.time())},
|
{"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
|
return fail_response
|
||||||
|
|
||||||
try:
|
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:
|
if "sub" not in token.claims:
|
||||||
logger.debug("user not set in jwt token")
|
logger.debug("user not set in jwt token")
|
||||||
return fail_response
|
return fail_response
|
||||||
|
|||||||
31
frigate/test/test_jwt_roundtrip.py
Normal file
31
frigate/test/test_jwt_roundtrip.py
Normal file
@ -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()
|
||||||
Loading…
Reference in New Issue
Block a user