diff --git a/frigate/api/auth.py b/frigate/api/auth.py index e0090e32cd..5f4de5204f 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -912,7 +912,7 @@ def create_user( HASH_ITERATIONS = request.app.frigate_config.auth.hash_iterations config_roles = list(request.app.frigate_config.auth.roles.keys()) - if not re.match("^[A-Za-z0-9._]+$", body.username): + if not re.match(r"^(?!\.+$)[A-Za-z0-9._]+$", body.username): return JSONResponse(content={"message": "Invalid username"}, status_code=400) if body.role not in config_roles: diff --git a/frigate/test/http_api/test_http_users.py b/frigate/test/http_api/test_http_users.py new file mode 100644 index 0000000000..a964e7c0af --- /dev/null +++ b/frigate/test/http_api/test_http_users.py @@ -0,0 +1,33 @@ +"""Tests for user creation.""" + +from unittest.mock import MagicMock + +from frigate.models import User +from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp + +PASSWORD = "a-valid-password-123" + + +class TestCreateUser(BaseTestHttp): + def setUp(self): + super().setUp([User]) + self.app = super().create_app() + self.app.config_publisher = MagicMock() + + def _create(self, username: str): + with AuthTestClient(self.app) as client: + return client.post( + "/users", + json={"username": username, "password": PASSWORD, "role": "viewer"}, + ) + + def test_rejects_dot_only_usernames(self): + # browsers resolve these as URL path segments, so the per-user + # endpoints can never be reached for them + for username in (".", "..", "..."): + assert self._create(username).status_code == 400 + assert User.get_or_none(User.username == username) is None + + def test_accepts_username_containing_dots(self): + assert self._create("john.doe_1").status_code == 200 + assert User.get_or_none(User.username == "john.doe_1") is not None diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 1f50863e46..0c26619bc7 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -1054,6 +1054,7 @@ "title": "Create New User", "desc": "Add a new user account and specify a role for access to areas of the Frigate UI.", "usernameOnlyInclude": "Username may only include letters, numbers, . or _", + "usernameOnlyDots": "Username cannot contain only periods", "confirmPassword": "Please confirm your password" }, "deleteUser": { diff --git a/web/src/components/overlay/CreateUserDialog.tsx b/web/src/components/overlay/CreateUserDialog.tsx index 3b003f2611..2953a29cb9 100644 --- a/web/src/components/overlay/CreateUserDialog.tsx +++ b/web/src/components/overlay/CreateUserDialog.tsx @@ -82,6 +82,9 @@ export default function CreateUserDialog({ .min(1, t("users.dialog.form.usernameIsRequired")) .regex(/^[A-Za-z0-9._]+$/, { message: t("users.dialog.createUser.usernameOnlyInclude"), + }) + .regex(/[^.]/, { + message: t("users.dialog.createUser.usernameOnlyDots"), }), password: z .string()