forked from 4clojure/4clojure
-
Notifications
You must be signed in to change notification settings - Fork 0
/
login.clj
156 lines (144 loc) · 5.91 KB
/
login.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
(ns foreclojure.login
(:import org.jasypt.util.password.StrongPasswordEncryptor)
(:use hiccup.form-helpers
hiccup.page-helpers
[foreclojure utils config users]
compojure.core
[amalloy.utils :only [rand-in-range keywordize]]
[clojail.core :only [thunk-timeout]]
clojure.stacktrace
somnium.congomongo)
(:require [sandbar.stateful-session :as session]
[ring.util.response :as response]))
(def-page my-login-page [location]
(when location
(session/session-put! :login-to location)
nil) ;; don't include this in HTML output
[:div.error
(session/flash-get :error)
(session/flash-get :message)]
(form-to [:post "/login"]
[:table
[:tr
[:td (label :user "Username")]
[:td (text-field :user)]]
[:tr
[:td (label :pwd "Password")]
[:td (password-field :pwd)]]
[:tr
[:td]
[:td [:button {:type "submit"} "Log In"]]]
[:tr
[:td]
[:td
[:a {:href "/login/reset"} "Forgot your password?"]]]]))
(defn do-login [user pwd]
(let [user (.toLowerCase user)
{db-pwd :pwd} (from-mongo (fetch-one :users :where {:user user}))
location (session/session-get :login-to)]
(if (and db-pwd (.checkPassword (StrongPasswordEncryptor.) pwd db-pwd))
(do (update! :users {:user user}
{:$set {:last-login (java.util.Date.)}}
:upsert false) ; never create new users accidentally
(session/session-put! :user user)
(session/session-delete-key! :login-to)
(response/redirect (or location "/problems")))
(flash-error "Error logging in." "/login"))))
(def-page update-password-page []
(with-user [{:keys [user] :as user-obj}]
[:div#account-settings
[:div#update-pwd
[:h2 "Change password for " user]
[:span.error (session/flash-get :error)]
[:table
(form-to [:post "/login/update"]
(map form-row
[[password-field :old-pwd "Current password"]
[password-field :pwd "New password"]
[password-field :repeat-pwd "Repeat password"]])
[:tr
[:td [:button {:type "submit"} "Reset now"]]])]]]))
(defn do-update-password! [old-pwd new-pwd repeat-pwd]
(with-user [{:keys [user pwd]}]
(let [encryptor (StrongPasswordEncryptor.)]
(assuming [(= new-pwd repeat-pwd)
"New password was not entered identically twice"
(.checkPassword encryptor old-pwd pwd)
"Old password incorrect"]
(let [new-pwd-hash (.encryptPassword encryptor new-pwd)]
(update! :users {:user user}
{:$set {:pwd new-pwd-hash}}
:upsert false)
(flash-msg (str "Password for " user " updated successfully")
"/problems"))
(flash-error why "/login/update")))))
(def-page reset-password-page []
[:div
[:div#reset-help
[:h3 "Forgot your password?"]
[:div "Enter your email address and we'll send you a new password."]
[:div
[:span.error (session/flash-get :error)]
(form-to [:post "/login/reset"]
(label :email "Email")
(text-field :email)
[:button {:type "submit"} "Reset!"])]]])
(let [pw-chars "abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXY1234567890"]
(defn random-pwd []
(let [pw (apply str
(repeatedly 10 #(rand-nth pw-chars)))
hash (.encryptPassword (StrongPasswordEncryptor.) pw)]
(keywordize [pw hash]))))
(defn try-to-email [email name id]
(let [{:keys [pw hash]} (random-pwd)]
(try
(thunk-timeout
(fn []
(update! :users
{:_id id}
{:$set {:pwd hash}})
(send-email
{:from "team@4clojure.com"
:to [email]
:subject "Password reset"
:body
(str "The password for your 4clojure.com account "
name " has been reset to " pw ". Make sure to change it"
" soon at https://4clojure.com/login/update - pick"
" something you'll remember!")})
{:success true})
10 :sec)
(catch Throwable t
{:success false, :exception t,
:message (.getMessage t),
:trace (with-out-str
(binding [*err* *out*]
(print-cause-trace t)))
:pw pw, :hash hash}))))
(defn do-reset-password! [email]
(if-let [{id :_id, name :user} (fetch-one :users
:where {:email email}
:only [:_id :user])]
(let [{:keys [success] :as diagnostics} (? (try-to-email email name id))]
(if success
(do (session/session-put! :login-to "/login/update")
(flash-msg "Your password has been reset! You should receive an email soon."
(login-url "/login/update")))
(do (spit (? (str name ".pwd")) (? diagnostics))
(flash-error (str "Something went wrong emailing your new password! Please contact <a href='mailto:team@4clojure.com?subject=Password Reset: " name "'>team@4clojure.com</a> - we'll reset it manually and look into the problem. When you do, please mention your username.")
"/login/reset"))))
(flash-error "We don't know anyone with that email address!"
"/login/reset")))
(defroutes login-routes
(GET "/login" [location] (my-login-page location))
(POST "/login" {{:strs [user pwd]} :form-params}
(do-login user pwd))
(GET "/login/update" [] (update-password-page))
(POST "/login/update" {{:strs [old-pwd pwd repeat-pwd]} :form-params}
(do-update-password! old-pwd pwd repeat-pwd))
(GET "/login/reset" [] (reset-password-page))
(POST "/login/reset" [email]
(do-reset-password! email))
(GET "/logout" []
(do (session/session-delete-key! :user)
(response/redirect "/"))))