add login/logout support. users defined in config.edn

no actions are protected yet
This commit is contained in:
Gered 2016-06-29 18:08:22 -04:00
parent 2676de8c74
commit ce9ef7b3e4
9 changed files with 182 additions and 10 deletions

View file

@ -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;
}

View 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")))

View 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"]]])))

View file

@ -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/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"]]]
[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

View file

@ -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))

View file

@ -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

View 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)))))))

View file

@ -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

View file

@ -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!
[]