diff --git a/.dir-locals.el b/.dir-locals.el
index 240f200944ce8031f67b547284a3efbc3f96a1aa..7e8f02585098cd448c17069d196d8052cc66ce13 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -33,6 +33,7 @@
                               (expect-with-all-drivers 1)
                               (expect-with-dataset 1)
                               (expect-with-datasets 1)
+                              (format-color 2)
                               (ins 1)
                               (let-400 1)
                               (let-404 1)
diff --git a/src/metabase/api/routes.clj b/src/metabase/api/routes.clj
index 4c7b7068d7e367e28f71f1ebbc604dfa73085038..f1fee83d1f5dc9c202cf0bc6e15095b6e0b1b6d4 100644
--- a/src/metabase/api/routes.clj
+++ b/src/metabase/api/routes.clj
@@ -17,15 +17,15 @@
                           [tiles :as tiles]
                           [user :as user]
                           [util :as util])
-            [metabase.middleware.auth :as auth]))
+            [metabase.middleware :as middleware]))
 
 (def ^:private +apikey
   "Wrap API-ROUTES so they may only be accessed with proper apikey credentials."
-  auth/enforce-api-key)
+  middleware/enforce-api-key)
 
 (def ^:private +auth
   "Wrap API-ROUTES so they may only be accessed with proper authentiaction credentials."
-  auth/enforce-authentication)
+  middleware/enforce-authentication)
 
 (defroutes routes
   (context "/activity"     [] (+auth activity/routes))
diff --git a/src/metabase/core.clj b/src/metabase/core.clj
index 0086c7b9723fb1f0ee642ada0e6fcdbfa1e70357..61e8b5e1ea5f3bf7e225111f71f3e156cdab64a6 100644
--- a/src/metabase/core.clj
+++ b/src/metabase/core.clj
@@ -1,8 +1,7 @@
 ;; -*- comment-column: 35; -*-
 (ns metabase.core
   (:gen-class)
-  (:require [clojure.java.browse :refer [browse-url]]
-            [clojure.string :as s]
+  (:require [clojure.string :as s]
             [clojure.tools.logging :as log]
             [colorize.core :as color]
             [ring.adapter.jetty :as ring-jetty]
@@ -13,17 +12,15 @@
                              [keyword-params :refer [wrap-keyword-params]]
                              [params :refer [wrap-params]]
                              [session :refer [wrap-session]])
-            [medley.core :as medley]
+            [medley.core :as m]
             (metabase [config :as config]
                       [db :as db]
                       [driver :as driver]
                       [events :as events]
+                      [middleware :as mb-middleware]
                       [routes :as routes]
                       [setup :as setup]
                       [task :as task])
-            (metabase.middleware [auth :as auth]
-                                 [log-api-call :refer :all]
-                                 [format :refer :all])
             (metabase.models [setting :refer [defsetting]]
                              [database :refer [Database]]
                              [user :refer [User]])))
@@ -54,21 +51,21 @@
 (def app
   "The primary entry point to the HTTP server"
   (-> routes/routes
-      (log-api-call :request :response)
-      add-security-headers         ; [METABASE] Add HTTP headers to API responses to prevent them from being cached
-      format-response              ; [METABASE] Do formatting before converting to JSON so serializer doesn't barf
-      (wrap-json-body              ; extracts json POST body and makes it avaliable on request
+      (mb-middleware/log-api-call :request :response)
+      mb-middleware/add-security-headers              ; [METABASE] Add HTTP headers to API responses to prevent them from being cached
+      mb-middleware/format-response                   ; [METABASE] Do formatting before converting to JSON so serializer doesn't barf
+      (wrap-json-body                                 ; extracts json POST body and makes it avaliable on request
         {:keywords? true})
-      wrap-json-response           ; middleware to automatically serialize suitable objects as JSON in responses
-      wrap-keyword-params          ; converts string keys in :params to keyword keys
-      wrap-params                  ; parses GET and POST params as :query-params/:form-params and both as :params
-      auth/bind-current-user       ; Binds *current-user* and *current-user-id* if :metabase-user-id is non-nil
-      auth/wrap-current-user-id    ; looks for :metabase-session-id and sets :metabase-user-id if Session ID is valid
-      auth/wrap-api-key            ; looks for a Metabase API Key on the request and assocs as :metabase-api-key
-      auth/wrap-session-id         ; looks for a Metabase Session ID and assoc as :metabase-session-id
-      wrap-cookies                 ; Parses cookies in the request map and assocs as :cookies
-      wrap-session                 ; reads in current HTTP session and sets :session/key
-      wrap-gzip))                  ; GZIP response if client can handle it
+      wrap-json-response                              ; middleware to automatically serialize suitable objects as JSON in responses
+      wrap-keyword-params                             ; converts string keys in :params to keyword keys
+      wrap-params                                     ; parses GET and POST params as :query-params/:form-params and both as :params
+      mb-middleware/bind-current-user                 ; Binds *current-user* and *current-user-id* if :metabase-user-id is non-nil
+      mb-middleware/wrap-current-user-id              ; looks for :metabase-session-id and sets :metabase-user-id if Session ID is valid
+      mb-middleware/wrap-api-key                      ; looks for a Metabase API Key on the request and assocs as :metabase-api-key
+      mb-middleware/wrap-session-id                   ; looks for a Metabase Session ID and assoc as :metabase-session-id
+      wrap-cookies                                    ; Parses cookies in the request map and assocs as :cookies
+      wrap-session                                    ; reads in current HTTP session and sets :session/key
+      wrap-gzip))                                     ; GZIP response if client can handle it
 
 (defn- -init-create-setup-token
   "Create and set a new setup token, and open the setup URL on the user's system."
@@ -129,14 +126,14 @@
   "Start the embedded Jetty web server."
   []
   (when-not @jetty-instance
-    (let [jetty-config (cond-> (medley/filter-vals identity {:port (config/config-int :mb-jetty-port)
-                                                             :host (config/config-str :mb-jetty-host)
-                                                             :max-threads (config/config-int :mb-jetty-maxthreads)
-                                                             :min-threads (config/config-int :mb-jetty-minthreads)
-                                                             :max-queued (config/config-int :mb-jetty-maxqueued)
-                                                             :max-idle-time (config/config-int :mb-jetty-maxidletime)})
-                               (config/config-str :mb-jetty-join) (assoc :join? (config/config-bool :mb-jetty-join))
-                               (config/config-str :mb-jetty-daemon) (assoc :daemon? (config/config-bool :mb-jetty-daemon)))]
+    (let [jetty-config (cond-> (m/filter-vals identity {:port (config/config-int :mb-jetty-port)
+                                                        :host (config/config-str :mb-jetty-host)
+                                                        :max-threads (config/config-int :mb-jetty-maxthreads)
+                                                        :min-threads (config/config-int :mb-jetty-minthreads)
+                                                        :max-queued (config/config-int :mb-jetty-maxqueued)
+                                                        :max-idle-time (config/config-int :mb-jetty-maxidletime)})
+                         (config/config-str :mb-jetty-join) (assoc :join? (config/config-bool :mb-jetty-join))
+                         (config/config-str :mb-jetty-daemon) (assoc :daemon? (config/config-bool :mb-jetty-daemon)))]
       (log/info "Launching Embedded Jetty Webserver with config:\n" (with-out-str (clojure.pprint/pprint jetty-config)))
       (->> (ring-jetty/run-jetty app jetty-config)
            (reset! jetty-instance)))))
diff --git a/src/metabase/middleware.clj b/src/metabase/middleware.clj
new file mode 100644
index 0000000000000000000000000000000000000000..2432a1df06bcda093d857c013e37b9ddf7cc2c83
--- /dev/null
+++ b/src/metabase/middleware.clj
@@ -0,0 +1,239 @@
+(ns metabase.middleware
+  "Metabase-specific middleware functions & configuration."
+  (:require [clojure.math.numeric-tower :as math]
+            [clojure.tools.logging :as log]
+            [clojure.walk :as walk]
+            (cheshire factory
+                      [generate :refer [add-encoder encode-str]])
+            [korma.core :as k]
+            [medley.core :refer [filter-vals map-vals]]
+            [metabase.api.common :refer [*current-user* *current-user-id*]]
+            [metabase.config :as config]
+            [metabase.db :refer [sel]]
+            (metabase.models [interface :refer [api-serialize]]
+                             [session :refer [Session]]
+                             [user :refer [User]])
+            [metabase.util :as u]))
+
+;;; # ------------------------------------------------------------ UTIL FNS ------------------------------------------------------------
+
+(defn- api-call?
+  "Is this ring request an API call (does path start with `/api`)?"
+  [{:keys [^String uri]}]
+  (and (>= (count uri) 4)
+       (= (.substring uri 0 4) "/api")))
+
+
+;;; # ------------------------------------------------------------ AUTH & SESSION MANAGEMENT ------------------------------------------------------------
+
+(def ^:const metabase-session-cookie "metabase.SESSION_ID")
+(def ^:const metabase-session-header "x-metabase-session")
+(def ^:const metabase-api-key-header "x-metabase-apikey")
+
+(def ^:const response-unauthentic {:status 401 :body "Unauthenticated"})
+(def ^:const response-forbidden   {:status 403 :body "Forbidden"})
+
+
+(defn wrap-session-id
+  "Middleware that sets the `:metabase-session-id` keyword on the request if a session id can be found.
+
+   We first check the request :cookies for `metabase.SESSION_ID`, then if no cookie is found we look in the
+   http headers for `X-METABASE-SESSION`.  If neither is found then then no keyword is bound to the request."
+  [handler]
+  (fn [{:keys [cookies headers] :as request}]
+    (if-let [session-id (or (get-in cookies [metabase-session-cookie :value]) (headers metabase-session-header))]
+      ;; alternatively we could always associate the keyword and just let it be nil if there is no value
+      (handler (assoc request :metabase-session-id session-id))
+      (handler request))))
+
+
+(defn wrap-current-user-id
+  "Add `:metabase-user-id` to the request if a valid session token was passed."
+  [handler]
+  (fn [{:keys [metabase-session-id] :as request}]
+    ;; TODO - what kind of validations can we do on the sessionid to make sure it's safe to handle?  str?  alphanumeric?
+    (handler (or (when metabase-session-id
+                   (when-let [session (first (k/select Session
+                                                       ;; NOTE: we join with the User table and ensure user.is_active = true
+                                                       (k/with User (k/where {:is_active true}))
+                                                       (k/fields :created_at :user_id)
+                                                       (k/where {:id metabase-session-id})))]
+                     (let [session-age-ms (- (System/currentTimeMillis) (.getTime ^java.util.Date (get session :created_at (java.util.Date. 0))))]
+                       ;; If the session exists and is not expired (max-session-age > session-age) then validation is good
+                       (when (and session (> (config/config-int :max-session-age) (quot session-age-ms 60000)))
+                         (assoc request :metabase-user-id (:user_id session))))))
+                 request))))
+
+
+(defn enforce-authentication
+  "Middleware that returns a 401 response if REQUEST has no associated `:metabase-user-id`."
+  [handler]
+  (fn [{:keys [metabase-user-id] :as request}]
+    (if metabase-user-id
+      (handler request)
+      response-unauthentic)))
+
+
+(defn bind-current-user
+  "Middleware that binds `metabase.api.common/*current-user*` and `*current-user-id*`
+
+   *  `*current-user-id*` int ID or nil of user associated with request
+   *  `*current-user*`    delay that returns current user (or nil) from DB"
+  [handler]
+  (fn [request]
+    (if-let [current-user-id (:metabase-user-id request)]
+      (binding [*current-user-id* current-user-id
+                *current-user*    (delay (sel :one `[User ~@(:metabase.models.interface/default-fields User) :is_active :is_staff], :id current-user-id))]
+        (handler request))
+      (handler request))))
+
+
+(defn wrap-api-key
+  "Middleware that sets the `:metabase-api-key` keyword on the request if a valid API Key can be found.
+   We check the request headers for `X-METABASE-APIKEY` and if it's not found then then no keyword is bound to the request."
+  [handler]
+  (fn [{:keys [headers] :as request}]
+    (if-let [api-key (headers metabase-api-key-header)]
+      (handler (assoc request :metabase-api-key api-key))
+      (handler request))))
+
+
+(defn enforce-api-key
+  "Middleware that enforces validation of the client via API Key, cancelling the request processing if the check fails.
+
+   Validation is handled by first checking for the presence of the `:metabase-api-key` on the request.  If the api key
+   is available then we validate it by checking it against the configured `:mb-api-key` value set in our global config.
+
+   If the request `:metabase-api-key` matches the configured `:mb-api-key` value then the request continues, otherwise we
+   reject the request and return a 403 Forbidden response."
+  [handler]
+  (fn [{:keys [metabase-api-key] :as request}]
+    (if (= (config/config-str :mb-api-key) metabase-api-key)
+      (handler request)
+      ;; default response is 403
+      response-forbidden)))
+
+
+;;; # ------------------------------------------------------------ SECURITY HEADERS ------------------------------------------------------------
+
+(defn add-security-headers
+  "Add HTTP headers to tell browsers not to cache API responses."
+  [handler]
+  (fn [request]
+    (let [response (handler request)]
+      (update response :headers merge (when (api-call? request)
+                                        {"Cache-Control" "max-age=0, no-cache, must-revalidate, proxy-revalidate"
+                                         "Expires"       "Tue, 03 Jul 2001 06:00:00 GMT" ; rando date in the past
+                                         "Last-Modified" "{now} GMT"})))))
+
+
+;;; # ------------------------------------------------------------ JSON SERIALIZATION CONFIG ------------------------------------------------------------
+
+;; Tell the JSON middleware to use a date format that includes milliseconds
+(intern 'cheshire.factory 'default-date-format "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
+
+;; ## Custom JSON encoders
+
+;; stringify JDBC clobs
+(add-encoder org.h2.jdbc.JdbcClob (fn [clob ^com.fasterxml.jackson.core.JsonGenerator json-generator]
+                                    (.writeString json-generator (u/jdbc-clob->str clob))))
+
+;; stringify Postgres binary objects (e.g. PostGIS geometries)
+(add-encoder org.postgresql.util.PGobject encode-str)
+
+;; Do the same for PG arrays
+(add-encoder org.postgresql.jdbc4.Jdbc4Array encode-str)
+
+;; Encode BSON IDs like strings
+(add-encoder org.bson.types.ObjectId encode-str)
+
+;; serialize sql dates (i.e., QueryProcessor results) like YYYY-MM-DD instead of as a full-blown timestamp
+(add-encoder java.sql.Date (fn [^java.sql.Date date ^com.fasterxml.jackson.core.JsonGenerator json-generator]
+                             (.writeString json-generator (.toString date))))
+
+(defn- remove-fns-and-delays
+  "Remove values that are fns or delays from map M."
+  [m]
+  (filter-vals #(not (or (delay? %)
+                         (fn? %)))
+               ;; Convert typed maps such as metabase.models.database/DatabaseInstance to plain maps because empty, which is used internally by filter-vals,
+               ;; will fail otherwise
+               (into {} m)))
+
+(defn format-response
+  "Middleware that recurses over Clojure object before it gets converted to JSON and makes adjustments neccessary so the formatter doesn't barf.
+   e.g. functions and delays are stripped and H2 Clobs are converted to strings."
+  [handler]
+  (let [-format-response (fn -format-response [obj]
+                           (cond
+                             (map? obj)  (->> (api-serialize obj)
+                                              remove-fns-and-delays
+                                              (map-vals -format-response)) ; recurse over all vals in the map
+                             (coll? obj) (map -format-response obj)        ; recurse over all items in the collection
+                             :else       obj))]
+    (fn [request]
+      (-format-response (handler request)))))
+
+
+
+;;; # ------------------------------------------------------------ LOGGING ------------------------------------------------------------
+
+(def ^:private ^:const sensitive-fields
+  "Fields that we should censor before logging."
+  #{:password})
+
+(defn- scrub-sensitive-fields
+  "Replace values of fields in `sensitive-fields` with `\"**********\"` before logging."
+  [request]
+  (walk/prewalk (fn [form]
+                  (if-not (and (vector? form)
+                               (= (count form) 2)
+                               (keyword? (first form))
+                               (contains? sensitive-fields (first form)))
+                    form
+                    [(first form) "**********"]))
+                request))
+
+(defn- log-request [{:keys [uri request-method body query-string]}]
+  (log/debug (u/format-color 'blue "%s %s "
+                             (.toUpperCase (name request-method)) (str uri
+                                                                       (when-not (empty? query-string)
+                                                                         (str "?" query-string)))
+                             (when (or (string? body) (coll? body))
+                               (str "\n" (u/pprint-to-str (scrub-sensitive-fields body)))))))
+
+(defn- log-response [{:keys [uri request-method]} {:keys [status body]} elapsed-time]
+  (let [log-error #(log/error %) ; these are macros so we can't pass by value :sad:
+        log-debug #(log/debug %)
+        log-warn  #(log/warn  %)
+        [error? color log-fn] (cond
+                                (>= status 500) [true  'red   log-error]
+                                (=  status 403) [true  'red   log-warn]
+                                (>= status 400) [true  'red   log-debug]
+                                :else           [false 'green log-debug])]
+    (log-fn (str (u/format-color color "%s %s %d (%d ms)" (.toUpperCase (name request-method)) uri status elapsed-time)
+                 ;; only print body on error so we don't pollute our environment by over-logging
+                 (when (and error?
+                            (or (string? body) (coll? body)))
+                   (str "\n" (u/pprint-to-str body)))))))
+
+(defn log-api-call
+  "Middleware to log `:request` and/or `:response` by passing corresponding OPTIONS."
+  [handler & options]
+  (let [{:keys [request response]} (set options)
+        log-request? request
+        log-response? response]
+    (fn [request]
+      (if-not (api-call? request) (handler request)
+              (do
+                (when log-request?
+                  (log-request request))
+                (let [start-time (System/nanoTime)
+                      response (handler request)
+                      elapsed-time (-> (- (System/nanoTime) start-time)
+                                       double
+                                       (/ 1000000.0)
+                                       math/round)]
+                  (when log-response?
+                    (log-response request response elapsed-time))
+                  response))))))
diff --git a/src/metabase/middleware/auth.clj b/src/metabase/middleware/auth.clj
deleted file mode 100644
index 3e99d6eb9cb4ec201e44fc5bca2b46d70ba30356..0000000000000000000000000000000000000000
--- a/src/metabase/middleware/auth.clj
+++ /dev/null
@@ -1,101 +0,0 @@
-(ns metabase.middleware.auth
-  "Middleware for dealing with authentication and session management."
-  (:require [korma.core :as k]
-            [metabase.config :as config]
-            [metabase.db :refer [sel]]
-            [metabase.api.common :refer [*current-user* *current-user-id*]]
-            (metabase.models [session :refer [Session]]
-                             [user :refer [User current-user-fields]])))
-
-
-(def ^:const metabase-session-cookie "metabase.SESSION_ID")
-(def ^:const metabase-session-header "x-metabase-session")
-(def ^:const metabase-api-key-header "x-metabase-apikey")
-
-(def ^:const response-unauthentic {:status 401 :body "Unauthenticated"})
-(def ^:const response-forbidden {:status 403 :body "Forbidden"})
-
-
-(defn wrap-session-id
-  "Middleware that sets the `:metabase-session-id` keyword on the request if a session id can be found.
-
-   We first check the request :cookies for `metabase.SESSION_ID`, then if no cookie is found we look in the
-   http headers for `X-METABASE-SESSION`.  If neither is found then then no keyword is bound to the request."
-  [handler]
-  (fn [{:keys [cookies headers] :as request}]
-    (if-let [session-id (or (get-in cookies [metabase-session-cookie :value]) (headers metabase-session-header))]
-      ;; alternatively we could always associate the keyword and just let it be nil if there is no value
-      (handler (assoc request :metabase-session-id session-id))
-      (handler request))))
-
-(defn wrap-current-user-id
-  "Add `:metabase-user-id` to the request if a valid session token was passed."
-  [handler]
-  (fn [{:keys [metabase-session-id] :as request}]
-    ;; TODO - what kind of validations can we do on the sessionid to make sure it's safe to handle?  str?  alphanumeric?
-    (handler (or (when metabase-session-id
-                   (when-let [session (first (k/select Session
-                                                       ;; NOTE: we join with the User table and ensure user.is_active = true
-                                                       (k/with User (k/where {:is_active true}))
-                                                       (k/fields :created_at :user_id)
-                                                       (k/where {:id metabase-session-id})))]
-                     (let [session-age-ms (- (System/currentTimeMillis) (.getTime ^java.util.Date (get session :created_at (java.util.Date. 0))))]
-                       ;; If the session exists and is not expired (max-session-age > session-age) then validation is good
-                       (when (and session (> (config/config-int :max-session-age) (quot session-age-ms 60000)))
-                         (assoc request :metabase-user-id (:user_id session))))))
-                 request))))
-
-
-(defn enforce-authentication
-  "Middleware that returns a 401 response if REQUEST has no associated `:metabase-user-id`."
-  [handler]
-  (fn [{:keys [metabase-user-id] :as request}]
-    (if metabase-user-id
-      (handler request)
-      response-unauthentic)))
-
-
-(defmacro sel-current-user [current-user-id]
-  `(sel :one [User ~@current-user-fields]
-     :id ~current-user-id))
-
-
-(defn bind-current-user
-  "Middleware that binds `metabase.api.common/*current-user*` and `*current-user-id*`
-
-   *current-user-id* int ID or nil of user associated with request
-   *current-user*    delay that returns current user (or nil) from DB"
-  [handler]
-  (fn [request]
-    (if-let [current-user-id (:metabase-user-id request)]
-      (binding [*current-user-id* current-user-id
-                *current-user*    (delay (sel-current-user current-user-id))]
-        (handler request))
-      (handler request))))
-
-
-(defn wrap-api-key
-  "Middleware that sets the :metabase-api-key keyword on the request if a valid API Key can be found.
-
-   We check the request headers for `X-METABASE-APIKEY` and if it's not found then then no keyword is bound to the request."
-  [handler]
-  (fn [{:keys [headers] :as request}]
-    (if-let [api-key (headers metabase-api-key-header)]
-      (handler (assoc request :metabase-api-key api-key))
-      (handler request))))
-
-
-(defn enforce-api-key
-  "Middleware that enforces validation of the client via API Key, cancelling the request processing if the check fails.
-
-   Validation is handled by first checking for the presence of the :metabase-api-key on the request.  If the api key
-   is available then we validate it by checking it against the configured :mb-api-key value set in our global config.
-
-   If the request :metabase-api-key matches the configured :mb-api-key value then the request continues, otherwise we
-   reject the request and return a 403 Forbidden response."
-  [handler]
-  (fn [{:keys [metabase-api-key] :as request}]
-    (if (= (config/config-str :mb-api-key) metabase-api-key)
-      (handler request)
-      ;; default response is 403
-      response-forbidden)))
diff --git a/src/metabase/middleware/format.clj b/src/metabase/middleware/format.clj
deleted file mode 100644
index 36117d5967d117c96f98f5f5bceb58b93adfc064..0000000000000000000000000000000000000000
--- a/src/metabase/middleware/format.clj
+++ /dev/null
@@ -1,69 +0,0 @@
-(ns metabase.middleware.format
-  (:require [clojure.core.match :refer [match]]
-            (cheshire factory
-                      [generate :refer [add-encoder encode-str]])
-            [medley.core :refer [filter-vals map-vals]]
-            [metabase.middleware.log-api-call :refer [api-call?]]
-            [metabase.models.interface :refer [api-serialize]]
-            [metabase.util :as util]))
-
-(declare -format-response)
-
-;; ## SHADY HACK
-;; Tell the JSON middleware to use a date format that includes milliseconds
-(intern 'cheshire.factory 'default-date-format "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
-
-
-;; ## Custom JSON encoders
-
-;; stringify JDBC clobs
-(add-encoder org.h2.jdbc.JdbcClob (fn [clob ^com.fasterxml.jackson.core.JsonGenerator json-generator]
-                                    (.writeString json-generator (util/jdbc-clob->str clob))))
-
-;; stringify Postgres binary objects (e.g. PostGIS geometries)
-(add-encoder org.postgresql.util.PGobject encode-str)
-
-;; Do the same for PG arrays
-(add-encoder org.postgresql.jdbc4.Jdbc4Array encode-str)
-
-;; Encode BSON IDs like strings
-(add-encoder org.bson.types.ObjectId encode-str)
-
-;; serialize sql dates (i.e., QueryProcessor results) like YYYY-MM-DD instead of as a full-blown timestamp
-(add-encoder java.sql.Date (fn [^java.sql.Date date ^com.fasterxml.jackson.core.JsonGenerator json-generator]
-                             (.writeString json-generator (.toString date))))
-
-(defn add-security-headers
-  "Add HTTP headers to tell browsers not to cache API responses."
-  [handler]
-  (fn [request]
-    (let [response (handler request)]
-      (update response :headers merge (when (api-call? request)
-                                        {"Cache-Control" "max-age=0, no-cache, must-revalidate, proxy-revalidate"
-                                         "Expires"       "Tue, 03 Jul 2001 06:00:00 GMT" ; rando date in the past
-                                         "Last-Modified" "{now} GMT"})))))
-
-;; ## FORMAT RESPONSE MIDDLEWARE
-(defn format-response
-  "Middleware that recurses over Clojure object before it gets converted to JSON and makes adjustments neccessary so the formatter doesn't barf.
-   e.g. functions and delays are stripped and H2 Clobs are converted to strings."
-  [handler]
-  (fn [request]
-    (-format-response (handler request))))
-
-(defn- remove-fns-and-delays
-  "Remove values that are fns or delays from map M."
-  [m]
-  (filter-vals #(not (or (delay? %)
-                         (fn? %)))
-               ;; Convert typed maps such as metabase.models.database/DatabaseInstance to plain maps because empty, which is used internally by filter-vals,
-               ;; will fail otherwise
-               (into {} m)))
-
-(defn- -format-response [obj]
-  (cond
-    (map? obj)  (->> (api-serialize obj)
-                     remove-fns-and-delays
-                     (map-vals -format-response)) ; recurse over all vals in the map
-    (coll? obj) (map -format-response obj) ; recurse over all items in the collection
-    :else       obj))
diff --git a/src/metabase/middleware/log_api_call.clj b/src/metabase/middleware/log_api_call.clj
deleted file mode 100644
index 4e096109054f30f9124bad21a653f3760453dc67..0000000000000000000000000000000000000000
--- a/src/metabase/middleware/log_api_call.clj
+++ /dev/null
@@ -1,78 +0,0 @@
-(ns metabase.middleware.log-api-call
-  "Middleware to log API calls. Primarily for debugging purposes."
-  (:require [clojure.math.numeric-tower :as math]
-            [clojure.pprint :refer [pprint]]
-            [clojure.tools.logging :as log]
-            [colorize.core :as color]))
-
-(declare api-call?
-         log-request
-         log-response)
-
-(def ^:private sensitive-fields
-  "Fields that we should censor before logging."
-  #{:password})
-
-(defn- scrub-sensitive-fields
-  "Replace values of fields in `sensitive-fields` with `\"**********\"` before logging."
-  [request]
-  (clojure.walk/prewalk (fn [form]
-                          (if-not (and (vector? form)
-                                       (= (count form) 2)
-                                       (keyword? (first form))
-                                       (contains? sensitive-fields (first form)))
-                            form
-                            [(first form) "**********"]))
-                        request))
-
-(def ^:private only-display-output-on-error
-  "Set this to `false` to see all API responses."
-  true)
-
-(defn log-api-call
-  "Middleware to log `:request` and/or `:response` by passing corresponding OPTIONS."
-  [handler & options]
-  (let [{:keys [request response]} (set options)
-        log-request? request
-        log-response? response]
-    (fn [request]
-      (if-not (api-call? request) (handler request)
-              (do
-                (when log-request?
-                  (log-request request))
-                (let [start-time (System/nanoTime)
-                      response (handler request)
-                      elapsed-time (-> (- (System/nanoTime) start-time)
-                                       double
-                                       (/ 1000000.0)
-                                       math/round)]
-                  (when log-response?
-                    (log-response request response elapsed-time))
-                  response))))))
-
-(defn api-call?
-  "Is this ring request an API call (does path start with `/api`)?"
-  [{:keys [^String uri]}]
-  (and (>= (count uri) 4)
-       (= (.substring uri 0 4) "/api")))
-
-(defn- log-request [{:keys [uri request-method body query-string]}]
-  (log/debug (color/blue (format "%s %s " (.toUpperCase (name request-method)) (str uri
-                                                                                    (when-not (empty? query-string)
-                                                                                      (str "?" query-string))))
-                         (when (or (string? body) (coll? body))
-                           (str "\n" (with-out-str (pprint (scrub-sensitive-fields body))))))))
-
-(defn- log-response [{:keys [uri request-method]} {:keys [status body]} elapsed-time]
-  (let [log-error (fn [& args] (log/error (apply str args))) ; inconveniently these are not macros
-        log-debug (fn [& args] (log/debug (apply str args)))
-        log-warn  (fn [& args] (log/warn  (apply str args)))
-        [error? color-fn log-fn] (cond
-                                   (>= status 500) [true  color/red   log-error]
-                                   (=  status 403) [true  color/red   log-warn]
-                                   (>= status 400) [true  color/red   log-debug]
-                                   :else           [false color/green log-debug])]
-    (log-fn (color-fn (format "%s %s %d (%d ms)" (.toUpperCase (name request-method)) uri status elapsed-time)
-                      (when (or error? (not only-display-output-on-error))
-                        (when (or (string? body) (coll? body))
-                          (str "\n" (with-out-str (pprint body)))))))))
diff --git a/src/metabase/models/user.clj b/src/metabase/models/user.clj
index ce14738436684a5af2bccc01a2e8c5615c962ad9..33764008815948b9db2905ce77068cdc31fe0814 100644
--- a/src/metabase/models/user.clj
+++ b/src/metabase/models/user.clj
@@ -53,13 +53,6 @@
     (cascade-delete 'ViewLog :user_id id)))
 
 
-(def ^:const current-user-fields
-  "The fields we should return for `*current-user*` (used by `metabase.middleware.current-user`)"
-  (concat (:metabase.models.interface/default-fields User)
-          [:is_active
-           :is_staff])) ; but not `password` !
-
-
 ;; ## Related Functions
 
 (declare create-user
diff --git a/test/metabase/api/notify_test.clj b/test/metabase/api/notify_test.clj
index 736f87eda7384211495e6f240206b71e8ddb7aba..bf937c6ee31bdf30c9eca6116b4803ed914df630 100644
--- a/test/metabase/api/notify_test.clj
+++ b/test/metabase/api/notify_test.clj
@@ -1,15 +1,15 @@
 (ns metabase.api.notify-test
   (:require [clj-http.lite.client :as client]
             [expectations :refer :all]
-            [metabase.http-client :as http]
-            [metabase.middleware.auth :as auth]))
+            (metabase [http-client :as http]
+                      [middleware :as middleware])))
 
 
 ;; ## /api/notify/* AUTHENTICATION Tests
 ;; We assume that all endpoints for a given context are enforced by the same middleware, so we don't run the same
 ;; authentication test on every single individual endpoint
 
-(expect (get auth/response-forbidden :body) (http/client :post 403 "notify/db/100"))
+(expect (get middleware/response-forbidden :body) (http/client :post 403 "notify/db/100"))
 
 
 ;; ## POST /api/notify/db/:id
diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj
index 0a36de959fcc9b9f9b453e11c6ee9b4fc877e665..03a3aa842852e15c22cd8537aa588efe0f7be34b 100644
--- a/test/metabase/api/table_test.clj
+++ b/test/metabase/api/table_test.clj
@@ -3,8 +3,8 @@
   (:require [expectations :refer :all]
             [metabase.db :refer :all]
             [metabase.driver.mongo.test-data :as mongo-data :refer [mongo-test-db-id]]
-            [metabase.http-client :as http]
-            [metabase.middleware.auth :as auth]
+            (metabase [http-client :as http]
+                      [middleware :as middleware])
             (metabase.models [field :refer [Field]]
                              [foreign-key :refer [ForeignKey]]
                              [table :refer [Table]])
@@ -19,8 +19,8 @@
 ;; We assume that all endpoints for a given context are enforced by the same middleware, so we don't run the same
 ;; authentication test on every single individual endpoint
 
-(expect (get auth/response-unauthentic :body) (http/client :get 401 "table"))
-(expect (get auth/response-unauthentic :body) (http/client :get 401 (format "table/%d" (id :users))))
+(expect (get middleware/response-unauthentic :body) (http/client :get 401 "table"))
+(expect (get middleware/response-unauthentic :body) (http/client :get 401 (format "table/%d" (id :users))))
 
 
 ;; ## GET /api/table?org
diff --git a/test/metabase/api/user_test.clj b/test/metabase/api/user_test.clj
index 3c3dabee270215cc7364e2d0db2c93321e92f3fd..3d7ca755df13cc0c8564ae0ab3ee7906e322717d 100644
--- a/test/metabase/api/user_test.clj
+++ b/test/metabase/api/user_test.clj
@@ -3,8 +3,8 @@
   (:require [expectations :refer :all]
             [korma.core :as k]
             [metabase.db :refer :all]
-            [metabase.http-client :as http]
-            [metabase.middleware.auth :as auth]
+            (metabase [http-client :as http]
+                      [middleware :as middleware])
             (metabase.models [session :refer [Session]]
                              [user :refer [User]])
             [metabase.test.data :refer :all]
@@ -15,8 +15,8 @@
 ;; We assume that all endpoints for a given context are enforced by the same middleware, so we don't run the same
 ;; authentication test on every single individual endpoint
 
-(expect (get auth/response-unauthentic :body) (http/client :get 401 "user"))
-(expect (get auth/response-unauthentic :body) (http/client :get 401 "user/current"))
+(expect (get middleware/response-unauthentic :body) (http/client :get 401 "user"))
+(expect (get middleware/response-unauthentic :body) (http/client :get 401 "user/current"))
 
 
 ;; ## Helper Fns
diff --git a/test/metabase/middleware/format_test.clj b/test/metabase/middleware/format_test.clj
deleted file mode 100644
index 0bf5372a44c9d9eaf21767738dce86666b1b4765..0000000000000000000000000000000000000000
--- a/test/metabase/middleware/format_test.clj
+++ /dev/null
@@ -1,29 +0,0 @@
-(ns metabase.middleware.format-test
-  (:require [expectations :refer :all]
-            [metabase.middleware.format :refer :all]))
-
-;; `format`, being a middleware function, expects a `handler`
-;; and returns a function that actually affects the response.
-;; Since we're just interested in testing the returned function pass it `identity` as a handler
-;; so whatever we pass it is unaffected
-(def fmt (format-response identity))
-
-;; check basic stripping
-(expect {:a 1}
-        (fmt {:a 1
-              :b (fn [] 2)}))
-
-;; check recursive stripping w/ map
-(expect {:response {:a 1}}
-        (fmt {:response {:a 1
-                         :b (fn [] 2)}}))
-
-;; check recursive stripping w/ array
-(expect [{:a 1}]
-        (fmt [{:a 1
-               :b (fn [] 2)}]))
-
-;; check combined recursive stripping
-(expect [{:a [{:b 1}]}]
-        (fmt [{:a [{:b 1
-                    :c (fn [] 2)} ]}]))
diff --git a/test/metabase/middleware/auth_test.clj b/test/metabase/middleware_test.clj
similarity index 85%
rename from test/metabase/middleware/auth_test.clj
rename to test/metabase/middleware_test.clj
index 58ac400655c86014fe3e3009d7c51339cfa6350f..002c168cb552a5a7b523abb5a7aaf9f3e2f752ec 100644
--- a/test/metabase/middleware/auth_test.clj
+++ b/test/metabase/middleware_test.clj
@@ -1,9 +1,9 @@
-(ns metabase.middleware.auth-test
+(ns metabase.middleware-test
   (:require [expectations :refer :all]
             [korma.core :as k]
             [ring.mock.request :as mock]
             [metabase.api.common :refer [*current-user-id* *current-user*]]
-            [metabase.middleware.auth :refer :all]
+            [metabase.middleware :refer :all]
             [metabase.models.session :refer [Session]]
             [metabase.test.data :refer :all]
             [metabase.test.data.users :refer :all]
@@ -170,3 +170,33 @@
 ;; invalid apikey, expect 403
 (expect response-forbidden
   (api-key-enforced-handler (request-with-api-key "foobar")))
+
+
+
+;;; # ------------------------------------------------------------ FORMATTING TESTS ------------------------------------------------------------
+
+;; `format`, being a middleware function, expects a `handler`
+;; and returns a function that actually affects the response.
+;; Since we're just interested in testing the returned function pass it `identity` as a handler
+;; so whatever we pass it is unaffected
+(def fmt (format-response identity))
+
+;; check basic stripping
+(expect {:a 1}
+        (fmt {:a 1
+              :b (fn [] 2)}))
+
+;; check recursive stripping w/ map
+(expect {:response {:a 1}}
+        (fmt {:response {:a 1
+                         :b (fn [] 2)}}))
+
+;; check recursive stripping w/ array
+(expect [{:a 1}]
+        (fmt [{:a 1
+               :b (fn [] 2)}]))
+
+;; check combined recursive stripping
+(expect [{:a [{:b 1}]}]
+        (fmt [{:a [{:b 1
+                    :c (fn [] 2)} ]}]))