├── resources
└── public
│ ├── favicon.ico
│ ├── img
│ ├── banner.png
│ ├── screen.png
│ └── marque-sm.png
│ ├── index.html
│ ├── css
│ └── style.css
│ └── js
│ └── vendor
│ └── amazon-cognito-identity.min.js
├── .gitignore
├── src
└── bib
│ └── customer
│ └── client
│ ├── views
│ ├── utils.cljs
│ ├── navbar.cljs
│ ├── home.cljs
│ └── auth
│ │ ├── verify.cljs
│ │ ├── signin.cljs
│ │ ├── register.cljs
│ │ └── forgot.cljs
│ ├── state
│ ├── usersync.cljs
│ └── auth.cljs
│ ├── store.cljs
│ ├── core.cljs
│ └── events
│ └── auth.cljs
├── externs
└── amazon-cognito-identity.externs.js
├── dev
└── user.clj
├── README.md
├── scss
└── style.scss
└── project.clj
/resources/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kironroy101/biblio/HEAD/resources/public/favicon.ico
--------------------------------------------------------------------------------
/resources/public/img/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kironroy101/biblio/HEAD/resources/public/img/banner.png
--------------------------------------------------------------------------------
/resources/public/img/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kironroy101/biblio/HEAD/resources/public/img/screen.png
--------------------------------------------------------------------------------
/resources/public/img/marque-sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kironroy101/biblio/HEAD/resources/public/img/marque-sm.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /resources/public/js/compiled/**
2 | figwheel_server.log
3 | pom.xml
4 | *jar
5 | /lib/
6 | /classes/
7 | /out/
8 | /target/
9 | .lein-deps-sum
10 | .lein-repl-history
11 | .lein-plugins/
12 | .repl
13 | .nrepl-port
14 | .DS_Store
--------------------------------------------------------------------------------
/src/bib/customer/client/views/utils.cljs:
--------------------------------------------------------------------------------
1 | (ns bib.customer.client.views.utils
2 | (:require [reagent.core :as reagent :refer [atom]]))
3 |
4 | (defn swap-on-value-changed! [cursor]
5 | #(swap! cursor (fn [c] (-> % .-target .-value))))
6 |
7 | (defn on-enter-fn [cb]
8 | (fn [e] (when (= 13 (.-charCode e)) (cb))))
9 |
--------------------------------------------------------------------------------
/src/bib/customer/client/state/usersync.cljs:
--------------------------------------------------------------------------------
1 | (ns bib.customer.client.state.usersync
2 | (:require-macros [cljs.core.async.macros :refer [go go-loop]]
3 | [mount.core :refer [defstate]])
4 | (:require [mount.core :as mount]
5 | [cljs.core.async :as async :refer [timeout chan ! alts! close!]]
6 | [bib.customer.client.events.auth :as auth-events]))
7 |
8 | (def user-sync-pid (atom nil))
9 |
10 | (defn start-cognito-user-sync []
11 | (let [close-ch (chan)
12 | id (random-uuid)]
13 | (reset! user-sync-pid id)
14 | (go-loop []
15 | (when (= @user-sync-pid id)
16 | (auth-events/update-user-info)
17 | (
2 |
3 |
4 |
5 |
17 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/bib/customer/client/views/navbar.cljs:
--------------------------------------------------------------------------------
1 | (ns bib.customer.client.views.navbar
2 | (:require [bib.customer.client.store :refer [app-state nav!]]
3 | [bib.customer.client.views.utils :as view-utils]
4 | [bib.customer.client.events.auth :as auth-events]))
5 |
6 | (defn- not-signed-in-auth-links []
7 | [:ul.navbar-right
8 | #_[:li [:a {:href "#/register"} "Register"]]
9 | #_[:li [:a {:href "#/verify"} "Verify Registration"]]
10 | #_[:li [:a {:href "#/forgot-password"} "Forgot Password"]]
11 | #_[:li [:a {:href "#/reset-forgot-password"} "Reset Password"]]
12 | [:li [:a {:href "#/signin"}
13 | [:i.fa.fa-sign-in]
14 | [:span "Sign In" ]]]])
15 |
16 | (defn- signed-in-auth-links []
17 | [:ul.navbar-right
18 | [:li [:a {:on-click #((auth-events/signout-user)
19 | (nav! :signin))}
20 | [:i.fa.fa-sign-out]
21 | [:span "Sign Out" ]]]])
22 |
23 | (defn view []
24 | [:nav.navbar
25 | [:div.container-fluid
26 | (let [signed-in (not-empty (:cognito-user @app-state))]
27 | [:div
28 | [:div.navbar-left
29 | [:a.brand {:href "/"}
30 | [:img {:src "img/marque-sm.png"}]]]
31 | (if-not signed-in
32 | [not-signed-in-auth-links]
33 | [signed-in-auth-links])
34 | (when-let [em (get-in @app-state [:cognito-user :email])]
35 | [:p.navbar-text.navbar-right "Hello, " em])])]])
36 |
--------------------------------------------------------------------------------
/src/bib/customer/client/views/home.cljs:
--------------------------------------------------------------------------------
1 | (ns bib.customer.client.views.home
2 | (:require-macros [cljs.core.async.macros :refer [go]])
3 | (:require
4 | [bib.customer.client.store :refer [app-state nav!]]
5 | [bib.customer.client.views.utils :as view-utils]
6 | [bib.customer.client.events.auth :as auth-events]
7 | [cljs.core.async :refer [timeout cognito` object to reflect the PoolID, ClientID, and Region for the Cognito UserPool and Client associated with the app.
29 |
30 | ### Launch the Project
31 |
32 | Run the following commands from the root of the project:
33 |
34 | ```
35 | lein figwheel
36 | ```
37 |
38 | The project will build and a browser window will launch showing the sign-in page.
39 |
40 | ```
41 | lein auto sassc once
42 | ```
43 |
44 | ---
45 |
46 | ## Contacting Us / Contributions
47 |
48 | Please use the [Github Issues](https://github.com/atowndata/trawler/issues) page for questions, ideas and bug reports. Pull requests are welcome.
49 |
50 | Trawler was built by the consulting team at [ATown Data](https://www.atowndata.com). Please [contact us](https://atowndata.com/contact/) if you have a project you'd like to talk to us about!
51 |
52 |
53 | ## License
54 |
55 | Distributed under the [Eclipse Public License](https://www.eclipse.org/legal/epl-v10.html) (the same license as Clojure).
56 | Copyright © 2017 [ATown Data](https://www.atowndata.com)
57 |
--------------------------------------------------------------------------------
/src/bib/customer/client/core.cljs:
--------------------------------------------------------------------------------
1 | (ns bib.customer.client.core
2 | (:require-macros [cljs.core.async.macros :refer [go go-loop]]
3 | [mount.core :refer [defstate]])
4 | (:require [reagent.core :as reagent :refer [atom]]
5 | [mount.core :as mount]
6 | [cljs.core.async :as async :refer [timeout chan ! alts! close!]]
7 | [secretary.core :as secretary :refer-macros [defroute]]
8 | [goog.events :as events]
9 | [goog.history.EventType :as EventType]
10 | [goog.history.Html5History :as Html5History]
11 | [bib.customer.client.store :refer [app-state nav! register-routes]]
12 | [bib.customer.client.core.views.auth.register :as register]
13 | [bib.customer.client.core.views.auth.signin :as signin]
14 | [bib.customer.client.core.views.auth.verify :as verify]
15 | [bib.customer.client.views.home :as home]
16 | [bib.customer.client.views.navbar :as navbar]
17 | [bib.customer.client.views.auth.forgot :as forgot]
18 | [bib.customer.client.events.auth :as auth-events]
19 | [bib.customer.client.state.auth :as auth-state]
20 | [bib.customer.client.views.utils :as view-utils]
21 | [bib.customer.client.state.usersync :refer [user-sync]])
22 |
23 |
24 | (:import goog.History
25 | [goog.history Html5History]
26 | ))
27 |
28 | (enable-console-print!)
29 |
30 | (defmulti render-view (fn [v & args] v))
31 |
32 | (defn wrap-content [& c]
33 | [:div.container
34 | [:div.row
35 | [:div.col-md-12
36 | c]]])
37 |
38 | (defmethod render-view :default
39 | [_ s]
40 | (home/view))
41 |
42 | (defmethod render-view :register
43 | [_ s] [register/view])
44 |
45 | (defmethod render-view :verify
46 | [_ s] [verify/view])
47 |
48 | (defmethod render-view :signin
49 | [_ s] [signin/view])
50 |
51 | (defmethod render-view :forgot-password-trigger
52 | [_ s] [forgot/trigger-recovery-view])
53 |
54 | (defmethod render-view :forgot-password-complete
55 | [_ s] [forgot/input-recovery-view])
56 |
57 | (defn page []
58 | (fn [] [:div
59 | [navbar/view]
60 | [:div.container
61 | (render-view (get @app-state :page) app-state)]]))
62 |
63 |
64 | (defn mount-components []
65 | (mount/start #'user-sync)
66 | (reagent/render-component
67 | [page] (. js/document (getElementById "app"))))
68 |
69 | (mount-components)
70 |
71 |
72 | (defn on-js-reload []
73 | (mount/stop #'user-sync)
74 | (mount/start #'user-sync)
75 | ;; optionally touch your app-state to force rerendering depending on
76 | ;; your application
77 | ;; (swap! app-state update-in [:__figwheel_counter] inc)
78 | )
79 |
--------------------------------------------------------------------------------
/src/bib/customer/client/views/auth/verify.cljs:
--------------------------------------------------------------------------------
1 | (ns bib.customer.client.core.views.auth.verify
2 | (:require
3 | [reagent.core :as reagent :refer [atom]]
4 | [bouncer.core :as b]
5 | [bouncer.validators :as v]
6 | [bib.customer.client.views.utils :as view-utils]
7 | [bib.customer.client.events.auth :as auth-events]
8 | [bib.customer.client.state.auth :as auth-state]))
9 |
10 | (def subscribe (partial auth-state/subscribe-form :verify))
11 |
12 | (defn validate-view [form-vals]
13 | (b/validate form-vals
14 | :email [v/required v/email]
15 | :code v/required))
16 |
17 | (defn view [state]
18 | (let [email-cursor (subscribe :email)
19 | code-cursor (subscribe :code)
20 | flash-cursor (subscribe :flash)
21 | touched (atom {:email false :code false})
22 | has-verr (fn [vs field]
23 | (and
24 | (contains? vs field)
25 | (field @touched)))
26 | fields (subscribe)]
27 | (fn []
28 | (let [verrs (first (validate-view @fields))]
29 | [:div.row>div.col-sm-6.col-sm-offset-3.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
30 | [:div.user-verify.auth-panel
31 | [:div.panel.panel-default
32 | [:div.panel-heading>h1 "Confirm Your Account"]
33 | [:div.panel-body
34 | (into [:div]
35 | (map
36 | (fn [v] [:div.alert
37 | {:class (name (:type v))}
38 | (:message v)])
39 | @flash-cursor))
40 | [:div.form
41 | [:div.form-group
42 | {:class (when (has-verr verrs :email) "has-error")}
43 | [:label {:for "email-input"} "Email Address"]
44 | [:input#email-input.form-control
45 | {:type "email" :placeholder "Email"
46 | :value @email-cursor
47 | :on-key-press
48 | (view-utils/on-enter-fn auth-events/verify-user)
49 | :on-change
50 | (comp
51 | (fn [_] (swap! touched assoc :email true))
52 | (view-utils/swap-on-value-changed! email-cursor))}]
53 | (when (has-verr verrs :email)
54 | [:small.text-help (:email verrs)])]
55 | [:div.form-group
56 | {:class (when (has-verr verrs :code) "has-error")}
57 | [:label {:for "code-input"} "Code"]
58 | [:input#code-input.form-control
59 | {:type "code" :placeholder "Code"
60 | :value @code-cursor
61 | :on-key-press
62 | (view-utils/on-enter-fn auth-events/verify-user)
63 | :on-change
64 | (comp
65 | (fn [_] (swap! touched assoc :code true))
66 | (view-utils/swap-on-value-changed! code-cursor))}]
67 | (when (has-verr verrs :code)
68 | [:small.text-help (:code verrs)])]
69 | [:a.btn.btn-default.btn-block
70 | {:on-click #(auth-events/verify-user)
71 | :class (when-not (empty? verrs)
72 | "disabled")}
73 | "Verify"]] ]]
74 | [:a.btn.btn-secondary.btn-block {:on-click
75 | (fn [_] (when (some? @email-cursor)
76 | (auth-events/resend-verification-code)))}
77 | "Resend Confirmation Code?"]] ] ))))
78 |
--------------------------------------------------------------------------------
/src/bib/customer/client/views/auth/signin.cljs:
--------------------------------------------------------------------------------
1 | (ns bib.customer.client.core.views.auth.signin
2 | (:require
3 | [reagent.core :as reagent :refer [atom]]
4 | [bouncer.core :as b]
5 | [bouncer.validators :as v]
6 | [bib.customer.client.store :refer [app-state nav!]]
7 | [bib.customer.client.views.utils :as view-utils]
8 | [bib.customer.client.events.auth :as auth-events]
9 | [bib.customer.client.state.auth :as auth-state]))
10 |
11 | (def subscribe (partial auth-state/subscribe-form :signin))
12 |
13 | (defn validate-view [form-vals]
14 | (b/validate form-vals
15 | :email [v/required v/email]
16 | :password v/required))
17 |
18 | (defn view [state]
19 | (let [email-cursor (subscribe :email)
20 | password-cursor (subscribe :password)
21 | flash-cursor (subscribe :flash)
22 | touched (atom {:email false :password false :confirm-password false})
23 | has-verr (fn [vs field]
24 | (and
25 | (contains? vs field)
26 | (field @touched)))
27 | fields (subscribe)]
28 | (fn []
29 | (let [verrs (first (validate-view @fields))
30 | signed-in (not-empty (:cognito-user @app-state))
31 | t (get-in @app-state [:cognito-user :jwt-token])]
32 | (if signed-in
33 | (do (nav! :home)
34 | [:div])
35 | (do [:div.row
36 | [:div.col-sm-6.col-sm-offset-3.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
37 | [:div.user-signin.auth-panel
38 | [:div.panel.panel-default
39 | [:div.panel-heading>h1 "Welcome back"]
40 | [:div.panel-body
41 | (into [:div]
42 | (map
43 | (fn [v] [:div.alert
44 | {:class (name (:type v))}
45 | (:message v)])
46 | @flash-cursor))
47 | [:div.form
48 | [:div.form-group
49 | {:class (when (has-verr verrs :email) "has-error")}
50 | [:label {:for "email-input"} "Email Address"]
51 | [:input#email-input.form-control
52 | {:type "email" :placeholder "Email"
53 | :value @email-cursor
54 | :on-change
55 | (comp
56 | (fn [_] (swap! touched assoc :email true))
57 | (view-utils/swap-on-value-changed! email-cursor))
58 | :on-key-press
59 | (view-utils/on-enter-fn auth-events/signin-user)
60 | }]
61 | (when (has-verr verrs :email)
62 | [:small.help-block (:email verrs)])]
63 | [:div.form-group.password-form-group
64 | {:class (when (has-verr verrs :password) "has-error")}
65 | [:label {:for "password-input"} "Password"]
66 | [:input#password-input.form-control
67 | {:type "password" :placeholder "Password"
68 | :value @password-cursor
69 | :on-change
70 | (comp
71 | (fn [_] (swap! touched assoc :password true))
72 | (view-utils/swap-on-value-changed! password-cursor) )
73 | :on-key-press
74 | (view-utils/on-enter-fn auth-events/signin-user)
75 | }]
76 | (when (has-verr verrs :password)
77 | [:small.help-block (:password verrs)])]
78 | [:div.form-group.clearfix
79 | [:small [:a.pull-right {:href "#/forgot-password"} "Forgot your password?"] ]]
80 | [:a.btn.btn-default.btn-block
81 | {:on-click #(auth-events/signin-user)
82 | :class (when-not (empty? verrs)
83 | "disabled") }
84 | "Sign in to your account"]]]]
85 | [:a.btn.btn-secondary.btn-block {:href "#/register"}
86 | [:span "Don't have an account? " ]
87 | [:u "Sign up" ]] ] ] ] ) )))))
88 |
--------------------------------------------------------------------------------
/src/bib/customer/client/views/auth/register.cljs:
--------------------------------------------------------------------------------
1 | (ns bib.customer.client.core.views.auth.register
2 | (:require
3 | [reagent.core :as reagent :refer [atom]]
4 | [bouncer.core :as b]
5 | [bouncer.validators :as v]
6 | [bib.customer.client.views.utils :as view-utils]
7 | [bib.customer.client.events.auth :as auth-events]
8 | [bib.customer.client.state.auth :as auth-state]))
9 |
10 | (def subscribe (partial auth-state/subscribe-form :register))
11 |
12 | (defn validate-view [form-vals]
13 | (b/validate form-vals
14 | :email [v/required v/email]
15 | :password v/required
16 | :confirm-password [v/required [(fn [cp]
17 | (= cp (:password form-vals)))
18 | :message
19 | "Password and Confirm Password must match"]]))
20 |
21 | (defn view []
22 | (let [email-cursor (subscribe :email)
23 | password-cursor (subscribe :password)
24 | confirm-password-cursor (subscribe :confirm-password)
25 | flash-cursor (subscribe :flash)
26 | touched (atom {:email false :password false :confirm-password false})
27 | has-verr (fn [vs field]
28 | (and
29 | (contains? vs field)
30 | (field @touched)))
31 | fields (subscribe)]
32 | (fn []
33 | (let [verrs (first (validate-view @fields))]
34 | [:div.row>div.col-sm-6.col-sm-offset-3.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
35 | [:div.user-register.auth-panel
36 | [:div.panel.panel-default
37 | [:div.panel-heading>h1 "Create Your Account"]
38 |
39 | [:div.panel-body
40 | (into [:div]
41 | (map
42 | (fn [v] [:div.alert.alert-danger
43 | {:class (name (:type v))}
44 | (:message v)])
45 | @flash-cursor))
46 | [:div.form
47 | [:div.form-group
48 | {:class (when (has-verr verrs :email) "has-error")}
49 | [:label {:for "email-input"} "Email Address"]
50 | [:input#email-input.form-control
51 | {:type "email" :placeholder "Email"
52 | :value @email-cursor
53 | :on-key-press
54 | (view-utils/on-enter-fn auth-events/register-new-user)
55 | :on-change
56 | (comp
57 | (fn [_] (swap! touched assoc :email true))
58 | (view-utils/swap-on-value-changed! email-cursor))}]
59 | (when (has-verr verrs :email)
60 | [:small.text-help (:email verrs)])]
61 | [:div.form-group
62 | {:class (when (has-verr verrs :password) "has-error")}
63 | [:label {:for "password-input"} "Password"]
64 | [:input#password-input.form-control
65 | {:type "password" :placeholder "Password"
66 | :value @password-cursor
67 | :on-key-press
68 | (view-utils/on-enter-fn auth-events/register-new-user)
69 | :on-change
70 | (comp
71 | (fn [_] (swap! touched assoc :password true))
72 | (view-utils/swap-on-value-changed! password-cursor) )}]
73 | (when (has-verr verrs :password)
74 | [:small.text-help (:password verrs)])]
75 | [:div.form-group
76 | {:class (when (has-verr verrs :confirm-password) "has-error")}
77 | [:label {:for "confirm-password-input"} "Confirm Password"]
78 | [:input#password-input.form-control
79 | {:type "password" :placeholder "Confirm Password"
80 | :value @confirm-password-cursor
81 | :on-key-press
82 | (view-utils/on-enter-fn auth-events/register-new-user)
83 | :on-change
84 | (comp
85 | (fn [_] (swap! touched assoc :confirm-password true))
86 | (view-utils/swap-on-value-changed! confirm-password-cursor))}]
87 | (when (has-verr verrs :confirm-password)
88 | [:small.text-help (:confirm-password verrs)])]
89 | [:a.btn.btn-default.btn-block
90 | {:on-click #(auth-events/register-new-user)
91 | :class (when-not (empty? verrs)
92 | "disabled")}
93 | "Register"]] ]
94 | ]
95 | [:a.btn.btn-secondary.btn-block {:href "#/signin"}
96 | [:span "Already have an account? " ]
97 | [:u "Sign in" ]]
98 |
99 | ] ] ))))
100 |
--------------------------------------------------------------------------------
/resources/public/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #f6f9fc;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; }
4 |
5 | input[type="email"]::-webkit-input-placeholder,
6 | input[type="text"]::-webkit-input-placeholder,
7 | input[type="password"]::-webkit-input-placeholder {
8 | /* Chrome/Opera/Safari */
9 | color: rgba(136, 152, 170, 0.3); }
10 |
11 | input[type="email"]::-moz-placeholder,
12 | input[type="text"]::-moz-placeholder,
13 | input[type="password"]::-moz-placeholder {
14 | /* Firefox 19+ */
15 | color: rgba(136, 152, 170, 0.3); }
16 |
17 | input[type="email"]:-ms-input-placeholder,
18 | input[type="text"]:-ms-input-placeholder,
19 | input[type="password"]:-ms-input-placeholder {
20 | /* IE 10+ */
21 | color: rgba(136, 152, 170, 0.3); }
22 |
23 | input[type="email"]:-moz-placeholder,
24 | input[type="password"]:-moz-placeholder {
25 | /* Firefox 18- */
26 | color: rgba(136, 152, 170, 0.3); }
27 |
28 | a {
29 | color: #6772e5;
30 | transition: all 0.15s ease; }
31 | a:hover {
32 | color: #32325d;
33 | text-decoration: none; }
34 |
35 | .btn-default {
36 | transition: all 0.15s ease; }
37 | .btn-default, .btn-default:hover {
38 | background: #6772e5;
39 | color: white;
40 | border: 0; }
41 | .btn-default:hover {
42 | background: #8898aa; }
43 |
44 | label {
45 | font-weight: 400;
46 | font-size: 12px;
47 | text-transform: uppercase;
48 | color: #8898aa; }
49 |
50 | .btn {
51 | padding-top: 10px;
52 | padding-bottom: 10px; }
53 | .form .btn {
54 | margin-top: 20px; }
55 |
56 | .panel-default {
57 | margin: 40px 0;
58 | border-radius: 4px;
59 | box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1), 0 5px 15px rgba(0, 0, 0, 0.07);
60 | transition-property: color, background-color, box-shadow, transform;
61 | transition-duration: .15s;
62 | border: 0; }
63 | .panel-default > .panel-heading {
64 | background: none;
65 | border-color: rgba(207, 215, 223, 0.35);
66 | text-align: center; }
67 | .panel-default > .panel-heading h1 {
68 | margin: 13px 0 10px;
69 | font-size: 17px;
70 | line-height: 26px;
71 | font-weight: 600;
72 | text-transform: uppercase;
73 | letter-spacing: .025em;
74 | color: #6772e5; }
75 |
76 | .panel-body {
77 | padding: 35px 40px 40px; }
78 |
79 | .navbar {
80 | background: #232a40;
81 | border-radius: 0;
82 | border: 0; }
83 | .navbar,
84 | .navbar .navbar-text {
85 | color: white; }
86 | .navbar a {
87 | cursor: pointer;
88 | color: #d0deee; }
89 | .navbar a:hover {
90 | color: #f6f9fc; }
91 |
92 | .navbar-right {
93 | float: right !important; }
94 |
95 | .navbar-left {
96 | float: left !important; }
97 |
98 | ul.navbar-right {
99 | margin: 0 0 0 10px;
100 | padding: 0; }
101 | ul.navbar-right li {
102 | list-style: none; }
103 | ul.navbar-right li a {
104 | display: inline-block;
105 | padding: 15px 20px; }
106 | ul.navbar-right li a i {
107 | display: inline-block;
108 | margin-right: 7px; }
109 |
110 | .brand {
111 | position: relative;
112 | display: block;
113 | width: 55px;
114 | height: 50px;
115 | margin-left: -15px;
116 | background: #6772e5; }
117 | .brand img {
118 | position: absolute;
119 | top: 50%;
120 | left: 50%;
121 | margin-top: -18px;
122 | margin-left: -16px;
123 | width: 31px;
124 | height: auto; }
125 |
126 | .password-form-group {
127 | margin-bottom: 5px; }
128 |
129 | .loader-wrapper {
130 | width: 100%;
131 | height: 100vh;
132 | padding: 50px; }
133 |
134 | .mask-loading {
135 | position: relative;
136 | top: 45%;
137 | transform: translateY(-50%); }
138 |
139 | .spinner {
140 | width: 50px;
141 | height: 50px;
142 | position: relative;
143 | margin: 0 auto; }
144 |
145 | .double-bounce1,
146 | .double-bounce2 {
147 | width: 100%;
148 | height: 100%;
149 | border-radius: 50%;
150 | background-color: #6772e5;
151 | opacity: 0.6;
152 | position: absolute;
153 | top: 0;
154 | left: 0;
155 | -webkit-animation: sk-bounce 2.0s infinite ease-in-out;
156 | animation: sk-bounce 2.0s infinite ease-in-out; }
157 |
158 | .double-bounce2 {
159 | -webkit-animation-delay: -1.0s;
160 | animation-delay: -1.0s; }
161 |
162 | @-webkit-keyframes sk-bounce {
163 | 0%,
164 | 100% {
165 | -webkit-transform: scale(0); }
166 | 50% {
167 | -webkit-transform: scale(1); } }
168 |
169 | @keyframes sk-bounce {
170 | 0%,
171 | 100% {
172 | transform: scale(0);
173 | -webkit-transform: scale(0); }
174 | 50% {
175 | transform: scale(1);
176 | -webkit-transform: scale(1); } }
177 |
178 | .auth-token {
179 | background: white;
180 | white-space: pre-wrap;
181 | /* css-3 */
182 | white-space: -moz-pre-wrap;
183 | /* Mozilla, since 1999 */
184 | white-space: -pre-wrap;
185 | /* Opera 4-6 */
186 | white-space: -o-pre-wrap;
187 | /* Opera 7 */
188 | word-wrap: break-word;
189 | /* Internet Explorer 5.5+ */ }
190 |
--------------------------------------------------------------------------------
/scss/style.scss:
--------------------------------------------------------------------------------
1 | // VARIABLES
2 | $light-border-color: rgba(207, 215, 223, 0.35);
3 | $light-text-color: #8898aa;
4 | $placeholder-color: rgba(136, 152, 170, 0.3);
5 | $light-grey-bg: #f6f9fc;
6 | $accent-text-color:#6772e5;
7 | body {
8 | background: $light-grey-bg;
9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
10 | }
11 |
12 | input[type="email"]::-webkit-input-placeholder,
13 | input[type="text"]::-webkit-input-placeholder,
14 | input[type="password"]::-webkit-input-placeholder {
15 | /* Chrome/Opera/Safari */
16 | color: $placeholder-color;
17 | }
18 |
19 | input[type="email"]::-moz-placeholder,
20 | input[type="text"]::-moz-placeholder,
21 | input[type="password"]::-moz-placeholder {
22 | /* Firefox 19+ */
23 | color: $placeholder-color;
24 | }
25 |
26 | input[type="email"]:-ms-input-placeholder,
27 | input[type="text"]:-ms-input-placeholder,
28 | input[type="password"]:-ms-input-placeholder {
29 | /* IE 10+ */
30 | color: $placeholder-color;
31 | }
32 |
33 | input[type="email"]:-moz-placeholder,
34 | input[type="password"]:-moz-placeholder {
35 | /* Firefox 18- */
36 | color: $placeholder-color;
37 | }
38 |
39 | a {
40 | color: #6772e5;
41 | transition: all 0.15s ease;
42 | &:hover {
43 | color: #32325d;
44 | text-decoration: none;
45 | }
46 | }
47 |
48 | .btn-default {
49 | transition: all 0.15s ease;
50 | &,
51 | &:hover {
52 | background: #6772e5;
53 | color: white;
54 | border: 0;
55 | }
56 | &:hover {
57 | background: $light-text-color;
58 | }
59 | }
60 |
61 | label {
62 | font-weight: 400;
63 | font-size: 12px;
64 | text-transform: uppercase;
65 | color: $light-text-color;
66 | }
67 |
68 | .btn {
69 | padding-top: 10px;
70 | padding-bottom: 10px;
71 | .form & {
72 | margin-top: 20px;
73 | }
74 | }
75 |
76 | .panel-default {
77 | margin: 40px 0;
78 | border-radius: 4px;
79 | box-shadow: 0 15px 35px rgba(50, 50, 93, .1), 0 5px 15px rgba(0, 0, 0, .07);
80 | transition-property: color, background-color, box-shadow, transform;
81 | transition-duration: .15s;
82 | border: 0;
83 | &>.panel-heading {
84 | background: none;
85 | border-color: $light-border-color;
86 | text-align: center;
87 | h1 {
88 | margin: 13px 0 10px;
89 | font-size: 17px;
90 | line-height: 26px;
91 | font-weight: 600;
92 | text-transform: uppercase;
93 | letter-spacing: .025em;
94 | color: $accent-text-color;
95 | }
96 | }
97 | }
98 |
99 | .panel-body {
100 | padding: 35px 40px 40px;
101 | }
102 |
103 | .navbar {
104 | background: #232a40;
105 | border-radius: 0;
106 | border: 0;
107 | &,
108 | .navbar-text {
109 | color: white;
110 | }
111 | a {
112 | cursor: pointer;
113 | color: #d0deee;
114 | &:hover {
115 | color: $light-grey-bg;
116 | }
117 | }
118 | }
119 |
120 | .navbar-right {
121 | float: right !important;
122 | }
123 |
124 | .navbar-left {
125 | float: left !important;
126 | }
127 |
128 | ul.navbar-right {
129 | margin: 0 0 0 10px;
130 | padding: 0;
131 | li {
132 | list-style: none;
133 | a {
134 | display: inline-block;
135 | padding: 15px 20px;
136 | i {
137 | display: inline-block;
138 | margin-right: 7px;
139 | }
140 | }
141 | }
142 | }
143 |
144 | .brand {
145 | position: relative;
146 | display: block;
147 | width: 55px;
148 | height: 50px;
149 | margin-left: -15px;
150 | background: $accent-text-color;
151 | img {
152 | position: absolute;
153 | top: 50%;
154 | left: 50%;
155 | margin-top: -18px;
156 | margin-left: -16px;
157 | width: 31px;
158 | height: auto;
159 | }
160 | }
161 |
162 | .password-form-group {
163 | margin-bottom: 5px;
164 | }
165 |
166 | .loader-wrapper {
167 | width: 100%;
168 | height: 100vh;
169 | padding: 50px;
170 | }
171 |
172 | .mask-loading {
173 | position: relative;
174 | top: 45%;
175 | transform: translateY(-50%);
176 | }
177 |
178 | .spinner {
179 | width: 50px;
180 | height: 50px;
181 | position: relative;
182 | margin: 0 auto;
183 | }
184 |
185 | .double-bounce1,
186 | .double-bounce2 {
187 | width: 100%;
188 | height: 100%;
189 | border-radius: 50%;
190 | background-color: $accent-text-color;
191 | opacity: 0.6;
192 | position: absolute;
193 | top: 0;
194 | left: 0;
195 | -webkit-animation: sk-bounce 2.0s infinite ease-in-out;
196 | animation: sk-bounce 2.0s infinite ease-in-out;
197 | }
198 |
199 | .double-bounce2 {
200 | -webkit-animation-delay: -1.0s;
201 | animation-delay: -1.0s;
202 | }
203 |
204 | @-webkit-keyframes sk-bounce {
205 | 0%,
206 | 100% {
207 | -webkit-transform: scale(0.0)
208 | }
209 | 50% {
210 | -webkit-transform: scale(1.0)
211 | }
212 | }
213 |
214 | @keyframes sk-bounce {
215 | 0%,
216 | 100% {
217 | transform: scale(0.0);
218 | -webkit-transform: scale(0.0);
219 | }
220 | 50% {
221 | transform: scale(1.0);
222 | -webkit-transform: scale(1.0);
223 | }
224 | }
225 |
226 | .auth-token {
227 | background: white;
228 | white-space: pre-wrap;
229 | /* css-3 */
230 | white-space: -moz-pre-wrap;
231 | /* Mozilla, since 1999 */
232 | white-space: -pre-wrap;
233 | /* Opera 4-6 */
234 | white-space: -o-pre-wrap;
235 | /* Opera 7 */
236 | word-wrap: break-word;
237 | /* Internet Explorer 5.5+ */
238 | }
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject bib.customer.client "0.0.1"
2 | :description "A ClojureScript/Reagent client for AWS Cognito"
3 | :url "http://github.com/atowndata/biblio"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 |
7 | :min-lein-version "2.7.1"
8 |
9 | :dependencies [[org.clojure/clojure "1.8.0"]
10 | [org.clojure/clojurescript "1.9.229"]
11 | [org.clojure/core.async "0.3.442"
12 | :exclusions [org.clojure/tools.reader]]
13 | [mount "0.1.11"]
14 | [reagent "0.6.0"]
15 | [reagent-utils "0.2.1"]
16 | [historian "1.1.1"]
17 | [secretary "1.2.3"]
18 | [com.andrewmcveigh/cljs-time "0.4.0"]
19 | [cljsjs/jquery-ui "1.11.4-0"]
20 | [cljsjs/jquery "2.2.4-0"]
21 | [cljs-ajax "0.6.0"]
22 | [bouncer "1.0.1"]]
23 |
24 | :plugins [[lein-figwheel "0.5.10"]
25 | [lein-cljsbuild "1.1.5" :exclusions [[org.clojure/clojure]]]
26 | [lein-sassc "0.10.4"]
27 | [lein-auto "0.1.3"]]
28 |
29 | :sassc
30 | [{:src "scss/style.scss"
31 | :output-to "resources/public/css/style.css"
32 | :style "nested"
33 | :import-path "scss"}]
34 |
35 | :auto
36 | {"sassc" {:file-pattern #"\.(scss|sass)$" :paths ["scss"]}}
37 |
38 | :hooks [leiningen.sassc]
39 |
40 | :source-paths ["src"]
41 |
42 | :cljsbuild {:builds
43 | [{:id "dev"
44 | :source-paths ["src"]
45 |
46 | ;; the presence of a :figwheel configuration here
47 | ;; will cause figwheel to inject the figwheel client
48 | ;; into your build
49 | :figwheel {:on-jsload "bib.customer.client.core/on-js-reload"
50 | ;; :open-urls will pop open your application
51 | ;; in the default browser once Figwheel has
52 | ;; started and complied your application.
53 | ;; Comment this out once it no longer serves you.
54 | :open-urls ["http://localhost:3449/index.html"]}
55 |
56 | :compiler {:main bib.customer.client.core
57 | :asset-path "js/compiled/out"
58 | :output-to "resources/public/js/compiled/bib/customer/client.js"
59 | :output-dir "resources/public/js/compiled/out"
60 | :source-map-timestamp true
61 | ;; To console.log CLJS data-structures make sure you enable devtools in Chrome
62 | ;; https://github.com/binaryage/cljs-devtools
63 | :preloads [devtools.preload]}}
64 | ;; This next build is an compressed minified build for
65 | ;; production. You can build this with:
66 | ;; lein cljsbuild once min
67 | {:id "min"
68 | :source-paths ["src"]
69 | :compiler {:output-to "resources/public/js/compiled/bib/customer/client.js"
70 | :main bib.customer.client.core
71 | :optimizations :advanced
72 | :pretty-print false
73 | :externs ["externs/aws-cognito-sdk.externs.js"
74 | "externs/amazon-cognito-identity.externs.js"]
75 | }}]}
76 |
77 | :figwheel {;; :http-server-root "public" ;; default and assumes "resources"
78 | ;; :server-port 3449 ;; default
79 | ;; :server-ip "127.0.0.1"
80 |
81 | :css-dirs ["resources/public/css"] ;; watch and update CSS
82 |
83 | ;; Start an nREPL server into the running figwheel process
84 | ;; :nrepl-port 7888
85 |
86 | ;; Server Ring Handler (optional)
87 | ;; if you want to embed a ring handler into the figwheel http-kit
88 | ;; server, this is for simple ring servers, if this
89 |
90 | ;; doesn't work for you just run your own server :) (see lein-ring)
91 |
92 | ;; :ring-handler hello_world.server/handler
93 |
94 | ;; To be able to open files in your editor from the heads up display
95 | ;; you will need to put a script on your path.
96 | ;; that script will have to take a file path and a line number
97 | ;; ie. in ~/bin/myfile-opener
98 | ;; #! /bin/sh
99 | ;; emacsclient -n +$2 $1
100 | ;;
101 | ;; :open-file-command "myfile-opener"
102 |
103 | ;; if you are using emacsclient you can just use
104 | ;; :open-file-command "emacsclient"
105 |
106 | ;; if you want to disable the REPL
107 | ;; :repl false
108 |
109 | ;; to configure a different figwheel logfile path
110 | ;; :server-logfile "tmp/logs/figwheel-logfile.log"
111 |
112 | ;; to pipe all the output to the repl
113 | ;; :server-logfile false
114 | }
115 |
116 |
117 | ;; setting up nREPL for Figwheel and ClojureScript dev
118 | ;; Please see:
119 | ;; https://github.com/bhauman/lein-figwheel/wiki/Using-the-Figwheel-REPL-within-NRepl
120 | :profiles {:dev {:dependencies [[binaryage/devtools "0.9.2"]
121 | [figwheel-sidecar "0.5.10"]
122 | [com.cemerick/piggieback "0.2.1"]]
123 | ;; need to add dev source path here to get user.clj loaded
124 | :source-paths ["src" "dev"]
125 | ;; for CIDER
126 | ;; :plugins [[cider/cider-nrepl "0.12.0"]]
127 | :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
128 | ;; need to add the compliled assets to the :clean-targets
129 | :clean-targets ^{:protect false} ["resources/public/js/compiled"
130 | :target-path]}})
131 |
--------------------------------------------------------------------------------
/src/bib/customer/client/views/auth/forgot.cljs:
--------------------------------------------------------------------------------
1 | (ns bib.customer.client.views.auth.forgot
2 | (:require
3 | [reagent.core :as reagent :refer [atom]]
4 | [bouncer.core :as b]
5 | [bouncer.validators :as v]
6 | [bib.customer.client.views.utils :as view-utils]
7 | [bib.customer.client.events.auth :as auth-events]
8 | [bib.customer.client.state.auth :as auth-state]))
9 |
10 | (def subscribe (partial auth-state/subscribe-form :forgot))
11 |
12 | (defn trigger-recovery-view [state]
13 | (let [email-cursor (subscribe :email)
14 | flash-cursor (subscribe :flash)
15 | fields (subscribe)
16 | touched (atom {:email false})
17 | has-verr (fn [vs field]
18 | (and
19 | (contains? vs field)
20 | (field @touched)))]
21 | (fn []
22 | [:div.row
23 | [:div.col-sm-6.col-sm-offset-3.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
24 | [:div.user-forgot.auth-panel
25 | [:div.panel.panel-default
26 | [:div.panel-heading>h1 "Reset Your Password"]
27 | [:div.panel-body
28 | (into [:div]
29 | (map
30 | (fn [v] [:div.alert
31 | {:class (name (:type v))}
32 | (:message v)])
33 | @flash-cursor))
34 | (let [verrs (first (b/validate @fields
35 | :email [v/required v/email]) )]
36 | [:div.form
37 | [:div.form-group
38 | {:class (when (has-verr verrs :email) "has-error")}
39 | [:label {:for "email-input"} "Email Address"]
40 | [:input#email-input.form-control
41 | {:type "email" :placeholder "Email"
42 | :value @email-cursor
43 | :on-change
44 | (comp
45 | (fn [_] (swap! touched assoc :email true))
46 | (view-utils/swap-on-value-changed! email-cursor))
47 | :on-key-press
48 | (view-utils/on-enter-fn auth-events/trigger-password-recovery)}]
49 | (when (has-verr verrs :email)
50 | [:small.text-help (:email verrs)])]
51 | [:a.btn.btn-default.btn-block
52 | {:on-click #(auth-events/trigger-password-recovery)
53 | :class (when-not (empty? verrs)
54 | "disabled")}
55 | "Send Password Reset Email"]
56 | ])]]]]])))
57 |
58 | (defn validate-password-reset [form-vals]
59 | (b/validate form-vals
60 | :email [v/required v/email]
61 | :code [v/required]
62 | :new-password v/required
63 | :new-password-confirmation [v/required [(fn [cp]
64 | (= cp (:new-password form-vals)))
65 | :message
66 | "Password and Confirm Password must match"]]))
67 |
68 | (defn input-recovery-view [state]
69 | (let [form-fields (subscribe)
70 | code-cursor (subscribe :code)
71 | email-cursor (subscribe :email)
72 | new-password-cursor (subscribe :new-password)
73 | new-password-confirmation-cursor (subscribe :new-password-confirmation)
74 | flash-cursor (auth-state/subscribe-form :reset-password :flash)
75 | fields (subscribe)
76 | touched (atom {:email false
77 | :code false
78 | :new-password false
79 | :new-password-confirmation false})
80 | has-verr (fn [vs field]
81 | (and
82 | (contains? vs field)
83 | (field @touched)))]
84 | (fn []
85 | (let [verrs (first (validate-password-reset @fields))]
86 | [:div.row>div.col-sm-6.col-sm-offset-3.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
87 | [:div.user-forgot.auth-panel
88 | [:div.panel.panel-default
89 | [:div.panel-heading>h1 "Reset Your Password"]
90 | [:div.panel-body
91 | (into [:div]
92 | (map
93 | (fn [v] [:div.alert
94 | {:class (name (:type v))}
95 | (:message v)])
96 | @flash-cursor))
97 | [:div.form
98 | [:div.form-group
99 | {:class (when (has-verr verrs :email) "has-error")}
100 | [:label {:for "email-input"} "Email Address"]
101 | [:input#email-input.form-control
102 | {:type "email" :placeholder "Email"
103 | :value @email-cursor
104 | :on-key-press
105 | (view-utils/on-enter-fn auth-events/confirm-password-recovery)
106 | :on-change
107 | (comp
108 | (fn [_] (swap! touched assoc :email true))
109 | (view-utils/swap-on-value-changed! email-cursor))}]
110 | (when (has-verr verrs :email)
111 | [:small.text-help (:email verrs)])]
112 | [:div.form-group
113 | {:class (when (has-verr verrs :code) "has-error")}
114 | [:label {:for "code-input"} "Verification Code"]
115 | [:input#email-input.form-control
116 | {:type "text" :placeholder "Code"
117 | :on-key-press
118 | (view-utils/on-enter-fn auth-events/confirm-password-recovery)
119 | :value @code-cursor
120 | :on-change
121 | (comp (fn [_] (swap! touched assoc :code true))
122 | (view-utils/swap-on-value-changed! code-cursor) )}]
123 | (when (has-verr verrs :code)
124 | [:small.text-help (:code verrs)])]
125 | [:div.form-group
126 | {:class (when (has-verr verrs :new-password) "has-error")}
127 | [:label {:for "password-input"} "Password"]
128 | [:input#password-input.form-control
129 | {:type "password" :placeholder "Password"
130 | :value @new-password-cursor
131 | :on-key-press
132 | (view-utils/on-enter-fn auth-events/confirm-password-recovery)
133 | :on-change
134 | (comp (fn [_] (swap! touched assoc :new-password true))
135 | (view-utils/swap-on-value-changed! new-password-cursor))}]
136 | (when (has-verr verrs :new-password)
137 | [:small.text-help (:new-password verrs)])]
138 | [:div.form-group
139 | {:class (when (has-verr verrs :new-password-confirmation) "has-error")}
140 | [:label {:for "confirm-password-input"} "Confirm Password"]
141 | [:input#password-input.form-control
142 | {:type "password" :placeholder "Confirm Password"
143 | :value @new-password-confirmation-cursor
144 | :on-key-press
145 | (view-utils/on-enter-fn auth-events/confirm-password-recovery)
146 | :on-change
147 | (comp (fn [_] (swap! touched assoc :new-password-confirmation true))
148 | (view-utils/swap-on-value-changed! new-password-confirmation-cursor))}]
149 | (when (has-verr verrs :new-password-confirmation)
150 | [:small.text-help (:new-password-confirmation verrs)])]
151 | [:a.btn.btn-default.btn-block
152 | {:on-click #(auth-events/confirm-password-recovery)
153 | :class (when-not (empty? verrs)
154 | "disabled")}
155 | "Reset Password"]
156 | ] ]] ] ]))))
157 |
--------------------------------------------------------------------------------
/src/bib/customer/client/events/auth.cljs:
--------------------------------------------------------------------------------
1 | (ns bib.customer.client.events.auth
2 | (:require-macros [cljs.core.async.macros :refer [go go-loop]])
3 | (:require [clojure.string :as string]
4 | [cljs.core.async :as async :refer [timeout chan ! alts! close!]]
5 | [bib.customer.client.store :refer [app-state nav!]]
6 | [bib.customer.client.state.auth :as auth-state]))
7 |
8 | (def CognitoUserPool (-> js/AmazonCognitoIdentity .-CognitoUserPool))
9 | (def CognitoUserAttribute (-> js/AmazonCognitoIdentity .-CognitoUserAttribute))
10 | (def CognitoUser (-> js/AmazonCognitoIdentity .-CognitoUser))
11 | (def AuthenticationDetails (-> js/AmazonCognitoIdentity .-AuthenticationDetails))
12 | (def AWSCognito js/AWSCognito)
13 |
14 | (defn to-username [email]
15 | (string/replace email "@" "-at-"))
16 |
17 | (defn to-email [username]
18 | (string/replace username "-at-" "@"))
19 |
20 | (defn get-user-pool []
21 | (new CognitoUserPool
22 | (clj->js
23 | {:UserPoolId (get-in @app-state [:cognito :userPoolId])
24 | :ClientId (get-in @app-state [:cognito :userPoolClientID])})))
25 |
26 | (defonce user-pool (get-user-pool))
27 |
28 | (defn get-authtoken [cb]
29 | (let [user (-> user-pool .getCurrentUser)]
30 | (if (some? user)
31 | (.getSession user
32 | (fn [err session]
33 | (cond (some? err)
34 | (cb err nil)
35 | (not (.isValid session))
36 | (cb nil nil)
37 | :default
38 | (cb nil (-> session .getIdToken .getJwtToken)))))
39 | (cb nil nil))))
40 |
41 | (defn- create-cognito-user [email]
42 | (new CognitoUser
43 | (clj->js
44 | {:Username (to-username email)
45 | :Pool user-pool})))
46 |
47 | (defn update-user-info []
48 | (let [u (.getCurrentUser user-pool)]
49 | (when (some? u)
50 | (go
51 | (let [field-ch (chan)
52 | err-ch (chan)
53 | email (to-email (.getUsername u))]
54 | (swap! app-state assoc-in [:cognito-user :email] email)
55 | (get-authtoken
56 | (fn [err t]
57 | (go (when (some? t) (>! field-ch t))
58 | (when (some? err) (>! err-ch err)))))
59 | (let [[token ch] (alts! [field-ch err-ch (timeout 10000)])]
60 | (cond
61 | (= ch field-ch) (swap! app-state assoc-in [:cognito-user :jwt-token] token)
62 | (= ch err-ch) (do (println "Error Retrieving User JWT Token" token)
63 | (auth-state/reset-cognito-user {}))
64 | :default (do (println "Timeout retrieving User JWT Token")
65 | (auth-state/reset-cognito-user {}))))
66 | (.getSession u
67 | (fn [err t]
68 | (go (when (some? t) (>! field-ch (.isValid t)))
69 | (when (some? err) (>! err-ch err)))))
70 | (let [[session ch] (alts! [field-ch err-ch (timeout 10000)])]
71 | (cond
72 | (= ch field-ch) (swap! app-state assoc-in [:cognito-user :valid-session]
73 | session)
74 | (= ch err-ch) (do (println "Error Retrieving User Session" session))
75 | :default (do (println "Timeout retrieving User Session"))))
76 | (close! field-ch)
77 | (close! err-ch))) ) ))
78 |
79 | (defn configure-aws-cognito []
80 | (set! (-> AWSCognito .-config .-region) (get-in @app-state [:cognito :region])))
81 |
82 |
83 | (def flash-register (partial auth-state/conj-form-flash :register))
84 | (def flash-verify (partial auth-state/conj-form-flash :verify))
85 | (def flash-signin (partial auth-state/conj-form-flash :signin))
86 | (def flash-forgot (partial auth-state/conj-form-flash :forgot))
87 | (def flash-reset-password (partial auth-state/conj-form-flash :reset-password))
88 |
89 | (defmulti handle-register-error #(keyword (.-name %)))
90 |
91 | (defmethod handle-register-error :UsernameExistsException
92 | [e]
93 | (flash-register
94 | {:type :alert-danger
95 | :message "The username is unavailable. Please choose a different one."}))
96 |
97 |
98 | (defmethod handle-register-error :InvalidParameterException
99 | [e]
100 | (flash-register
101 | {:type :alert-danger
102 | :message (.-message e)}))
103 |
104 | (defmethod handle-register-error :InvalidPasswordException
105 | [e]
106 | (flash-register
107 | {:type :alert-danger
108 | :message (str
109 | "The password specified is invalid. Please make sure you have at"
110 | " least of each of uppercase letter, lowercase letter, and special character.")}))
111 |
112 | (defmethod handle-register-error :default
113 | [e]
114 | (flash-register
115 | {:type :alert-danger
116 | :message "Could no complete registraion. Please try again. If this persists, please contact system administrator"})
117 | (println "Unhandled register error" e))
118 |
119 | (defn -handle-register-error [e]
120 | (auth-state/clear-form-flash :register)
121 | (handle-register-error e))
122 |
123 | (defn register-new-user []
124 | (let [password (get-in @app-state [:auth-form :register :password])
125 | email (get-in @app-state [:auth-form :register :email])
126 | attr-email (new CognitoUserAttribute
127 | (clj->js {:Name "email" :Value email}))]
128 | (auth-state/clear-form-flash :register)
129 | (.signUp user-pool
130 | (to-username email)
131 | password
132 | (to-array [attr-email]) nil
133 | (fn [err result]
134 | (if (some? err)
135 | (-handle-register-error err)
136 | (do (flash-verify {:type :alert-success
137 | :message "Please check your email for a verification code."})
138 | (nav! :verify))
139 | )))))
140 |
141 | (defn verify-user []
142 | (let [email (get-in @app-state [:auth-form :verify :email])
143 | code (get-in @app-state [:auth-form :verify :code])
144 | u (create-cognito-user email)]
145 | (auth-state/clear-form-flash :verify)
146 | (.confirmRegistration u
147 | code true
148 | (fn [err result]
149 | (if (some? err)
150 | (do (flash-verify
151 | {:type :alert-danger
152 | :message (.-message err)}))
153 | (do (println "Verified")
154 | (flash-signin
155 | {:type :alert-success
156 | :message "Successfully Verified. Please sign in."})
157 | (nav! :signin)))))))
158 |
159 | (defmulti handle-signin-error #(keyword (.-name %)))
160 |
161 |
162 | (defn- handle-invalid-pair []
163 | (flash-signin {:type :alert-danger
164 | :message "Invalid Username/Password Pair"}))
165 |
166 | ;; handles Incorrect Pairs and attempts exceeded
167 | (defmethod handle-signin-error :NotAuthorizedException
168 | [e]
169 | (println "Not Authorized" (.-message e))
170 | (cond
171 | (some? (string/index-of (.-message e) "Password attempts exceeded"))
172 | (flash-signin {:type :alert-danger :message "Password attempts exceeded"})
173 | :default
174 | (handle-invalid-pair))
175 | )
176 |
177 | (defmethod handle-signin-error :UserNotFoundException
178 | [e]
179 | (handle-invalid-pair))
180 |
181 | (defmethod handle-signin-error :UserNotConfirmedException
182 | [e]
183 | (flash-signin {:type :alert-danger
184 | :message "User is not confirmed. Please check your email for a verification code."}))
185 |
186 | (defmethod handle-signin-error :default
187 | [e]
188 | (println "Unhandled Signin Error" e)
189 | (flash-signin {:type :alert-danger
190 | :message "Could not complete login"}))
191 |
192 | (defn- clear-signin-form []
193 | (auth-state/clear-form-flash :signin)
194 | (auth-state/clear-signin-password))
195 |
196 | (defn -handle-signin-error [e]
197 | (clear-signin-form)
198 | (handle-signin-error e))
199 |
200 |
201 | (defn signin-user []
202 | (let [email (get-in @app-state [:auth-form :signin :email])
203 | password (get-in @app-state [:auth-form :signin :password])
204 | user (create-cognito-user email)
205 | auth-details (new AuthenticationDetails
206 | (clj->js
207 | {:Username (to-username email)
208 | :Password password}))]
209 | (.authenticateUser user
210 | auth-details
211 | (clj->js
212 | {:onSuccess (fn []
213 | (clear-signin-form)
214 | (update-user-info)
215 | (nav! :home))
216 | :onFailure -handle-signin-error}))))
217 |
218 | (defn signout-user []
219 | (-> (get-user-pool)
220 | .getCurrentUser
221 | .signOut)
222 | (auth-state/reset-cognito-user {}))
223 |
224 | (defn trigger-password-recovery []
225 | (let [email (get-in @app-state [:auth-form :forgot :email])
226 | user (create-cognito-user email)]
227 | (auth-state/clear-form-flash :forgot)
228 | (.forgotPassword user
229 | (clj->js
230 | {:onSuccess (fn []
231 | (flash-reset-password
232 | {:type :alert-success
233 | :message "Please check your Email for a recovery code."})
234 | (nav! :forgot-password-complete))
235 | :onFailure (fn [e]
236 | (flash-forgot
237 | {:type :alert-danger
238 | :message (.-message e)}))}))
239 | ))
240 |
241 | (defn confirm-password-recovery []
242 | (let [email (get-in @app-state [:auth-form :forgot :email])
243 | code (get-in @app-state [:auth-form :forgot :code])
244 | new-password (get-in @app-state [:auth-form :forgot :new-password])
245 | user (create-cognito-user email)]
246 | (auth-state/clear-form-flash :reset-password)
247 | (.confirmPassword user
248 | code
249 | new-password
250 | (clj->js
251 | {:onSuccess (fn []
252 | (flash-signin
253 | {:type :alert-success
254 | :message
255 | "Your password has been reset"})
256 | (nav! :signin))
257 | :onFailure (fn [e]
258 | (flash-reset-password
259 | {:type :alert-danger
260 | :message
261 | (.-message e)}))}))))
262 |
263 | (defn resend-verification-code []
264 | (let [email (get-in @app-state [:auth-form :verify :email])]
265 | (auth-state/clear-form-flash :verify)
266 | (.resendConfirmationCode (create-cognito-user email)
267 | (fn [err r]
268 | (if (some? err)
269 | (flash-verify
270 | {:type :alert-danger
271 | :message
272 | (.-message err)})
273 | (flash-verify
274 | {:type :alert-success
275 | :message
276 | "New confirmation code has been sent. Please check your email."}))
277 | ))))
278 |
--------------------------------------------------------------------------------
/resources/public/js/vendor/amazon-cognito-identity.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Copyright 2016 Amazon.com,
3 | * Inc. or its affiliates. All Rights Reserved.
4 | *
5 | * Licensed under the Amazon Software License (the "License").
6 | * You may not use this file except in compliance with the
7 | * License. A copy of the License is located at
8 | *
9 | * http://aws.amazon.com/asl/
10 | *
11 | * or in the "license" file accompanying this file. This file is
12 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
13 | * CONDITIONS OF ANY KIND, express or implied. See the License
14 | * for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("aws-sdk/global"),require("aws-sdk/clients/cognitoidentityserviceprovider")):"function"==typeof define&&define.amd?define(["aws-sdk/global","aws-sdk/clients/cognitoidentityserviceprovider"],t):"object"==typeof exports?exports.AmazonCognitoIdentity=t(require("aws-sdk/global"),require("aws-sdk/clients/cognitoidentityserviceprovider")):e.AmazonCognitoIdentity=t(e.AWSCognito,e.AWSCognito.CognitoIdentityServiceProvider)}(this,function(e,t){return function(e){function t(i){if(n[i])return n[i].exports;var s=n[i]={exports:{},id:i,loaded:!1};return e[i].call(s.exports,s,s.exports,t),s.loaded=!0,s.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function i(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(15);Object.keys(r).forEach(function(e){"default"!==e&&"__esModule"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return r[e]}})});var o=n(12),a=s(o),u=i(r);Object.keys(u).forEach(function(e){a.default[e]=u[e]}),"undefined"!=typeof window&&!window.crypto&&window.msCrypto&&(window.crypto=window.msCrypto)},function(t,n){t.exports=e},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function s(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n
=0;){var o=t*this[e++]+n[i]+s;s=Math.floor(o/67108864),n[i++]=67108863&o}return s}function r(e){return J.charAt(e)}function o(e,t){var n=G[e.charCodeAt(t)];return null==n?-1:n}function a(e){for(var t=this.t-1;t>=0;--t)e[t]=this[t];e.t=this.t,e.s=this.s}function u(e){this.t=1,this.s=e<0?-1:0,e>0?this[0]=e:e<-1?this[0]=e+this.DV:this.t=0}function l(e){var t=i();return t.fromInt(e),t}function c(e,t){var i;if(16==t)i=4;else if(8==t)i=3;else if(2==t)i=1;else if(32==t)i=5;else{if(4!=t)throw new Error("Only radix 2, 4, 8, 16, 32 are supported");i=2}this.t=0,this.s=0;for(var s=e.length,r=!1,a=0;--s>=0;){var u=o(e,s);u<0?"-"==e.charAt(s)&&(r=!0):(r=!1,0==a?this[this.t++]=u:a+i>this.DB?(this[this.t-1]|=(u&(1<>this.DB-a):this[this.t-1]|=u<=this.DB&&(a-=this.DB))}this.clamp(),r&&n.ZERO.subTo(this,this)}function h(){for(var e=this.s&this.DM;this.t>0&&this[this.t-1]==e;)--this.t}function f(e){if(this.s<0)return"-"+this.negate().toString();var t;if(16==e)t=4;else if(8==e)t=3;else if(2==e)t=1;else if(32==e)t=5;else{if(4!=e)throw new Error("Only radix 2, 4, 8, 16, 32 are supported");t=2}var n,i=(1<0)for(u>u)>0&&(s=!0,o=r(n));a>=0;)u>(u+=this.DB-t)):(n=this[a]>>(u-=t)&i,u<=0&&(u+=this.DB,--a)),n>0&&(s=!0),s&&(o+=r(n));return s?o:"0"}function d(){var e=i();return n.ZERO.subTo(this,e),e}function v(){return this.s<0?this.negate():this}function g(e){var t=this.s-e.s;if(0!=t)return t;var n=this.t;if(t=n-e.t,0!=t)return this.s<0?-t:t;for(;--n>=0;)if(0!=(t=this[n]-e[n]))return t;return 0}function m(e){var t,n=1;return 0!=(t=e>>>16)&&(e=t,n+=16),0!=(t=e>>8)&&(e=t,n+=8),0!=(t=e>>4)&&(e=t,n+=4),0!=(t=e>>2)&&(e=t,n+=2),0!=(t=e>>1)&&(e=t,n+=1),n}function p(){return this.t<=0?0:this.DB*(this.t-1)+m(this[this.t-1]^this.s&this.DM)}function y(e,t){var n;for(n=this.t-1;n>=0;--n)t[n+e]=this[n];for(n=e-1;n>=0;--n)t[n]=0;t.t=this.t+e,t.s=this.s}function S(e,t){for(var n=e;n=0;--n)t[n+o+1]=this[n]>>s|a,a=(this[n]&r)<=0;--n)t[n]=0;t[o]=a,t.t=this.t+o+1,t.s=this.s,t.clamp()}function w(e,t){t.s=this.s;var n=Math.floor(e/this.DB);if(n>=this.t)return void(t.t=0);var i=e%this.DB,s=this.DB-i,r=(1<>i;for(var o=n+1;o>i;i>0&&(t[this.t-n-1]|=(this.s&r)<>=this.DB;if(e.t>=this.DB;i+=this.s}else{for(i+=this.s;n>=this.DB;i-=e.s}t.s=i<0?-1:0,i<-1?t[n++]=this.DV+i:i>0&&(t[n++]=i),t.t=n,t.clamp()}function A(e,t){var i=this.abs(),s=e.abs(),r=i.t;for(t.t=r+s.t;--r>=0;)t[r]=0;for(r=0;r=0;)e[n]=0;for(n=0;n=t.DV&&(e[n+t.t]-=t.DV,e[n+t.t+1]=1)}e.t>0&&(e[e.t-1]+=t.am(n,t[n],e,2*n,0,1)),e.s=0,e.clamp()}function U(e,t,s){var r=e.abs();if(!(r.t<=0)){var o=this.abs();if(o.t0?(r.lShiftTo(c,a),o.lShiftTo(c,s)):(r.copyTo(a),o.copyTo(s));var h=a.t,f=a[h-1];if(0!=f){var d=f*(1<1?a[h-2]>>this.F2:0),v=this.FV/d,g=(1<=0&&(s[s.t++]=1,s.subTo(C,s)),n.ONE.dlShiftTo(h,C),C.subTo(a,a);a.t=0;){var w=s[--y]==f?this.DM:Math.floor(s[y]*v+(s[y-1]+p)*g);if((s[y]+=a.am(0,w,s,S,0,h))0&&s.rShiftTo(c,s),u<0&&n.ZERO.subTo(s,s)}}}function E(e){var t=i();return this.abs().divRemTo(e,null,t),this.s<0&&t.compareTo(n.ZERO)>0&&e.subTo(t,t),t}function D(){if(this.t<1)return 0;var e=this[0];if(0==(1&e))return 0;var t=3&e;return t=t*(2-(15&e)*t)&15,t=t*(2-(255&e)*t)&255,t=t*(2-((65535&e)*t&65535))&65535,t=t*(2-e*t%this.DV)%this.DV,t>0?this.DV-t:-t}function I(e){return 0==this.compareTo(e)}function b(e,t){for(var n=0,i=0,s=Math.min(e.t,this.t);n>=this.DB;if(e.t>=this.DB;i+=this.s}else{for(i+=this.s;n>=this.DB;i+=e.s}t.s=i<0?-1:0,i>0?t[n++]=i:i<-1&&(t[n++]=this.DV+i),t.t=n,t.clamp()}function P(e){var t=i();return this.addTo(e,t),t}function R(e){var t=i();return this.subTo(e,t),t}function F(e){var t=i();return this.multiplyTo(e,t),t}function _(e){var t=i();return this.divRemTo(e,t,null),t}function B(e){this.m=e,this.mp=e.invDigit(),this.mpl=32767&this.mp,this.mph=this.mp>>15,this.um=(1<0&&this.m.subTo(t,t),t}function M(e){var t=i();return e.copyTo(t),this.reduce(t),t}function N(e){for(;e.t<=this.mt2;)e[e.t++]=0;for(var t=0;t>15)*this.mpl&this.um)<<15)&e.DM;for(n=t+this.m.t,e[n]+=this.m.am(0,i,e,t,0,this.m.t);e[n]>=e.DV;)e[n]-=e.DV,e[++n]++}e.clamp(),e.drShiftTo(this.m.t,e),e.compareTo(this.m)>=0&&e.subTo(this.m,e)}function V(e,t){e.squareTo(t),this.reduce(t)}function K(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function q(e,t){var n,s=e.bitLength(),r=l(1),o=new B(t);if(s<=0)return r;n=s<18?1:s<48?3:s<144?4:s<768?5:6;var a=new Array,u=3,c=n-1,h=(1<1){var f=i();for(o.sqrTo(a[1],f);u<=h;)a[u]=i(),o.mulTo(f,a[u-2],a[u]),u+=2}var d,v,g=e.t-1,p=!0,y=i();for(s=m(e[g])-1;g>=0;){for(s>=c?d=e[g]>>s-c&h:(d=(e[g]&(1<0&&(d|=e[g-1]>>this.DB+s-c)),u=n;0==(1&d);)d>>=1,--u;if((s-=u)<0&&(s+=this.DB,--g),p)a[d].copyTo(r),p=!1;else{for(;u>1;)o.sqrTo(r,y),o.sqrTo(y,r),u-=2;u>0?o.sqrTo(r,y):(v=r,r=y,y=v),o.mulTo(y,a[d],r)}for(;g>=0&&0==(e[g]&1<0&&void 0!==arguments[0]?arguments[0]:{},n=t.AccessToken;i(this,e),this.jwtToken=n||""}return s(e,[{key:"getJwtToken",value:function(){return this.jwtToken}},{key:"getExpiration",value:function(){var e=this.jwtToken.split(".")[1],t=JSON.parse(r.util.base64.decode(e).toString("utf8"));return t.exp}}]),e}();t.default=o},function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{},n=t.IdToken;i(this,e),this.jwtToken=n||""}return s(e,[{key:"getJwtToken",value:function(){return this.jwtToken}},{key:"getExpiration",value:function(){var e=this.jwtToken.split(".")[1],t=JSON.parse(r.util.base64.decode(e).toString("utf8"));return t.exp}}]),e}();t.default=o},function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{},i=t.RefreshToken;n(this,e),this.token=i||""}return i(e,[{key:"getToken",value:function(){return this.token}}]),e}();t.default=s},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function s(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{},i=t.Name,s=t.Value;n(this,e),this.Name=i||"",this.Value=s||""}return i(e,[{key:"getValue",value:function(){return this.Value}},{key:"setValue",value:function(e){return this.Value=e,this}},{key:"getName",value:function(){return this.Name}},{key:"setName",value:function(e){return this.Name=e,this}},{key:"toString",value:function(){return JSON.stringify(this)}},{key:"toJSON",value:function(){return{Name:this.Name,Value:this.Value}}}]),e}();t.default=s},function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{},i=t.IdToken,s=t.RefreshToken,r=t.AccessToken;if(n(this,e),null==r||null==i)throw new Error("Id token and Access Token must be present.");this.idToken=i,this.refreshToken=s,this.accessToken=r}return i(e,[{key:"getIdToken",value:function(){return this.idToken}},{key:"getRefreshToken",value:function(){return this.refreshToken}},{key:"getAccessToken",value:function(){return this.accessToken}},{key:"isValid",value:function(){var e=Math.floor(new Date/1e3);return e