Add basic-auth to notebook example (#314)

This commit is contained in:
Michiel Borkent 2020-03-28 20:35:03 +01:00 committed by GitHub
parent 5f6b26e7f3
commit d73144bd4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -5,7 +5,11 @@
'[clojure.java.shell :refer [sh]] '[clojure.java.shell :refer [sh]]
'[clojure.string :as str]) '[clojure.string :as str])
(def debug? false) (def debug? true)
(def user "admin")
(def password "admin")
(def base64 (-> (.getEncoder java.util.Base64)
(.encodeToString (.getBytes (str user ":" password)))))
(def notes-file (io/file (System/getProperty "user.home") ".notes" "notes.txt")) (def notes-file (io/file (System/getProperty "user.home") ".notes" "notes.txt"))
(io/make-parents notes-file) (io/make-parents notes-file)
@ -36,9 +40,24 @@
(str/join " " (map html v)) (str/join " " (map html v))
:else (str v))) :else (str v)))
(defn write-response [out session-id status headers content]
(let [cookie-header (str "Set-Cookie: notes-id=" session-id)
headers (str/join "\r\n" (conj headers cookie-header))
response (str "HTTP/1.1 " status "\r\n"
(str headers "\r\n")
"Content-Length: " (if content (count content)
0)
"\r\n\r\n"
(when content
(str content)))]
(when debug? (println response))
(binding [*out* out]
(print response)
(flush))))
;; the home page ;; the home page
(defn home [] (defn home-response [out session-id]
(str (let [body (str
"<!DOCTYPE html>\n" "<!DOCTYPE html>\n"
(html (html
[:html [:html
@ -49,7 +68,42 @@
[:pre (slurp notes-file)] [:pre (slurp notes-file)]
[:form {:action "/" :method "post"} [:form {:action "/" :method "post"}
[:input {:type "text" :name "note"}] [:input {:type "text" :name "note"}]
[:input {:type "submit" :value "Submit"}]]]]))) [:input {:type "submit" :value "Submit"}]]]]))]
(write-response out session-id "200 OK" nil body)))
(defn basic-auth-response [out session-id]
(write-response out session-id
"401 Unauthorized"
["WWW-Authenticate: Basic realm=\"notes\""]
nil))
(def known-sessions
(atom #{}))
(defn new-session! []
(let [uuid (str (java.util.UUID/randomUUID))]
(swap! known-sessions conj uuid)
uuid))
(defn get-session-id [headers]
(if-let [cookie-header (first (filter #(str/starts-with? % "Cookie: ") headers))]
(let [parts (str/split cookie-header #"; ")]
(if-let [notes-id (first (filter #(str/starts-with? % "notes-id") parts))]
(str/replace notes-id "notes-id=" "")
(new-session!)))
(new-session!)))
(defn basic-auth-header [headers]
(some #(str/starts-with? % "Basic-Auth: ") headers))
(def authenticated-sessions
(atom #{}))
(defn authenticate! [session-id headers]
(or (contains? @authenticated-sessions session-id)
(when (some #(= % (str "Authorization: Basic " base64)) headers)
(swap! authenticated-sessions conj session-id)
true)))
;; run the server ;; run the server
(with-open [server-socket (let [s (new ServerSocket 8080)] (with-open [server-socket (let [s (new ServerSocket 8080)]
@ -60,12 +114,14 @@
(let [out (io/writer (.getOutputStream client-socket)) (let [out (io/writer (.getOutputStream client-socket))
is (.getInputStream client-socket) is (.getInputStream client-socket)
in (io/reader is) in (io/reader is)
response (loop [headers []] [_req & headers :as response]
(loop [headers []]
(let [line (.readLine in)] (let [line (.readLine in)]
(if (str/blank? line) (if (str/blank? line)
headers headers
(recur (conj headers line))))) (recur (conj headers line)))))
data (let [sb (StringBuilder.)] session-id (get-session-id headers)
form-data (let [sb (StringBuilder.)]
(loop [] (loop []
(when (.ready in) (when (.ready in)
(.append sb (char (.read in))) (.append sb (char (.read in)))
@ -73,15 +129,17 @@
(-> (str sb) (-> (str sb)
(java.net.URLDecoder/decode))) (java.net.URLDecoder/decode)))
_ (when debug? (println (str/join "\n" response))) _ (when debug? (println (str/join "\n" response)))
_ (when-not (str/blank? data) _ (when-not (str/blank? form-data)
(when debug? (println data)) (when debug? (println form-data))
(let [note (str/replace data "note=" "")] (let [note (str/replace form-data "note=" "")]
(spit notes-file (str note "\n") :append true))) (spit notes-file (str note "\n") :append true)))
_ (when debug? (println)) _ (when debug? (println))]
body (home)] (cond
(.write out (format "HTTP/1.1 %s OK\r\nContent-Length: %s\r\n\r\n%s" ;; if we didn't see this session before, we want the user to re-authenticate
200 (not (contains? @known-sessions session-id))
(count body) (let [uuid (new-session!)]
body)) (basic-auth-response out uuid))
(.flush out)) (not (authenticate! session-id headers))
(basic-auth-response out session-id)
:else (home-response out session-id)))
(recur))) (recur)))