diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index 75f7989b7..9d69886b2 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -58,7 +58,9 @@ (assoc response :cookies {cookie-name {:path "/" :http-only true :value id - :same-site (if cors? :none :strict) + :same-site (cond (not secure?) :lax + cors? :none + :else :strict) :secure secure?}}))) (defn- clear-cookies diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index b33090e25..22d6e06c0 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -335,9 +335,9 @@ ;; --- MUTATION: Logout (s/def ::logout - (s/keys :req-un [::profile-id])) + (s/keys :opt-un [::profile-id])) -(sv/defmethod ::logout +(sv/defmethod ::logout {:auth false} [{:keys [session] :as cfg} _] (with-meta {} {:transform-response (:delete session)})) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index d5e64f95b..afd344dc6 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -7,6 +7,7 @@ (ns app.main.data.users (:require [app.common.data :as d] + [app.common.exceptions :as ex] [app.common.spec :as us] [app.common.uuid :as uuid] [app.config :as cf] @@ -90,6 +91,8 @@ ;; --- EVENT: fetch-profile +(declare logout) + (def profile-fetched? (ptk/type? ::profile-fetched)) @@ -102,18 +105,18 @@ ptk/UpdateEvent (update [_ state] - (-> state - (assoc :profile-id id) - (assoc :profile profile))) + (cond-> state + (is-authenticated? profile) + (-> (assoc :profile-id id) + (assoc :profile profile)))) ptk/EffectEvent (effect [_ state _] - (let [profile (:profile state)] - (when (not= uuid/zero (:id profile)) - (swap! storage assoc :profile profile) - (i18n/set-locale! (:lang profile)) - (some-> (:theme profile) - (theme/set-current-theme!))))))) + (when-let [profile (:profile state)] + (swap! storage assoc :profile profile) + (i18n/set-locale! (:lang profile)) + (some-> (:theme profile) + (theme/set-current-theme!)))))) (defn fetch-profile [] @@ -154,38 +157,65 @@ ptk/WatchEvent (watch [_ _ _] - (let [team-id (:default-team-id profile)] - (->> (rx/concat - (rx/of (profile-fetched profile) - (fetch-teams)) - (->> (rx/of (rt/nav' :dashboard-projects {:team-id team-id})) - (rx/delay 1000))) - (rx/observe-on :async)))))) + (when (is-authenticated? profile) + (let [team-id (:default-team-id profile)] + (->> (rx/of (profile-fetched profile) + (fetch-teams) + (rt/nav' :dashboard-projects {:team-id team-id})) + (rx/observe-on :async))))))) (s/def ::login-params (s/keys :req-un [::email ::password])) +(declare login-from-register) + (defn login [{:keys [email password] :as data}] (us/verify ::login-params data) (ptk/reify ::login ptk/WatchEvent - (watch [_ _ _] + (watch [_ _ stream] (let [{:keys [on-error on-success] :or {on-error rx/throw on-success identity}} (meta data) params {:email email :password password :scope "webapp"}] - (->> (rx/timer 100) - (rx/mapcat #(rp/mutation :login params)) - (rx/tap on-success) - (rx/catch on-error) - (rx/map (fn [profile] - (with-meta profile - {::ev/source "login"}))) - (rx/map logged-in) - (rx/observe-on :async)))) + + ;; NOTE: We can't take the profile value from login because + ;; there are cases when login is successfull but the cookie is + ;; not set properly (because of possible misconfiguration). + ;; So, we proceed to make an additional call to fetch the + ;; profile, and ensure that cookie is set correctly. If + ;; profile fetch is successful, we mark the user logged in, if + ;; the returned profile is an NOT authenticated profile, we + ;; proceed to logout and show an error message. + + (rx/merge + (->> (rp/mutation :login params) + (rx/map fetch-profile) + (rx/catch on-error)) + + (->> stream + (rx/filter profile-fetched?) + (rx/take 1) + (rx/map deref) + (rx/filter (complement is-authenticated?)) + (rx/tap on-error) + (rx/map #(ex/raise :type :authentication)) + (rx/observe-on :async)) + + (->> stream + (rx/filter profile-fetched?) + (rx/take 1) + (rx/map deref) + (rx/filter is-authenticated?) + (rx/map (fn [profile] + (with-meta profile + {::ev/source "login"}))) + (rx/tap on-success) + (rx/map logged-in) + (rx/observe-on :async))))) ptk/EffectEvent (effect [_ _ _] diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index dc1459b0c..dc65ebdc6 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -13,6 +13,7 @@ [app.main.data.users :as du] [app.main.sentry :as sentry] [app.main.store :as st] + [app.util.i18n :refer [tr]] [app.util.router :as rt] [app.util.timers :as ts] [cljs.pprint :refer [pprint]] @@ -48,7 +49,9 @@ ;; here and not in app.main.errors because of circular dependency. (defmethod ptk/handle-error :authentication [_] - (ts/schedule (st/emitf (du/logout)))) + (let [msg (tr "errors.auth.unable-to-login")] + (st/emit! (du/logout)) + (ts/schedule 500 (st/emitf (dm/warn msg))))) ;; That are special case server-errors that should be treated diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index a7bcaaa0d..0ef678935 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -73,29 +73,30 @@ :dashboard-font-providers :dashboard-team-members :dashboard-team-settings) - (when profile - (let [props (:props profile)] - [:* - #_[:div.modal-wrapper - #_[:& app.main.ui.onboarding/onboarding-templates-modal] - [:& app.main.ui.onboarding/onboarding-modal] - #_[:& app.main.ui.onboarding/onboarding-team-modal] - ] - (cond - (and cf/onboarding-form-id - (not (:onboarding-questions-answered props false))) - [:& app.main.ui.onboarding.questions/questions - {:profile profile - :form-id cf/onboarding-form-id}] - (not (:onboarding-viewed props)) - [:& app.main.ui.onboarding/onboarding-modal {}] + [:* + #_[:div.modal-wrapper + #_[:& app.main.ui.onboarding/onboarding-templates-modal] + #_[:& app.main.ui.onboarding/onboarding-modal] + #_[:& app.main.ui.onboarding/onboarding-team-modal] + ] + (when-let [props (some-> profile (get :props {}))] + (cond + (and cf/onboarding-form-id + (not (:onboarding-questions-answered props false))) + [:& app.main.ui.onboarding.questions/questions + {:profile profile + :form-id cf/onboarding-form-id}] - (and (:onboarding-viewed props) - (not= (:release-notes-viewed props) (:main @cf/version)) - (not= "0.0" (:main @cf/version))) - [:& app.main.ui.releases/release-notes-modal {}]) - [:& dashboard {:route route}]])) + (not (:onboarding-viewed props)) + [:& app.main.ui.onboarding/onboarding-modal {}] + + (and (:onboarding-viewed props) + (not= (:release-notes-viewed props) (:main @cf/version)) + (not= "0.0" (:main @cf/version))) + [:& app.main.ui.releases/release-notes-modal {}])) + + [:& dashboard {:route route :profile profile}]] :viewer (let [{:keys [query-params path-params]} route diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 40f9df49c..eee51941c 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -74,9 +74,8 @@ nil)]) (mf/defc dashboard - [{:keys [route] :as props}] - (let [profile (mf/deref refs/profile) - section (get-in route [:data :name]) + [{:keys [route profile] :as props}] + (let [section (get-in route [:data :name]) params (parse-params route) project-id (:project-id params) diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index 963460870..39d14f2c0 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -514,7 +514,7 @@ [:li {:on-click (partial on-click :settings-password)} [:span.icon i/lock] [:span.text (tr "labels.password")]] - [:li {:on-click (partial on-click (du/logout))} + [:li {:on-click #(on-click (du/logout) %)} [:span.icon i/exit] [:span.text (tr "labels.logout")]] diff --git a/frontend/src/app/main/ui/onboarding/questions.cljs b/frontend/src/app/main/ui/onboarding/questions.cljs index 51cce790a..a7eef6925 100644 --- a/frontend/src/app/main/ui/onboarding/questions.cljs +++ b/frontend/src/app/main/ui/onboarding/questions.cljs @@ -17,7 +17,7 @@ (defn load-arengu-sdk [container-ref email form-id] (letfn [(on-init [] - (let [container (mf/ref-val container-ref)] + (when-let [container (mf/ref-val container-ref)] (-> (.embed js/ArenguForms form-id container) (p/then (fn [form] (.setHiddenField ^js form "email" email)))))) diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index 6da1307a3..78c4817f4 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -88,6 +88,7 @@ :credentials credentials :referrerPolicy "no-referrer" :signal signal}] + (-> (js/fetch (str uri) params) (p/then (fn [response] (vreset! abortable? false) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 3c5db28bf..6f8729c87 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -3255,3 +3255,6 @@ msgstr "Owner can't leave team, you must reassign the owner role." msgid "errors.team-leave.insufficient-members" msgstr "Insufficient members to leave team, you probably want to delete it." + +msgid "errors.auth.unable-to-login" +msgstr "Unable to login, looks like you are not authenticated."