From ce9ef7b3e452cb03e6a74e695b911eb35e7085b7 Mon Sep 17 00:00:00 2001 From: gered Date: Wed, 29 Jun 2016 18:08:22 -0400 Subject: [PATCH] add login/logout support. users defined in config.edn no actions are protected yet --- resources/public/css/app.css | 5 +++ src/mtgcoll/client/auth.cljs | 37 ++++++++++++++++ src/mtgcoll/client/components/auth.cljs | 56 +++++++++++++++++++++++++ src/mtgcoll/client/page.cljs | 22 +++++++--- src/mtgcoll/client/views.cljs | 27 +++++++++++- src/mtgcoll/core.clj | 4 +- src/mtgcoll/routes/auth.clj | 35 ++++++++++++++++ src/mtgcoll/routes/main_page.clj | 2 + src/mtgcoll/views/sente.clj | 4 +- 9 files changed, 182 insertions(+), 10 deletions(-) create mode 100644 src/mtgcoll/client/auth.cljs create mode 100644 src/mtgcoll/client/components/auth.cljs create mode 100644 src/mtgcoll/routes/auth.clj diff --git a/resources/public/css/app.css b/resources/public/css/app.css index da22c51..08deaa5 100644 --- a/resources/public/css/app.css +++ b/resources/public/css/app.css @@ -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; } diff --git a/src/mtgcoll/client/auth.cljs b/src/mtgcoll/client/auth.cljs new file mode 100644 index 0000000..34005e2 --- /dev/null +++ b/src/mtgcoll/client/auth.cljs @@ -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"))) diff --git a/src/mtgcoll/client/components/auth.cljs b/src/mtgcoll/client/components/auth.cljs new file mode 100644 index 0000000..b168a60 --- /dev/null +++ b/src/mtgcoll/client/components/auth.cljs @@ -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"]]]))) \ No newline at end of file diff --git a/src/mtgcoll/client/page.cljs b/src/mtgcoll/client/page.cljs index 7c21d22..44c9c51 100644 --- a/src/mtgcoll/client/page.cljs +++ b/src/mtgcoll/client/page.cljs @@ -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 diff --git a/src/mtgcoll/client/views.cljs b/src/mtgcoll/client/views.cljs index 2e44243..e205ccf 100644 --- a/src/mtgcoll/client/views.cljs +++ b/src/mtgcoll/client/views.cljs @@ -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)) diff --git a/src/mtgcoll/core.clj b/src/mtgcoll/core.clj index 6b81db2..3c62f92 100644 --- a/src/mtgcoll/core.clj +++ b/src/mtgcoll/core.clj @@ -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 diff --git a/src/mtgcoll/routes/auth.clj b/src/mtgcoll/routes/auth.clj new file mode 100644 index 0000000..defd0d4 --- /dev/null +++ b/src/mtgcoll/routes/auth.clj @@ -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))))))) diff --git a/src/mtgcoll/routes/main_page.clj b/src/mtgcoll/routes/main_page.clj index 703e75e..597d460 100644 --- a/src/mtgcoll/routes/main_page.clj +++ b/src/mtgcoll/routes/main_page.clj @@ -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 diff --git a/src/mtgcoll/views/sente.clj b/src/mtgcoll/views/sente.clj index 2d3befb..fb93463 100644 --- a/src/mtgcoll/views/sente.clj +++ b/src/mtgcoll/views/sente.clj @@ -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! []