Skip to content
This repository
Fetching contributors…

Cannot retrieve contributors at this time

file 129 lines (118 sloc) 4.73 kb
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
(ns foreclojure.login
  (:require [noir.session :as session]
            [ring.util.response :as response])
  (:import [org.jasypt.util.password StrongPasswordEncryptor])
  (:use [hiccup.form :only [form-to label text-field password-field check-box]]
            [foreclojure.utils :only [from-mongo flash-error flash-msg form-row assuming send-email login-url]]
            [foreclojure.template :only [def-page content-page]]
            [foreclojure.messages :only [err-msg]]
            [compojure.core :only [defroutes GET POST]]
            [useful.map :only [keyed]]
            [clojail.core :only [thunk-timeout]]
            [clojure.stacktrace :only [print-cause-trace]]
            [somnium.congomongo :only [update! fetch-one]]))

(def password-reset-url "https://www.4clojure.com/settings")

(def login-box
  (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?"]]]]))

(def-page my-login-page [location]
  (do
    (if location (session/put! :login-to location))
    {:title "4clojure - login"
     :content
     (content-page
      {:main login-box})}))

(defn do-login [user pwd]
  (let [user (.toLowerCase user)
        {db-pwd :pwd} (from-mongo (fetch-one :users :where {:user user}))
        location (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/put! :user user)
          (session/remove! :login-to)
          (response/redirect (or location "/problems")))
      (flash-error "/login" "Error logging in."))))

(def-page reset-password-page []
  {:title "Reset password"
   :content
   [: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)]
      (keyed [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"
           :text
           (str "The password for your 4clojure.com account "
                name " has been reset to " pw ". Make sure to change it"
                " soon at " password-reset-url " - 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/put! :login-to password-reset-url)
            (flash-msg (login-url password-reset-url)
              "Your password has been reset! You should receive an email soon."))
        (do (spit (str name ".pwd") diagnostics)
            (flash-error "/login/reset"
              (err-msg "security.err-pwd-email" name)))))
    (flash-error "/login/reset"
      (err-msg "security.err-unknown"))))

(defroutes login-routes
  (GET "/login" [location] (my-login-page location))
  (POST "/login" {{:strs [user pwd]} :form-params}
    (do-login user pwd))

  (GET "/login/reset" [] (reset-password-page))
  (POST "/login/reset" [email]
    (do-reset-password! email))

  (GET "/logout" []
    (do (session/remove! :user)
        (response/redirect "/"))))
Something went wrong with that request. Please try again.