0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-09 00:10:11 -05:00

Merge pull request #5137 from penpot/niwinz-enhancements-1

 Add limits for invitation creation RPC method
This commit is contained in:
Alejandro 2024-10-03 07:18:18 +02:00 committed by GitHub
commit ae7e28b71b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 68 additions and 30 deletions

View file

@ -6,6 +6,7 @@
- Fix problem with Ctrl+F shortcut on the dashboard [Taiga #8876](https://tree.taiga.io/project/penpot/issue/8876)
- Fix visual problem with the font-size dropdown in assets [Taiga #8872](https://tree.taiga.io/project/penpot/issue/8872)
- Add limits for invitation RPC methods (hard limit 25 emails per request)
## 2.2.0

View file

@ -908,6 +908,10 @@
[:role schema:role]
[:emails [::sm/set ::sm/email]]])
(def ^:private max-invitations-by-request-threshold
"The number of invitations can be sent in a single rpc request"
25)
(sv/defmethod ::create-team-invitations
"A rpc call that allow to send a single or multiple invitations to
join the team."
@ -920,6 +924,12 @@
team (db/get-by-id conn :team team-id)
emails (into #{} (map profile/clean-email) emails)]
(when (> (count emails) max-invitations-by-request-threshold)
(ex/raise :type :validation
:code :max-invitations-by-request
:hint "the maximum of invitation on single request is reached"
:threshold max-invitations-by-request-threshold))
(run! (partial quotes/check-quote! conn)
(list {::quotes/id ::quotes/invitations-per-team
::quotes/profile-id profile-id
@ -994,6 +1004,12 @@
profile (db/get-by-id conn :profile profile-id)
emails (into #{} (map profile/clean-email) emails)]
(when (> (count emails) max-invitations-by-request-threshold)
(ex/raise :type :validation
:code :max-invitations-by-request
:hint "the maximum of invitation on single request is reached"
:threshold max-invitations-by-request-threshold))
(let [props {:name name :features features}
event (-> (audit/event-from-rpc-params params)
(assoc ::audit/name "create-team")

View file

@ -448,7 +448,7 @@
(defn parse-email
[s]
(if (string? s)
(re-matches email-re s)
(first (re-seq email-re s))
nil))
(defn email-string?

View file

@ -10,7 +10,6 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.config :as cfg]
[app.main.data.dashboard :as dd]
[app.main.data.events :as ev]
@ -30,7 +29,6 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[beicon.v2.core :as rx]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
@ -129,17 +127,10 @@
]
(filterv identity)))
(s/def ::emails (s/and ::us/set-of-valid-emails d/not-empty?))
(s/def ::role ::us/keyword)
(s/def ::team-id ::us/uuid)
(s/def ::invite-member-form
(s/keys :req-un [::role ::emails ::team-id]))
(def ^:private schema:invite-member-form
[:map {:title "InviteMemberForm"}
[:role :keyword]
[:emails [::sm/set {:kind ::sm/email :min 1}]]
[:emails [::sm/set {:min 1} ::sm/email]]
[:team-id ::sm/uuid]])
(mf/defc invite-members-modal
@ -181,6 +172,10 @@
(st/emit! (ntf/error (tr "errors.profile-is-muted"))
(modal/hide))
(and (= :validation type)
(= :max-invitations-by-request code))
(swap! error-text (tr "errors.maximum-invitations-by-request-reached" (:threshold error)))
(or (= :member-is-muted code)
(= :email-has-permanent-bounces code)
(= :email-has-complaints code))
@ -226,10 +221,9 @@
:name :emails
:auto-focus? true
:trim true
:valid-item-fn us/parse-email
:valid-item-fn sm/parse-email
:caution-item-fn current-members-emails
:label (tr "modals.invite-member.emails")
:on-submit on-submit
:invite-email invite-email}]]
[:div {:class (stl/css :action-buttons)}

View file

@ -11,11 +11,11 @@
[app.common.schema :as sm]
[app.main.data.dashboard :as dd]
[app.main.data.events :as ev]
[app.main.data.notifications :as ntf]
[app.main.data.users :as du]
[app.main.store :as st]
[app.main.ui.components.forms :as fm]
[app.main.ui.icons :as i]
[app.main.ui.notifications.context-notification :refer [context-notification]]
[app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt]
[potok.v2.core :as ptk]
@ -57,7 +57,7 @@
(def ^:private schema:invite-form
[:map {:title "InviteForm"}
[:role :keyword]
[:emails {:optional true} [::sm/set {:kind ::sm/email}]]])
[:emails {:optional true} [::sm/set ::sm/email]]])
(defn- get-available-roles
[]
@ -67,17 +67,14 @@
(mf/defc team-form-step-2
{::mf/props :obj}
[{:keys [name on-back go-to-team?]}]
(let [initial (mf/use-memo
#(do {:role "editor"
:name name}))
(let [initial (mf/with-memo []
{:role "editor" :name name})
form (fm/use-form :schema schema:invite-form
:initial initial)
params (:clean-data @form)
emails (:emails params)
roles (mf/use-memo get-available-roles)
error* (mf/use-state nil)
on-success
(mf/use-fn
@ -90,8 +87,24 @@
on-error
(mf/use-fn
(fn [_]
(st/emit! (ntf/error (tr "errors.generic")))))
(fn [cause]
(let [{:keys [type code] :as error} (ex-data cause)]
(cond
(and (= :validation type)
(= :profile-is-muted code))
(swap! error* (tr "errors.profile-is-muted"))
(and (= :validation type)
(= :max-invitations-by-request code))
(swap! error* (tr "errors.maximum-invitations-by-request-reached" (:threshold error)))
(or (= :member-is-muted code)
(= :email-has-permanent-bounces code)
(= :email-has-complaints code))
(swap! error* (tr "errors.email-spam-or-permanent-bounces" (:email error)))
:else
(swap! error* (tr "errors.generic"))))))
on-invite-later
(mf/use-fn
@ -111,7 +124,7 @@
on-invite-now
(mf/use-fn
(fn [{:keys [name] :as params}]
(fn [{:keys [name emails] :as params}]
(let [mdata {:on-success on-success
:on-error on-error}]
@ -143,6 +156,10 @@
[:& fm/form {:form form
:class (stl/css :modal-form-invitations)
:on-submit on-submit}
(when-let [content (deref error*)]
[:& context-notification {:content content :level :error}])
[:div {:class (stl/css :role-select)}
[:p {:class (stl/css :role-title)} (tr "onboarding.choice.team-up.roles")]
[:& fm/select {:name :role :options roles}]]
@ -155,18 +172,22 @@
:valid-item-fn sm/parse-email
:caution-item-fn #{}
:label (tr "modals.invite-member.emails")
:on-submit on-submit}]]
;; :on-submit on-submit
}]]
[:div {:class (stl/css :action-buttons)}
[:button {:class (stl/css :back-button)
:on-click on-back}
(tr "labels.back")]
[:> fm/submit-button*
{:class (stl/css :accept-button)
:label (if (> (count emails) 0)
(tr "onboarding.choice.team-up.create-team-and-invite")
(tr "onboarding.choice.team-up.create-team-without-invite"))}]]
(let [params (:clean-data @form)
emails (:emails params)]
[:> fm/submit-button*
{:class (stl/css :accept-button)
:label (if (> (count emails) 0)
(tr "onboarding.choice.team-up.create-team-and-invite")
(tr "onboarding.choice.team-up.create-team-without-invite"))}])]
[:div {:class (stl/css :modal-hint)}
"(" (tr "onboarding.choice.team-up.create-team-and-send-invites-description") ")"]]]

View file

@ -923,6 +923,9 @@ msgstr "Are you sure?"
msgid "errors.auth-provider-not-allowed"
msgstr "Auth provider not allowed for this profile"
msgid "errors.maximum-invitations-by-request-reached"
msgstr "The maximum (%s) number of emails that can be invited in a single request has been reached"
#: src/app/main/ui/auth/login.cljs:61
msgid "errors.auth-provider-not-configured"
msgstr "Authentication provider not configured."

View file

@ -6094,3 +6094,6 @@ msgstr "Actualizar"
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "Pulsar para cerrar la ruta"
msgid "errors.maximum-invitations-by-request-reached"
msgstr "Se ha alcanzado el número máximo (%s) de correos electrónicos que se pueden invitar en una sola solicitud"