Skip to content

Commit 6524e1f

Browse files
committed
Don't throw with invalid address on password reset
If the user doesn't enter a value on the password reset form, we look up a user with an email of "", which matches deleted users. We then try to send an email to that address, which throws. This now catches and logs that case, and gives the default message to the user that we will send an email if a user was found.
1 parent 43b2f04 commit 6524e1f

File tree

3 files changed

+68
-34
lines changed

3 files changed

+68
-34
lines changed

src/clojars/email.clj

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
(ns clojars.email
22
(:require
3+
[clojars.config :as config]
34
[clojars.log :as log]
45
[clojure.edn :as edn]
56
[clojure.java.io :as io])
@@ -15,29 +16,35 @@
1516
(def ^:private email-denylist
1617
(edn/read-string (slurp (io/resource "email-denylist.edn"))))
1718

18-
(defn simple-mailer [{:keys [hostname username password port tls? from]}]
19+
(defn- build-email
20+
[{:keys [hostname username password port tls? from]} ^String to subject message]
21+
(if (contains? email-denylist to)
22+
(log/info {:status :denylist})
23+
(let [mail (doto (SimpleEmail.)
24+
(.setHostName (or hostname "localhost"))
25+
(.setSmtpPort (or port 25))
26+
(.setStartTLSEnabled (boolean tls?))
27+
(.setStartTLSRequired (boolean tls?))
28+
(.setFrom (or from "contact@clojars.org") "Clojars")
29+
(.addTo to)
30+
(.setSubject subject)
31+
(.setMsg message))]
32+
(when tls?
33+
(.setSslSmtpPort mail (str (or port 25))))
34+
(when (and username password)
35+
(.setAuthentication mail username password))
36+
mail)))
37+
38+
(defn simple-mailer
39+
[config]
1940
(fn [^String to subject message]
2041
(log/with-context {:tag :email
2142
:email-to to
2243
:email-subject subject}
2344
(try
24-
(if (contains? email-denylist to)
25-
(log/info {:status :denylist})
26-
(let [mail (doto (SimpleEmail.)
27-
(.setHostName (or hostname "localhost"))
28-
(.setSmtpPort (or port 25))
29-
(.setStartTLSEnabled (boolean tls?))
30-
(.setStartTLSRequired (boolean tls?))
31-
(.setFrom (or from "contact@clojars.org") "Clojars")
32-
(.addTo to)
33-
(.setSubject subject)
34-
(.setMsg message))]
35-
(when tls?
36-
(.setSslSmtpPort mail (str (or port 25))))
37-
(when (and username password)
38-
(.setAuthentication mail username password))
39-
(.send mail)
40-
(log/info {:status :success})))
45+
(let [^SimpleEmail mail (build-email config to subject message)]
46+
(.send mail)
47+
(log/info {:status :success}))
4148
(catch Exception e
4249
(log/error {:status :failed
4350
:error e})
@@ -67,5 +74,7 @@
6774
(defn mock-mailer []
6875
(expect-mock-emails)
6976
(fn [to subject message]
70-
(swap! mock-emails conj [to subject message])
77+
;; Allows us to test the email generation logic
78+
(let [email (build-email (:mail (config/config)) to subject message)]
79+
(swap! mock-emails conj [to subject message email]))
7180
(.countDown ^CountDownLatch @email-latch))))

src/clojars/web/user.clj

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -191,24 +191,32 @@
191191
:email-or-username)
192192
(submit-button "Email me a password reset link"))]))
193193

194+
(defn- send-forgot-password-email
195+
[db mailer user details]
196+
(let [reset-code (db/set-password-reset-code! db (:user user))
197+
base-url (:base-url (config/config))
198+
reset-password-url (str base-url "/password-resets/" reset-code)]
199+
(log/info {:tag :password-reset-code-generated})
200+
(try
201+
(mailer (:email user)
202+
"Password reset for Clojars"
203+
(->> ["Hello,"
204+
(format "We received a request from someone, hopefully you, to reset the password of the clojars user: %s." (:user user))
205+
"To continue with the reset password process, click on the following link:"
206+
reset-password-url
207+
"This link is valid for 24 hours, after which you will need to generate a new one."
208+
(notif-common/details-table details)
209+
"If you didn't reset your password then you can ignore this email."]
210+
(interpose "\n\n")
211+
(apply str)))
212+
(catch Exception e
213+
(log/error {:tag :failed-password-reset-email
214+
:error e})))))
215+
194216
(defn forgot-password [db mailer {:keys [email-or-username]} details]
195217
(log/with-context {:email-or-username email-or-username}
196218
(if-let [user (find-user-by-user-or-email db email-or-username)]
197-
(let [reset-code (db/set-password-reset-code! db (:user user))
198-
base-url (:base-url (config/config))
199-
reset-password-url (str base-url "/password-resets/" reset-code)]
200-
(log/info {:tag :password-reset-code-generated})
201-
(mailer (:email user)
202-
"Password reset for Clojars"
203-
(->> ["Hello,"
204-
(format "We received a request from someone, hopefully you, to reset the password of the clojars user: %s." (:user user))
205-
"To continue with the reset password process, click on the following link:"
206-
reset-password-url
207-
"This link is valid for 24 hours, after which you will need to generate a new one."
208-
(notif-common/details-table details)
209-
"If you didn't reset your password then you can ignore this email."]
210-
(interpose "\n\n")
211-
(apply str))))
219+
(send-forgot-password-email db mailer user details)
212220
(log/info {:tag :password-reset-user-not-found})))
213221
(html-doc "Forgot password?" {}
214222
[:div.small-section [:h1 "Forgot password?"]

test/clojars/integration/users_test.clj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,23 @@
360360
(is (re-find #"has changed the password on your 'fixture'" body))
361361
(is (re-find #"Client IP" body))))
362362

363+
(deftest user-can-enter-invalid-email-for-password-reset
364+
(email/expect-mock-emails 0)
365+
(-> (session (help/app))
366+
(register-as "fixture" "fixture@example.org" "password"))
367+
;; force an invalid email address
368+
(db/update-user help/*db* "fixture" "" nil)
369+
(-> (session (help/app))
370+
(visit "/")
371+
(follow "login")
372+
(follow "Forgot your username or password?")
373+
(fill-in "Email or Username" "fixture")
374+
(press "Email me a password reset link")
375+
(has (status? 200))
376+
(within [:p]
377+
(has (text? "If your account was found, you should get an email with a link to reset your password soon."))))
378+
(is (empty? @email/mock-emails)))
379+
363380
(deftest bad-reset-code-shows-message
364381
(-> (session (help/app))
365382
(visit "/password-resets/this-code-does-not-exist")

0 commit comments

Comments
 (0)