From 5858f3f180a4466cc37955fe55d46af0a3a4f2d4 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 11 Feb 2021 13:36:46 +0100 Subject: [PATCH] :sparkles: Improve auth module. --- backend/src/app/http.clj | 4 - backend/src/app/http/auth.clj | 31 ----- backend/src/app/http/auth/github.clj | 120 ++++++++--------- backend/src/app/http/auth/gitlab.clj | 126 ++++++++---------- backend/src/app/http/auth/google.clj | 29 ++-- backend/src/app/http/auth/ldap.clj | 11 +- backend/src/app/http/session.clj | 41 ++++-- backend/src/app/rpc/mutations/profile.clj | 34 ++--- .../src/app/rpc/mutations/verify_token.clj | 20 +-- .../tests/app/tests/test_services_profile.clj | 91 +++++++++++++ frontend/src/app/main/repo.cljs | 12 -- frontend/src/app/main/ui/auth/login.cljs | 4 +- 12 files changed, 269 insertions(+), 254 deletions(-) delete mode 100644 backend/src/app/http/auth.clj diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index ab267e36b..ffe534971 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -12,7 +12,6 @@ [app.common.data :as d] [app.common.spec :as us] [app.config :as cfg] - [app.http.auth :as auth] [app.http.errors :as errors] [app.http.middleware :as middleware] [app.metrics :as mtx] @@ -147,9 +146,6 @@ ["/github" {:post (:auth-handler github-auth)}] ["/github/callback" {:get (:callback-handler github-auth)}]] - ["/login" {:post #(auth/login-handler cfg %)}] - ["/logout" {:post #(auth/logout-handler cfg %)}] - ["/login-ldap" {:post ldap-auth}] ["/rpc" {:middleware [(:middleware session)]} diff --git a/backend/src/app/http/auth.clj b/backend/src/app/http/auth.clj deleted file mode 100644 index 4dec0d3b4..000000000 --- a/backend/src/app/http/auth.clj +++ /dev/null @@ -1,31 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL - -(ns app.http.auth - (:require - [app.http.session :as session])) - -(defn login-handler - [{:keys [session rpc] :as cfg} request] - (let [data (:params request) - uagent (get-in request [:headers "user-agent"]) - method (get-in rpc [:methods :mutation :login]) - profile (method data) - id (session/create! session {:profile-id (:id profile) - :user-agent uagent})] - {:status 200 - :cookies (session/cookies session {:value id}) - :body profile})) - -(defn logout-handler - [{:keys [session] :as cfg} request] - (session/delete! cfg request) - {:status 204 - :cookies (session/cookies session {:value "" :max-age -1}) - :body ""}) diff --git a/backend/src/app/http/auth/github.clj b/backend/src/app/http/auth/github.clj index 83d38136c..c01200126 100644 --- a/backend/src/app/http/auth/github.clj +++ b/backend/src/app/http/auth/github.clj @@ -12,7 +12,6 @@ [app.common.exceptions :as ex] [app.common.spec :as us] [app.config :as cfg] - [app.http.session :as session] [app.util.http :as http] [app.util.time :as dt] [clojure.data.json :as json] @@ -38,7 +37,6 @@ (def scope "user:email") - (defn- build-redirect-url [cfg] (let [public (u/uri (:public-uri cfg))] @@ -46,57 +44,47 @@ (defn- get-access-token [cfg state code] - (let [params {:client_id (:client-id cfg) - :client_secret (:client-secret cfg) - :code code - :state state - :redirect_uri (build-redirect-url cfg)} - req {:method :post - :headers {"content-type" "application/x-www-form-urlencoded" - "accept" "application/json"} - :uri (str token-url) - :body (u/map->query-string params)} - res (http/send! req)] + (try + (let [params {:client_id (:client-id cfg) + :client_secret (:client-secret cfg) + :code code + :state state + :redirect_uri (build-redirect-url cfg)} + req {:method :post + :headers {"content-type" "application/x-www-form-urlencoded" + "accept" "application/json"} + :uri (str token-url) + :timeout 6000 + :body (u/map->query-string params)} + res (http/send! req)] - (when (not= 200 (:status res)) - (ex/raise :type :internal - :code :invalid-response-from-github - :context {:status (:status res) - :body (:body res)})) - (try - (let [data (json/read-str (:body res))] - (get data "access_token")) - (catch Throwable e - (log/error "unexpected error on parsing response body from github access token request" e) - nil)))) + (when (= 200 (:status res)) + (-> (json/read-str (:body res)) + (get "access_token")))) + + (catch Exception e + (log/error e "unexpected error on get-access-token") + nil))) (defn- get-user-info [token] - (let [req {:uri (str user-info-url) - :headers {"authorization" (str "token " token)} - :method :get} - res (http/send! req)] - - (when (not= 200 (:status res)) - (ex/raise :type :internal - :code :invalid-response-from-github - :context {:status (:status res) - :body (:body res)})) - - (try - (let [data (json/read-str (:body res))] - {:email (get data "email") - :fullname (get data "name")}) - (catch Throwable e - (log/error "unexpected error on parsing response body from github access token request" e) - nil)))) + (try + (let [req {:uri (str user-info-url) + :headers {"authorization" (str "token " token)} + :timeout 6000 + :method :get} + res (http/send! req)] + (when (= 200 (:status res)) + (let [data (json/read-str (:body res))] + {:email (get data "email") + :fullname (get data "name")}))) + (catch Exception e + (log/error e "unexpected exception on get-user-info") + nil))) (defn auth [{:keys [tokens] :as cfg} _request] - (let [state (tokens :generate - {:iss :github-oauth - :exp (dt/in-future "15m")}) - + (let [state (tokens :generate {:iss :github-oauth :exp (dt/in-future "15m")}) params {:client_id (:client-id cfg/config) :redirect_uri (build-redirect-url cfg) :state state @@ -109,37 +97,37 @@ (defn callback [{:keys [tokens rpc session] :as cfg} request] - (let [state (get-in request [:params :state]) - _ (tokens :verify {:token state :iss :github-oauth}) - info (some->> (get-in request [:params :code]) - (get-access-token cfg state) - (get-user-info))] + (try + (let [state (get-in request [:params :state]) + _ (tokens :verify {:token state :iss :github-oauth}) + info (some->> (get-in request [:params :code]) + (get-access-token cfg state) + (get-user-info)) - (when-not info - (ex/raise :type :authentication - :code :unable-to-authenticate-with-github)) + _ (when-not info + (ex/raise :type :internal + :code :unable-to-auth)) - (let [method-fn (get-in rpc [:methods :mutation :login-or-register]) + method-fn (get-in rpc [:methods :mutation :login-or-register]) profile (method-fn {:email (:email info) :fullname (:fullname info)}) - uagent (get-in request [:headers "user-agent"]) - token (tokens :generate {:iss :auth :exp (dt/in-future "15m") :profile-id (:id profile)}) - uri (-> (u/uri (:public-uri cfg/config)) (assoc :path "/#/auth/verify-token") (assoc :query (u/map->query-string {:token token}))) - - sid (session/create! session {:profile-id (:id profile) - :user-agent uagent})] - - {:status 302 - :headers {"location" (str uri)} - :cookies (session/cookies session/cookies {:value sid}) - :body ""}))) + sxf ((:create session) (:id profile)) + rsp {:status 302 :headers {"location" (str uri)} :body ""}] + (sxf request rsp)) + (catch Exception _e + (let [uri (-> (u/uri (:public-uri cfg)) + (assoc :path "/#/auth/login") + (assoc :query (u/map->query-string {:error "unable-to-auth"})))] + {:status 302 + :headers {"location" (str uri)} + :body ""})))) ;; --- ENTRY POINT diff --git a/backend/src/app/http/auth/gitlab.clj b/backend/src/app/http/auth/gitlab.clj index 6aeb64e71..5e78f1071 100644 --- a/backend/src/app/http/auth/gitlab.clj +++ b/backend/src/app/http/auth/gitlab.clj @@ -12,88 +12,75 @@ [app.common.data :as d] [app.common.exceptions :as ex] [app.common.spec :as us] - [app.http.session :as session] [app.util.http :as http] [app.util.time :as dt] [clojure.data.json :as json] [clojure.spec.alpha :as s] [clojure.tools.logging :as log] [integrant.core :as ig] - [lambdaisland.uri :as uri])) + [lambdaisland.uri :as u])) (def scope "read_user") (defn- build-redirect-url [cfg] - (let [public (uri/uri (:public-uri cfg))] + (let [public (u/uri (:public-uri cfg))] (str (assoc public :path "/api/oauth/gitlab/callback")))) - (defn- build-oauth-uri [cfg] - (let [base-uri (uri/uri (:base-uri cfg))] + (let [base-uri (u/uri (:base-uri cfg))] (assoc base-uri :path "/oauth/authorize"))) - (defn- build-token-url [cfg] - (let [base-uri (uri/uri (:base-uri cfg))] + (let [base-uri (u/uri (:base-uri cfg))] (str (assoc base-uri :path "/oauth/token")))) - (defn- build-user-info-url [cfg] - (let [base-uri (uri/uri (:base-uri cfg))] + (let [base-uri (u/uri (:base-uri cfg))] (str (assoc base-uri :path "/api/v4/user")))) (defn- get-access-token [cfg code] - (let [params {:client_id (:client-id cfg) - :client_secret (:client-secret cfg) - :code code - :grant_type "authorization_code" - :redirect_uri (build-redirect-url cfg)} - req {:method :post - :headers {"content-type" "application/x-www-form-urlencoded"} - :uri (build-token-url cfg) - :body (uri/map->query-string params)} + (try + (let [params {:client_id (:client-id cfg) + :client_secret (:client-secret cfg) + :code code + :grant_type "authorization_code" + :redirect_uri (build-redirect-url cfg)} + req {:method :post + :headers {"content-type" "application/x-www-form-urlencoded"} + :uri (build-token-url cfg) + :body (u/map->query-string params)} res (http/send! req)] - (when (not= 200 (:status res)) - (ex/raise :type :internal - :code :invalid-response-from-gitlab - :context {:status (:status res) - :body (:body res)})) - - (try - (let [data (json/read-str (:body res))] - (get data "access_token")) - (catch Throwable e - (log/error "unexpected error on parsing response body from gitlab access token request" e) - nil)))) + (when (= 200 (:status res)) + (-> (json/read-str (:body res)) + (get "access_token")))) + (catch Exception e + (log/error e "unexpected error on get-access-token") + nil))) (defn- get-user-info [cfg token] - (let [req {:uri (build-user-info-url cfg) - :headers {"Authorization" (str "Bearer " token)} - :method :get} - res (http/send! req)] + (try + (let [req {:uri (build-user-info-url cfg) + :headers {"Authorization" (str "Bearer " token)} + :timeout 6000 + :method :get} + res (http/send! req)] - (when (not= 200 (:status res)) - (ex/raise :type :internal - :code :invalid-response-from-gitlab - :context {:status (:status res) - :body (:body res)})) + (when (= 200 (:status res)) + (let [data (json/read-str (:body res))] + {:email (get data "email") + :fullname (get data "name")}))) - (try - (let [data (json/read-str (:body res))] - ;; (clojure.pprint/pprint data) - {:email (get data "email") - :fullname (get data "name")}) - (catch Throwable e - (log/error "unexpected error on parsing response body from gitlab access token request" e) - nil)))) + (catch Exception e + (log/error e "unexpected exception on get-user-info") + nil))) (defn auth [{:keys [tokens] :as cfg} _request] @@ -105,7 +92,7 @@ :response_type "code" :state token :scope scope} - query (uri/map->query-string params) + query (u/map->query-string params) uri (-> (build-oauth-uri cfg) (assoc :query query))] {:status 200 @@ -113,36 +100,37 @@ (defn callback [{:keys [tokens rpc session] :as cfg} request] - (let [token (get-in request [:params :state]) - _ (tokens :verify {:token token :iss :gitlab-oauth}) - info (some->> (get-in request [:params :code]) - (get-access-token cfg) - (get-user-info cfg))] + (try + (let [token (get-in request [:params :state]) + _ (tokens :verify {:token token :iss :gitlab-oauth}) + info (some->> (get-in request [:params :code]) + (get-access-token cfg) + (get-user-info cfg)) + _ (when-not info + (ex/raise :type :internal + :code :unable-to-auth)) - (when-not info - (ex/raise :type :authentication - :code :unable-to-authenticate-with-gitlab)) - - (let [method-fn (get-in rpc [:methods :mutation :login-or-register]) + method-fn (get-in rpc [:methods :mutation :login-or-register]) profile (method-fn {:email (:email info) :fullname (:fullname info)}) - uagent (get-in request [:headers "user-agent"]) - token (tokens :generate {:iss :auth :exp (dt/in-future "15m") :profile-id (:id profile)}) - uri (-> (uri/uri (:public-uri cfg)) + uri (-> (u/uri (:public-uri cfg)) (assoc :path "/#/auth/verify-token") - (assoc :query (uri/map->query-string {:token token}))) - - sid (session/create! session {:profile-id (:id profile) - :user-agent uagent})] - {:status 302 - :headers {"location" (str uri)} - :cookies (session/cookies session {:value sid}) - :body ""}))) + (assoc :query (u/map->query-string {:token token}))) + sxf ((:create session) (:id profile)) + rsp {:status 302 :headers {"location" (str uri)} :body ""}] + (sxf request rsp)) + (catch Exception _e + (let [uri (-> (u/uri (:public-uri cfg)) + (assoc :path "/#/auth/login") + (assoc :query (u/map->query-string {:error "unable-to-auth"})))] + {:status 302 + :headers {"location" (str uri)} + :body ""})))) (s/def ::client-id ::us/not-empty-string) (s/def ::client-secret ::us/not-empty-string) diff --git a/backend/src/app/http/auth/google.clj b/backend/src/app/http/auth/google.clj index a615ddd80..12a2d7034 100644 --- a/backend/src/app/http/auth/google.clj +++ b/backend/src/app/http/auth/google.clj @@ -11,14 +11,13 @@ (:require [app.common.exceptions :as ex] [app.common.spec :as us] - [app.http.session :as session] [app.util.http :as http] [app.util.time :as dt] [clojure.data.json :as json] [clojure.spec.alpha :as s] [clojure.tools.logging :as log] [integrant.core :as ig] - [lambdaisland.uri :as uri])) + [lambdaisland.uri :as u])) (def base-goauth-uri "https://accounts.google.com/o/oauth2/v2/auth") @@ -30,7 +29,7 @@ (defn- build-redirect-url [cfg] - (let [public (uri/uri (:public-uri cfg))] + (let [public (u/uri (:public-uri cfg))] (str (assoc public :path "/api/oauth/google/callback")))) (defn- get-access-token @@ -44,7 +43,7 @@ req {:method :post :headers {"content-type" "application/x-www-form-urlencoded"} :uri "https://oauth2.googleapis.com/token" - :body (uri/map->query-string params)} + :body (u/map->query-string params)} res (http/send! req)] (when (= 200 (:status res)) @@ -80,8 +79,8 @@ :response_type "code" :redirect_uri (build-redirect-url cfg) :client_id (:client-id cfg)} - query (uri/map->query-string params) - uri (-> (uri/uri base-goauth-uri) + query (u/map->query-string params) + uri (-> (u/uri base-goauth-uri) (assoc :query query))] {:status 200 :body {:redirect-uri (str uri)}})) @@ -100,24 +99,20 @@ method-fn (get-in rpc [:methods :mutation :login-or-register]) profile (method-fn {:email (:email info) :fullname (:fullname info)}) - uagent (get-in request [:headers "user-agent"]) token (tokens :generate {:iss :auth :exp (dt/in-future "15m") :profile-id (:id profile)}) - uri (-> (uri/uri (:public-uri cfg)) + uri (-> (u/uri (:public-uri cfg)) (assoc :path "/#/auth/verify-token") - (assoc :query (uri/map->query-string {:token token}))) + (assoc :query (u/map->query-string {:token token}))) - sid (session/create! session {:profile-id (:id profile) - :user-agent uagent})] - {:status 302 - :headers {"location" (str uri)} - :cookies (session/cookies session {:value sid}) - :body ""}) + sxf ((:create session) (:id profile)) + rsp {:status 302 :headers {"location" (str uri)} :body ""}] + (sxf request rsp)) (catch Exception _e - (let [uri (-> (uri/uri (:public-uri cfg)) + (let [uri (-> (u/uri (:public-uri cfg)) (assoc :path "/#/auth/login") - (assoc :query (uri/map->query-string {:error "unable-to-auth"})))] + (assoc :query (u/map->query-string {:error "unable-to-auth"})))] {:status 302 :headers {"location" (str uri)} :body ""})))) diff --git a/backend/src/app/http/auth/ldap.clj b/backend/src/app/http/auth/ldap.clj index 02a82fae9..ec6dc16dc 100644 --- a/backend/src/app/http/auth/ldap.clj +++ b/backend/src/app/http/auth/ldap.clj @@ -11,7 +11,6 @@ (:require [app.common.exceptions :as ex] [app.config :as cfg] - [app.http.session :as session] [clj-ldap.client :as client] [clojure.set :as set] [clojure.spec.alpha :as s] @@ -66,12 +65,10 @@ (let [method-fn (get-in rpc [:methods :mutation :login-or-register]) profile (method-fn {:email (:email info) :fullname (:fullname info)}) - uagent (get-in request [:headers "user-agent"]) - sid (session/create! session {:profile-id (:id profile) - :user-agent uagent})] - {:status 200 - :cookies (session/cookies session {:value sid}) - :body profile})))) + + sxf ((:create session) (:id profile)) + rsp {:status 200 :body profile}] + (sxf request rsp))))) {::conn conn}))) (defmethod ig/halt-key! ::client diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index 98ccb9ee0..45e25699f 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -16,14 +16,16 @@ [clojure.spec.alpha :as s] [integrant.core :as ig])) -(defn next-session-id +;; --- IMPL + +(defn- next-session-id ([] (next-session-id 96)) ([n] (-> (bn/random-nonce n) (bc/bytes->b64u) (bc/bytes->str)))) -(defn create! +(defn- create [{:keys [conn] :as cfg} {:keys [profile-id user-agent]}] (let [id (next-session-id)] (db/insert! conn :http-session {:id id @@ -31,28 +33,28 @@ :user-agent user-agent}) id)) -(defn delete! - [{:keys [conn cookie-name] :as cfg} request] - (when-let [token (get-in request [:cookies cookie-name :value])] +(defn- delete + [{:keys [conn cookie-name] :as cfg} {:keys [cookies] :as request}] + (when-let [token (get-in cookies [cookie-name :value])] (db/delete! conn :http-session {:id token})) nil) -(defn retrieve +(defn- retrieve [{:keys [conn] :as cfg} token] (when token (-> (db/exec-one! conn ["select profile_id from http_session where id = ?" token]) (:profile-id)))) -(defn retrieve-from-request - [{:keys [cookie-name] :as cfg} request] - (->> (get-in request [:cookies cookie-name :value]) +(defn- retrieve-from-request + [{:keys [cookie-name] :as cfg} {:keys [cookies] :as request}] + (->> (get-in cookies [cookie-name :value]) (retrieve cfg))) -(defn cookies +(defn- cookies [{:keys [cookie-name] :as cfg} vals] {cookie-name (merge vals {:path "/" :http-only true})}) -(defn middleware +(defn- middleware [cfg handler] (fn [request] (if-let [profile-id (retrieve-from-request cfg request)] @@ -61,6 +63,8 @@ (handler (assoc request :profile-id profile-id))) (handler request)))) +;; --- STATE INIT + (defmethod ig/pre-init-spec ::session [_] (s/keys :req-un [::db/pool])) @@ -71,4 +75,17 @@ (defmethod ig/init-key ::session [_ {:keys [pool] :as cfg}] (let [cfg (assoc cfg :conn pool)] - (merge cfg {:middleware #(middleware cfg %)}))) + (-> cfg + (assoc :middleware #(middleware cfg %)) + (assoc :create (fn [profile-id] + (fn [request response] + (let [uagent (get-in request [:headers "user-agent"]) + value (create cfg {:profile-id profile-id :user-agent uagent})] + (assoc response :cookies (cookies cfg {:value value})))))) + (assoc :delete (fn [request response] + (delete cfg request) + (assoc response + :status 204 + :body "" + :cookies (cookies cfg {:value "" :max-age -1}))))))) + diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index 8ee5d7e8f..3b18c3a36 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -16,7 +16,6 @@ [app.db :as db] [app.db.profile-initial-data :refer [create-profile-initial-data]] [app.emails :as emails] - [app.http.session :as session] [app.media :as media] [app.rpc.mutations.projects :as projects] [app.rpc.mutations.teams :as teams] @@ -95,13 +94,7 @@ (with-meta (assoc profile :is-active true :claims claims) - {:transform-response - (fn [request response] - (let [uagent (get-in request [:headers "user-agent"]) - id (session/create! session {:profile-id (:id profile) - :user-agent uagent})] - (assoc response - :cookies (session/cookies session {:value id}))))})) + {:transform-response ((:create session) (:id profile))})) ;; If no token is provided, send a verification email (let [token (tokens :generate @@ -217,7 +210,7 @@ :opt-un [::scope])) (sv/defmethod ::login {:auth false :rlimit :password} - [{:keys [pool] :as cfg} {:keys [email password scope] :as params}] + [{:keys [pool session] :as cfg} {:keys [email password scope] :as params}] (letfn [(check-password [profile password] (when (= (:password profile) "!") (ex/raise :type :validation @@ -240,8 +233,21 @@ (let [prof (-> (profile/retrieve-profile-data-by-email conn email) (validate-profile) (profile/strip-private-attrs)) - addt (profile/retrieve-additional-data conn (:id prof))] - (merge prof addt))))) + addt (profile/retrieve-additional-data conn (:id prof)) + prof (merge prof addt)] + (with-meta prof + {:transform-response ((:create session) (:id prof))}))))) + + +;; --- Mutation: Logout + +(s/def ::logout + (s/keys :req-un [::profile-id])) + +(sv/defmethod ::logout + [{:keys [pool session] :as cfg} {:keys [profile-id] :as params}] + (with-meta {} + {:transform-response (:delete session)})) ;; --- Mutation: Register if not exists @@ -480,11 +486,7 @@ {:id profile-id}) (with-meta {} - {:transform-response - (fn [request response] - (session/delete! session request) - (assoc response - :cookies (session/cookies session {:value "" :max-age -1})))}))) + {:transform-response (:delete session)}))) (def sql:owned-teams "with owner_teams as ( diff --git a/backend/src/app/rpc/mutations/verify_token.clj b/backend/src/app/rpc/mutations/verify_token.clj index 8f2a7d0f4..29eefd5ca 100644 --- a/backend/src/app/rpc/mutations/verify_token.clj +++ b/backend/src/app/rpc/mutations/verify_token.clj @@ -5,14 +5,13 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) 2020-2021 UXBOX Labs SL (ns app.rpc.mutations.verify-token (:require [app.common.exceptions :as ex] [app.common.spec :as us] [app.db :as db] - [app.http.session :as session] [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] [app.util.services :as sv] @@ -57,14 +56,7 @@ {:id (:id profile)})) (with-meta claims - {:transform-response - (fn [request response] - (let [uagent (get-in request [:headers "user-agent"]) - id (session/create! session {:profile-id profile-id - :user-agent uagent})] - (assoc response - :cookies (session/cookies session {:value id}))))}))) - + {:transform-response ((:create session) profile-id)}))) (defmethod process-token :auth [{:keys [conn] :as cfg} _params {:keys [profile-id] :as claims}] @@ -116,13 +108,7 @@ ;; the user clicking the link he already has access to the ;; email account. (with-meta claims - {:transform-response - (fn [request response] - (let [uagent (get-in request [:headers "user-agent"]) - id (session/create! session {:profile-id member-id - :user-agent uagent})] - (assoc response - :cookies (session/cookies session {:value id}))))}))) + {:transform-response ((:create session) member-id)}))) ;; In this case, we wait until frontend app redirect user to ;; registeration page, the user is correctly registered and the diff --git a/backend/tests/app/tests/test_services_profile.clj b/backend/tests/app/tests/test_services_profile.clj index ab32ca184..e6c9bf6ab 100644 --- a/backend/tests/app/tests/test_services_profile.clj +++ b/backend/tests/app/tests/test_services_profile.clj @@ -191,3 +191,94 @@ ;; TODO: profile deletion with owner teams ;; TODO: profile registration ;; TODO: profile password recovery + +(t/deftest test-register-when-registration-disabled + (with-mocks [mock {:target 'app.config/get + :return (th/mock-config-get-with + {:registration-enabled false})}] + (let [data {::th/type :register-profile + :email "user@example.com" + :password "foobar" + :fullname "foobar"} + out (th/mutation! data) + error (:error out) + edata (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type edata) :restriction)) + (t/is (= (:code edata) :registration-disabled))))) + +(t/deftest test-register-existing-profile + (let [profile (th/create-profile* 1) + data {::th/type :register-profile + :email (:email profile) + :password "foobar" + :fullname "foobar"} + out (th/mutation! data) + error (:error out) + edata (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type edata) :validation)) + (t/is (= (:code edata) :email-already-exists)))) + +(t/deftest test-register-profile + (with-mocks [mock {:target 'app.emails/send! + :return nil}] + (let [pool (:app.db/pool th/*system*) + data {::th/type :register-profile + :email "user@example.com" + :password "foobar" + :fullname "foobar"} + out (th/mutation! data)] + ;; (th/print-result! out) + (let [mock (deref mock) + [_ _ params] (:call-args mock)] + ;; (clojure.pprint/pprint params) + (t/is (:called? mock)) + (t/is (= (:email data) (:to params))) + (t/is (contains? params :extra-data)) + (t/is (contains? params :token))) + + (let [result (:result out)] + (t/is (false? (:is-demo result))) + (t/is (= (:email data) (:email result))) + (t/is (= "penpot" (:auth-backend result))) + (t/is (= "foobar" (:fullname result))) + (t/is (not (contains? result :password))))))) + +(t/deftest test-register-profile-with-bounced-email + (with-mocks [mock {:target 'app.emails/send! + :return nil}] + (let [pool (:app.db/pool th/*system*) + data {::th/type :register-profile + :email "user@example.com" + :password "foobar" + :fullname "foobar"} + _ (th/create-global-complaint-for pool {:type :bounce :email "user@example.com"}) + out (th/mutation! data)] + ;; (th/print-result! out) + + (let [mock (deref mock)] + (t/is (false? (:called? mock)))) + + (let [error (:error out) + edata (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type edata) :validation)) + (t/is (= (:code edata) :email-has-permanent-bounces)))))) + +(t/deftest test-register-profile-with-complained-email + (with-mocks [mock {:target 'app.emails/send! + :return nil}] + (let [pool (:app.db/pool th/*system*) + data {::th/type :register-profile + :email "user@example.com" + :password "foobar" + :fullname "foobar"} + _ (th/create-global-complaint-for pool {:type :complaint :email "user@example.com"}) + out (th/mutation! data)] + + (let [mock (deref mock)] + (t/is (true? (:called? mock)))) + + (let [result (:result out)] + (t/is (= (:email data) (:email result))))))) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index d6effae0e..45fa61166 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -122,18 +122,6 @@ (seq params)) (send-mutation! id form))) -(defmethod mutation :login - [id params] - (let [uri (str cfg/public-uri "/api/login")] - (->> (http/send! {:method :post :uri uri :body params}) - (rx/mapcat handle-response)))) - -(defmethod mutation :logout - [id params] - (let [uri (str cfg/public-uri "/api/logout")] - (->> (http/send! {:method :post :uri uri :body params}) - (rx/mapcat handle-response)))) - (defmethod mutation :login-with-ldap [id params] (let [uri (str cfg/public-uri "/api/login-ldap")] diff --git a/frontend/src/app/main/ui/auth/login.cljs b/frontend/src/app/main/ui/auth/login.cljs index bbdc805e7..0a196b0aa 100644 --- a/frontend/src/app/main/ui/auth/login.cljs +++ b/frontend/src/app/main/ui/auth/login.cljs @@ -63,7 +63,6 @@ on-error (fn [form event] - (js/console.log error?) (reset! error? true)) on-submit @@ -107,8 +106,7 @@ :help-icon i/eye :label (tr "auth.password")}]] [:& fm/submit-button - {:label (tr "auth.login-submit") - :on-click on-submit}] + {:label (tr "auth.login-submit")}] (when cfg/login-with-ldap [:& fm/submit-button