reject users whose role was removed from the config

A user whose role was removed from the config fell back to viewer, which has no camera restrictions, so a restricted role removed from yaml gained access to every camera. /auth now rejects that user's tokens, and login refuses them until an admin assigns a valid role.
This commit is contained in:
Josh Hawkins
2026-10-18 13:12:43 -05:00
parent 859a5caffb
commit f8f79e7427
2 changed files with 24 additions and 11 deletions
+12 -7
View File
@@ -824,9 +824,11 @@ def auth(request: Request):
logger.debug("jwt token issued before password change")
return fail_response
# matches the fallback login applies to a role removed from the config
if role not in auth_config.roles:
role = "viewer"
logger.debug(
"jwt user %s has role %s, which is not in the config", user, role
)
return fail_response
current_time = int(time.time())
@@ -961,13 +963,16 @@ def login(request: Request, body: AppPostLoginBody):
password_hash = db_user.password_hash
if verify_password(password, password_hash):
role = getattr(db_user, "role", "viewer")
config_roles_set = set(request.app.frigate_config.auth.roles.keys())
if role not in config_roles_set:
role = db_user.role
if role not in request.app.frigate_config.auth.roles:
logger.warning(
f"User {db_user.username} has an invalid role {role}, falling back to 'viewer'."
"Login refused for user '%s' with role '%s', which is not in the config",
user,
role,
)
role = "viewer"
failed_logins.record(user, time.time(), known=True)
return JSONResponse(content={"message": "Login failed"}, status_code=401)
expiration = int(time.time()) + JWT_SESSION_LENGTH
encoded_jwt = create_encoded_jwt(user, role, expiration, request.app.jwt_token)
response = Response("", 200)
@@ -115,14 +115,22 @@ class TestAuthJwtRole(BaseTestHttp):
self.assertEqual(resp.status_code, 202)
self.assertEqual(resp.headers["remote-role"], "garage")
def test_role_removed_from_config_falls_back_to_viewer(self):
def test_role_removed_from_config_is_rejected(self):
User.update(role="removed_role").where(User.username == "bob").execute()
app = self._create_app()
with AuthTestClient(app) as client:
resp = self._auth(client, self._token(app, "bob", "removed_role"))
for source in ("bearer", "cookie"):
resp = self._auth(client, self._token(app, "bob", "garage"), source)
self.assertEqual(resp.status_code, 401)
self.assertNotIn("remote-role", resp.headers)
self.assertEqual(resp.status_code, 202)
self.assertEqual(resp.headers["remote-role"], "viewer")
def test_login_refused_for_role_removed_from_config(self):
User.update(role="removed_role").where(User.username == "bob").execute()
with AuthTestClient(self._create_app()) as client:
resp = client.post("/login", json={"user": "bob", "password": PASSWORD})
self.assertEqual(resp.status_code, 401)
self.assertNotIn("set-cookie", resp.headers)
def test_demoted_user_gets_new_role_on_next_request(self):
app = self._create_app()