From 52425a993aa9a91ba0b1789cf62448ced103d5db Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 29 Jul 2024 16:17:11 +0200 Subject: [PATCH] :bug: Check complaints reports in the same way as bounces are checked --- backend/src/app/email.clj | 8 ++ backend/src/app/http/awsns.clj | 132 +++++++++++------- backend/src/app/rpc/commands/auth.clj | 93 +++++++----- backend/src/app/rpc/commands/profile.clj | 47 +++++-- backend/src/app/rpc/commands/teams.clj | 13 +- .../test/backend_tests/rpc_profile_test.clj | 41 +++--- backend/test/backend_tests/rpc_team_test.clj | 6 +- .../app/main/ui/auth/recovery_request.cljs | 3 +- frontend/src/app/main/ui/auth/register.cljs | 8 +- frontend/src/app/main/ui/dashboard/team.cljs | 65 ++++----- .../src/app/main/ui/dashboard/team_form.cljs | 5 +- .../app/main/ui/settings/change_email.cljs | 25 ++-- 12 files changed, 272 insertions(+), 174 deletions(-) diff --git a/backend/src/app/email.clj b/backend/src/app/email.clj index 0f7e356b6..2b1eb4c12 100644 --- a/backend/src/app/email.clj +++ b/backend/src/app/email.clj @@ -448,3 +448,11 @@ {:email email :type "bounce"} {:limit 10}))] (>= (count reports) threshold)))) + +(defn has-reports? + ([conn email] (has-reports? conn email nil)) + ([conn email {:keys [threshold] :or {threshold 1}}] + (let [reports (db/exec! conn (sql/select :global-complaint-report + {:email email} + {:limit 10}))] + (>= (count reports) threshold)))) diff --git a/backend/src/app/http/awsns.clj b/backend/src/app/http/awsns.clj index 88060bb20..77ae6c5d6 100644 --- a/backend/src/app/http/awsns.clj +++ b/backend/src/app/http/awsns.clj @@ -9,6 +9,7 @@ (:require [app.common.exceptions :as ex] [app.common.logging :as l] + [app.common.pprint :as pp] [app.db :as db] [app.db.sql :as sql] [app.http.client :as http] @@ -16,10 +17,10 @@ [app.setup :as-alias setup] [app.tokens :as tokens] [app.worker :as-alias wrk] + [clojure.data.json :as j] [clojure.spec.alpha :as s] [cuerdas.core :as str] [integrant.core :as ig] - [jsonista.core :as j] [promesa.exec :as px] [ring.request :as rreq] [ring.response :as-alias rres])) @@ -136,83 +137,110 @@ (defn- parse-json [v] - (ex/ignoring - (j/read-value v))) + (try + (j/read-str v) + (catch Throwable cause + (l/wrn :hint "unable to decode request body" + :cause cause)))) (defn- register-bounce-for-profile [{:keys [::db/pool]} {:keys [type kind profile-id] :as report}] (when (= kind "permanent") - (db/with-atomic [conn pool] - (db/insert! conn :profile-complaint-report + (try + (db/insert! pool :profile-complaint-report {:profile-id profile-id :type (name type) :content (db/tjson report)}) - ;; TODO: maybe also try to find profiles by mail and if exists - ;; register profile reports for them? - (doseq [recipient (:recipients report)] - (db/insert! conn :global-complaint-report - {:email (:email recipient) - :type (name type) - :content (db/tjson report)})) + (catch Throwable cause + (l/warn :hint "unable to persist profile complaint" + :cause cause))) - (let [profile (db/exec-one! conn (sql/select :profile {:id profile-id}))] - (when (some #(= (:email profile) (:email %)) (:recipients report)) - ;; If the report matches the profile email, this means that - ;; the report is for itself, can be caused when a user - ;; registers with an invalid email or the user email is - ;; permanently rejecting receiving the email. In this case we - ;; have no option to mark the user as muted (and in this case - ;; the profile will be also inactive. - (db/update! conn :profile - {:is-muted true} - {:id profile-id})))))) - -(defn- register-complaint-for-profile - [{:keys [::db/pool]} {:keys [type profile-id] :as report}] - (db/with-atomic [conn pool] - (db/insert! conn :profile-complaint-report - {:profile-id profile-id - :type (name type) - :content (db/tjson report)}) - - ;; TODO: maybe also try to find profiles by email and if exists - ;; register profile reports for them? - (doseq [email (:recipients report)] - (db/insert! conn :global-complaint-report - {:email email + (doseq [recipient (:recipients report)] + (db/insert! pool :global-complaint-report + {:email (:email recipient) :type (name type) :content (db/tjson report)})) - (let [profile (db/exec-one! conn (sql/select :profile {:id profile-id}))] - (when (some #(= % (:email profile)) (:recipients report)) + (let [profile (db/exec-one! pool (sql/select :profile {:id profile-id}))] + (when (some #(= (:email profile) (:email %)) (:recipients report)) ;; If the report matches the profile email, this means that - ;; the report is for itself, rare case but can happen; In this - ;; case just mark profile as muted (very rare case). - (db/update! conn :profile + ;; the report is for itself, can be caused when a user + ;; registers with an invalid email or the user email is + ;; permanently rejecting receiving the email. In this case we + ;; have no option to mark the user as muted (and in this case + ;; the profile will be also inactive. + + (l/inf :hint "mark profile: muted" + :profile-id (str (:id profile)) + :email (:email profile) + :reason "bounce report" + :report-id (:feedback-id report)) + + (db/update! pool :profile {:is-muted true} - {:id profile-id}))))) + {:id profile-id} + {::db/return-keys false}))))) + +(defn- register-complaint-for-profile + [{:keys [::db/pool]} {:keys [type profile-id] :as report}] + + (try + (db/insert! pool :profile-complaint-report + {:profile-id profile-id + :type (name type) + :content (db/tjson report)}) + (catch Throwable cause + (l/warn :hint "unable to persist profile complaint" + :cause cause))) + + ;; TODO: maybe also try to find profiles by email and if exists + ;; register profile reports for them? + (doseq [email (:recipients report)] + (db/insert! pool :global-complaint-report + {:email email + :type (name type) + :content (db/tjson report)})) + + (let [profile (db/exec-one! pool (sql/select :profile {:id profile-id}))] + (when (some #(= % (:email profile)) (:recipients report)) + ;; If the report matches the profile email, this means that + ;; the report is for itself, rare case but can happen; In this + ;; case just mark profile as muted (very rare case). + (l/inf :hint "mark profile: muted" + :profile-id (str (:id profile)) + :email (:email profile) + :reason "complaint report" + :report-id (:feedback-id report)) + + (db/update! pool :profile + {:is-muted true} + {:id profile-id} + {::db/return-keys false})))) (defn- process-report [cfg {:keys [type profile-id] :as report}] - (l/trace :action "processing report" :report (pr-str report)) (cond ;; In this case we receive a bounce/complaint notification without ;; confirmed identity, we just emit a warning but do nothing about ;; it because this is not a normal case. All notifications should ;; come with profile identity. (nil? profile-id) - (l/warn :msg "a notification without identity received from AWS" - :report (pr-str report)) + (l/wrn :hint "not-identified report" + ::l/body (pp/pprint-str report {:length 40 :level 6})) (= "bounce" type) - (register-bounce-for-profile cfg report) + (do + (l/trc :hint "bounce report" + ::l/body (pp/pprint-str report {:length 40 :level 6})) + (register-bounce-for-profile cfg report)) (= "complaint" type) - (register-complaint-for-profile cfg report) + (do + (l/trc :hint "complaint report" + ::l/body (pp/pprint-str report {:length 40 :level 6})) + (register-complaint-for-profile cfg report)) :else - (l/warn :msg "unrecognized report received from AWS" - :report (pr-str report)))) - - + (l/wrn :hint "unrecognized report" + ::l/body (pp/pprint-str report {:length 20 :level 4})))) diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index 50f575755..ff07a041c 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -209,7 +209,19 @@ (str/lower (:password params))) (ex/raise :type :validation :code :email-as-password - :hint "you can't use your email as password"))) + :hint "you can't use your email as password")) + + (when (eml/has-bounce-reports? cfg (:email params)) + (ex/raise :type :restriction + :code :email-has-permanent-bounces + :email (:email params) + :hint "looks like the email has bounce reports")) + + (when (eml/has-complaint-reports? cfg (:email params)) + (ex/raise :type :restriction + :code :email-has-complaints + :email (:email params) + :hint "looks like the email has complaint reports"))) (defn prepare-register [{:keys [::db/pool] :as cfg} {:keys [email] :as params}] @@ -398,20 +410,22 @@ ::audit/profile-id (:id profile)})) (do - (send-email-verification! cfg profile) + (when-not (eml/has-reports? conn (:email profile)) + (send-email-verification! cfg profile)) + (rph/with-meta {:email (:email profile)} {::audit/replace-props props ::audit/context {:action "email-verification"} ::audit/profile-id (:id profile)}))) :else - (let [elapsed? (elapsed-verify-threshold? profile) - bounce? (eml/has-bounce-reports? conn (:email profile)) - action (if bounce? - "ignore-because-bounce" - (if elapsed? - "resend-email-verification" - "ignore"))] + (let [elapsed? (elapsed-verify-threshold? profile) + complaints? (eml/has-reports? conn (:email profile)) + action (if complaints? + "ignore-because-complaints" + (if elapsed? + "resend-email-verification" + "ignore"))] (l/wrn :hint "repeated registry detected" :profile-id (str (:id profile)) @@ -446,7 +460,7 @@ ;; ---- COMMAND: Request Profile Recovery (defn- request-profile-recovery - [{:keys [::db/pool] :as cfg} {:keys [email] :as params}] + [{:keys [::db/conn] :as cfg} {:keys [email] :as params}] (letfn [(create-recovery-token [{:keys [id] :as profile}] (let [token (tokens/generate (::setup/props cfg) {:iss :password-recovery @@ -468,39 +482,42 @@ :extra-data ptoken}) nil))] - (db/with-atomic [conn pool] - (let [profile (->> (profile/clean-email email) - (profile/get-profile-by-email conn))] + (let [profile (->> (profile/clean-email email) + (profile/get-profile-by-email conn))] - (cond - (not profile) - (l/wrn :hint "attempt of profile recovery: no profile found" - :profile-email email) + (cond + (not profile) + (l/wrn :hint "attempt of profile recovery: no profile found" + :profile-email email) - (not (eml/allow-send-emails? conn profile)) - (l/wrn :hint "attempt of profile recovery: profile is muted" - :profile-id (str (:id profile)) - :profile-email (:email profile)) + (not (eml/allow-send-emails? conn profile)) + (l/wrn :hint "attempt of profile recovery: profile is muted" + :profile-id (str (:id profile)) + :profile-email (:email profile)) - (eml/has-bounce-reports? conn (:email profile)) - (l/wrn :hint "attempt of profile recovery: email has bounces" - :profile-id (str (:id profile)) - :profile-email (:email profile)) + (eml/has-bounce-reports? conn (:email profile)) + (l/wrn :hint "attempt of profile recovery: email has bounces" + :profile-id (str (:id profile)) + :profile-email (:email profile)) - (not (elapsed-verify-threshold? profile)) - (l/wrn :hint "attempt of profile recovery: retry attempt threshold not elapsed" - :profile-id (str (:id profile)) - :profile-email (:email profile)) + (eml/has-complaint-reports? conn (:email profile)) + (l/wrn :hint "attempt of profile recovery: email has complaints" + :profile-id (str (:id profile)) + :profile-email (:email profile)) + (not (elapsed-verify-threshold? profile)) + (l/wrn :hint "attempt of profile recovery: retry attempt threshold not elapsed" + :profile-id (str (:id profile)) + :profile-email (:email profile)) - :else - (do - (db/update! conn :profile - {:modified-at (dt/now)} - {:id (:id profile)}) - (->> profile - (create-recovery-token) - (send-email-notification conn)))))))) + :else + (do + (db/update! conn :profile + {:modified-at (dt/now)} + {:id (:id profile)}) + (->> profile + (create-recovery-token) + (send-email-notification conn))))))) (def schema:request-profile-recovery @@ -512,6 +529,6 @@ ::doc/added "1.15" ::sm/params schema:request-profile-recovery} [cfg params] - (request-profile-recovery cfg params)) + (db/tx-run! cfg request-profile-recovery params)) diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index 985a8b211..40b8b8a43 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -276,19 +276,19 @@ (sv/defmethod ::request-email-change {::doc/added "1.0" ::sm/params schema:request-email-change} - [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id email] :as params}] - (db/with-atomic [conn pool] - (let [profile (db/get-by-id conn :profile profile-id) - cfg (assoc cfg ::conn conn) - params (assoc params - :profile profile - :email (clean-email email))] - (if (contains? cf/flags :smtp) - (request-email-change! cfg params) - (change-email-immediately! cfg params))))) + [cfg {:keys [::rpc/profile-id email] :as params}] + (db/tx-run! cfg + (fn [cfg] + (let [profile (db/get-by-id cfg :profile profile-id) + params (assoc params + :profile profile + :email (clean-email email))] + (if (contains? cf/flags :smtp) + (request-email-change! cfg params) + (change-email-immediately! cfg params)))))) (defn- change-email-immediately! - [{:keys [::conn]} {:keys [profile email] :as params}] + [{:keys [::db/conn]} {:keys [profile email] :as params}] (when (not= email (:email profile)) (check-profile-existence! conn params)) @@ -299,7 +299,7 @@ {:changed true}) (defn- request-email-change! - [{:keys [::conn] :as cfg} {:keys [profile email] :as params}] + [{:keys [::db/conn] :as cfg} {:keys [profile email] :as params}] (let [token (tokens/generate (::setup/props cfg) {:iss :change-email :exp (dt/in-future "15m") @@ -319,9 +319,28 @@ :hint "looks like the profile has reported repeatedly as spam or has permanent bounces.")) (when (eml/has-bounce-reports? conn email) - (ex/raise :type :validation + (ex/raise :type :restriction :code :email-has-permanent-bounces - :hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce")) + :email email + :hint "looks like the email has bounce reports")) + + (when (eml/has-complaint-reports? conn email) + (ex/raise :type :restriction + :code :email-has-complaints + :email email + :hint "looks like the email has spam complaint reports")) + + (when (eml/has-bounce-reports? conn (:email profile)) + (ex/raise :type :restriction + :code :email-has-permanent-bounces + :email (:email profile) + :hint "looks like the email has bounce reports")) + + (when (eml/has-complaint-reports? conn (:email profile)) + (ex/raise :type :restriction + :code :email-has-complaints + :email (:email profile) + :hint "looks like the email has spam complaint reports")) (eml/send! {::eml/conn conn ::eml/factory eml/change-email diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index e01e2ae36..e297b24ad 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -734,12 +734,19 @@ :email email :hint "the profile has reported repeatedly as spam or has bounces")) - ;; Secondly check if the invited member email is part of the global spam/bounce report. + ;; Secondly check if the invited member email is part of the global bounce report. (when (eml/has-bounce-reports? conn email) - (ex/raise :type :validation + (ex/raise :type :restriction :code :email-has-permanent-bounces :email email - :hint "the email you invite has been repeatedly reported as spam or bounce")) + :hint "the email you invite has been repeatedly reported as bounce")) + + ;; Secondly check if the invited member email is part of the global complain report. + (when (eml/has-complaint-reports? conn email) + (ex/raise :type :restriction + :code :email-has-complaints + :email email + :hint "the email you invite has been repeatedly reported as spam")) ;; When we have email verification disabled and invitation user is ;; already present in the database, we proceed to add it to the diff --git a/backend/test/backend_tests/rpc_profile_test.clj b/backend/test/backend_tests/rpc_profile_test.clj index 839494a17..7a90c9a81 100644 --- a/backend/test/backend_tests/rpc_profile_test.clj +++ b/backend/test/backend_tests/rpc_profile_test.clj @@ -590,9 +590,10 @@ (th/create-global-complaint-for pool {:type :bounce :email "user@example.com"}) (let [out (th/command! data)] - (t/is (th/success? out)) - (let [result (:result out)] - (t/is (contains? result :token)))))) + (t/is (not (th/success? out))) + (let [edata (-> out :error ex-data)] + (t/is (= :restriction (:type edata))) + (t/is (= :email-has-permanent-bounces (:code edata))))))) (t/deftest register-profile-with-complained-email (let [pool (:app.db/pool th/*system*) @@ -603,9 +604,11 @@ (th/create-global-complaint-for pool {:type :complaint :email "user@example.com"}) (let [out (th/command! data)] - (t/is (th/success? out)) - (let [result (:result out)] - (t/is (contains? result :token)))))) + (t/is (not (th/success? out))) + + (let [edata (-> out :error ex-data)] + (t/is (= :restriction (:type edata))) + (t/is (= :email-has-complaints (:code edata))))))) (t/deftest register-profile-with-email-as-password (let [data {::th/type :prepare-register-profile @@ -636,20 +639,26 @@ ;; with complaints (th/create-global-complaint-for pool {:type :complaint :email (:email data)}) - (let [out (th/command! data)] + (let [out (th/command! data)] ;; (th/print-result! out) (t/is (nil? (:result out))) - (t/is (= 2 (:call-count @mock)))) + + (let [edata (-> out :error ex-data)] + (t/is (= :restriction (:type edata))) + (t/is (= :email-has-complaints (:code edata)))) + + (t/is (= 1 (:call-count @mock)))) ;; with bounces (th/create-global-complaint-for pool {:type :bounce :email (:email data)}) - (let [out (th/command! data) - error (:error out)] + (let [out (th/command! data)] ;; (th/print-result! out) - (t/is (th/ex-info? error)) - (t/is (th/ex-of-type? error :validation)) - (t/is (th/ex-of-code? error :email-has-permanent-bounces)) - (t/is (= 2 (:call-count @mock))))))) + + (let [edata (-> out :error ex-data)] + (t/is (= :restriction (:type edata))) + (t/is (= :email-has-permanent-bounces (:code edata)))) + + (t/is (= 1 (:call-count @mock))))))) (t/deftest email-change-request-without-smtp @@ -714,7 +723,7 @@ out (th/command! data)] ;; (th/print-result! out) (t/is (nil? (:result out))) - (t/is (= 2 (:call-count @mock)))) + (t/is (= 1 (:call-count @mock)))) ;; with valid email and active user with global bounce (th/create-global-complaint-for pool {:type :bounce :email (:email profile2)}) @@ -723,7 +732,7 @@ (t/is (nil? (:result out))) (t/is (nil? (:error out))) ;; (th/print-result! out) - (t/is (= 2 (:call-count @mock)))))))) + (t/is (= 1 (:call-count @mock)))))))) (t/deftest update-profile-password diff --git a/backend/test/backend_tests/rpc_team_test.clj b/backend/test/backend_tests/rpc_team_test.clj index 3bd6ac3b9..8b4ccda3f 100644 --- a/backend/test/backend_tests/rpc_team_test.clj +++ b/backend/test/backend_tests/rpc_team_test.clj @@ -62,8 +62,8 @@ (th/reset-mock! mock) (let [data (assoc data :emails ["foo@bar.com"]) out (th/command! data)] - (t/is (th/success? out)) - (t/is (= 1 (:call-count (deref mock))))) + (t/is (not (th/success? out))) + (t/is (= 0 (:call-count (deref mock))))) ;; get invitation token (let [params {::th/type :get-team-invitation-token @@ -86,7 +86,7 @@ (t/is (= 0 (:call-count @mock))) (let [edata (-> out :error ex-data)] - (t/is (= :validation (:type edata))) + (t/is (= :restriction (:type edata))) (t/is (= :email-has-permanent-bounces (:code edata))))) ;; invite internal user that is muted diff --git a/frontend/src/app/main/ui/auth/recovery_request.cljs b/frontend/src/app/main/ui/auth/recovery_request.cljs index 43988fb3c..e3b9ec6ca 100644 --- a/frontend/src/app/main/ui/auth/recovery_request.cljs +++ b/frontend/src/app/main/ui/auth/recovery_request.cljs @@ -59,7 +59,8 @@ :profile-is-muted (rx/of (msg/error (tr "errors.profile-is-muted"))) - :email-has-permanent-bounces + (:email-has-permanent-bounces + :email-has-complaints) (rx/of (msg/error (tr "errors.email-has-permanent-bounces" (:email data)))) (rx/throw cause))))) diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 1c1d0c5a1..f7625c55e 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -54,7 +54,7 @@ (defn- on-prepare-register-error [form cause] - (let [{:keys [type code]} (ex-data cause)] + (let [{:keys [type code] :as edata} (ex-data cause)] (condp = [type code] [:restriction :registration-disabled] (st/emit! (msg/error (tr "errors.registration-disabled"))) @@ -62,6 +62,12 @@ [:restriction :email-domain-is-not-allowed] (st/emit! (msg/error (tr "errors.email-domain-not-allowed"))) + [:restriction :email-has-permanent-bounces] + (st/emit! (msg/error (tr "errors.email-has-permanent-bounces" (:email edata)))) + + [:restriction :email-has-complaints] + (st/emit! (msg/error (tr "errors.email-has-permanent-bounces" (:email edata)))) + [:validation :email-as-password] (swap! form assoc-in [:errors :password] {:message "errors.email-as-password"}) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index f5860f85c..ed6b241a2 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -33,7 +33,6 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) - (def ^:private arrow-icon (i/icon-xref :arrow (stl/css :arrow-icon))) @@ -62,10 +61,10 @@ {::mf/wrap [mf/memo] ::mf/wrap-props false} [{:keys [section team]}] - (let [on-nav-members (mf/use-fn #(st/emit! (dd/go-to-team-members))) - on-nav-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings))) - on-nav-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations))) - on-nav-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks))) + (let [on-nav-members (mf/use-fn #(st/emit! (dd/go-to-team-members))) + on-nav-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings))) + on-nav-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations))) + on-nav-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks))) members-section? (= section :dashboard-team-members) settings-section? (= section :dashboard-team-settings) @@ -157,21 +156,22 @@ (dd/fetch-team-invitations))) on-error - (fn [{:keys [type code] :as error}] - (cond - (and (= :validation type) - (= :profile-is-muted code)) - (st/emit! (msg/error (tr "errors.profile-is-muted")) - (modal/hide)) + (fn [_form cause] + (let [{:keys [type code] :as error} (ex-data cause)] + (cond + (and (= :validation type) + (= :profile-is-muted code)) + (st/emit! (msg/error (tr "errors.profile-is-muted")) + (modal/hide)) - (and (= :validation type) - (or (= :member-is-muted code) - (= :email-has-permanent-bounces code))) - (swap! error-text (tr "errors.email-spam-or-permanent-bounces" (:email error))) + (or (= :member-is-muted code) + (= :email-has-permanent-bounces code) + (= :email-has-complaints code)) + (swap! error-text (tr "errors.email-spam-or-permanent-bounces" (:email error))) - :else - (st/emit! (msg/error (tr "errors.generic")) - (modal/hide)))) + :else + (st/emit! (msg/error (tr "errors.generic")) + (modal/hide))))) on-submit (fn [form] @@ -563,22 +563,24 @@ on-error (mf/use-fn (mf/deps email) - (fn [{:keys [type code] :as error}] - (cond - (and (= :validation type) - (= :profile-is-muted code)) - (rx/of (msg/error (tr "errors.profile-is-muted"))) + (fn [cause] + (let [{:keys [type code] :as error} (ex-data cause)] + (cond + (and (= :validation type) + (= :profile-is-muted code)) + (rx/of (msg/error (tr "errors.profile-is-muted"))) - (and (= :validation type) - (= :member-is-muted code)) - (rx/of (msg/error (tr "errors.member-is-muted"))) + (and (= :validation type) + (= :member-is-muted code)) + (rx/of (msg/error (tr "errors.member-is-muted"))) - (and (= :validation type) - (= :email-has-permanent-bounces code)) - (rx/of (msg/error (tr "errors.email-has-permanent-bounces" email))) + (and (= :restriction type) + (or (= :email-has-permanent-bounces code) + (= :email-has-complaints code))) + (rx/of (msg/error (tr "errors.email-has-permanent-bounces" email))) - :else - (rx/throw error)))) + :else + (rx/throw cause))))) on-delete (mf/use-fn @@ -588,7 +590,6 @@ mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}] (st/emit! (dd/delete-team-invitation (with-meta params mdata)))))) - on-resend-success (mf/use-fn (fn [] diff --git a/frontend/src/app/main/ui/dashboard/team_form.cljs b/frontend/src/app/main/ui/dashboard/team_form.cljs index 1fb08a697..98555fc36 100644 --- a/frontend/src/app/main/ui/dashboard/team_form.cljs +++ b/frontend/src/app/main/ui/dashboard/team_form.cljs @@ -70,8 +70,9 @@ (on-update-submit form) (on-create-submit form)))) -(mf/defc team-form-modal {::mf/register modal/components - ::mf/register-as :team-form} +(mf/defc team-form-modal + {::mf/register modal/components + ::mf/register-as :team-form} [{:keys [team] :as props}] (let [initial (mf/use-memo (fn [] (or team {}))) form (fm/use-form :spec ::team-form diff --git a/frontend/src/app/main/ui/settings/change_email.cljs b/frontend/src/app/main/ui/settings/change_email.cljs index b90a55ee6..39daae480 100644 --- a/frontend/src/app/main/ui/settings/change_email.cljs +++ b/frontend/src/app/main/ui/settings/change_email.cljs @@ -39,21 +39,22 @@ (s/keys :req-un [::email-1 ::email-2])) (defn- on-error - [form error] - (case (:code (ex-data error)) - :email-already-exists - (swap! form (fn [data] - (let [error {:message (tr "errors.email-already-exists")}] - (assoc-in data [:errors :email-1] error)))) + [form cause] + (let [{:keys [code] :as error} (ex-data cause)] + (case code + :email-already-exists + (swap! form (fn [data] + (let [error {:message (tr "errors.email-already-exists")}] + (assoc-in data [:errors :email-1] error)))) - :profile-is-muted - (rx/of (msg/error (tr "errors.profile-is-muted"))) + :profile-is-muted + (rx/of (msg/error (tr "errors.profile-is-muted"))) - :email-has-permanent-bounces - (let [email (get @form [:data :email-1])] - (rx/of (msg/error (tr "errors.email-has-permanent-bounces" email)))) + (:email-has-permanent-bounces + :email-has-complaints) + (rx/of (msg/error (tr "errors.email-has-permanent-bounces" (:email error)))) - (rx/throw error))) + (rx/throw cause)))) (defn- on-success [profile data]