From cfa595bb94de37c67777a56fe70c5a8a18daef35 Mon Sep 17 00:00:00 2001 From: Yamila Moreno Date: Fri, 24 Apr 2026 15:56:12 +0200 Subject: [PATCH] :bug: Prevent invitations to blacklisted domains --- CHANGES.md | 1 + backend/scripts/_env | 2 +- .../app/rpc/commands/teams_invitations.clj | 7 ++++ backend/test/backend_tests/rpc_team_test.clj | 41 +++++++++++++++++++ frontend/src/app/main/ui/dashboard/team.cljs | 5 +++ 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index df90267bad..fa69244e4d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ ### :bug: Bugs fixed - Fix incorrect handling of version restore operation [Github #9041](https://github.com/penpot/penpot/pull/9041) +- Prevent invitations to blacklisted domains [Github #9150](https://github.com/penpot/penpot/pull/9150) ## 2.14.4 diff --git a/backend/scripts/_env b/backend/scripts/_env index e6ff68b7f4..5367a031b9 100644 --- a/backend/scripts/_env +++ b/backend/scripts/_env @@ -12,7 +12,7 @@ export PENPOT_PUBLIC_URI=https://localhost:3449 export PENPOT_FLAGS="\ $PENPOT_FLAGS \ - enable-login-with-password + enable-login-with-password \ disable-login-with-ldap \ disable-login-with-oidc \ disable-login-with-google \ diff --git a/backend/src/app/rpc/commands/teams_invitations.clj b/backend/src/app/rpc/commands/teams_invitations.clj index 5cffdd0c69..dfc83000a5 100644 --- a/backend/src/app/rpc/commands/teams_invitations.clj +++ b/backend/src/app/rpc/commands/teams_invitations.clj @@ -19,6 +19,7 @@ [app.config :as cf] [app.db :as db] [app.email :as eml] + [app.email.blacklist :as email.blacklist] [app.loggers.audit :as audit] [app.main :as-alias main] [app.rpc :as-alias rpc] @@ -91,6 +92,12 @@ (let [email (profile/clean-email email) member (profile/get-profile-by-email conn email)] + (when (and (email.blacklist/enabled? cfg) + (email.blacklist/contains? cfg email)) + (ex/raise :type :restriction + :code :email-domain-is-not-allowed + :hint "email domain is in the blacklist")) + ;; When we have email verification disabled and invitation user is ;; already present in the database, we proceed to add it to the ;; team as-is, without email roundtrip. diff --git a/backend/test/backend_tests/rpc_team_test.clj b/backend/test/backend_tests/rpc_team_test.clj index daf09e72a7..8fc553ff7e 100644 --- a/backend/test/backend_tests/rpc_team_test.clj +++ b/backend/test/backend_tests/rpc_team_test.clj @@ -11,6 +11,7 @@ [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] + [app.email.blacklist :as email.blacklist] [app.http :as http] [app.rpc :as-alias rpc] [app.storage :as sto] @@ -102,6 +103,46 @@ (t/is (= :validation (:type edata))) (t/is (= :member-is-muted (:code edata)))))))) +(t/deftest create-team-invitations-blacklisted-domain + (with-mocks [mock {:target 'app.email/send! :return nil}] + (let [profile1 (th/create-profile* 1 {:is-active true}) + team (th/create-team* 1 {:profile-id (:id profile1)}) + data {::th/type :create-team-invitations + ::rpc/profile-id (:id profile1) + :team-id (:id team) + :role :editor}] + + ;; invite from a directly blacklisted domain should fail + (with-redefs [email.blacklist/enabled? (constantly true) + email.blacklist/contains? (fn [_ email] + (clojure.string/ends-with? email "@blacklisted.com"))] + (let [out (th/command! (assoc data :emails ["user@blacklisted.com"]))] + (t/is (not (th/success? out))) + (t/is (= 0 (:call-count @mock))) + (let [edata (-> out :error ex-data)] + (t/is (= :restriction (:type edata))) + (t/is (= :email-domain-is-not-allowed (:code edata)))))) + + ;; invite from a subdomain of a blacklisted domain should also fail + (th/reset-mock! mock) + (with-redefs [email.blacklist/enabled? (constantly true) + email.blacklist/contains? (fn [_ email] + (clojure.string/ends-with? email "@sub.blacklisted.com"))] + (let [out (th/command! (assoc data :emails ["user@sub.blacklisted.com"]))] + (t/is (not (th/success? out))) + (t/is (= 0 (:call-count @mock))) + (let [edata (-> out :error ex-data)] + (t/is (= :restriction (:type edata))) + (t/is (= :email-domain-is-not-allowed (:code edata)))))) + + ;; invite from a non-blacklisted domain should succeed + (th/reset-mock! mock) + (with-redefs [email.blacklist/enabled? (constantly true) + email.blacklist/contains? (constantly false)] + (let [out (th/command! (assoc data :emails ["user@allowed.com"]))] + (t/is (th/success? out)) + (t/is (= 1 (:call-count @mock)))))))) + (t/deftest create-team-invitations-with-request-access (with-mocks [mock {:target 'app.email/send! :return nil}] (let [profile1 (th/create-profile* 1 {:is-active true}) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 96afda2563..993b1623a8 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -195,6 +195,11 @@ (= :email-has-complaints code)) (swap! error-text (tr "errors.email-spam-or-permanent-bounces" (:email error))) + (and (= :restriction type) + (= :email-domain-is-not-allowed code)) + (st/emit! (ntf/error (tr "errors.email-domain-not-allowed")) + (modal/hide)) + :else (st/emit! (ntf/error (tr "errors.generic")) (modal/hide)))))