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;
|
padding-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* what the fuck? */
|
||||||
|
ul.nav.navbar-nav.navbar-right {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
div.card-title > h1 > small {
|
div.card-title > h1 > small {
|
||||||
margin-left: 30px;
|
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]
|
[reagent.core :as r]
|
||||||
[webtools.cljs.dom :as dom]
|
[webtools.cljs.dom :as dom]
|
||||||
[webtools.reagent.bootstrap :as bs]
|
[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))
|
(defonce error (r/atom nil))
|
||||||
|
|
||||||
|
@ -31,11 +33,18 @@
|
||||||
[:a#logo {:href "#/"}
|
[:a#logo {:href "#/"}
|
||||||
[:span [:img {:src (->url "/img/mtg_icon.png")}]]
|
[:span [:img {:src (->url "/img/mtg_icon.png")}]]
|
||||||
"Card Collection"]]]
|
"Card Collection"]]]
|
||||||
[bs/Nav
|
[bs/Navbar.Collapse
|
||||||
[bs/NavItem {:href "#/owned" :active (= :owned active-breadcrumb)} "Owned"]
|
[bs/Nav
|
||||||
[bs/NavItem {:href "#/all" :active (= :all active-breadcrumb)} "All"]
|
[bs/NavItem {:href "#/owned" :active (= :owned active-breadcrumb)} "Owned"]
|
||||||
[bs/NavItem {:href "#/sets" :active (= :sets active-breadcrumb)} "Sets"]
|
[bs/NavItem {:href "#/all" :active (= :all active-breadcrumb)} "All"]
|
||||||
[bs/NavItem {:href "#/stats" :active (= :stats active-breadcrumb)} "Statistics"]]]
|
[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
|
[bs/Modal
|
||||||
{:show (boolean @error)
|
{:show (boolean @error)
|
||||||
:on-hide clear-error!}
|
:on-hide clear-error!}
|
||||||
|
@ -44,6 +53,7 @@
|
||||||
[:p @error]]
|
[:p @error]]
|
||||||
[bs/Modal.Footer
|
[bs/Modal.Footer
|
||||||
[bs/Button {:on-click clear-error!} "Close"]]]
|
[bs/Button {:on-click clear-error!} "Close"]]]
|
||||||
|
[login-form]
|
||||||
page-component]))
|
page-component]))
|
||||||
|
|
||||||
(defn page
|
(defn page
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
(ns mtgcoll.client.views
|
(ns mtgcoll.client.views
|
||||||
(:require
|
(:require
|
||||||
[taoensso.sente :as sente]
|
[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 {}))
|
(defonce sente-socket (atom {}))
|
||||||
|
|
||||||
|
@ -22,6 +23,27 @@
|
||||||
(= :ws (:type state)))
|
(= :ws (:type state)))
|
||||||
(.clearInterval js/window @(get-in @sente-socket [:chsk :kalive-timer_])))))
|
(.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!
|
(defn reconnect!
|
||||||
[]
|
[]
|
||||||
(clear-keepalive-interval!)
|
(clear-keepalive-interval!)
|
||||||
|
@ -32,4 +54,5 @@
|
||||||
(if (chsk-exists?)
|
(if (chsk-exists?)
|
||||||
(reconnect!)
|
(reconnect!)
|
||||||
(reset! sente-socket (sente/make-channel-socket! "/chsk" {})))
|
(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.views.sente :as sente]
|
||||||
[mtgcoll.routes.main-page :refer [main-page-routes]]
|
[mtgcoll.routes.main-page :refer [main-page-routes]]
|
||||||
[mtgcoll.routes.images :refer [image-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
|
(defn init
|
||||||
[]
|
[]
|
||||||
|
@ -47,6 +48,7 @@
|
||||||
|
|
||||||
(def handler
|
(def handler
|
||||||
(-> (routes
|
(-> (routes
|
||||||
|
auth-routes
|
||||||
collection-routes
|
collection-routes
|
||||||
image-routes
|
image-routes
|
||||||
main-page-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"]]]
|
[:a {:href "https://github.com/gered/mtgcoll"} "Source code"] " licensed under MIT"]]]
|
||||||
(js-env-settings "" (boolean (config/get :dev?)))
|
(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")]))
|
(include-js "cljs/app.js")]))
|
||||||
|
|
||||||
(def main-page-routes
|
(def main-page-routes
|
||||||
|
|
|
@ -20,7 +20,9 @@
|
||||||
(reset! sente-socket
|
(reset! sente-socket
|
||||||
(sente/make-channel-socket!
|
(sente/make-channel-socket!
|
||||||
sente-web-server-adapter
|
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!
|
(defn shutdown!
|
||||||
[]
|
[]
|
||||||
|
|
Loading…
Reference in a new issue