add login/logout support. users defined in config.edn
no actions are protected yet
This commit is contained in:
parent
2676de8c74
commit
ce9ef7b3e4
|
@ -45,6 +45,11 @@ div.popover.card-image .popover-content {
|
|||
padding-right: 7px;
|
||||
}
|
||||
|
||||
/* what the fuck? */
|
||||
ul.nav.navbar-nav.navbar-right {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
div.card-title > h1 > small {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
|
37
src/mtgcoll/client/auth.cljs
Normal file
37
src/mtgcoll/client/auth.cljs
Normal file
|
@ -0,0 +1,37 @@
|
|||
(ns mtgcoll.client.auth
|
||||
(:require
|
||||
[reagent.core :as r]
|
||||
[webtools.cljs.utils :refer [->url]]
|
||||
[webtools.cljs.ajax :as ajax]
|
||||
[mtgcoll.client.utils :refer [get-field-value]]))
|
||||
|
||||
(defonce user-profile (r/atom nil))
|
||||
|
||||
(defonce show-login (r/atom false))
|
||||
|
||||
(defn auth-required?
|
||||
[]
|
||||
(if-not (undefined? js/__authRequired)
|
||||
(boolean js/__authRequired)
|
||||
false))
|
||||
|
||||
(defn authenticated?
|
||||
[]
|
||||
(not (nil? @user-profile)))
|
||||
|
||||
(defn set-user-profile!
|
||||
[profile]
|
||||
(reset! user-profile profile))
|
||||
|
||||
(defn show-login-form!
|
||||
[]
|
||||
(reset! show-login true))
|
||||
|
||||
(defn hide-login-form!
|
||||
[]
|
||||
(reset! show-login false))
|
||||
|
||||
(defn logout!
|
||||
[]
|
||||
(reset! user-profile nil)
|
||||
(ajax/POST (->url "/logout")))
|
56
src/mtgcoll/client/components/auth.cljs
Normal file
56
src/mtgcoll/client/components/auth.cljs
Normal file
|
@ -0,0 +1,56 @@
|
|||
(ns mtgcoll.client.components.auth
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[reagent.core :as r]
|
||||
[webtools.cljs.utils :refer [->url]]
|
||||
[webtools.cljs.ajax :as ajax]
|
||||
[webtools.reagent.bootstrap :as bs]
|
||||
[mtgcoll.client.auth :as auth]
|
||||
[mtgcoll.client.views :as views]
|
||||
[mtgcoll.client.utils :refer [get-field-value]]))
|
||||
|
||||
(defn login-form
|
||||
[]
|
||||
(let [values (r/atom nil)
|
||||
error (r/atom nil)
|
||||
on-close (fn []
|
||||
(reset! values nil)
|
||||
(reset! error nil)
|
||||
(auth/hide-login-form!))
|
||||
on-submit (fn []
|
||||
(reset! error nil)
|
||||
(let [{:keys [username password]} @values]
|
||||
(if (or (string/blank? username) (string/blank? password))
|
||||
(reset! error "Username and password cannot be blank.")
|
||||
(ajax/POST (->url "/login")
|
||||
:params {:username username :password password}
|
||||
:on-error #(reset! error "Invalid username/password.")
|
||||
:on-success (fn [_]
|
||||
(on-close)
|
||||
(views/reconnect!))))))]
|
||||
(fn []
|
||||
[bs/Modal
|
||||
{:show (boolean @auth/show-login)
|
||||
:on-hide on-close}
|
||||
[bs/Modal.Header [bs/Modal.Title "Login"]]
|
||||
[bs/Modal.Body
|
||||
(if @error
|
||||
[bs/Alert {:bsStyle "danger"} @error])
|
||||
[bs/Form {:horizontal true}
|
||||
[bs/FormGroup
|
||||
[bs/Col {:class "text-right" :sm 4} [bs/ControlLabel "Username"]]
|
||||
[bs/Col {:sm 6}
|
||||
[bs/FormControl
|
||||
{:type "text"
|
||||
:value (or (:username @values) "")
|
||||
:on-change #(swap! values assoc :username (get-field-value %))}]]]
|
||||
[bs/FormGroup
|
||||
[bs/Col {:class "text-right" :sm 4} [bs/ControlLabel "Password"]]
|
||||
[bs/Col {:sm 6}
|
||||
[bs/FormControl
|
||||
{:type "password"
|
||||
:value (or (:password @values) "")
|
||||
:on-change #(swap! values assoc :password (get-field-value %))}]]]]]
|
||||
[bs/Modal.Footer
|
||||
[bs/Button {:bsStyle "primary" :on-click on-submit} "Login"]
|
||||
[bs/Button {:on-click on-close} "Cancel"]]])))
|
|
@ -3,7 +3,9 @@
|
|||
[reagent.core :as r]
|
||||
[webtools.cljs.dom :as dom]
|
||||
[webtools.reagent.bootstrap :as bs]
|
||||
[webtools.cljs.utils :refer [->url]]))
|
||||
[webtools.cljs.utils :refer [->url]]
|
||||
[mtgcoll.client.auth :as auth]
|
||||
[mtgcoll.client.components.auth :refer [login-form]]))
|
||||
|
||||
(defonce error (r/atom nil))
|
||||
|
||||
|
@ -31,11 +33,18 @@
|
|||
[:a#logo {:href "#/"}
|
||||
[:span [:img {:src (->url "/img/mtg_icon.png")}]]
|
||||
"Card Collection"]]]
|
||||
[bs/Nav
|
||||
[bs/NavItem {:href "#/owned" :active (= :owned active-breadcrumb)} "Owned"]
|
||||
[bs/NavItem {:href "#/all" :active (= :all active-breadcrumb)} "All"]
|
||||
[bs/NavItem {:href "#/sets" :active (= :sets active-breadcrumb)} "Sets"]
|
||||
[bs/NavItem {:href "#/stats" :active (= :stats active-breadcrumb)} "Statistics"]]]
|
||||
[bs/Navbar.Collapse
|
||||
[bs/Nav
|
||||
[bs/NavItem {:href "#/owned" :active (= :owned active-breadcrumb)} "Owned"]
|
||||
[bs/NavItem {:href "#/all" :active (= :all active-breadcrumb)} "All"]
|
||||
[bs/NavItem {:href "#/sets" :active (= :sets active-breadcrumb)} "Sets"]
|
||||
[bs/NavItem {:href "#/stats" :active (= :stats active-breadcrumb)} "Statistics"]]
|
||||
(if (auth/auth-required?)
|
||||
[bs/Nav {:pull-right true}
|
||||
(if (auth/authenticated?)
|
||||
[bs/NavDropdown {:title (:username @auth/user-profile)}
|
||||
[bs/MenuItem {:on-click #(auth/logout!)} "Logout"]]
|
||||
[bs/NavItem {:on-click auth/show-login-form!} "Login"])])]]
|
||||
[bs/Modal
|
||||
{:show (boolean @error)
|
||||
:on-hide clear-error!}
|
||||
|
@ -44,6 +53,7 @@
|
|||
[:p @error]]
|
||||
[bs/Modal.Footer
|
||||
[bs/Button {:on-click clear-error!} "Close"]]]
|
||||
[login-form]
|
||||
page-component]))
|
||||
|
||||
(defn page
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
(ns mtgcoll.client.views
|
||||
(:require
|
||||
[taoensso.sente :as sente]
|
||||
[views.reagent.sente.client :as vr]))
|
||||
[views.reagent.sente.client :as vr]
|
||||
[mtgcoll.client.auth :as auth]))
|
||||
|
||||
(defonce sente-socket (atom {}))
|
||||
|
||||
|
@ -22,6 +23,27 @@
|
|||
(= :ws (:type state)))
|
||||
(.clearInterval js/window @(get-in @sente-socket [:chsk :kalive-timer_])))))
|
||||
|
||||
(defn sente-event-msg-handler
|
||||
[{:keys [event id client-id] :as ev}]
|
||||
(let [[ev-id ev-data] event]
|
||||
(cond
|
||||
(and (= :chsk/state ev-id)
|
||||
(:open? ev-data))
|
||||
(vr/on-open! @sente-socket ev)
|
||||
|
||||
(= :chsk/handshake ev-id)
|
||||
(let [[_ _ handshake-data] ev-data
|
||||
{:keys [user]} handshake-data]
|
||||
(auth/set-user-profile! user))
|
||||
|
||||
(= :chsk/recv id)
|
||||
(when-not (vr/on-receive! @sente-socket ev)
|
||||
; on-receive! returns true if the event was a views.reagent event and it
|
||||
; handled it.
|
||||
;
|
||||
; you could put your code to handle your app's own events here
|
||||
))))
|
||||
|
||||
(defn reconnect!
|
||||
[]
|
||||
(clear-keepalive-interval!)
|
||||
|
@ -32,4 +54,5 @@
|
|||
(if (chsk-exists?)
|
||||
(reconnect!)
|
||||
(reset! sente-socket (sente/make-channel-socket! "/chsk" {})))
|
||||
(vr/init! @sente-socket {:use-default-sente-router? true}))
|
||||
(sente/start-chsk-router! (:ch-recv @sente-socket) sente-event-msg-handler)
|
||||
(vr/init! @sente-socket))
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
[mtgcoll.views.sente :as sente]
|
||||
[mtgcoll.routes.main-page :refer [main-page-routes]]
|
||||
[mtgcoll.routes.images :refer [image-routes]]
|
||||
[mtgcoll.routes.collection :refer [collection-routes]]))
|
||||
[mtgcoll.routes.collection :refer [collection-routes]]
|
||||
[mtgcoll.routes.auth :refer [auth-routes]]))
|
||||
|
||||
(defn init
|
||||
[]
|
||||
|
@ -47,6 +48,7 @@
|
|||
|
||||
(def handler
|
||||
(-> (routes
|
||||
auth-routes
|
||||
collection-routes
|
||||
image-routes
|
||||
main-page-routes
|
||||
|
|
35
src/mtgcoll/routes/auth.clj
Normal file
35
src/mtgcoll/routes/auth.clj
Normal file
|
@ -0,0 +1,35 @@
|
|||
(ns mtgcoll.routes.auth
|
||||
(:require
|
||||
[clojure.tools.logging :as log]
|
||||
[compojure.core :refer [routes GET POST]]
|
||||
[webtools.response :as response]
|
||||
[webtools.session :as session]
|
||||
[mtgcoll.config :as config]))
|
||||
|
||||
(def auth-routes
|
||||
(routes
|
||||
(POST "/login" [username password :as request]
|
||||
(if-let [user (->> (config/get :users)
|
||||
(filter #(and (= username (:username %))
|
||||
(= password (:password %))))
|
||||
(first))]
|
||||
(do
|
||||
(log/info username " logged in.")
|
||||
(-> (response/content "ok")
|
||||
(session/set-from-request request)
|
||||
(session/assoc :user user)))
|
||||
(do
|
||||
(log/warn "Unsuccessful login attempt by: " username)
|
||||
(-> (response/content "bad username/password")
|
||||
(response/status 401)))))
|
||||
|
||||
(POST "/logout" request
|
||||
(if-let [user (get-in request [:session :user])]
|
||||
(do
|
||||
(log/info (:username user) " logged out.")
|
||||
(-> (response/content "ok")
|
||||
(session/set-from-request request)
|
||||
(session/dissoc :user)))
|
||||
(do
|
||||
(-> (response/content "not logged in")
|
||||
(response/status 400)))))))
|
|
@ -31,6 +31,8 @@
|
|||
" | "
|
||||
[:a {:href "https://github.com/gered/mtgcoll"} "Source code"] " licensed under MIT"]]]
|
||||
(js-env-settings "" (boolean (config/get :dev?)))
|
||||
(javascript-tag
|
||||
(string/join "\n" [(str "var __authRequired = " (boolean (seq (config/get :users))))]))
|
||||
(include-js "cljs/app.js")]))
|
||||
|
||||
(def main-page-routes
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
(reset! sente-socket
|
||||
(sente/make-channel-socket!
|
||||
sente-web-server-adapter
|
||||
{:user-id-fn (fn [request] (get-in request [:params :client-id]))})))
|
||||
{:user-id-fn (fn [request] (get-in request [:params :client-id]))
|
||||
:handshake-data-fn (fn [request]
|
||||
{:user (get-in request [:session :user])})})))
|
||||
|
||||
(defn shutdown!
|
||||
[]
|
||||
|
|
Loading…
Reference in a new issue