diff --git a/backend/deps.edn b/backend/deps.edn
index 273672f4bc..acc9cd7035 100644
--- a/backend/deps.edn
+++ b/backend/deps.edn
@@ -18,6 +18,7 @@
instaparse/instaparse {:mvn/version "1.4.10"}
com.cognitect/transit-clj {:mvn/version "0.8.319"}
+ ;; TODO: vendorize pgclient under `vertx-clojure/vertx-pgclient`
io.vertx/vertx-pg-client {:mvn/version "3.8.4"}
vertx-clojure/vertx
diff --git a/backend/resources/emails/en/password-recovery.mustache b/backend/resources/emails/en/password-recovery.mustache
new file mode 100644
index 0000000000..df68f236a6
--- /dev/null
+++ b/backend/resources/emails/en/password-recovery.mustache
@@ -0,0 +1,42 @@
+-- begin :subject
+Password recovery.
+-- end
+
+-- begin :body-text
+Hello {{name}}!
+
+You have requested a password recovery.
+
+The token is:
+
+{{ token }}
+-- end
+
+-- begin :body-html
+
+
+
+
+ title
+ {{> ../partials/inline_style }}
+
+
+
+
+
+ |
+
+
+ 
+
+ TODO
+ {{ token }}
+ |
+ |
+
+
+
+ {{> ../partials/en/footer }}
+
+
+-- end
\ No newline at end of file
diff --git a/backend/src/uxbox/core.clj b/backend/src/uxbox/core.clj
index c5586f7b87..c259375ec7 100644
--- a/backend/src/uxbox/core.clj
+++ b/backend/src/uxbox/core.clj
@@ -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
+;; This Source Code Form is "Incompatible With Secondary Licenses", as
+;; defined by the Mozilla Public License, v. 2.0.
+;;
+;; Copyright (c) 2019-2020 Andrey Antukh
(ns uxbox.core
(:require
diff --git a/backend/src/uxbox/emails.clj b/backend/src/uxbox/emails.clj
index 51d1c5436f..dafedba3a8 100644
--- a/backend/src/uxbox/emails.clj
+++ b/backend/src/uxbox/emails.clj
@@ -23,16 +23,6 @@
{:static media/resolve-asset
:comment (constantly nil)})
-;; --- Register Email
-
-(s/def ::name ::us/string)
-(s/def ::register
- (s/keys :req-un [::name]))
-
-(def register
- "A new profile registration welcome email."
- (emails/build ::register default-context))
-
;; --- Public API
(defn render
@@ -56,3 +46,22 @@
values ($1, $2) returning *"]
(-> (db/query-one db/pool [sql data priority])
(p/then' (constantly nil)))))
+
+;; --- Emails
+
+(s/def ::name ::us/string)
+(s/def ::register
+ (s/keys :req-un [::name]))
+
+(def register
+ "A new profile registration welcome email."
+ (emails/build ::register default-context))
+
+(s/def ::token ::us/string)
+(s/def ::password-recovery
+ (s/keys :req-un [::name ::token]))
+
+(def password-recovery
+ "A password recovery notification email."
+ (emails/build ::password-recovery default-context))
+
diff --git a/backend/src/uxbox/http.clj b/backend/src/uxbox/http.clj
index 5a1701a354..ae58a3e234 100644
--- a/backend/src/uxbox/http.clj
+++ b/backend/src/uxbox/http.clj
@@ -51,12 +51,6 @@
:timeout 200
:name "login-handler"})
- echo-handler (rl/ratelimit handlers/echo-handler
- {:limit 1
- :period 5000
- :timeout 10
- :name "echo-handler"})
-
routes [["/sub/:file-id" {:interceptors [(vxi/cookies)
(vxi/cors cors-opts)
interceptors/format-response-body
@@ -64,10 +58,9 @@
:get ws/handler}]
["/api" {:interceptors interceptors}
- ["/echo" {:all echo-handler}]
+ ["/echo" {:all handlers/echo-handler}]
["/login" {:post login-handler}]
["/logout" {:post handlers/logout-handler}]
- ["/register" {:post handlers/register-handler}]
["/debug"
["/emails" {:get debug/emails-list}]
["/emails/:id" {:get debug/email}]]
diff --git a/backend/src/uxbox/http/handlers.clj b/backend/src/uxbox/http/handlers.clj
index 25ee3cad4e..734bf73fd1 100644
--- a/backend/src/uxbox/http/handlers.clj
+++ b/backend/src/uxbox/http/handlers.clj
@@ -16,28 +16,49 @@
[vertx.web :as vw]
[vertx.eventbus :as ve]))
+(def mutation-types-hierarchy
+ (-> (make-hierarchy)
+ (derive :login ::unauthenticated)
+ (derive :logout ::unauthenticated)
+ (derive :register-profile ::unauthenticated)
+ (derive :request-profile-recovery ::unauthenticated)
+ (derive :recover-profile ::unauthenticated)))
+
+(def query-types-hierarchy
+ (make-hierarchy))
+
(defn query-handler
[req]
- (let [type (get-in req [:path-params :type])
+ (let [type (keyword (get-in req [:path-params :type]))
data (merge (:params req)
- {::sq/type (keyword type)
+ {::sq/type type
:user (:user req)})]
- (-> (sq/handle (with-meta data {:req req}))
- (p/then' (fn [result]
- {:status 200
- :body result})))))
+ (if (or (:user req)
+ (isa? query-types-hierarchy type ::unauthenticated))
+ (-> (sq/handle (with-meta data {:req req}))
+ (p/then' (fn [result]
+ {:status 200
+ :body result})))
+ {:status 403
+ :body {:type :authentication
+ :code :unauthorized}})))
(defn mutation-handler
[req]
- (let [type (get-in req [:path-params :type])
+ (let [type (keyword (get-in req [:path-params :type]))
data (merge (:params req)
(:body-params req)
(:uploads req)
- {::sm/type (keyword type)
+ {::sm/type type
:user (:user req)})]
- (-> (sm/handle (with-meta data {:req req}))
- (p/then' (fn [result]
- {:status 200 :body result})))))
+ (if (or (:user req)
+ (isa? mutation-types-hierarchy type ::unauthenticated))
+ (-> (sm/handle (with-meta data {:req req}))
+ (p/then' (fn [result]
+ {:status 200 :body result})))
+ {:status 403
+ :body {:type :authentication
+ :code :unauthorized}})))
(defn login-handler
[req]
@@ -60,23 +81,20 @@
:cookies {"auth-token" nil}
:body ""})))))
-(defn register-handler
- [req]
- (let [data (merge (:body-params req)
- {::sm/type :register-profile})
- user-agent (get-in req [:headers "user-agent"])]
- (-> (sm/handle (with-meta data {:req req}))
- (p/then (fn [{:keys [id] :as user}]
- (session/create id user-agent)))
- (p/then' (fn [token]
- {:status 204
- :cookies {"auth-token" {:value token}}
- :body ""})))))
+;; (defn register-handler
+;; [req]
+;; (let [data (merge (:body-params req)
+;; {::sm/type :register-profile})
+;; user-agent (get-in req [:headers "user-agent"])]
+;; (-> (sm/handle (with-meta data {:req req}))
+;; (p/then (fn [{:keys [id] :as user}]
+;; (session/create id user-agent)))
+;; (p/then' (fn [token]
+;; {:status 204
+;; :body ""})))))
(defn echo-handler
[req]
- ;; (locking echo-handler
- ;; (prn "echo-handler" (Thread/currentThread)))
{:status 200
:body {:params (:params req)
:cookies (:cookies req)
diff --git a/backend/src/uxbox/http/session.clj b/backend/src/uxbox/http/session.clj
index 877138948a..5dfd76827d 100644
--- a/backend/src/uxbox/http/session.clj
+++ b/backend/src/uxbox/http/session.clj
@@ -17,9 +17,10 @@
(defn retrieve
"Retrieves a user id associated with the provided auth token."
[token]
- (let [sql "select user_id from sessions where id = $1"]
- (-> (db/query-one db/pool [sql token])
- (p/then' (fn [row] (when row (:user-id row)))))))
+ (when token
+ (let [sql "select user_id from sessions where id = $1"]
+ (-> (db/query-one db/pool [sql token])
+ (p/then' (fn [row] (when row (:user-id row))))))))
(defn create
[user-id user-agent]
@@ -52,11 +53,5 @@
(p/then' (fn [user-id]
(if user-id
(update data :request assoc :user user-id)
- (spx/terminate (assoc data ::unauthorized true)))))
- (vc/handle-on-context))))
- :leave (fn [data]
- (if (::unauthorized data)
- (update data :response
- assoc :status 403 :body {:type :authentication
- :code :unauthorized})
- data))})
+ data)))
+ (vc/handle-on-context))))})
diff --git a/backend/src/uxbox/jobs/sendmail.clj b/backend/src/uxbox/jobs/sendmail.clj
index 6435133b55..9c8cd570fa 100644
--- a/backend/src/uxbox/jobs/sendmail.clj
+++ b/backend/src/uxbox/jobs/sendmail.clj
@@ -75,7 +75,7 @@
:pass (:smtp-password config)
:ssl (:smtp-ssl config)
:tls (:smtp-tls config)
- :noop (not (:smtp-enabled config))})
+ :enabled (:smtp-enabled config)})
(defn- send-email-to-console
[email]
@@ -98,9 +98,9 @@
[email]
(p/future
(let [config (get-smtp-config cfg/config)
- result (if (:noop config)
- (send-email-to-console email)
- (postal/send-message config email))]
+ result (if (:enabled config)
+ (postal/send-message config email)
+ (send-email-to-console email))]
(when (not= (:error result) :SUCCESS)
(ex/raise :type :sendmail-error
:code :email-not-sent
diff --git a/backend/src/uxbox/main.clj b/backend/src/uxbox/main.clj
index 18494e4a5a..3a9ec4ce49 100644
--- a/backend/src/uxbox/main.clj
+++ b/backend/src/uxbox/main.clj
@@ -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
+;; 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
(ns uxbox.main
(:require [mount.core :as mount]
diff --git a/backend/src/uxbox/services/init.clj b/backend/src/uxbox/services/init.clj
index bda9a1af85..6bc9e89631 100644
--- a/backend/src/uxbox/services/init.clj
+++ b/backend/src/uxbox/services/init.clj
@@ -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
+;; This Source Code Form is "Incompatible With Secondary Licenses", as
+;; defined by the Mozilla Public License, v. 2.0.
+;;
+;; Copyright (c) 2019-2020 Andrey Antukh
(ns uxbox.services.init
"A initialization of services."
@@ -26,8 +29,7 @@
(require 'uxbox.services.mutations.projects)
(require 'uxbox.services.mutations.project-files)
(require 'uxbox.services.mutations.project-pages)
- (require 'uxbox.services.mutations.auth)
- (require 'uxbox.services.mutations.users)
+ (require 'uxbox.services.mutations.profile)
(require 'uxbox.services.mutations.user-attrs))
(defstate query-services
diff --git a/backend/src/uxbox/services/mutations.clj b/backend/src/uxbox/services/mutations.clj
index 3aba95007e..050492d1ad 100644
--- a/backend/src/uxbox/services/mutations.clj
+++ b/backend/src/uxbox/services/mutations.clj
@@ -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
+;; This Source Code Form is "Incompatible With Secondary Licenses", as
+;; defined by the Mozilla Public License, v. 2.0.
+;;
+;; Copyright (c) 2019-2020 Andrey Antukh
(ns uxbox.services.mutations
(:require
diff --git a/backend/src/uxbox/services/mutations/auth.clj b/backend/src/uxbox/services/mutations/auth.clj
deleted file mode 100644
index d88af4c73d..0000000000
--- a/backend/src/uxbox/services/mutations/auth.clj
+++ /dev/null
@@ -1,48 +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/.
-;;
-;; Copyright (c) 2019 Andrey Antukh
-
-(ns uxbox.services.mutations.auth
- (:require
- [clojure.spec.alpha :as s]
- [sodi.pwhash :as pwhash]
- [promesa.core :as p]
- [uxbox.config :as cfg]
- [uxbox.common.exceptions :as ex]
- [uxbox.common.spec :as us]
- [uxbox.db :as db]
- [uxbox.services.mutations :as sm]))
-
-(def ^:private user-by-username-sql
- "select id, password
- from users
- where username=$1 or email=$1
- and deleted_at is null")
-
-(s/def ::username ::us/string)
-(s/def ::password ::us/string)
-(s/def ::scope ::us/string)
-
-(s/def ::login
- (s/keys :req-un [::username ::password]
- :opt-un [::scope]))
-
-(sm/defmutation ::login
- [{:keys [username password scope] :as params}]
- (letfn [(check-password [user password]
- (let [result (pwhash/verify password (:password user))]
- (:valid result)))
-
- (check-user [user]
- (when-not user
- (ex/raise :type :validation
- :code ::wrong-credentials))
- (when-not (check-password user password)
- (ex/raise :type :validation
- :code ::wrong-credentials))
-
- {:id (:id user)})]
- (-> (db/query-one db/pool [user-by-username-sql username])
- (p/then' check-user))))
diff --git a/backend/src/uxbox/services/mutations/users.clj b/backend/src/uxbox/services/mutations/profile.clj
similarity index 57%
rename from backend/src/uxbox/services/mutations/users.clj
rename to backend/src/uxbox/services/mutations/profile.clj
index 3156e29a6a..7aa2b7ddf5 100644
--- a/backend/src/uxbox/services/mutations/users.clj
+++ b/backend/src/uxbox/services/mutations/profile.clj
@@ -2,19 +2,24 @@
;; 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
+;; 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
-(ns uxbox.services.mutations.users
+(ns uxbox.services.mutations.profile
(:require
- [sodi.pwhash :as pwhash]
[clojure.spec.alpha :as s]
[datoteka.core :as fs]
[datoteka.storages :as ds]
[promesa.core :as p]
[promesa.exec :as px]
- [uxbox.config :as cfg]
+ [sodi.prng]
+ [sodi.pwhash]
+ [sodi.util]
[uxbox.common.exceptions :as ex]
[uxbox.common.spec :as us]
+ [uxbox.config :as cfg]
[uxbox.db :as db]
[uxbox.emails :as emails]
[uxbox.images :as images]
@@ -40,6 +45,46 @@
(s/def ::user ::us/uuid)
(s/def ::username ::us/string)
+;; --- Utilities
+
+(su/defstr sql:user-by-username-or-email
+ "select u.*
+ from users as u
+ where u.username=$1 or u.email=$1
+ and u.deleted_at is null")
+
+(defn- retrieve-user
+ [conn username]
+ (db/query-one conn [sql:user-by-username-or-email username]))
+
+;; --- Mutation: Login
+
+(s/def ::username ::us/string)
+(s/def ::password ::us/string)
+(s/def ::scope ::us/string)
+
+(s/def ::login
+ (s/keys :req-un [::username ::password]
+ :opt-un [::scope]))
+
+(sm/defmutation ::login
+ [{:keys [username password scope] :as params}]
+ (letfn [(check-password [user password]
+ (let [result (sodi.pwhash/verify password (:password user))]
+ (:valid result)))
+
+ (check-user [user]
+ (when-not user
+ (ex/raise :type :validation
+ :code ::wrong-credentials))
+ (when-not (check-password user password)
+ (ex/raise :type :validation
+ :code ::wrong-credentials))
+
+ {:id (:id user)})]
+ (-> (retrieve-user db/pool username)
+ (p/then' check-user))))
+
;; --- Mutation: Update Profile (own)
(defn- check-username-and-email!
@@ -55,7 +100,7 @@
and id != $1
) as val"]
(p/let [res1 (db/query-one conn [sql1 id username])
- res2 (db/query-one conn [sql2 id email])]
+ res2 (db/query-one conn [sql2 id email])]
(when (:val res1)
(ex/raise :type :validation
:code ::username-already-exists))
@@ -64,17 +109,21 @@
:code ::email-already-exists))
params)))
+(su/defstr sql:update-profile
+ "update users
+ set username = $2,
+ email = $3,
+ fullname = $4,
+ metadata = $5
+ where id = $1
+ and deleted_at is null
+ returning *")
+
(defn- update-profile
[conn {:keys [id username email fullname metadata] :as params}]
- (let [sql "update users
- set username = $2,
- email = $3,
- fullname = $4,
- metadata = $5
- where id = $1
- and deleted_at is null
- returning *"]
- (-> (db/query-one conn [sql id username email fullname (blob/encode metadata)])
+ (let [sqlv [sql:update-profile id username
+ email fullname (blob/encode metadata)]]
+ (-> (db/query-one conn sqlv)
(p/then' su/raise-not-found-if-nil)
(p/then' decode-profile-row)
(p/then' strip-private-attrs))))
@@ -94,7 +143,7 @@
(defn- validate-password
[conn {:keys [user old-password] :as params}]
(p/let [profile (get-profile conn user)
- result (pwhash/verify old-password (:password profile))]
+ result (sodi.pwhash/verify old-password (:password profile))]
(when-not (:valid result)
(ex/raise :type :validation
:code ::old-password-not-match))
@@ -125,7 +174,6 @@
;; --- Mutation: Update Photo
-
(s/def :uxbox$upload/name ::us/string)
(s/def :uxbox$upload/size ::us/integer)
(s/def :uxbox$upload/mtype ::us/string)
@@ -194,7 +242,7 @@
[conn {:keys [id username fullname email password metadata] :as params}]
(let [id (or id (uuid/next))
metadata (blob/encode metadata)
- password (pwhash/derive password)
+ password (sodi.pwhash/derive password)
sqlv [create-user-sql
id
fullname
@@ -209,19 +257,15 @@
[conn params]
(-> (create-profile conn params)
(p/then' strip-private-attrs)
- #_(p/then (fn [profile]
- (-> (emails/send! {::emails/id :users/register
- ::emails/to (:email params)
- ::emails/priority :high
- :name (:fullname params)})
- (p/then' (constantly profile)))))))
+ (p/then (fn [profile]
+ (-> (emails/send! emails/register {:to (:email params)
+ :name (:fullname params)})
+ (p/then' (constantly profile)))))))
(s/def ::register-profile
(s/keys :req-un [::username ::email ::password ::fullname]))
-(sm/defmutation :register-profile
- {:doc "Register new user."
- :spec ::register-profile}
+(sm/defmutation ::register-profile
[params]
(when-not (:registration-enabled cfg/config)
(ex/raise :type :restriction
@@ -231,115 +275,56 @@
(p/then (partial check-profile-existence! conn))
(p/then (partial register-profile conn)))))
-;; --- Password Recover
+;; --- Mutation: Request Profile Recovery
-;; (defn- recovery-token-exists?
-;; "Checks if the token exists in the system. Just
-;; return `true` or `false`."
-;; [conn token]
-;; (let [sqlv (sql/recovery-token-exists? {:token token})
-;; result (db/fetch-one conn sqlv)]
-;; (:token_exists result)))
+(s/def ::request-profile-recovery
+ (s/keys :req-un [::username]))
-;; (defn- retrieve-user-for-recovery-token
-;; "Retrieve a user id (uuid) for the given token. If
-;; no user is found, an exception is raised."
-;; [conn token]
-;; (let [sqlv (sql/get-recovery-token {:token token})
-;; data (db/fetch-one conn sqlv)]
-;; (or (:user data)
-;; (ex/raise :type :validation
-;; :code ::invalid-token))))
+(su/defstr sql:insert-recovery-token
+ "insert into tokens (user_id, token) values ($1, $2)")
-;; (defn- mark-token-as-used
-;; [conn token]
-;; (let [sqlv (sql/mark-recovery-token-used {:token token})]
-;; (pos? (db/execute conn sqlv))))
+(sm/defmutation ::request-profile-recovery
+ [{:keys [username] :as params}]
+ (letfn [(create-recovery-token [conn {:keys [id] :as user}]
+ (let [token (-> (sodi.prng/random-bytes 32)
+ (sodi.util/bytes->b64s))
+ sql sql:insert-recovery-token]
+ (-> (db/query-one conn [sql id token])
+ (p/then (constantly (assoc user :token token))))))
+ (send-email-notification [conn user]
+ (emails/send! emails/password-recovery
+ {:to (:email user)
+ :token (:token user)
+ :name (:fullname user)}))]
+ (db/with-atomic [conn db/pool]
+ (-> (retrieve-user conn username)
+ (p/then' su/raise-not-found-if-nil)
+ (p/then #(create-recovery-token conn %))
+ (p/then #(send-email-notification conn %))
+ (p/then (constantly nil))))))
-;; (defn- recover-password
-;; "Given a token and password, resets the password
-;; to corresponding user or raise an exception."
-;; [conn {:keys [token password]}]
-;; (let [user (retrieve-user-for-recovery-token conn token)]
-;; (update-password conn {:user user :password password})
-;; (mark-token-as-used conn token)
-;; nil))
+;; --- Mutation: Recover Profile
-;; (defn- create-recovery-token
-;; "Creates a new recovery token for specified user and return it."
-;; [conn userid]
-;; (let [token (token/random)
-;; sqlv (sql/create-recovery-token {:user userid
-;; :token token})]
-;; (db/execute conn sqlv)
-;; token))
+(s/def ::token ::us/not-empty-string)
+(s/def ::recover-profile
+ (s/keys :req-un [::token ::password]))
-;; (defn- retrieve-user-for-password-recovery
-;; [conn username]
-;; (let [user (find-user-by-username-or-email conn username)]
-;; (when-not user
-;; (ex/raise :type :validation :code ::user-does-not-exists))
-;; user))
-
-;; (defn- request-password-recovery
-;; "Creates a new recovery password token and sends it via email
-;; to the correspondig to the given username or email address."
-;; [conn username]
-;; (let [user (retrieve-user-for-password-recovery conn username)
-;; token (create-recovery-token conn (:id user))]
-;; (emails/send! {:email/name :users/password-recovery
-;; :email/to (:email user)
-;; :name (:fullname user)
-;; :token token})
-;; token))
-
-;; (defmethod core/query :validate-profile-password-recovery-token
-;; [{:keys [token]}]
-;; (us/assert ::us/token token)
-;; (with-open [conn (db/connection)]
-;; (recovery-token-exists? conn token)))
-
-;; (defmethod core/novelty :request-profile-password-recovery
-;; [{:keys [username]}]
-;; (us/assert ::us/username username)
-;; (with-open [conn (db/connection)]
-;; (db/atomic conn
-;; (request-password-recovery conn username))))
-
-;; (s/def ::recover-password
-;; (s/keys :req-un [::us/token ::us/password]))
-
-;; (defmethod core/novelty :recover-profile-password
-;; [params]
-;; (us/assert ::recover-password params)
-;; (with-open [conn (db/connection)]
-;; (db/apply-atomic conn recover-password params)))
-
-;; --- Query Helpers
-
-;; (defn find-full-user-by-id
-;; "Find user by its id. This function is for internal
-;; use only because it returns a lot of sensitive information.
-;; If no user is found, `nil` is returned."
-;; [conn id]
-;; (let [sqlv (sql/get-profile {:id id})]
-;; (some-> (db/fetch-one conn sqlv)
-;; (data/normalize-attrs))))
-
-;; (defn find-user-by-id
-;; "Find user by its id. If no user is found, `nil` is returned."
-;; [conn id]
-;; (let [sqlv (sql/get-profile {:id id})]
-;; (some-> (db/fetch-one conn sqlv)
-;; (data/normalize-attrs)
-;; (trim-user-attrs)
-;; (dissoc :password))))
-
-;; (defn find-user-by-username-or-email
-;; "Finds a user in the database by username and email. If no
-;; user is found, `nil` is returned."
-;; [conn username]
-;; (let [sqlv (sql/get-profile-by-username {:username username})]
-;; (some-> (db/fetch-one conn sqlv)
-;; (trim-user-attrs))))
+(su/defstr sql:remove-recovery-token
+ "delete from tokenes where user_id=$1 and token=$2")
+(sm/defmutation ::recover-profile
+ [{:keys [token password]}]
+ (letfn [(validate-token [conn token]
+ (let [sql "delete from tokens where token=$1 returning *"
+ sql "select * from tokens where token=$1"]
+ (-> (db/query-one conn [sql token])
+ (p/then' :user-id)
+ (p/then' su/raise-not-found-if-nil))))
+ (update-password [conn user-id]
+ (let [sql "update users set password=$2 where id=$1"
+ pwd (sodi.pwhash/derive password)]
+ (-> (db/query-one conn [sql user-id pwd])
+ (p/then' (constantly nil)))))]
+ (db/with-atomic [conn db/pool]
+ (-> (validate-token conn token)
+ (p/then (fn [user-id] (update-password conn user-id)))))))
diff --git a/backend/src/uxbox/services/mutations/project_files.clj b/backend/src/uxbox/services/mutations/project_files.clj
index fb8283a87b..96bfe9c597 100644
--- a/backend/src/uxbox/services/mutations/project_files.clj
+++ b/backend/src/uxbox/services/mutations/project_files.clj
@@ -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
+;; This Source Code Form is "Incompatible With Secondary Licenses", as
+;; defined by the Mozilla Public License, v. 2.0.
+;;
+;; Copyright (c) 2019-2020 Andrey Antukh
(ns uxbox.services.mutations.project-files
(:require
diff --git a/backend/tests/uxbox/tests/helpers.clj b/backend/tests/uxbox/tests/helpers.clj
index 195b33f81d..1d5ba0e542 100644
--- a/backend/tests/uxbox/tests/helpers.clj
+++ b/backend/tests/uxbox/tests/helpers.clj
@@ -5,7 +5,7 @@
[cuerdas.core :as str]
[mount.core :as mount]
[datoteka.storages :as st]
- [uxbox.services.mutations.users :as users]
+ [uxbox.services.mutations.profile :as profile]
[uxbox.services.mutations.projects :as projects]
[uxbox.services.mutations.project-files :as files]
[uxbox.services.mutations.project-pages :as pages]
@@ -61,11 +61,11 @@
[prefix & args]
(uuid/namespaced uuid/oid (apply str prefix args)))
-;; --- Users creation
+;; --- Profile creation
(defn create-user
[conn i]
- (users/create-profile conn {:id (mk-uuid "user" i)
+ (profile/create-profile conn {:id (mk-uuid "user" i)
:fullname (str "User " i)
:username (str "user" i)
:email (str "user" i ".test@uxbox.io")
diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json
index e9473b4714..0335ecf192 100644
--- a/frontend/resources/locales.json
+++ b/frontend/resources/locales.json
@@ -13,14 +13,58 @@
"fr" : "Mot de passe oublié ?"
}
},
- "auth.message.password-recovered" : {
+
+ "profile.recovery.password-changed" : {
"used-in" : [ "src/uxbox/main/data/auth.cljs:178" ],
"translations" : {
- "en" : "Password successfully recovered.",
- "fr" : "Mot de passe récupéré avec succès."
+ "en" : "Password successfully changed",
+ "fr" : "TODO"
}
},
- "auth.message.recovery-token-sent" : {
+
+ "profile.recovery.username-or-email": {
+ "translations" : {
+ "en" : "Username or Email Address",
+ "fr" : "adresse email ou nom d'utilisateur"
+ }
+ },
+
+ "profile.recovery.token": {
+ "translations" : {
+ "en" : "Recovery token (sent by email)",
+ "fr" : null
+ }
+ },
+
+ "profile.recovery.password": {
+ "translations" : {
+ "en" : "Type a new password",
+ "fr" : null
+ }
+ },
+
+ "profile.recovery.submit-request": {
+ "translations" : {
+ "en" : "Recover Password",
+ "fr" : null
+ }
+ },
+
+ "profile.recovery.submit-recover": {
+ "translations" : {
+ "en" : "Change your password",
+ "fr" : null
+ }
+ },
+
+ "profile.recovery.go-to-login": {
+ "translations" : {
+ "en" : "Go back!",
+ "fr" : "Retour!"
+ }
+ },
+
+ "profile.recovery.recovery-token-sent" : {
"used-in" : [ "src/uxbox/main/data/auth.cljs:141" ],
"translations" : {
"en" : "Password recovery link sent to your inbox.",
@@ -398,7 +442,7 @@
"fr" : "Une erreur inattendue c'est produite"
}
},
- "errors.auth.invalid-recovery-token" : {
+ "profile.recovery.invalid-token" : {
"used-in" : [ "src/uxbox/main/data/auth.cljs:174", "src/uxbox/main/data/auth.cljs:151" ],
"translations" : {
"en" : "The recovery token is invalid.",
@@ -468,28 +512,28 @@
"fr" : "Envoyer un fichier"
}
},
- "register.already-have-account" : {
+ "profile.register.already-have-account" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:131" ],
"translations" : {
"en" : "Already have an account?",
"fr" : "Vous avez déjà un compte ?"
}
},
- "register.fullname.placeholder" : {
+ "profile.register.fullname" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:72" ],
"translations" : {
"en" : "Full Name",
"fr" : "Nom complet"
}
},
- "register.get-started" : {
+ "profile.register.get-started" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:127" ],
"translations" : {
"en" : "Get started",
"fr" : "Commencer"
}
},
- "register.password.placeholder" : {
+ "profile.register.password" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:115" ],
"translations" : {
"en" : "Password",
@@ -615,7 +659,7 @@
"fr" : "Votre avatar"
}
},
- "settings.profile.your-email" : {
+ "profile.register.email" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:111", "src/uxbox/main/ui/auth/register.cljs:101" ],
"translations" : {
"en" : "Your email",
@@ -629,7 +673,7 @@
"fr" : "Votre nom complet"
}
},
- "settings.profile.your-username" : {
+ "profile.register.username" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:98", "src/uxbox/main/ui/auth/register.cljs:87" ],
"translations" : {
"en" : "Your username",
diff --git a/frontend/src/uxbox/main.cljs b/frontend/src/uxbox/main.cljs
index f792c0194c..a0e8e1582a 100644
--- a/frontend/src/uxbox/main.cljs
+++ b/frontend/src/uxbox/main.cljs
@@ -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) 2015-2019 Andrey Antukh
(ns ^:figwheel-hooks uxbox.main
diff --git a/frontend/src/uxbox/main/data/auth.cljs b/frontend/src/uxbox/main/data/auth.cljs
index bdac80ea12..d72369579d 100644
--- a/frontend/src/uxbox/main/data/auth.cljs
+++ b/frontend/src/uxbox/main/data/auth.cljs
@@ -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) 2015-2016 Andrey Antukh
+;; 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
(ns uxbox.main.data.auth
(:require
@@ -10,7 +13,7 @@
[beicon.core :as rx]
[potok.core :as ptk]
[uxbox.common.spec :as us]
- [uxbox.main.repo :as rp]
+ [uxbox.main.repo.core :as rp]
[uxbox.main.store :refer [initial-state]]
[uxbox.main.data.users :as du]
[uxbox.util.messages :as um]
@@ -57,8 +60,7 @@
:password password
:scope "webapp"}
on-error #(rx/of (um/error (tr "errors.auth.unauthorized")))]
- (->> (rp/req :auth/login params)
- (rx/map :payload)
+ (->> (rp/mutation :login params)
(rx/map logged-in)
(rx/catch rp/client-error? on-error))))))
@@ -72,7 +74,7 @@
ptk/WatchEvent
(watch [_ state s]
- (->> (rp/req :auth/logout)
+ (->> (rp/mutation :logout)
(rx/ignore)))
ptk/EffectEvent
@@ -84,12 +86,12 @@
(ptk/reify ::logout
ptk/WatchEvent
(watch [_ state s]
- (rx/of (rt/nav :auth/login)
+ (rx/of (rt/nav :login)
clear-user-data))))
;; --- Register
-(s/def ::register-params
+(s/def ::register
(s/keys :req-un [::fullname
::username
::password
@@ -98,84 +100,53 @@
(defn register
"Create a register event instance."
[data on-error]
- (us/assert ::register-params data)
- (us/assert fn? on-error)
+ (s/assert ::register data)
+ (s/assert fn? on-error)
(ptk/reify ::register
ptk/WatchEvent
(watch [_ state stream]
(letfn [(handle-error [{payload :payload}]
(on-error payload)
(rx/empty))]
- (rx/merge
- (->> (rp/req :auth/register data)
- (rx/map :payload)
- (rx/map (constantly ::registered))
- (rx/catch rp/client-error? handle-error))
- (->> stream
- (rx/filter #(= % ::registered))
- (rx/take 1)
- (rx/map #(login data))))))))
+ (->> (rp/mutation :register-profile data)
+ (rx/map (fn [_] (login data)))
+ (rx/catch rp/client-error? handle-error))))))
;; --- Recovery Request
-(s/def ::recovery-request-params
+(s/def ::recovery-request
(s/keys :req-un [::username]))
-(defn recovery-request
- [data]
- (us/assert ::recovery-request-params data)
- (ptk/reify ::recover-request
+(defn request-profile-recovery
+ [data on-success]
+ (us/assert ::recovery-request data)
+ (us/assert fn? on-success)
+ (ptk/reify ::request-profile-recovery
ptk/WatchEvent
(watch [_ state stream]
(letfn [(on-error [{payload :payload}]
- (println "on-error" payload)
(rx/empty))]
- (rx/merge
- (->> (rp/req :auth/recovery-request data)
- (rx/map (constantly ::recovery-requested))
- (rx/catch rp/client-error? on-error))
- (->> stream
- (rx/filter #(= % ::recovery-requested))
- (rx/take 1)
- ;; TODO: this should be moved to the UI part
- (rx/map #(um/info (tr "auth.message.recovery-token-sent")))))))))
-
-;; --- Check Recovery Token
-
-(defrecord ValidateRecoveryToken [token]
- ptk/WatchEvent
- (watch [_ state stream]
- (letfn [(on-error [{payload :payload}]
- (rx/of
- (rt/navigate :auth/login)
- (um/error (tr "errors.auth.invalid-recovery-token"))))]
- (->> (rp/req :auth/validate-recovery-token token)
- (rx/ignore)
- (rx/catch rp/client-error? on-error)))))
-
-(defn validate-recovery-token
- [token]
- {:pre [(string? token)]}
- (ValidateRecoveryToken. token))
+ (->> (rp/mutation :request-profile-recovery data)
+ (rx/tap on-success)
+ (rx/catch rp/client-error? on-error))))))
;; --- Recovery (Password)
(s/def ::token string?)
-(s/def ::recovery-params
- (s/keys :req-un [::username ::token]))
+(s/def ::on-error fn?)
+(s/def ::on-success fn?)
-(defn recovery
- [{:keys [token password] :as data}]
- (us/assert ::recovery-params data)
- (ptk/reify ::recovery
+(s/def ::recover-profile
+ (s/keys :req-un [::password ::token ::on-error ::on-success]))
+
+(defn recover-profile
+ [{:keys [token password on-error on-success] :as data}]
+ (us/assert ::recover-profile data)
+ (ptk/reify ::recover-profile
ptk/WatchEvent
(watch [_ state stream]
- (letfn [(on-error [{payload :payload}]
- (rx/of (um/error (tr "errors.auth.invalid-recovery-token"))))
- (on-success [{payload :payload}]
- (rx/of
- (rt/navigate :auth/login)
- (um/info (tr "auth.message.password-recovered"))))]
- (->> (rp/req :auth/recovery {:token token :password password})
- (rx/mapcat on-success)
- (rx/catch rp/client-error? on-error))))))
+ (->> (rp/mutation :recover-profile {:token token :password password})
+ (rx/tap on-success)
+ (rx/catch (fn [err]
+ (on-error)
+ (rx/empty)))))))
diff --git a/frontend/src/uxbox/main/repo/core.cljs b/frontend/src/uxbox/main/repo/core.cljs
index bc5086d540..0f4ad716ba 100644
--- a/frontend/src/uxbox/main/repo/core.cljs
+++ b/frontend/src/uxbox/main/repo/core.cljs
@@ -133,3 +133,24 @@
(.append form (name key) val))
(seq params))
(send-mutation! id form)))
+
+(defmethod mutation :login
+ [id params]
+ (let [url (str url "/login")]
+ (->> (impl-send {:method :post :url url :body params})
+ (rx/map conditional-decode)
+ (rx/mapcat handle-response))))
+
+(defmethod mutation :logout
+ [id params]
+ (let [url (str url "/logout")]
+ (->> (impl-send {:method :post :url url :body params :auth false})
+ (rx/map conditional-decode)
+ (rx/mapcat handle-response))))
+
+;; (defmethod mutation :register-profile
+;; [id params]
+;; (let [url (str url "/register")]
+;; (->> (impl-send {:method :post :url url :body params :auth false})
+;; (rx/map conditional-decode)
+;; (rx/mapcat handle-response))))
diff --git a/frontend/src/uxbox/main/ui.cljs b/frontend/src/uxbox/main/ui.cljs
index 85a03fa991..01621c908f 100644
--- a/frontend/src/uxbox/main/ui.cljs
+++ b/frontend/src/uxbox/main/ui.cljs
@@ -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) 2015-2017 Juan de la Cruz
;; Copyright (c) 2015-2019 Andrey Antukh
@@ -16,7 +19,11 @@
[uxbox.main.data.auth :refer [logout]]
[uxbox.main.data.projects :as dp]
[uxbox.main.store :as st]
- [uxbox.main.ui.auth :as auth]
+ [uxbox.main.ui.login :refer [login-page]]
+ [uxbox.main.ui.profile.register :refer [profile-register-page]]
+ [uxbox.main.ui.profile.recovery-request :refer [profile-recovery-request-page]]
+ [uxbox.main.ui.profile.recovery :refer [profile-recovery-page]]
+
[uxbox.main.ui.dashboard :as dashboard]
[uxbox.main.ui.settings :as settings]
[uxbox.main.ui.shapes]
@@ -29,14 +36,18 @@
[uxbox.util.router :as rt]
[uxbox.util.timers :as ts]))
+(def route-iref
+ (-> (l/key :route)
+ (l/derive st/state)))
+
;; --- Routes
(def routes
- [["/auth"
- ["/login" :auth/login]
- ["/register" :auth/register]
- ["/recovery/request" :auth/recovery-request]
- ["/recovery/token/:token" :auth/recovery]]
+ [["/login" :login]
+ ["/profile"
+ ["/register" :profile-register]
+ ["/recovery/request" :profile-recovery-request]
+ ["/recovery" :profile-recovery]]
["/settings"
["/profile" :settings/profile]
@@ -51,58 +62,15 @@
["/workspace/:file-id" :workspace]])
-;; --- Error Handling
-
-(defn- on-error
- "A default error handler."
- [{:keys [type code] :as error}]
- (reset! st/loader false)
- (cond
- (and (map? error)
- (= :validation type)
- (= :spec-validation code))
- (do
- (println "============ SERVER RESPONSE ERROR ================")
- (println (:explain error))
- (println "============ END SERVER RESPONSE ERROR ================"))
-
- ;; Unauthorized or Auth timeout
- (and (map? error)
- (= :authentication type)
- (= :unauthorized code))
- (ts/schedule 0 #(st/emit! (rt/nav :auth/login)))
-
- ;; Network error
- (and (map? error)
- (= :unexpected type)
- (= :abort code))
- (ts/schedule 100 #(st/emit! (uum/error (tr "errors.network"))))
-
- ;; Something else
- :else
- (do
- (js/console.error error)
- (ts/schedule 100 #(st/emit! (uum/error (tr "errors.generic")))))))
-
-(set! st/*on-error* on-error)
-
-;; --- Main App (Component)
-
-(def route-iref
- (-> (l/key :route)
- (l/derive st/state)))
-
(mf/defc app
[props]
(let [route (mf/deref route-iref)]
(case (get-in route [:data :name])
- :auth/login (mf/element auth/login-page)
- :auth/register (mf/element auth/register-page)
+ :login (mf/element login-page)
- ;; :auth/recovery-request (auth/recovery-request-page)
- ;; :auth/recovery
- ;; (let [token (get-in route [:params :path :token])]
- ;; (auth/recovery-page token))
+ :profile-register (mf/element profile-register-page)
+ :profile-recovery-request (mf/element profile-recovery-request-page)
+ :profile-recovery (mf/element profile-recovery-page)
(:settings/profile
:settings/password
@@ -125,3 +93,37 @@
:key file-id}])
nil)))
+;; --- Error Handling
+
+(defn- on-error
+ "A default error handler."
+ [{:keys [type code] :as error}]
+ (reset! st/loader false)
+ (cond
+ (and (map? error)
+ (= :validation type)
+ (= :spec-validation code))
+ (do
+ (println "============ SERVER RESPONSE ERROR ================")
+ (println (:explain error))
+ (println "============ END SERVER RESPONSE ERROR ================"))
+
+ ;; Unauthorized or Auth timeout
+ (and (map? error)
+ (= :authentication type)
+ (= :unauthorized code))
+ (ts/schedule 0 #(st/emit! (rt/nav :login)))
+
+ ;; Network error
+ (and (map? error)
+ (= :unexpected type)
+ (= :abort code))
+ (ts/schedule 100 #(st/emit! (uum/error (tr "errors.network"))))
+
+ ;; Something else
+ :else
+ (do
+ (js/console.error error)
+ (ts/schedule 100 #(st/emit! (uum/error (tr "errors.generic")))))))
+
+(set! st/*on-error* on-error)
diff --git a/frontend/src/uxbox/main/ui/auth.cljs b/frontend/src/uxbox/main/ui/auth.cljs
deleted file mode 100644
index 72359a9ca7..0000000000
--- a/frontend/src/uxbox/main/ui/auth.cljs
+++ /dev/null
@@ -1,16 +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/.
-;;
-;; Copyright (c) 2016 Andrey Antukh
-
-(ns uxbox.main.ui.auth
- (:require [uxbox.main.ui.auth.login :as login]
- [uxbox.main.ui.auth.register :as register]
- #_[uxbox.main.ui.auth.recovery-request :as recovery-request]
- #_[uxbox.main.ui.auth.recovery :as recovery]))
-
-(def login-page login/login-page)
-(def register-page register/register-page)
-;; (def recovery-page recovery/recovery-page)
-;; (def recovery-request-page recovery-request/recovery-request-page)
diff --git a/frontend/src/uxbox/main/ui/auth/recovery.cljs b/frontend/src/uxbox/main/ui/auth/recovery.cljs
deleted file mode 100644
index daa9b7a099..0000000000
--- a/frontend/src/uxbox/main/ui/auth/recovery.cljs
+++ /dev/null
@@ -1,82 +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/.
-;;
-;; Copyright (c) 2015-2017 Juan de la Cruz
-;; Copyright (c) 2015-2019 Andrey Antukh
-
-(ns uxbox.main.ui.auth.recovery
- (:require
- [cljs.spec.alpha :as s]
- [cuerdas.core :as str]
- [lentes.core :as l]
- [rumext.core :as mx :include-macros true]
- [uxbox.builtins.icons :as i]
- [uxbox.main.data.auth :as uda]
- [uxbox.main.store :as st]
- [uxbox.main.ui.messages :refer [messages-widget]]
- [uxbox.main.ui.navigation :as nav]
- [uxbox.util.dom :as dom]
- [uxbox.util.forms :as fm]
- [uxbox.util.i18n :refer (tr)]
- [uxbox.util.router :as rt]))
-
-;; (def form-data (fm/focus-data :recovery st/state))
-;; (def form-errors (fm/focus-errors :recovery st/state))
-
-;; (def assoc-value (partial fm/assoc-value :recovery))
-;; (def assoc-errors (partial fm/assoc-errors :recovery))
-;; (def clear-form (partial fm/clear-form :recovery))
-
-;; ;; --- Recovery Form
-
-;; (s/def ::password ::fm/non-empty-string)
-;; (s/def ::recovery-form
-;; (s/keys :req-un [::password]))
-
-;; (mx/defc recovery-form
-;; {:mixins [mx/static mx/reactive]}
-;; [token]
-;; (let [data (merge (mx/react form-data) {:token token})
-;; valid? (fm/valid? ::recovery-form data)]
-;; (letfn [(on-change [field event]
-;; (let [value (dom/event->value event)]
-;; (st/emit! (assoc-value field value))))
-;; (on-submit [event]
-;; (dom/prevent-default event)
-;; (st/emit! (uda/recovery data)
-;; (clear-form)))]
-;; [:form {:on-submit on-submit}
-;; [:div.login-content
-;; [:input.input-text
-;; {:name "password"
-;; :value (:password data "")
-;; :on-change (partial on-change :password)
-;; :placeholder (tr "recover.password.placeholder")
-;; :type "password"}]
-;; [:input.btn-primary
-;; {:name "login"
-;; :class (when-not valid? "btn-disabled")
-;; :disabled (not valid?)
-;; :value (tr "recover.recover-password")
-;; :type "submit"}]
-;; [:div.login-links
-;; [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recover.go-back")]]]])))
-
-;; ;; --- Recovery Page
-
-;; (defn- recovery-page-init
-;; [own]
-;; (let [[token] (::mx/args own)]
-;; (st/emit! (uda/validate-recovery-token token))
-;; own))
-
-;; (mx/defc recovery-page
-;; {:mixins [mx/static (fm/clear-mixin st/store :recovery)]
-;; :init recovery-page-init}
-;; [token]
-;; [:div.login
-;; [:div.login-body
-;; (messages-widget)
-;; [:a i/logo]
-;; (recovery-form token)]])
diff --git a/frontend/src/uxbox/main/ui/auth/recovery_request.cljs b/frontend/src/uxbox/main/ui/auth/recovery_request.cljs
deleted file mode 100644
index 156c7df6b5..0000000000
--- a/frontend/src/uxbox/main/ui/auth/recovery_request.cljs
+++ /dev/null
@@ -1,71 +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/.
-;;
-;; Copyright (c) 2015-2017 Andrey Antukh
-;; Copyright (c) 2015-2017 Juan de la Cruz
-
-(ns uxbox.main.ui.auth.recovery-request
- (:require [cljs.spec.alpha :as s :include-macros true]
- [lentes.core :as l]
- [cuerdas.core :as str]
- [uxbox.builtins.icons :as i]
- [uxbox.main.store :as st]
- [uxbox.main.data.auth :as uda]
- [uxbox.main.ui.messages :refer [messages-widget]]
- [uxbox.main.ui.navigation :as nav]
- [uxbox.util.i18n :refer (tr)]
- [uxbox.util.dom :as dom]
- [uxbox.util.forms :as fm]
- [rumext.core :as mx :include-macros true]
- [uxbox.util.router :as rt]))
-
-;; (def form-data (fm/focus-data :recovery-request st/state))
-;; (def form-errors (fm/focus-errors :recovery-request st/state))
-
-;; (def assoc-value (partial fm/assoc-value :profile-password))
-;; (def assoc-errors (partial fm/assoc-errors :profile-password))
-;; (def clear-form (partial fm/clear-form :profile-password))
-
-;; (s/def ::username ::fm/non-empty-string)
-;; (s/def ::recovery-request-form (s/keys :req-un [::username]))
-
-;; (mx/defc recovery-request-form
-;; {:mixins [mx/static mx/reactive]}
-;; []
-;; (let [data (mx/react form-data)
-;; valid? (fm/valid? ::recovery-request-form data)]
-;; (letfn [(on-change [event]
-;; (let [value (dom/event->value event)]
-;; (st/emit! (assoc-value :username value))))
-;; (on-submit [event]
-;; (dom/prevent-default event)
-;; (st/emit! (uda/recovery-request data)
-;; (clear-form)))]
-;; [:form {:on-submit on-submit}
-;; [:div.login-content
-;; [:input.input-text
-;; {:name "username"
-;; :value (:username data "")
-;; :on-change on-change
-;; :placeholder (tr "recovery-request.username-or-email.placeholder")
-;; :type "text"}]
-;; [:input.btn-primary
-;; {:name "login"
-;; :class (when-not valid? "btn-disabled")
-;; :disabled (not valid?)
-;; :value (tr "recovery-request.recover-password")
-;; :type "submit"}]
-;; [:div.login-links
-;; [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recovery-request.go-back")]]]])))
-
-;; ;; --- Recovery Request Page
-
-;; (mx/defc recovery-request-page
-;; {:mixins [mx/static (fm/clear-mixin st/store :recovery-request)]}
-;; []
-;; [:div.login
-;; [:div.login-body
-;; (messages-widget)
-;; [:a i/logo]
-;; (recovery-request-form)]])
diff --git a/frontend/src/uxbox/main/ui/auth/login.cljs b/frontend/src/uxbox/main/ui/login.cljs
similarity index 86%
rename from frontend/src/uxbox/main/ui/auth/login.cljs
rename to frontend/src/uxbox/main/ui/login.cljs
index 31c654871f..970e89fdb8 100644
--- a/frontend/src/uxbox/main/ui/auth/login.cljs
+++ b/frontend/src/uxbox/main/ui/login.cljs
@@ -2,10 +2,13 @@
;; 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) 2015-2016 Andrey Antukh
-;; Copyright (c) 2015-2016 Juan de la Cruz
+;; This Source Code Form is "Incompatible With Secondary Licenses", as
+;; defined by the Mozilla Public License, v. 2.0.
+;;
+;; Copyright (c) 2015-2020 Andrey Antukh
+;; Copyright (c) 2015-2020 Juan de la Cruz
-(ns uxbox.main.ui.auth.login
+(ns uxbox.main.ui.login
(:require
[cljs.spec.alpha :as s]
[rumext.alpha :as mf]
@@ -78,10 +81,10 @@
:type "submit"}]
[:div.login-links
- [:a {:on-click #(st/emit! (rt/nav :auth/recovery-request))
+ [:a {:on-click #(st/emit! (rt/nav :profile-recovery-request))
:tab-index "5"}
(tr "auth.forgot-password")]
- [:a {:on-click #(st/emit! (rt/nav :auth/register))
+ [:a {:on-click #(st/emit! (rt/nav :profile-register))
:tab-index "6"}
(tr "auth.no-account")]]]]))
diff --git a/frontend/src/uxbox/main/ui/profile/recovery.cljs b/frontend/src/uxbox/main/ui/profile/recovery.cljs
new file mode 100644
index 0000000000..b073876614
--- /dev/null
+++ b/frontend/src/uxbox/main/ui/profile/recovery.cljs
@@ -0,0 +1,90 @@
+;; 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) 2015-2017 Andrey Antukh
+;; Copyright (c) 2015-2017 Juan de la Cruz
+
+(ns uxbox.main.ui.profile.recovery
+ (:require
+ [cljs.spec.alpha :as s]
+ [cuerdas.core :as str]
+ [lentes.core :as l]
+ [rumext.alpha :as mf]
+ [uxbox.builtins.icons :as i]
+ [uxbox.common.spec :as us]
+ [uxbox.main.data.auth :as uda]
+ [uxbox.main.store :as st]
+ [uxbox.main.ui.messages :refer [messages-widget]]
+ [uxbox.main.ui.navigation :as nav]
+ [uxbox.util.messages :as um]
+ [uxbox.util.dom :as dom]
+ [uxbox.util.forms :as fm]
+ [uxbox.util.i18n :as i18n]
+ [uxbox.util.router :as rt]))
+
+(s/def ::token ::us/not-empty-string)
+(s/def ::password ::us/not-empty-string)
+(s/def ::recovery-form (s/keys :req-un [::token ::password]))
+
+(mf/defc recovery-form
+ []
+ (let [{:keys [data] :as form} (fm/use-form ::recovery-form {})
+ tr (i18n/use-translations)
+ on-success
+ (fn []
+ (st/emit! (um/info (tr "profile.recovery.password-changed"))
+ (rt/nav :login)))
+
+ on-error
+ (fn []
+ (st/emit! (um/error (tr "profile.recovery.invalid-token"))))
+
+ on-submit
+ (fn [event]
+ (dom/prevent-default event)
+ (st/emit! (uda/recover-profile (assoc (:clean-data form)
+ :on-error on-error
+ :on-success on-success))))]
+ [:form {:on-submit on-submit}
+ [:div.login-content
+ [:input.input-text
+ {:name "token"
+ :value (:token data "")
+ :class (fm/error-class form :token)
+ :on-blur (fm/on-input-blur form :token)
+ :on-change (fm/on-input-change form :token)
+ :placeholder (tr "profile.recovery.token")
+ :auto-complete "off"
+ :type "text"}]
+ [:input.input-text
+ {:name "password"
+ :value (:password data "")
+ :class (fm/error-class form :password)
+ :on-blur (fm/on-input-blur form :password)
+ :on-change (fm/on-input-change form :password)
+ :placeholder (tr "profile.recovery.password")
+ :type "password"}]
+ [:input.btn-primary
+ {:name "recover"
+ :class (when-not (:valid form) "btn-disabled")
+ :disabled (not (:valid form))
+ :value (tr "profile.recovery.submit-recover")
+ :type "submit"}]
+
+ [:div.login-links
+ [:a {:on-click #(st/emit! (rt/nav :login))}
+ (tr "profile.recovery.go-to-login")]]]]))
+
+;; --- Recovery Request Page
+
+(mf/defc profile-recovery-page
+ []
+ [:div.login
+ [:div.login-body
+ [:& messages-widget]
+ [:a i/logo]
+ [:& recovery-form]]])
diff --git a/frontend/src/uxbox/main/ui/profile/recovery_request.cljs b/frontend/src/uxbox/main/ui/profile/recovery_request.cljs
new file mode 100644
index 0000000000..3154840386
--- /dev/null
+++ b/frontend/src/uxbox/main/ui/profile/recovery_request.cljs
@@ -0,0 +1,72 @@
+;; 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) 2015-2017 Andrey Antukh
+;; Copyright (c) 2015-2017 Juan de la Cruz
+
+(ns uxbox.main.ui.profile.recovery-request
+ (:require
+ [cljs.spec.alpha :as s]
+ [cuerdas.core :as str]
+ [lentes.core :as l]
+ [rumext.alpha :as mf]
+ [uxbox.builtins.icons :as i]
+ [uxbox.common.spec :as us]
+ [uxbox.main.data.auth :as uda]
+ [uxbox.main.store :as st]
+ [uxbox.main.ui.messages :refer [messages-widget]]
+ [uxbox.main.ui.navigation :as nav]
+ [uxbox.util.messages :as um]
+ [uxbox.util.dom :as dom]
+ [uxbox.util.forms :as fm]
+ [uxbox.util.i18n :as i18n]
+ [uxbox.util.router :as rt]))
+
+(s/def ::username ::us/not-empty-string)
+(s/def ::recovery-request-form (s/keys :req-un [::username]))
+
+(mf/defc recovery-form
+ []
+ (let [{:keys [data] :as form} (fm/use-form ::recovery-request-form {})
+ tr (i18n/use-translations)
+ on-success
+ (fn []
+ (st/emit! (um/info (tr "profile.recovery.recovery-token-sent"))))
+ on-submit
+ (fn [event]
+ (dom/prevent-default event)
+ (st/emit! (uda/request-profile-recovery (:clean-data form) on-success)))]
+ [:form {:on-submit on-submit}
+ [:div.login-content
+ [:input.input-text
+ {:name "username"
+ :value (:username data "")
+ :class (fm/error-class form :username)
+ :on-blur (fm/on-input-blur form :username)
+ :on-change (fm/on-input-change form :username)
+ :placeholder (tr "profile.recovery.username-or-email")
+ :type "text"}]
+ [:input.btn-primary
+ {:name "login"
+ :class (when-not (:valid form) "btn-disabled")
+ :disabled (not (:valid form))
+ :value (tr "profile.recovery.submit-request")
+ :type "submit"}]
+
+ [:div.login-links
+ [:a {:on-click #(st/emit! (rt/nav :login))}
+ (tr "profile.recovery.go-to-login")]]]]))
+
+;; --- Recovery Request Page
+
+(mf/defc profile-recovery-request-page
+ []
+ [:div.login
+ [:div.login-body
+ [:& messages-widget]
+ [:a i/logo]
+ [:& recovery-form]]])
diff --git a/frontend/src/uxbox/main/ui/auth/register.cljs b/frontend/src/uxbox/main/ui/profile/register.cljs
similarity index 89%
rename from frontend/src/uxbox/main/ui/auth/register.cljs
rename to frontend/src/uxbox/main/ui/profile/register.cljs
index 226ec6f39d..a111d0ad01 100644
--- a/frontend/src/uxbox/main/ui/auth/register.cljs
+++ b/frontend/src/uxbox/main/ui/profile/register.cljs
@@ -5,7 +5,7 @@
;; Copyright (c) 2015-2017 Andrey Antukh
;; Copyright (c) 2015-2017 Juan de la Cruz
-(ns uxbox.main.ui.auth.register
+(ns uxbox.main.ui.profile.register
(:require
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
@@ -69,7 +69,7 @@
:class (fm/error-class form :fullname)
:on-blur (fm/on-input-blur form :fullname)
:on-change (fm/on-input-change form :fullname)
- :placeholder (tr "register.fullname.placeholder")
+ :placeholder (tr "profile.register.fullname")
:type "text"}]
[:& fm/field-error {:form form
@@ -84,7 +84,7 @@
:on-blur (fm/on-input-blur form :username)
:on-change (fm/on-input-change form :username)
:value (:username data "")
- :placeholder (tr "settings.profile.your-username")}]
+ :placeholder (tr "profile.register.username")}]
[:& fm/field-error {:form form
:type #{::api}
@@ -98,7 +98,7 @@
:on-blur (fm/on-input-blur form :email)
:on-change (fm/on-input-change form :email)
:value (:email data "")
- :placeholder (tr "settings.profile.your-email")}]
+ :placeholder (tr "profile.register.email")}]
[:& fm/field-error {:form form
:type #{::api}
@@ -112,7 +112,7 @@
:class (fm/error-class form :password)
:on-blur (fm/on-input-blur form :password)
:on-change (fm/on-input-change form :password)
- :placeholder (tr "register.password.placeholder")
+ :placeholder (tr "profile.register.password")
:type "password"}]
[:& fm/field-error {:form form
@@ -124,15 +124,15 @@
:tab-index "5"
:class (when-not (:valid form) "btn-disabled")
:disabled (not (:valid form))
- :value (tr "register.get-started")}]
+ :value (tr "profile.register.get-started")}]
[:div.login-links
- [:a {:on-click #(st/emit! (rt/nav :auth/login))}
- (tr "register.already-have-account")]]]]))
+ [:a {:on-click #(st/emit! (rt/nav :login))}
+ (tr "profile.register.already-have-account")]]]]))
;; --- Register Page
-(mf/defc register-page
+(mf/defc profile-register-page
[props]
[:div.login
[:div.login-body
diff --git a/frontend/src/uxbox/main/ui/settings/header.cljs b/frontend/src/uxbox/main/ui/settings/header.cljs
index c347c42fca..bad3063810 100644
--- a/frontend/src/uxbox/main/ui/settings/header.cljs
+++ b/frontend/src/uxbox/main/ui/settings/header.cljs
@@ -42,7 +42,7 @@
[:& header-link {:section :settings/notifications
:content (tr "settings.notifications")}]]
#_[:li {:on-click #(st/emit! (da/logout))}
- [:& header-link {:section :auth/login
+ [:& header-link {:section :logout
:content (tr "settings.exit")}]]]
[:& user]]))