🐛 Prevent invitations to blacklisted domains

This commit is contained in:
Yamila Moreno 2026-04-24 15:56:12 +02:00 committed by Andrey Antukh
parent d380efdb0c
commit 7031052c4e
4 changed files with 54 additions and 1 deletions

View File

@ -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 \

View File

@ -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.

View File

@ -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})

View File

@ -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)))))