mirror of
https://github.com/penpot/penpot.git
synced 2025-03-16 01:31:22 -05:00
🎉 Add authentication with google.
This commit is contained in:
parent
5268a7663f
commit
19cd84597d
23 changed files with 589 additions and 276 deletions
14
backend/resources/migrations/0010-add-http-session-table.sql
Normal file
14
backend/resources/migrations/0010-add-http-session-table.sql
Normal file
|
@ -0,0 +1,14 @@
|
|||
DROP TABLE session;
|
||||
|
||||
CREATE TABLE http_session (
|
||||
id text PRIMARY KEY,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
|
||||
profile_id uuid REFERENCES profile(id) ON DELETE CASCADE,
|
||||
user_agent text NULL
|
||||
);
|
||||
|
||||
CREATE INDEX http_session__profile_id__idx
|
||||
ON http_session(profile_id);
|
|
@ -26,7 +26,8 @@
|
|||
:database-username "uxbox"
|
||||
:database-password "uxbox"
|
||||
|
||||
:public-url "http://localhost:3449"
|
||||
:public-uri "http://localhost:3449"
|
||||
:backend-uri "http://localhost:6060"
|
||||
|
||||
:redis-uri "redis://redis/0"
|
||||
:media-directory "resources/public/media"
|
||||
|
@ -69,13 +70,19 @@
|
|||
(s/def ::registration-enabled ::us/boolean)
|
||||
(s/def ::registration-domain-whitelist ::us/string)
|
||||
(s/def ::debug-humanize-transit ::us/boolean)
|
||||
(s/def ::public-url ::us/string)
|
||||
(s/def ::public-uri ::us/string)
|
||||
(s/def ::backend-uri ::us/string)
|
||||
|
||||
(s/def ::google-client-id ::us/string)
|
||||
(s/def ::google-client-secret ::us/string)
|
||||
|
||||
(s/def ::config
|
||||
(s/keys :opt-un [::http-server-cors
|
||||
::http-server-debug
|
||||
::http-server-port
|
||||
::public-url
|
||||
::google-client-id
|
||||
::google-client-secret
|
||||
::public-uri
|
||||
::database-username
|
||||
::database-password
|
||||
::database-uri
|
||||
|
|
|
@ -75,8 +75,10 @@
|
|||
(jdbc/get-connection pool))
|
||||
|
||||
(defn exec!
|
||||
[ds sv]
|
||||
(jdbc/execute! ds sv {:builder-fn as-kebab-maps}))
|
||||
([ds sv]
|
||||
(exec! ds sv {}))
|
||||
([ds sv opts]
|
||||
(jdbc/execute! ds sv (assoc opts :builder-fn as-kebab-maps))))
|
||||
|
||||
(defn exec-one!
|
||||
([ds sv] (exec-one! ds sv {}))
|
||||
|
@ -120,6 +122,15 @@
|
|||
([ds table id opts]
|
||||
(get-by-params ds table {:id id} opts)))
|
||||
|
||||
(defn query
|
||||
([ds table params]
|
||||
(query ds table params nil))
|
||||
([ds table params opts]
|
||||
(let [opts (cond-> (merge default-options opts)
|
||||
(:for-update opts)
|
||||
(assoc :suffix "for update"))]
|
||||
(exec! ds (jdbc-bld/for-query table params opts) opts))))
|
||||
|
||||
(defn pgobject?
|
||||
[v]
|
||||
(instance? PGobject v))
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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 uxbox.emails
|
||||
"Main api for send emails."
|
||||
|
@ -63,7 +66,7 @@
|
|||
"A password recovery notification email."
|
||||
(emails/build ::password-recovery default-context))
|
||||
|
||||
(s/def ::pending-email ::us/string)
|
||||
(s/def ::pending-email ::us/email)
|
||||
(s/def ::change-email
|
||||
(s/keys :req-un [::name ::pending-email ::token]))
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
[uxbox.db :as db]
|
||||
[uxbox.media :as media]
|
||||
[uxbox.migrations]
|
||||
[uxbox.services.mutations.profile :as mt.profile]
|
||||
[uxbox.services.mutations.profile :as profile]
|
||||
[uxbox.util.blob :as blob]))
|
||||
|
||||
(defn- mk-uuid
|
||||
|
@ -66,6 +66,11 @@
|
|||
[f items]
|
||||
(reduce #(conj %1 (f %2)) [] items))
|
||||
|
||||
(defn- register-profile
|
||||
[conn params]
|
||||
(->> (#'profile/create-profile conn params)
|
||||
(#'profile/create-profile-relations conn)))
|
||||
|
||||
(defn impl-run
|
||||
[opts]
|
||||
(let [rng (java.util.Random. 1)
|
||||
|
@ -74,12 +79,12 @@
|
|||
(fn [conn index]
|
||||
(let [id (mk-uuid "profile" index)]
|
||||
(log/info "create profile" id)
|
||||
(mt.profile/register-profile conn
|
||||
{:id id
|
||||
:fullname (str "Profile " index)
|
||||
:password "123123"
|
||||
:demo? true
|
||||
:email (str "profile" index ".test@uxbox.io")})))
|
||||
(register-profile conn
|
||||
{:id id
|
||||
:fullname (str "Profile " index)
|
||||
:password "123123"
|
||||
:demo? true
|
||||
:email (str "profile" index ".test@uxbox.io")})))
|
||||
|
||||
create-profiles
|
||||
(fn [conn]
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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 uxbox.http
|
||||
(:require
|
||||
|
@ -14,6 +17,8 @@
|
|||
[uxbox.http.debug :as debug]
|
||||
[uxbox.http.errors :as errors]
|
||||
[uxbox.http.handlers :as handlers]
|
||||
[uxbox.http.auth :as auth]
|
||||
[uxbox.http.auth.google :as google]
|
||||
[uxbox.http.middleware :as middleware]
|
||||
[uxbox.http.session :as session]
|
||||
[uxbox.http.ws :as ws]
|
||||
|
@ -31,12 +36,17 @@
|
|||
[middleware/multipart-params]
|
||||
[middleware/keyword-params]
|
||||
[middleware/cookies]]}
|
||||
|
||||
["/oauth"
|
||||
["/google" {:post google/auth}]
|
||||
["/google/callback" {:get google/callback}]]
|
||||
|
||||
["/echo" {:get handlers/echo-handler
|
||||
:post handlers/echo-handler}]
|
||||
|
||||
["/login" {:handler handlers/login-handler
|
||||
["/login" {:handler auth/login-handler
|
||||
:method :post}]
|
||||
["/logout" {:handler handlers/logout-handler
|
||||
["/logout" {:handler auth/logout-handler
|
||||
:method :post}]
|
||||
|
||||
["/w" {:middleware [session/auth]}
|
||||
|
|
33
backend/src/uxbox/http/auth.clj
Normal file
33
backend/src/uxbox/http/auth.clj
Normal file
|
@ -0,0 +1,33 @@
|
|||
;; 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 uxbox.http.auth
|
||||
(:require
|
||||
[uxbox.common.exceptions :as ex]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.http.session :as session]
|
||||
[uxbox.services.mutations :as sm]))
|
||||
|
||||
(defn login-handler
|
||||
[req]
|
||||
(let [data (:body-params req)
|
||||
uagent (get-in req [:headers "user-agent"])]
|
||||
(let [profile (sm/handle (assoc data ::sm/type :login))
|
||||
id (session/create (:id profile) uagent)]
|
||||
{:status 200
|
||||
:cookies (session/cookies id)
|
||||
:body profile})))
|
||||
|
||||
(defn logout-handler
|
||||
[req]
|
||||
(some-> (session/extract-auth-token req)
|
||||
(session/delete))
|
||||
{:status 200
|
||||
:cookies (session/cookies "" {:max-age -1})
|
||||
:body ""})
|
133
backend/src/uxbox/http/auth/google.clj
Normal file
133
backend/src/uxbox/http/auth/google.clj
Normal file
|
@ -0,0 +1,133 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.http.auth.google
|
||||
(:require
|
||||
[clojure.data.json :as json]
|
||||
[clojure.tools.logging :as log]
|
||||
[lambdaisland.uri :as uri]
|
||||
[uxbox.common.exceptions :as ex]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.services.tokens :as tokens]
|
||||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.http.session :as session]
|
||||
[uxbox.util.http :as http]))
|
||||
|
||||
(def base-goauth-uri "https://accounts.google.com/o/oauth2/v2/auth")
|
||||
|
||||
(def scope
|
||||
(str "email profile "
|
||||
"https://www.googleapis.com/auth/userinfo.email "
|
||||
"https://www.googleapis.com/auth/userinfo.profile "
|
||||
"openid"))
|
||||
|
||||
(defn- build-redirect-url
|
||||
[]
|
||||
(let [public (uri/uri (:backend-uri cfg/config))]
|
||||
(str (assoc public :path "/api/oauth/google/callback"))))
|
||||
|
||||
(defn- get-access-token
|
||||
[code]
|
||||
(let [params {:code code
|
||||
:client_id (:google-client-id cfg/config)
|
||||
:client_secret (:google-client-secret cfg/config)
|
||||
:redirect_uri (build-redirect-url)
|
||||
:grant_type "authorization_code"}
|
||||
req {:method :post
|
||||
:headers {"content-type" "application/x-www-form-urlencoded"}
|
||||
:uri "https://oauth2.googleapis.com/token"
|
||||
:body (uri/map->query-string params)}
|
||||
res (http/send! req)]
|
||||
|
||||
(when (not= 200 (:status res))
|
||||
(ex/raise :type :internal
|
||||
:code :invalid-response-from-google
|
||||
: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 google access tooken request" e)
|
||||
nil))))
|
||||
|
||||
|
||||
(defn- get-user-info
|
||||
[token]
|
||||
(let [req {:uri "https://openidconnect.googleapis.com/v1/userinfo"
|
||||
:headers {"Authorization" (str "Bearer " token)}
|
||||
:method :get}
|
||||
res (http/send! req)]
|
||||
|
||||
(when (not= 200 (:status res))
|
||||
(ex/raise :type :internal
|
||||
:code :invalid-response-from-google
|
||||
:context {:status (:status res)
|
||||
:body (:body res)}))
|
||||
|
||||
(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 google access tooken request" e)
|
||||
nil))))
|
||||
|
||||
(defn auth
|
||||
[req]
|
||||
(let [token (tokens/create! db/pool {:type :google-oauth})
|
||||
params {:scope scope
|
||||
:access_type "offline"
|
||||
:include_granted_scopes true
|
||||
:state token
|
||||
:response_type "code"
|
||||
:redirect_uri (build-redirect-url)
|
||||
:client_id (:google-client-id cfg/config)}
|
||||
query (uri/map->query-string params)
|
||||
uri (-> (uri/uri base-goauth-uri)
|
||||
(assoc :query query))]
|
||||
{:status 200
|
||||
:body {:redirect-uri (str uri)}}))
|
||||
|
||||
|
||||
(defn callback
|
||||
[req]
|
||||
(let [token (get-in req [:params :state])
|
||||
tdata (tokens/retrieve db/pool token)
|
||||
info (some-> (get-in req [:params :code])
|
||||
(get-access-token)
|
||||
(get-user-info))]
|
||||
|
||||
(when (not= :google-oauth (:type tdata))
|
||||
(ex/raise :type :validation
|
||||
:code ::tokens/invalid-token))
|
||||
|
||||
(when-not info
|
||||
(ex/raise :type :authentication
|
||||
:code ::unable-to-authenticate-with-google))
|
||||
|
||||
(let [profile (sm/handle {::sm/type :login-or-register
|
||||
:email (:email info)
|
||||
:fullname (:fullname info)})
|
||||
uagent (get-in req [:headers "user-agent"])
|
||||
|
||||
tdata {:type :authentication
|
||||
:profile profile}
|
||||
token (tokens/create! db/pool tdata {:valid {:minutes 10}})
|
||||
|
||||
uri (-> (uri/uri (:public-uri cfg/config))
|
||||
(assoc :path "/#/auth/verify-token")
|
||||
(assoc :query (uri/map->query-string {:token token})))
|
||||
sid (session/create (:id profile) uagent)]
|
||||
|
||||
{:status 302
|
||||
:headers {"location" (str uri)}
|
||||
:cookies (session/cookies sid)
|
||||
:body ""})))
|
||||
|
|
@ -2,12 +2,14 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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 uxbox.http.handlers
|
||||
(:require
|
||||
[uxbox.common.exceptions :as ex]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.emails :as emails]
|
||||
[uxbox.http.session :as session]
|
||||
[uxbox.services.init]
|
||||
|
@ -54,11 +56,10 @@
|
|||
(let [body (sm/handle (with-meta data {:req req}))]
|
||||
(if (= type :delete-profile)
|
||||
(do
|
||||
(some-> (get-in req [:cookies "auth-token" :value])
|
||||
(uuid/uuid)
|
||||
(some-> (session/extract-auth-token req)
|
||||
(session/delete))
|
||||
{:status 204
|
||||
:cookies {"auth-token" {:value "" :max-age -1}}
|
||||
:cookies (session/cookies "" {:max-age -1})
|
||||
:body ""})
|
||||
{:status 200
|
||||
:body body}))
|
||||
|
@ -66,25 +67,6 @@
|
|||
:body {:type :authentication
|
||||
:code :unauthorized}})))
|
||||
|
||||
(defn login-handler
|
||||
[req]
|
||||
(let [data (:body-params req)
|
||||
user-agent (get-in req [:headers "user-agent"])]
|
||||
(let [profile (sm/handle (assoc data ::sm/type :login))
|
||||
token (session/create (:id profile) user-agent)]
|
||||
{:status 200
|
||||
:cookies {"auth-token" {:value token :path "/"}}
|
||||
:body profile})))
|
||||
|
||||
(defn logout-handler
|
||||
[req]
|
||||
(some-> (get-in req [:cookies "auth-token" :value])
|
||||
(uuid/uuid)
|
||||
(session/delete))
|
||||
{:status 200
|
||||
:cookies {"auth-token" {:value "" :max-age -1}}
|
||||
:body ""})
|
||||
|
||||
(defn echo-handler
|
||||
[req]
|
||||
{:status 200
|
||||
|
|
|
@ -2,49 +2,51 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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 uxbox.http.session
|
||||
(:require
|
||||
[uxbox.db :as db]
|
||||
[uxbox.services.tokens :as tokens]
|
||||
[uxbox.common.uuid :as uuid]))
|
||||
|
||||
;; --- Main API
|
||||
|
||||
(defn retrieve
|
||||
"Retrieves a user id associated with the provided auth token."
|
||||
[token]
|
||||
(when token
|
||||
(let [row (db/get-by-params db/pool :session {:id token})]
|
||||
(:profile-id row))))
|
||||
(-> (db/query db/pool :http-session {:id token})
|
||||
(first)
|
||||
(:profile-id))))
|
||||
|
||||
(defn create
|
||||
[user-id user-agent]
|
||||
(let [id (uuid/random)]
|
||||
(db/insert! db/pool :session {:id id
|
||||
:profile-id user-id
|
||||
:user-agent user-agent})
|
||||
(str id)))
|
||||
[profile-id user-agent]
|
||||
(let [id (tokens/next)]
|
||||
(db/insert! db/pool :http-session {:id id
|
||||
:profile-id profile-id
|
||||
:user-agent user-agent})
|
||||
id))
|
||||
|
||||
(defn delete
|
||||
[token]
|
||||
(db/delete! db/pool :session {:id token})
|
||||
(db/delete! db/pool :http-session {:id token})
|
||||
nil)
|
||||
|
||||
;; --- Interceptor
|
||||
(defn cookies
|
||||
([id] (cookies id {}))
|
||||
([id opts]
|
||||
{"auth-token" (merge opts {:value id :path "/" :http-only true})}))
|
||||
|
||||
(defn- parse-token
|
||||
[request]
|
||||
(try
|
||||
(when-let [token (get-in request [:cookies "auth-token"])]
|
||||
(uuid/uuid (:value token)))
|
||||
(catch java.lang.IllegalArgumentException e
|
||||
nil)))
|
||||
(defn extract-auth-token
|
||||
[req]
|
||||
(get-in req [:cookies "auth-token" :value]))
|
||||
|
||||
(defn wrap-auth
|
||||
[handler]
|
||||
(fn [request]
|
||||
(let [token (parse-token request)
|
||||
(let [token (get-in request [:cookies "auth-token" :value])
|
||||
profile-id (retrieve token)]
|
||||
(if profile-id
|
||||
(handler (assoc request :profile-id profile-id))
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2016-2020 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main
|
||||
(:require
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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 uxbox.migrations
|
||||
(:require
|
||||
|
@ -44,12 +47,16 @@
|
|||
:fn (mg/resource "migrations/0007-drop-version-field-from-page-table.sql")}
|
||||
|
||||
{:desc "Add generic token related tables."
|
||||
:name "0008-add-generic-token-table.sql"
|
||||
:name "0008-add-generic-token-table"
|
||||
:fn (mg/resource "migrations/0008-add-generic-token-table.sql")}
|
||||
|
||||
{:desc "Drop the profile_email table"
|
||||
:name "0009-drop-profile-email-table.sql"
|
||||
:fn (mg/resource "migrations/0009-drop-profile-email-table.sql")}]})
|
||||
:name "0009-drop-profile-email-table"
|
||||
:fn (mg/resource "migrations/0009-drop-profile-email-table.sql")}
|
||||
|
||||
{:desc "Add new HTTP session table"
|
||||
:name "0010-add-http-session-table"
|
||||
:fn (mg/resource "migrations/0010-add-http-session-table.sql")}]})
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Entry point
|
||||
|
|
|
@ -29,13 +29,15 @@
|
|||
email (str "demo-" sem ".demo@nodomain.com")
|
||||
fullname (str "Demo User " sem)
|
||||
password (-> (sodi.prng/random-bytes 12)
|
||||
(sodi.util/bytes->b64s))]
|
||||
(sodi.util/bytes->b64s))
|
||||
params {:id id
|
||||
:email email
|
||||
:fullname fullname
|
||||
:demo? true
|
||||
:password password}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(#'profile/register-profile conn {:id id
|
||||
:email email
|
||||
:fullname fullname
|
||||
:demo? true
|
||||
:password password})
|
||||
(->> (#'profile/create-profile conn params)
|
||||
(#'profile/create-profile-relations conn))
|
||||
|
||||
;; Schedule deletion of the demo profile
|
||||
(tasks/submit! conn {:name "delete-profile"
|
||||
|
|
|
@ -25,10 +25,11 @@
|
|||
[uxbox.emails :as emails]
|
||||
[uxbox.images :as images]
|
||||
[uxbox.media :as media]
|
||||
[uxbox.services.tokens :as tokens]
|
||||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.services.mutations.images :as imgs]
|
||||
[uxbox.services.mutations.projects :as mt.projects]
|
||||
[uxbox.services.mutations.teams :as mt.teams]
|
||||
[uxbox.services.mutations.projects :as projects]
|
||||
[uxbox.services.mutations.teams :as teams]
|
||||
[uxbox.services.queries.profile :as profile]
|
||||
[uxbox.tasks :as tasks]
|
||||
[uxbox.util.blob :as blob]
|
||||
|
@ -46,12 +47,100 @@
|
|||
(s/def ::old-password ::us/string)
|
||||
(s/def ::theme ::us/string)
|
||||
|
||||
(defn decode-token-row
|
||||
[{:keys [content] :as row}]
|
||||
(when row
|
||||
(cond-> row
|
||||
content (assoc :content (blob/decode content)))))
|
||||
;; --- Mutation: Register Profile
|
||||
|
||||
(declare check-profile-existence!)
|
||||
(declare create-profile)
|
||||
(declare create-profile-relations)
|
||||
|
||||
(s/def ::register-profile
|
||||
(s/keys :req-un [::email ::password ::fullname]))
|
||||
|
||||
(defn email-domain-in-whitelist?
|
||||
"Returns true if email's domain is in the given whitelist or if given
|
||||
whitelist is an empty string."
|
||||
[whitelist email]
|
||||
(if (str/blank? whitelist)
|
||||
true
|
||||
(let [domains (str/split whitelist #",\s*")
|
||||
email-domain (second (str/split email #"@"))]
|
||||
(contains? (set domains) email-domain))))
|
||||
|
||||
(sm/defmutation ::register-profile
|
||||
[params]
|
||||
(when-not (:registration-enabled cfg/config)
|
||||
(ex/raise :type :restriction
|
||||
:code ::registration-disabled))
|
||||
|
||||
(when-not (email-domain-in-whitelist? (:registration-domain-whitelist cfg/config)
|
||||
(:email params))
|
||||
(ex/raise :type :validation
|
||||
:code ::email-domain-is-not-allowed))
|
||||
|
||||
(db/with-atomic [conn db/pool]
|
||||
(check-profile-existence! conn params)
|
||||
(let [profile (->> (create-profile conn params)
|
||||
(create-profile-relations conn))
|
||||
payload {:type :verify-email
|
||||
:profile-id (:id profile)
|
||||
:email (:email profile)}
|
||||
|
||||
token (tokens/create! conn payload {:valid {:days 30}})]
|
||||
|
||||
(emails/send! conn emails/register
|
||||
{:to (:email profile)
|
||||
:name (:fullname profile)
|
||||
:public-url (:public-uri cfg/config)
|
||||
:token token})
|
||||
profile)))
|
||||
|
||||
(def ^:private sql:profile-existence
|
||||
"select exists (select * from profile
|
||||
where email = ?
|
||||
and deleted_at is null) as val")
|
||||
|
||||
(defn- check-profile-existence!
|
||||
[conn {:keys [email] :as params}]
|
||||
(let [email (str/lower email)
|
||||
result (db/exec-one! conn [sql:profile-existence email])]
|
||||
(when (:val result)
|
||||
(ex/raise :type :validation
|
||||
:code ::email-already-exists))
|
||||
params))
|
||||
|
||||
(defn- create-profile
|
||||
"Create the profile entry on the database with limited input
|
||||
filling all the other fields with defaults."
|
||||
[conn {:keys [id fullname email password demo?] :as params}]
|
||||
(let [id (or id (uuid/next))
|
||||
demo? (if (boolean? demo?) demo? false)
|
||||
password (sodi.pwhash/derive password)]
|
||||
(db/insert! conn :profile
|
||||
{:id id
|
||||
:fullname fullname
|
||||
:email (str/lower email)
|
||||
:pending-email (if demo? nil email)
|
||||
:photo ""
|
||||
:password password
|
||||
:is-demo demo?})))
|
||||
|
||||
(defn- create-profile-relations
|
||||
[conn profile]
|
||||
(let [team (teams/create-team conn {:profile-id (:id profile)
|
||||
:name "Default"
|
||||
:default? true})
|
||||
proj (projects/create-project conn {:profile-id (:id profile)
|
||||
:team-id (:id team)
|
||||
:name "Drafts"
|
||||
:default? true})]
|
||||
(teams/create-team-profile conn {:team-id (:id team)
|
||||
:profile-id (:id profile)})
|
||||
(projects/create-project-profile conn {:project-id (:id proj)
|
||||
:profile-id (:id profile)})
|
||||
|
||||
(merge (profile/strip-private-attrs profile)
|
||||
{:default-team-id (:id team)
|
||||
:default-project-id (:id proj)})))
|
||||
|
||||
;; --- Mutation: Login
|
||||
|
||||
|
@ -70,7 +159,7 @@
|
|||
(let [result (sodi.pwhash/verify password (:password profile))]
|
||||
(:valid result)))
|
||||
|
||||
(check-profile [profile]
|
||||
(validate-profile [profile]
|
||||
(when-not profile
|
||||
(ex/raise :type :validation
|
||||
:code ::wrong-credentials))
|
||||
|
@ -80,14 +169,51 @@
|
|||
profile)]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(let [prof (-> (retrieve-profile-by-email conn email)
|
||||
(check-profile)
|
||||
(validate-profile)
|
||||
(profile/strip-private-attrs))
|
||||
addt (profile/retrieve-additional-data conn (:id prof))]
|
||||
(merge prof addt)))))
|
||||
|
||||
(def sql:profile-by-email
|
||||
"select * from profile
|
||||
where email=? and deleted_at is null
|
||||
for update")
|
||||
|
||||
(defn- retrieve-profile-by-email
|
||||
[conn email]
|
||||
(db/get-by-params conn :profile {:email email} {:for-update true}))
|
||||
(let [email (str/lower email)]
|
||||
(db/exec-one! conn [sql:profile-by-email email])))
|
||||
|
||||
|
||||
|
||||
;; --- Mutation: Register if not exists
|
||||
|
||||
(sm/defmutation ::login-or-register
|
||||
[{:keys [email fullname] :as params}]
|
||||
(letfn [(populate-additional-data [conn profile]
|
||||
(let [data (profile/retrieve-additional-data conn (:id profile))]
|
||||
(merge profile data)))
|
||||
|
||||
(create-profile [conn {:keys [fullname email]}]
|
||||
(db/insert! conn :profile
|
||||
{:id (uuid/next)
|
||||
:fullname fullname
|
||||
:email (str/lower email)
|
||||
:pending-email nil
|
||||
:photo ""
|
||||
:password "!"
|
||||
:is-demo false}))
|
||||
|
||||
(register-profile [conn params]
|
||||
(->> (create-profile conn params)
|
||||
(create-profile-relations conn)))]
|
||||
|
||||
(db/with-atomic [conn db/pool]
|
||||
(let [profile (retrieve-profile-by-email conn email)
|
||||
profile (if profile
|
||||
(populate-additional-data conn profile)
|
||||
(register-profile conn params))]
|
||||
(profile/strip-private-attrs profile)))))
|
||||
|
||||
|
||||
;; --- Mutation: Update Profile (own)
|
||||
|
@ -182,108 +308,6 @@
|
|||
nil)
|
||||
|
||||
|
||||
;; --- Mutation: Register Profile
|
||||
|
||||
(declare check-profile-existence!)
|
||||
(declare register-profile)
|
||||
|
||||
(s/def ::register-profile
|
||||
(s/keys :req-un [::email ::password ::fullname]))
|
||||
|
||||
(defn email-domain-in-whitelist?
|
||||
"Returns true if email's domain is in the given whitelist or if given
|
||||
whitelist is an empty string."
|
||||
[whitelist email]
|
||||
(if (str/blank? whitelist)
|
||||
true
|
||||
(let [domains (str/split whitelist #",\s*")
|
||||
email-domain (second (str/split email #"@"))]
|
||||
(contains? (set domains) email-domain))))
|
||||
|
||||
(sm/defmutation ::register-profile
|
||||
[params]
|
||||
(when-not (:registration-enabled cfg/config)
|
||||
(ex/raise :type :restriction
|
||||
:code ::registration-disabled))
|
||||
|
||||
(when-not (email-domain-in-whitelist? (:registration-domain-whitelist cfg/config)
|
||||
(:email params))
|
||||
(ex/raise :type :validation
|
||||
:code ::email-domain-is-not-allowed))
|
||||
|
||||
(db/with-atomic [conn db/pool]
|
||||
(check-profile-existence! conn params)
|
||||
(let [profile (register-profile conn params)
|
||||
token (-> (sodi.prng/random-bytes 32)
|
||||
(sodi.util/bytes->b64s))
|
||||
payload {:type :verify-email
|
||||
:profile-id (:id profile)
|
||||
:email (:email profile)}]
|
||||
|
||||
(db/insert! conn :generic-token
|
||||
{:token token
|
||||
:valid-until (dt/plus (dt/now)
|
||||
(dt/duration {:days 30}))
|
||||
:content (blob/encode payload)})
|
||||
|
||||
(emails/send! conn emails/register
|
||||
{:to (:email profile)
|
||||
:name (:fullname profile)
|
||||
:public-url (:public-url cfg/config)
|
||||
:token token})
|
||||
profile)))
|
||||
|
||||
(def ^:private sql:profile-existence
|
||||
"select exists (select * from profile
|
||||
where email = ?
|
||||
and deleted_at is null) as val")
|
||||
|
||||
(defn- check-profile-existence!
|
||||
[conn {:keys [email] :as params}]
|
||||
(let [email (str/lower email)
|
||||
result (db/exec-one! conn [sql:profile-existence email])]
|
||||
(when (:val result)
|
||||
(ex/raise :type :validation
|
||||
:code ::email-already-exists))
|
||||
params))
|
||||
|
||||
(defn- create-profile
|
||||
"Create the profile entry on the database with limited input
|
||||
filling all the other fields with defaults."
|
||||
[conn {:keys [id fullname email password demo?] :as params}]
|
||||
(let [id (or id (uuid/next))
|
||||
demo? (if (boolean? demo?) demo? false)
|
||||
password (sodi.pwhash/derive password)]
|
||||
(db/insert! conn :profile
|
||||
{:id id
|
||||
:fullname fullname
|
||||
:email (str/lower email)
|
||||
:pending-email (if demo? nil email)
|
||||
:photo ""
|
||||
:password password
|
||||
:is-demo demo?})))
|
||||
|
||||
(defn register-profile
|
||||
[conn params]
|
||||
(let [prof (create-profile conn params)
|
||||
team (mt.teams/create-team conn {:profile-id (:id prof)
|
||||
:name "Default"
|
||||
:default? true})
|
||||
proj (mt.projects/create-project conn {:profile-id (:id prof)
|
||||
:team-id (:id team)
|
||||
:name "Drafts"
|
||||
:default? true})]
|
||||
(mt.teams/create-team-profile conn {:team-id (:id team)
|
||||
:profile-id (:id prof)})
|
||||
(mt.projects/create-project-profile conn {:project-id (:id proj)
|
||||
:profile-id (:id prof)})
|
||||
|
||||
;; TODO: rename to -default-team-id...
|
||||
(merge (profile/strip-private-attrs prof)
|
||||
{:default-team (:id team)
|
||||
:default-project (:id proj)})))
|
||||
|
||||
|
||||
;; --- Mutation: Request Email Change
|
||||
|
||||
(declare select-profile-for-update)
|
||||
|
@ -296,11 +320,11 @@
|
|||
(db/with-atomic [conn db/pool]
|
||||
(let [email (str/lower email)
|
||||
profile (select-profile-for-update conn profile-id)
|
||||
token (-> (sodi.prng/random-bytes 32)
|
||||
(sodi.util/bytes->b64s))
|
||||
payload {:type :change-email
|
||||
:profile-id profile-id
|
||||
:email email}]
|
||||
:email email}
|
||||
|
||||
token (tokens/create! conn payload)]
|
||||
|
||||
(when (not= email (:email profile))
|
||||
(check-profile-existence! conn params))
|
||||
|
@ -309,21 +333,14 @@
|
|||
{:pending-email email}
|
||||
{:id profile-id})
|
||||
|
||||
(db/insert! conn :generic-token
|
||||
{:token token
|
||||
:valid-until (dt/plus (dt/now)
|
||||
(dt/duration {:hours 48}))
|
||||
:content (blob/encode payload)})
|
||||
|
||||
(emails/send! conn emails/change-email
|
||||
{:to (:email profile)
|
||||
:name (:fullname profile)
|
||||
:public-url (:public-url cfg/config)
|
||||
:public-url (:public-uri cfg/config)
|
||||
:pending-email email
|
||||
:token token})
|
||||
nil)))
|
||||
|
||||
|
||||
(defn- select-profile-for-update
|
||||
[conn id]
|
||||
(db/get-by-id conn :profile id {:for-update true}))
|
||||
|
@ -334,16 +351,14 @@
|
|||
;; Generic mutation for perform token based verification for auth
|
||||
;; domain.
|
||||
|
||||
(declare retrieve-token)
|
||||
|
||||
(s/def ::verify-profile-token
|
||||
(s/keys :req-un [::token]))
|
||||
|
||||
(sm/defmutation ::verify-profile-token
|
||||
[{:keys [token] :as params}]
|
||||
(letfn [(handle-email-change [conn token]
|
||||
(let [profile (select-profile-for-update conn (:profile-id token))]
|
||||
(when (not= (:email token)
|
||||
(letfn [(handle-email-change [conn tdata]
|
||||
(let [profile (select-profile-for-update conn (:profile-id tdata))]
|
||||
(when (not= (:email tdata)
|
||||
(:pending-email profile))
|
||||
(ex/raise :type :validation
|
||||
:code ::email-does-not-match))
|
||||
|
@ -353,48 +368,31 @@
|
|||
:email (:pending-email profile)}
|
||||
{:id (:id profile)})
|
||||
|
||||
token))
|
||||
tdata))
|
||||
|
||||
(handle-email-verify [conn token]
|
||||
(let [profile (select-profile-for-update conn (:profile-id token))]
|
||||
(handle-email-verify [conn tdata]
|
||||
(let [profile (select-profile-for-update conn (:profile-id tdata))]
|
||||
(when (or (not= (:email profile)
|
||||
(:pending-email profile))
|
||||
(not= (:email profile)
|
||||
(:email token)))
|
||||
(:email tdata)))
|
||||
(ex/raise :type :validation
|
||||
:code ::invalid-token))
|
||||
:code ::tokens/invalid-token))
|
||||
|
||||
(db/update! conn :profile
|
||||
{:pending-email nil}
|
||||
{:id (:id profile)})
|
||||
token))]
|
||||
tdata))]
|
||||
|
||||
(db/with-atomic [conn db/pool]
|
||||
(let [token (retrieve-token conn token)]
|
||||
(db/delete! conn :generic-token {:token (:token params)})
|
||||
|
||||
;; Validate the token expiration
|
||||
(when (> (inst-ms (dt/now))
|
||||
(inst-ms (:valid-until token)))
|
||||
(let [tdata (tokens/retrieve conn token {:delete true})]
|
||||
(tokens/delete! conn token)
|
||||
(case (:type tdata)
|
||||
:change-email (handle-email-change conn tdata)
|
||||
:verify-email (handle-email-verify conn tdata)
|
||||
:authentication tdata
|
||||
(ex/raise :type :validation
|
||||
:code ::invalid-token))
|
||||
|
||||
(case (:type token)
|
||||
:change-email (handle-email-change conn token)
|
||||
:verify-email (handle-email-verify conn token)
|
||||
(ex/raise :type :validation
|
||||
:code ::invalid-token))))))
|
||||
|
||||
(defn- retrieve-token
|
||||
[conn token]
|
||||
(let [row (-> (db/get-by-params conn :generic-token {:token token})
|
||||
(decode-token-row))]
|
||||
(when-not row
|
||||
(ex/raise :type :validation
|
||||
:code ::invalid-token))
|
||||
(-> row
|
||||
(dissoc :content)
|
||||
(merge (:content row)))))
|
||||
:code ::tokens/invalid-token))))))
|
||||
|
||||
;; --- Mutation: Cancel Email Change
|
||||
|
||||
|
@ -421,20 +419,15 @@
|
|||
(sm/defmutation ::request-profile-recovery
|
||||
[{:keys [email] :as params}]
|
||||
(letfn [(create-recovery-token [conn {:keys [id] :as profile}]
|
||||
(let [token (-> (sodi.prng/random-bytes 32)
|
||||
(sodi.util/bytes->b64s))
|
||||
payload {:type :password-recovery-token
|
||||
:profile-id id}]
|
||||
(db/insert! conn :generic-token
|
||||
{:token token
|
||||
:valid-until (dt/plus (dt/now) (dt/duration {:hours 24}))
|
||||
:content (blob/encode payload)})
|
||||
(let [payload {:type :password-recovery-token
|
||||
:profile-id id}
|
||||
token (tokens/create! conn payload)]
|
||||
(assoc profile :token token)))
|
||||
|
||||
(send-email-notification [conn profile]
|
||||
(emails/send! conn emails/password-recovery
|
||||
{:to (:email profile)
|
||||
:public-url (:public-url cfg/config)
|
||||
:public-url (:public-uri cfg/config)
|
||||
:token (:token profile)
|
||||
:name (:fullname profile)}))]
|
||||
|
||||
|
@ -454,13 +447,11 @@
|
|||
(sm/defmutation ::recover-profile
|
||||
[{:keys [token password]}]
|
||||
(letfn [(validate-token [conn token]
|
||||
(let [{:keys [token content]}
|
||||
(-> (db/get-by-params conn :generic-token {:token token})
|
||||
(decode-token-row))]
|
||||
(when (not= (:type content) :password-recovery-token)
|
||||
(let [tpayload (tokens/retrieve conn token)]
|
||||
(when (not= (:type tpayload) :password-recovery-token)
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-token))
|
||||
(:profile-id content)))
|
||||
:code ::tokens/invalid-token))
|
||||
(:profile-id tpayload)))
|
||||
|
||||
(update-password [conn profile-id]
|
||||
(let [pwd (sodi.pwhash/derive password)]
|
||||
|
|
|
@ -91,5 +91,5 @@
|
|||
|
||||
(defn strip-private-attrs
|
||||
"Only selects a publicy visible profile attrs."
|
||||
[o]
|
||||
(dissoc o :password :deleted-at))
|
||||
[row]
|
||||
(dissoc row :password :deleted-at))
|
||||
|
|
80
backend/src/uxbox/services/tokens.clj
Normal file
80
backend/src/uxbox/services/tokens.clj
Normal file
|
@ -0,0 +1,80 @@
|
|||
;; 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 uxbox.services.tokens
|
||||
(:refer-clojure :exclude [next])
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[sodi.prng]
|
||||
[sodi.util]
|
||||
[uxbox.common.exceptions :as ex]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.util.time :as dt]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.db :as db]))
|
||||
|
||||
(defn next
|
||||
([] (next 64))
|
||||
([n]
|
||||
(-> (sodi.prng/random-bytes n)
|
||||
(sodi.util/bytes->b64s))))
|
||||
|
||||
(def default-duration
|
||||
(dt/duration {:hours 48}))
|
||||
|
||||
(defn- decode-row
|
||||
[{:keys [content] :as row}]
|
||||
(when row
|
||||
(cond-> row
|
||||
content (assoc :content (blob/decode content)))))
|
||||
|
||||
(defn create!
|
||||
([conn payload] (create! conn payload {}))
|
||||
([conn payload {:keys [valid] :or {valid default-duration}}]
|
||||
(let [token (next)
|
||||
until (dt/plus (dt/now) (dt/duration valid))]
|
||||
(db/insert! conn :generic-token
|
||||
{:content (blob/encode payload)
|
||||
:token token
|
||||
:valid-until until})
|
||||
token)))
|
||||
|
||||
(defn delete!
|
||||
[conn token]
|
||||
(db/delete! conn :generic-token {:token token}))
|
||||
|
||||
(defn retrieve
|
||||
([conn token] (retrieve conn token {}))
|
||||
([conn token {:keys [delete] :or {delete false}}]
|
||||
(let [row (->> (db/query conn :generic-token {:token token})
|
||||
(map decode-row)
|
||||
(first))]
|
||||
|
||||
(when-not row
|
||||
(ex/raise :type :validation
|
||||
:code ::invalid-token))
|
||||
|
||||
;; Validate the token expiration
|
||||
(when (> (inst-ms (dt/now))
|
||||
(inst-ms (:valid-until row)))
|
||||
(ex/raise :type :validation
|
||||
:code ::invalid-token))
|
||||
|
||||
(when delete
|
||||
(db/delete! conn :generic-token {:token token}))
|
||||
|
||||
(-> row
|
||||
(dissoc :content)
|
||||
(merge (:content row))))))
|
||||
|
||||
|
||||
|
|
@ -82,10 +82,12 @@
|
|||
|
||||
(defn create-profile
|
||||
[conn i]
|
||||
(#'profile/register-profile conn {:id (mk-uuid "profile" i)
|
||||
:fullname (str "Profile " i)
|
||||
:email (str "profile" i ".test@nodomain.com")
|
||||
:password "123123"}))
|
||||
(let [params {:id (mk-uuid "profile" i)
|
||||
:fullname (str "Profile " i)
|
||||
:email (str "profile" i ".test@nodomain.com")
|
||||
:password "123123"}]
|
||||
(->> (#'profile/create-profile conn params)
|
||||
(#'profile/create-profile-relations conn))))
|
||||
|
||||
(defn create-team
|
||||
[conn profile-id i]
|
||||
|
|
|
@ -52,4 +52,9 @@
|
|||
.form-container {
|
||||
width: 368px;
|
||||
}
|
||||
|
||||
.btn-google-auth {
|
||||
margin-bottom: $medium;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.data.auth
|
||||
(:require
|
||||
|
@ -34,7 +34,7 @@
|
|||
(watch [this state stream]
|
||||
(let [team-id (:default-team-id data)]
|
||||
(rx/of (du/profile-fetched data)
|
||||
(rt/navigate :dashboard-team {:team-id team-id}))))))
|
||||
(rt/nav :dashboard-team {:team-id team-id}))))))
|
||||
|
||||
;; --- Login
|
||||
|
||||
|
@ -63,6 +63,20 @@
|
|||
(on-error err)
|
||||
(rx/empty)))
|
||||
(rx/map logged-in))))))
|
||||
|
||||
(defn login-from-token
|
||||
[{:keys [profile] :as tdata}]
|
||||
(ptk/reify ::login-from-token
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(merge state (dissoc initial-state :route :router)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [this state s]
|
||||
(let [team-id (:default-team-id profile)]
|
||||
(rx/of (du/profile-fetched profile)
|
||||
(rt/nav' :dashboard-team {:team-id team-id}))))))
|
||||
|
||||
;; --- Logout
|
||||
|
||||
(def clear-user-data
|
||||
|
|
|
@ -62,6 +62,12 @@
|
|||
([id] (mutation id {}))
|
||||
([id params] (mutation id params)))
|
||||
|
||||
(defmethod mutation :login-with-google
|
||||
[id params]
|
||||
(let [url (str url "/api/oauth/google")]
|
||||
(->> (http/send! {:method :post :url url})
|
||||
(rx/mapcat handle-response))))
|
||||
|
||||
(defmethod mutation :upload-image
|
||||
[id params]
|
||||
(let [form (js/FormData.)]
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[beicon.core :as rx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.data.users :as du]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.store :as st]
|
||||
|
@ -66,6 +67,10 @@
|
|||
(st/emit! (rt/nav :settings-profile)
|
||||
du/fetch-profile)))
|
||||
|
||||
(defn- handle-authentication
|
||||
[tdata]
|
||||
(st/emit! (da/login-from-token tdata)))
|
||||
|
||||
(mf/defc verify-token
|
||||
[{:keys [route] :as props}]
|
||||
(let [token (get-in route [:query-params :token])]
|
||||
|
@ -73,10 +78,11 @@
|
|||
(fn []
|
||||
(->> (rp/mutation :verify-profile-token {:token token})
|
||||
(rx/subs
|
||||
(fn [response]
|
||||
(case (:type response)
|
||||
:verify-email (handle-email-verified response)
|
||||
:change-email (handle-email-changed response)
|
||||
(fn [tdata]
|
||||
(case (:type tdata)
|
||||
:verify-email (handle-email-verified tdata)
|
||||
:change-email (handle-email-changed tdata)
|
||||
:authentication (handle-authentication tdata)
|
||||
nil))
|
||||
(fn [error]
|
||||
(case (:code error)
|
||||
|
|
|
@ -10,13 +10,16 @@
|
|||
(ns uxbox.main.ui.auth.login
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[beicon.core :as rx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.repo :as rp]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.ui.components.forms :refer [input submit-button form]]
|
||||
[uxbox.util.object :as obj]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :refer [tr t]]
|
||||
|
@ -38,6 +41,13 @@
|
|||
{:on-error (partial on-error form)})]
|
||||
(st/emit! (da/login params))))
|
||||
|
||||
(defn- login-with-google
|
||||
[event]
|
||||
(dom/prevent-default event)
|
||||
(->> (rp/mutation! :login-with-google {})
|
||||
(rx/subs (fn [{:keys [redirect-uri] :as rsp}]
|
||||
(.replace js/location redirect-uri)))))
|
||||
|
||||
(mf/defc login-form
|
||||
[{:keys [locale] :as props}]
|
||||
[:& form {:on-submit on-submit
|
||||
|
@ -60,6 +70,7 @@
|
|||
|
||||
(mf/defc login-page
|
||||
[{:keys [locale] :as props}]
|
||||
|
||||
[:div.generic-form.login-form
|
||||
[:div.form-container
|
||||
[:h1 (t locale "auth.login-title")]
|
||||
|
@ -67,6 +78,10 @@
|
|||
|
||||
[:& login-form {:locale locale}]
|
||||
|
||||
[:a.btn-secondary.btn-large.btn-google-auth
|
||||
{:on-click login-with-google}
|
||||
"Login with google"]
|
||||
|
||||
[:div.links
|
||||
[:div.link-entry
|
||||
[:a {:on-click #(st/emit! (rt/nav :auth-recovery-request))
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
;; 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 uxbox.main.ui.settings.header
|
||||
|
@ -55,21 +58,3 @@
|
|||
{:class "foobar"
|
||||
:on-click #(st/emit! (rt/nav :settings-profile))}
|
||||
(t locale "settings.teams")]]]))
|
||||
|
||||
|
||||
|
||||
|
||||
;; [:div.main-logo
|
||||
;; {:on-click #(st/emit! (rt/nav :dashboard-team {:team-id team-id}))}
|
||||
;; i/logo-icon]
|
||||
;; [:section.main-bar
|
||||
;; [:nav
|
||||
;; [:a.nav-item
|
||||
;; {:class (when profile? "current")
|
||||
;; :on-click #(st/emit! (rt/nav :settings-profile))}
|
||||
;; (t locale "settings.profile")]
|
||||
;; [:a.nav-item
|
||||
;; {:class (when password? "current")
|
||||
;; :on-click #(st/emit! (rt/nav :settings-password))}
|
||||
;; (t locale "settings.password")]]]]))
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue