diff --git a/backend/src/app/rpc/commands/verify_token.clj b/backend/src/app/rpc/commands/verify_token.clj index e2433fec95..13f36da82a 100644 --- a/backend/src/app/rpc/commands/verify_token.clj +++ b/backend/src/app/rpc/commands/verify_token.clj @@ -184,13 +184,7 @@ {:columns [:id :email :default-team-id]}) registration-disabled? (not (contains? cf/flags :registration)) - org-invitation? (and (contains? cf/flags :nitrate) organization-id) - ;; Membership only makes sense for a logged-in profile; querying it for - ;; an anonymous recipient would call nitrate with a nil profile-id and - ;; mask the clean :invalid-token response with a generic error. - membership (when (and profile org-invitation?) - (nitrate/call cfg :get-org-membership {:profile-id profile-id - :organization-id organization-id}))] + org-invitation? (and (contains? cf/flags :nitrate) organization-id)] (if profile (do @@ -201,23 +195,32 @@ :reason :email-mismatch :hint "logged-in user does not matches the invitation")) - (when (:is-member membership) - (ex/raise :type :validation - :code :already-an-org-member - :team-id (:default-team-id membership) - :hint "the user is already a member of the organization")) - - (when (and org-invitation? (not (:organization-id membership))) - (ex/raise :type :validation - :code :org-not-found - :team-id (:default-team-id profile) - :hint "the organization doesn't exist")) - (when (nil? invitation) (ex/raise :type :validation - :code :invalid-token - :hint "no invitation associated with the token")) + :code (if organization-id :canceled-invitation :invalid-token) + :hint (if organization-id + "the invitation has been canceled" + "no invitation associated with the token"))) + ;; Membership only makes sense for a logged-in profile with an + ;; existing invitation; querying it when the invitation is absent + ;; would call nitrate needlessly and could mask the clean + ;; :canceled-invitation/:invalid-token response with a generic error. + (let [membership (when org-invitation? + (nitrate/call cfg :get-org-membership {:profile-id profile-id + :organization-id organization-id}))] + + (when (:is-member membership) + (ex/raise :type :validation + :code :already-an-org-member + :team-id (:default-team-id membership) + :hint "the user is already a member of the organization")) + + (when (and org-invitation? (not (:organization-id membership))) + (ex/raise :type :validation + :code :org-not-found + :team-id (:default-team-id profile) + :hint "the organization doesn't exist"))) ;; if we have logged-in user and it matches the invitation we proceed ;; with accepting the invitation and joining the current profile to the @@ -251,12 +254,17 @@ (assoc :org-team-id accepted-team-id))))) (do - ;; If the user is not logged-in and the token is invalid we throw the error - ;; Taiga issue #14182 + ;; If the user is not logged-in and the invitation has been canceled + ;; we return a specific error code so the frontend can redirect to + ;; login with an appropriate message instead of showing the error page. + ;; This only applies to org invitations; team invitations keep the + ;; existing :invalid-token behavior. (when (nil? invitation) (ex/raise :type :validation - :code :invalid-token - :hint "no invitation associated with the token")) + :code (if organization-id :canceled-invitation :invalid-token) + :hint (if organization-id + "the invitation has been canceled" + "no invitation associated with the token"))) ;; If we have not logged-in user, and invitation comes with member-id we ;; redirect user to login, if no member-id is present and in the invitation diff --git a/frontend/src/app/main/ui/auth/verify_token.cljs b/frontend/src/app/main/ui/auth/verify_token.cljs index 337e89c89f..d67be7e5eb 100644 --- a/frontend/src/app/main/ui/auth/verify_token.cljs +++ b/frontend/src/app/main/ui/auth/verify_token.cljs @@ -96,6 +96,17 @@ (rt/nav :dashboard-recent {:team-id team-id}) (ntf/error (tr "errors.org-not-found"))) + (= :canceled-invitation code) + (let [profile (:profile @st/state) + authenticated? (da/is-authenticated? profile)] + (if authenticated? + (st/emit! + (dcm/go-to-dashboard-recent :team-id :default) + (ntf/warn (tr "notifications.invitation-canceled"))) + (st/emit! + (rt/nav :auth-login) + (ntf/warn (tr "notifications.invitation-canceled"))))) + (or (= :validation type) (= :invalid-token code) (= :token-expired reason)) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index b5dabef677..ea0d60d7ec 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -10106,3 +10106,6 @@ msgstr "Autosaved versions will be kept for %s days." #, unused msgid "workspace.viewport.click-to-close-path" msgstr "Click to close the path" + +msgid "notifications.invitation-canceled" +msgstr "This invitation is no longer available." \ No newline at end of file diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 6384ee4a9f..d117f9ca38 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -9757,3 +9757,6 @@ msgstr "Los autoguardados duran %s días." #, unused msgid "workspace.viewport.click-to-close-path" msgstr "Pulsar para cerrar la ruta" + +msgid "notifications.invitation-canceled" +msgstr "Esta invitación ya no está disponible." \ No newline at end of file