From d627d1cfacada0db72289a21eb9d6f6aaa252431 Mon Sep 17 00:00:00 2001 From: Yamila Moreno Date: Wed, 29 Apr 2026 08:59:09 +0200 Subject: [PATCH] :sparkles: Improve team name validation (#9176) --- backend/src/app/rpc/commands/teams.clj | 4 +- backend/test/backend_tests/rpc_team_test.clj | 79 +++++++++++++++++++ common/src/app/common/types/team.cljc | 6 ++ .../src/app/main/ui/dashboard/team_form.cljs | 4 +- .../app/main/ui/onboarding/team_choice.cljs | 3 +- frontend/translations/en.po | 4 + 6 files changed, 95 insertions(+), 5 deletions(-) diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index 603e12187b..79d2008be9 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -497,7 +497,7 @@ (def ^:private schema:create-team [:map {:title "create-team"} - [:name [:string {:max 250}]] + [:name types.team/schema:team-name] [:features {:optional true} ::cfeat/features] [:id {:optional true} ::sm/uuid]]) @@ -591,7 +591,7 @@ (def ^:private schema:update-team [:map {:title "update-team"} - [:name [:string {:max 250}]] + [:name types.team/schema:team-name] [:id ::sm/uuid]]) (sv/defmethod ::update-team diff --git a/backend/test/backend_tests/rpc_team_test.clj b/backend/test/backend_tests/rpc_team_test.clj index 8fc553ff7e..66b412824b 100644 --- a/backend/test/backend_tests/rpc_team_test.clj +++ b/backend/test/backend_tests/rpc_team_test.clj @@ -767,3 +767,82 @@ (t/is (th/success? (th/command! data))) (t/is (= 1 (:call-count @mock)))))) +(t/deftest create-team-with-invalid-name + (let [profile (th/create-profile* 1 {:is-active true})] + + ;; name with a dot should fail + (let [data {::th/type :create-team + ::rpc/profile-id (:id profile) + :name "foo.bar"} + out (th/command! data)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; name with a colon should fail + (let [data {::th/type :create-team + ::rpc/profile-id (:id profile) + :name "foo:bar"} + out (th/command! data)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; name with a slash should fail + (let [data {::th/type :create-team + ::rpc/profile-id (:id profile) + :name "foo/bar"} + out (th/command! data)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; valid name should succeed + (let [data {::th/type :create-team + ::rpc/profile-id (:id profile) + :name "My Valid Team"} + out (th/command! data)] + (t/is (th/success? out))))) + +(t/deftest update-team-with-invalid-name + (let [profile (th/create-profile* 1 {:is-active true}) + team (th/create-team* 1 {:profile-id (:id profile)})] + + ;; name with a dot should fail + (let [data {::th/type :update-team + ::rpc/profile-id (:id profile) + :id (:id team) + :name "foo.bar"} + out (th/command! data)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; name with a colon should fail + (let [data {::th/type :update-team + ::rpc/profile-id (:id profile) + :id (:id team) + :name "foo:bar"} + out (th/command! data)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; name with a slash should fail + (let [data {::th/type :update-team + ::rpc/profile-id (:id profile) + :id (:id team) + :name "foo/bar"} + out (th/command! data)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; valid name should succeed + (let [data {::th/type :update-team + ::rpc/profile-id (:id profile) + :id (:id team) + :name "My Valid Team"} + out (th/command! data)] + (t/is (th/success? out))))) + diff --git a/common/src/app/common/types/team.cljc b/common/src/app/common/types/team.cljc index ad9bac999c..73a4085819 100644 --- a/common/src/app/common/types/team.cljc +++ b/common/src/app/common/types/team.cljc @@ -20,6 +20,12 @@ (def schema:role [::sm/one-of {:title "TeamRole"} valid-roles]) +(def schema:team-name + [:and + [::sm/text {:max 250}] + [:fn {:error/code "errors.team-name-invalid-chars"} + (fn [s] (not (re-find #"[.:/]" s)))]]) + ;; FIXME: specify more fields (def schema:team [:map {:title "Team"} diff --git a/frontend/src/app/main/ui/dashboard/team_form.cljs b/frontend/src/app/main/ui/dashboard/team_form.cljs index 5f6fdaae90..a2ef4d1490 100644 --- a/frontend/src/app/main/ui/dashboard/team_form.cljs +++ b/frontend/src/app/main/ui/dashboard/team_form.cljs @@ -7,7 +7,7 @@ (ns app.main.ui.dashboard.team-form (:require-macros [app.main.style :as stl]) (:require - [app.common.schema :as sm] + [app.common.types.team :as ctt] [app.main.data.common :as dcm] [app.main.data.event :as ev] [app.main.data.modal :as modal] @@ -24,7 +24,7 @@ (def ^:private schema:team-form [:map {:title "TeamForm"} - [:name [::sm/text {:max 250}]]]) + [:name ctt/schema:team-name]]) (defn- on-create-success [_form response] diff --git a/frontend/src/app/main/ui/onboarding/team_choice.cljs b/frontend/src/app/main/ui/onboarding/team_choice.cljs index bb896e49b5..0163de6c3d 100644 --- a/frontend/src/app/main/ui/onboarding/team_choice.cljs +++ b/frontend/src/app/main/ui/onboarding/team_choice.cljs @@ -8,6 +8,7 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.schema :as sm] + [app.common.types.team :as ctt] [app.main.data.common :as dcm] [app.main.data.event :as ev] [app.main.data.profile :as du] @@ -59,7 +60,7 @@ (def ^:private schema:team-form [:map {:title "TeamForm"} - [:name [::sm/text {:max 250}]] + [:name ctt/schema:team-name] [:role :keyword] [:emails {:optional true} [::sm/set ::sm/email]]]) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index bfe1c4b6fe..2cb6116e2f 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1435,6 +1435,10 @@ msgstr "The recovery token is invalid." msgid "errors.invalid-text" msgstr "Invalid text" +#: common/src/app/common/types/team.cljc:26 +msgid "errors.team-name-invalid-chars" +msgstr "The team name can't contain any of the following characters:'.', ':' or '/'" + #: src/app/main/ui/static.cljs:74 msgid "errors.invite-invalid" msgstr "Invite invalid"