🐛 Fix nitrate sso doesn't notify workspace on activation (#10955)

This commit is contained in:
Pablo Alba 2026-07-30 11:56:06 +02:00 committed by GitHub
parent be5d9f9e9c
commit 03f9393200
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 111 additions and 71 deletions

View File

@ -11,7 +11,9 @@
[app.common.data.macros :as dm]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.organization :as co]
[app.common.types.team :as ctt]
[app.config :as cf]
[app.main.data.helpers :as dsh]
[app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
@ -232,6 +234,63 @@
;; Delay so the navigation can finish
(rx/delay 250))))))))
(defn- check-team-sso
[team-id]
(let [url (rt/get-current-href)]
(->> (rp/cmd! :check-nitrate-sso {:team-id team-id :url url})
(rx/mapcat (fn [{:keys [authorized redirect-uri]}]
(if authorized
(rx/empty)
(if redirect-uri
(rx/of (rt/nav-raw :uri (str redirect-uri)))
(rx/empty))))))))
(defn handle-change-team-org
"Handle :team-org-change websocket messages on dashboard and workspace.
Updates local team org data and redirects to SSO when required."
[{:keys [team notification]}]
(ptk/reify ::handle-change-team-org
ptk/WatchEvent
(watch [_ state _]
(let [current-team-id (:current-team-id state)
organization (:organization team)
current-team? (= (:id team) current-team-id)]
(when (and (contains? cf/flags :nitrate)
current-team?)
(rx/concat
(when notification
(rx/of (ntf/show {:content (tr notification (:name organization))
:type :toast
:level :info
:timeout nil})))
(when (:id organization)
(check-team-sso current-team-id))))))
ptk/UpdateEvent
(update [_ state]
(if (contains? cf/flags :nitrate)
(let [team-id (:id team)
team-name (:name team)
organization (:organization team)]
(d/update-in-when state [:teams team-id]
(fn [team]
(cond-> (co/apply-organization team organization)
team-name (assoc :name team-name)))))
state))))
(defn handle-organization-change-sso
"Handle :organization-change-sso websocket messages on dashboard and workspace.
Redirects to the org SSO login when the current team belongs to that org."
[{:keys [organization-id]}]
(ptk/reify ::handle-organization-change-sso
ptk/WatchEvent
(watch [_ state _]
(when (contains? cf/flags :nitrate)
(let [team-id (:current-team-id state)
team (dm/get-in state [:teams team-id])
org-id (dm/get-in team [:organization :id])]
(when (= organization-id org-id)
(check-team-sso team-id)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PROGRESS EVENTS

View File

@ -13,7 +13,6 @@
[app.common.logging :as log]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.organization :as co]
[app.common.types.project :refer [valid-project?]]
[app.common.uuid :as uuid]
[app.config :as cf]
@ -27,7 +26,6 @@
[app.main.data.team :as dtm]
[app.main.data.websocket :as dws]
[app.main.repo :as rp]
[app.main.router :as rt]
[app.main.store :as st]
[app.util.i18n :as i18n :refer [tr]]
[app.util.sse :as sse]
@ -687,47 +685,6 @@
(rx/of (dcm/change-team-role params)
(modal/hide)))))
(defn- check-team-sso
[team-id]
(let [url (rt/get-current-href)]
(->> (rp/cmd! :check-nitrate-sso {:team-id team-id :url url})
(rx/mapcat (fn [{:keys [authorized redirect-uri]}]
(if authorized
(rx/empty)
(if redirect-uri
(rx/of (rt/nav-raw :uri (str redirect-uri)))
(rx/empty))))))))
(defn handle-change-team-org
[{:keys [team notification]}]
(ptk/reify ::handle-change-team-org
ptk/WatchEvent
(watch [_ state _]
(let [current-team-id (:current-team-id state)
organization (:organization team)
current-team? (= (:id team) current-team-id)]
(when (and (contains? cf/flags :nitrate)
current-team?)
(rx/concat
(when notification
(rx/of (ntf/show {:content (tr notification (:name organization))
:type :toast
:level :info
:timeout nil})))
(when (:id organization)
(check-team-sso current-team-id))))))
ptk/UpdateEvent
(update [_ state]
(if (contains? cf/flags :nitrate)
(let [team-id (:id team)
team-name (:name team)
organization (:organization team)]
(d/update-in-when state [:teams team-id]
(fn [team]
(cond-> (co/apply-organization team organization)
team-name (assoc :name team-name)))))
state))))
(defn- handle-user-org-change
[{:keys [organization-id organization-name notification]}]
(ptk/reify ::handle-user-org-change
@ -772,28 +729,16 @@
(when fetch? ;; If the user belonged to the org
(rx/of (dtm/fetch-teams)))))))))
(defn- handle-nitrate-change-sso
[{:keys [organization-id]}]
(ptk/reify ::handle-nitrate-change-sso
ptk/WatchEvent
(watch [_ state _]
(when (contains? cf/flags :nitrate)
(let [team-id (:current-team-id state)
team (dm/get-in state [:teams team-id])
org-id (dm/get-in team [:organization :id])]
(when (= organization-id org-id)
(check-team-sso team-id)))))))
(defn- process-message
[{:keys [type] :as msg}]
(case type
:notification (dcm/handle-notification msg)
:team-role-change (handle-change-team-role msg)
:team-membership-change (dcm/team-membership-change msg)
:team-org-change (handle-change-team-org msg)
:team-org-change (dcm/handle-change-team-org msg)
:user-org-change (handle-user-org-change msg)
:organization-deleted (handle-organization-deleted msg)
:organization-change-sso (handle-nitrate-change-sso msg)
:organization-change-sso (dcm/handle-organization-change-sso msg)
nil))

View File

@ -125,18 +125,20 @@
(defn- process-message
[{:keys [type] :as msg}]
(case type
:join-file (handle-presence msg)
:leave-file (handle-presence msg)
:presence (handle-presence msg)
:disconnect (handle-presence msg)
:pointer-update (handle-pointer-update msg)
:file-change (handle-file-change msg)
:file-deleted (handle-file-deleted msg)
:file-restored (handle-file-restored msg)
:library-change (handle-library-change msg)
:notification (dc/handle-notification msg)
:team-role-change (handle-change-team-role msg)
:team-membership-change (dc/team-membership-change msg)
:join-file (handle-presence msg)
:leave-file (handle-presence msg)
:presence (handle-presence msg)
:disconnect (handle-presence msg)
:pointer-update (handle-pointer-update msg)
:file-change (handle-file-change msg)
:file-deleted (handle-file-deleted msg)
:file-restored (handle-file-restored msg)
:library-change (handle-library-change msg)
:notification (dc/handle-notification msg)
:team-role-change (handle-change-team-role msg)
:team-membership-change (dc/team-membership-change msg)
:team-org-change (dc/handle-change-team-org msg)
:organization-change-sso (dc/handle-organization-change-sso msg)
nil))
(defn- handle-pointer-send

View File

@ -8,7 +8,7 @@
(:require
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.dashboard :as dd]
[app.main.data.common :as dcm]
[app.main.repo :as rp]
[app.main.router :as rt]
[beicon.v2.core :as rx]
@ -23,7 +23,7 @@
current-url (str "https://penpot.example.com/#/dashboard/recent?team-id=" team-id)
redirect-url "https://idp.example.com/authorize"
state {:current-team-id team-id}
event (dd/handle-change-team-org
event (dcm/handle-change-team-org
{:team {:id team-id :organization organization}
:notification nil})]
(mock/with-mocks
@ -48,3 +48,37 @@
(fn []
(done')))))
done))))
(t/deftest organization-sso-activation-redirects-current-team
(t/async done
(let [team-id (uuid/next)
org-id (uuid/next)
current-url (str "https://penpot.example.com/#/workspace?team-id=" team-id)
redirect-url "https://idp.example.com/authorize"
state {:current-team-id team-id
:teams {team-id {:id team-id
:organization {:id org-id}}}}
event (dcm/handle-organization-change-sso
{:organization-id org-id})]
(mock/with-mocks
{cf/flags (conj cf/flags :nitrate)
rp/cmd! (mock/stub
(fn [cmd params]
(if (= [:check-nitrate-sso
{:team-id team-id :url current-url}]
[cmd params])
(rx/of {:authorized false :redirect-uri redirect-url})
(rx/throw (ex-info "unexpected RPC" {:cmd cmd :params params})))))
rt/get-current-href (mock/stub (constantly current-url))}
(fn [done']
(->> (ptk/watch event state nil)
(rx/reduce conj [])
(rx/subs!
(fn [events]
(t/is (= [::rt/nav-raw] (mapv ptk/type events))))
(fn [error]
(t/is false (str "unexpected error: " error))
(done'))
(fn []
(done')))))
done))))