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/.gitignore b/.gitignore
index a0c6add0b45cdbcbce98526aeebfcf9ec8b395f3..e3f5b92725889d9990d684d465d7e48d4e6602b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,4 @@ profiles.clj
 /coverage
 /resources/sample-dataset.db.trace.db
 /deploy/artifacts/*
+/resources/version.properties
diff --git a/build-uberjar b/build-uberjar
index 5295575e089647bffaeb19a12d2eddeaa2a73d20..debfed992ce61980ce296ff1f99bc20853ef799d 100755
--- a/build-uberjar
+++ b/build-uberjar
@@ -1,17 +1,55 @@
 #! /bin/bash
 
-echo "Running 'npm install' to download javascript dependencies..." &&
-npm install &&
+# Generate the resources/version.properties file
+version() {
+    # Skip on CircleCI since this is interactive
+    if [ ! $CI ]; then
+        VERSION=$(./version)
+        SHORT_VERSION=$(./version --short)
 
-echo "Running 'webpack -p' to assemble and minify frontend assets..." &&
-./node_modules/webpack/bin/webpack.js -p &&
+        echo "Tagging uberjar with version '$VERSION'..."
 
-if [ -f resources/sample-dataset.db.mv.db ]; then
-    echo "Sample Dataset already generated."
-else
-    echo "Running 'lein generate-sample-dataset' to generate the sample dataset..."
-    lein generate-sample-dataset
-fi &&
+        # Ok, now generate the appropriate version.properties file.
+        echo "long=$VERSION" > resources/version.properties
+        echo "short=$SHORT_VERSION" >> resources/version.properties
+    fi
+}
+
+frontend() {
+    echo "Running 'npm install' to download javascript dependencies..." &&
+    npm install &&
+
+    echo "Running 'webpack -p' to assemble and minify frontend assets..." &&
+    ./node_modules/webpack/bin/webpack.js -p
+}
+
+sample-dataset() {
+    if [ -f resources/sample-dataset.db.mv.db ]; then
+        echo "Sample Dataset already generated."
+    else
+        echo "Running 'lein generate-sample-dataset' to generate the sample dataset..."
+        lein generate-sample-dataset
+    fi
+}
 
-echo "Running 'lein uberjar'..." &&
-lein uberjar
+uberjar() {
+    echo "Running 'lein uberjar'..."
+    lein uberjar
+}
+
+all() {
+    version && frontend && sample-dataset && uberjar
+}
+
+# Default to running all but let someone specify one or more sub-tasks to run instead if desired
+# e.g.
+# ./build-uberjar                  # do everything
+# ./build-uberjar version          # just update version.properties
+# ./build-uberjar version uberjar  # just update version.properties and build uberjar
+if [ "$1" ]; then
+    for cmd in "$@"; do
+        $cmd
+    done
+else
+    all
+fi
diff --git a/project.clj b/project.clj
index bbb222084c0d97c503014d2b06d1839a6f00a82e..1d4b118af2945eef9dabeeb531071c0948d28ca8 100644
--- a/project.clj
+++ b/project.clj
@@ -70,7 +70,7 @@
                                   [expectations "2.1.2"]              ; unit tests
                                   [marginalia "0.8.0"]                ; for documentation
                                   [ring/ring-mock "0.2.0"]]
-                   :plugins [[cider/cider-nrepl "0.9.1"]              ; Interactive development w/ cider NREPL in Emacs
+                   :plugins [[cider/cider-nrepl "0.10.0-SNAPSHOT"]    ; Interactive development w/ cider NREPL in Emacs
                              [jonase/eastwood "0.2.1"]                ; Linting
                              [lein-ancient "0.6.7"]                   ; Check project for outdated dependencies + plugins w/ 'lein ancient'
                              [lein-bikeshed "0.2.0"]                  ; Linting
@@ -78,8 +78,9 @@
                              [lein-expectations "0.0.8"]              ; run unit tests with 'lein expectations'
                              [lein-instant-cheatsheet "2.1.4"]        ; use awesome instant cheatsheet created by yours truly w/ 'lein instant-cheatsheet'
                              [lein-marginalia "0.8.0"]                ; generate documentation with 'lein marg'
-                             [refactor-nrepl "1.1.0"]]                ; support for advanced refactoring in Emacs/LightTable
+                             [refactor-nrepl "2.0.0-SNAPSHOT"]]       ; support for advanced refactoring in Emacs/LightTable
                    :global-vars {*warn-on-reflection* true}           ; Emit warnings on all reflection calls
+                   :env {:mb-run-mode "dev"}
                    :jvm-opts ["-Dlogfile.path=target/log"
                               "-Xms1024m"                             ; give JVM a decent heap size to start with
                               "-Xmx2048m"                             ; hard limit of 2GB so we stop hitting the 4GB container limit on CircleCI
diff --git a/resources/frontend_client/app/auth/auth.controllers.js b/resources/frontend_client/app/auth/auth.controllers.js
index b92fe389b83f06dd60b1fa41fd67d3b94cdb28e0..c99d631b6dbe47373f7a07f4730e1983f7ab2a5a 100644
--- a/resources/frontend_client/app/auth/auth.controllers.js
+++ b/resources/frontend_client/app/auth/auth.controllers.js
@@ -1,5 +1,7 @@
 'use strict';
 
+import MetabaseSettings from "metabase/lib/settings";
+
 var AuthControllers = angular.module('metabase.auth.controllers', [
     'metabase.auth.services',
     'ipCookie',
@@ -99,6 +101,7 @@ AuthControllers.controller('PasswordReset', ['$scope', '$routeParams', '$locatio
         }
 
         $scope.resetSuccess = false;
+        $scope.passwordComplexity = MetabaseSettings.passwordComplexity(false);
         $scope.newUserJoining = ($location.hash() === 'new');
 
         $scope.resetPassword = function(password) {
diff --git a/resources/frontend_client/app/auth/partials/password_reset.html b/resources/frontend_client/app/auth/partials/password_reset.html
index fb2728b94cb1173f12f903aa8049ede3157d4528..4b95a9e7e1c857f6727bb775fda69751e2c4d165 100644
--- a/resources/frontend_client/app/auth/partials/password_reset.html
+++ b/resources/frontend_client/app/auth/partials/password_reset.html
@@ -8,7 +8,7 @@
                 <form class="ForgotForm Login-wrapper bg-white Form-new bordered rounded shadowed" name="form" novalidate>
                     <h3 class="Login-header Form-offset">New password</h3>
 
-                    <p class="Form-offset text-grey-3 mb4">To keep your data secure, passwords need to be at least <b>8 characters</b> and include <b>at least one uppercase letter</b>, <b>one digit</b>, and <b>one special character.</b></p>
+                    <p class="Form-offset text-grey-3 mb4">To keep your data secure, passwords {{passwordComplexity}}</p>
 
                     <mb-form-message></mb-form-message>
 
diff --git a/resources/frontend_client/app/lib/settings.js b/resources/frontend_client/app/lib/settings.js
index 18ffd015d1633a9f3a4001bfdbbaf53724f8d1e4..7e2ae48bfa8a60b1f30415665c7c8d81591698dd 100644
--- a/resources/frontend_client/app/lib/settings.js
+++ b/resources/frontend_client/app/lib/settings.js
@@ -26,7 +26,7 @@ const MetabaseSettings = {
         return (mb_settings.setup_token !== undefined && mb_settings.setup_token !== null);
     },
 
-    passwordComplexity: function() {
+    passwordComplexity: function(capitalize) {
         const complexity = this.get('password_complexity');
 
         const clauseDescription = function(clause) {
@@ -38,7 +38,7 @@ const MetabaseSettings = {
             }
         };
 
-        let description = "Must be "+complexity.total+" characters long",
+        let description = (capitalize === false) ? "must be "+complexity.total+" characters long" : "Must be "+complexity.total+" characters long",
             clauses = [];
 
         ["lower", "upper", "digit", "special"].forEach(function(clause) {
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/config.clj b/src/metabase/config.clj
index 99d3b40a238ed6aa36f441d41aa7b7cb3feebf15..45aa07d22759672c8077bd0f1713459499d60a6a 100644
--- a/src/metabase/config.clj
+++ b/src/metabase/config.clj
@@ -1,9 +1,12 @@
 (ns metabase.config
-  (:require [environ.core :as environ]
+  (:require (clojure.java [io :as io]
+                          [shell :as shell])
+            [clojure.string :as s]
+            [environ.core :as environ]
             [medley.core :as m])
-  (:import (clojure.lang Keyword)))
+  (:import clojure.lang.Keyword))
 
-(def ^:const app-defaults
+(def ^:private ^:const app-defaults
   "Global application defaults"
   {;; Database Configuration  (general options?  dburl?)
    :mb-run-mode "prod"
@@ -39,15 +42,16 @@
 
 
 ;; These are convenience functions for accessing config values that ensures a specific return type
-(defn ^Integer config-int [k] (when-let [val (config-str k)] (Integer/parseInt val)))
-(defn ^Boolean config-bool [k] (when-let [val (config-str k)] (Boolean/parseBoolean val)))
-(defn ^Keyword config-kw [k] (when-let [val (config-str k)] (keyword val)))
+(defn ^Integer config-int  [k] (some-> k config-str Integer/parseInt))
+(defn ^Boolean config-bool [k] (some-> k config-str Boolean/parseBoolean))
+(defn ^Keyword config-kw   [k] (some-> k config-str keyword))
 
 
 (def ^:const config-all
   "Global application configuration as a dictionary.
    Combines hard coded defaults with optional user specified overrides from environment variables."
-  (into {} (map (fn [k] [k (config-str k)]) (keys app-defaults))))
+  (into {} (for [k (keys app-defaults)]
+               [k (config-str k)])))
 
 
 (defn config-match
@@ -64,5 +68,31 @@
           (m/filter-keys (fn [k] (re-matches prefix-regex (str k))) environ/env))
       (m/map-keys (fn [k] (let [kstr (str k)] (keyword (subs kstr (+ 1 (count prefix))))))))))
 
+(defn ^Boolean is-dev?  [] (= :dev  (config-kw :mb-run-mode)))
 (defn ^Boolean is-prod? [] (= :prod (config-kw :mb-run-mode)))
 (defn ^Boolean is-test? [] (= :test (config-kw :mb-run-mode)))
+
+
+;;; Version stuff
+;; Metabase version is of the format `GIT-TAG (GIT-SHORT-HASH GIT-BRANCH)`
+
+(defn- version-info-from-shell-script []
+  {:long  (-> (shell/sh "./version")           :out s/trim)
+   :short (-> (shell/sh "./version" "--short") :out s/trim)})
+
+(defn- version-info-from-properties-file []
+  (with-open [reader (io/reader "resources/version.properties")]
+    (let [props (java.util.Properties.)]
+      (.load props reader)
+      (into {} (for [[k v] props]
+                 [(keyword k) v])))))
+
+(defn mb-version-info
+  "Return information about the current version of Metabase.
+   This comes from `resources/version.properties` for prod builds and is fetched from `git` via the `./version` script for dev.
+
+     (mb-version) -> {:long \"v0.11.1 (6509c49 master)\", :short \"v0.11.1\"}"
+  []
+  (if (is-prod?)
+    (version-info-from-properties-file)
+    (version-info-from-shell-script)))
diff --git a/src/metabase/core.clj b/src/metabase/core.clj
index 693d90aad11f698116e746dca900a234fd92e379..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."
@@ -95,7 +92,7 @@
 (defn init
   "General application initialization function which should be run once at application startup."
   []
-  (log/info "Metabase Initializing ... ")
+  (log/info (format "Starting Metabase version %s..." ((config/mb-version-info) :long)))
   ;; First of all, lets register a shutdown hook that will tidy things up for us on app exit
   (.addShutdownHook (Runtime/getRuntime) (Thread. ^Runnable destroy))
   (log/debug "Using Config:\n" (with-out-str (clojure.pprint/pprint config/config-all)))
@@ -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/src/metabase/routes.clj b/src/metabase/routes.clj
index 8ac9e442714ee9bf40ddf1c0bfb2c2880449364b..a35a6aa9754c50ad8fbbde9691214b434ea80b4f 100644
--- a/src/metabase/routes.clj
+++ b/src/metabase/routes.clj
@@ -1,28 +1,24 @@
 (ns metabase.routes
-  (:require [cheshire.core :as cheshire]
-            [compojure.core :refer [context defroutes GET]]
-            [compojure.route :as route]
+  (:require [clojure.java.io :as io]
+            [cheshire.core :as json]
+            (compojure [core :refer [context defroutes GET]]
+                       [route :as route])
             [ring.util.response :as resp]
             [stencil.core :as stencil]
             [metabase.api.routes :as api]
-            [metabase.setup :as setup]
-            [metabase.util :as u]))
+            (metabase.models common
+                             [setting :as setting])
+            (metabase [config :as config]
+                      [setup :as setup]
+                      [util :as u])
+            metabase.util.password))
 
-(defn- load-index-template
-  "Slurp in the Metabase index.html file as a `String`"
-  []
-  (slurp (clojure.java.io/resource "frontend_client/index.html")))
-
-(def load-index
-  "Memoized version of `load-index-template`"
-  ;(memoize load-index-template)
-  load-index-template)
-
-(def date-format-rfc2616
+(def ^:private ^:const date-format-rfc2616
   "Java SimpleDateFormat representing rfc2616 style date used in http headers."
   "EEE, dd MMM yyyy HH:mm:ss zzz")
 
-(defn index-page-vars
+
+(defn- index-page-vars
   "Static values that we inject into the index.html page via Mustache."
   []
   {:ga_code               "UA-60817802-1"
@@ -30,23 +26,26 @@
    :password_complexity   (metabase.util.password/active-password-complexity)
    :setup_token           (setup/token-value)
    :timezones             metabase.models.common/timezones
+   :version               (config/mb-version-info)
    ;; all of these values are dynamic settings from the admin UI but we include them here for bootstrapping availability
-   :anon-tracking-enabled (metabase.models.setting/get :anon-tracking-enabled)
-   :-site-name            (metabase.models.setting/get :-site-name)})
+   :anon-tracking-enabled (setting/get :anon-tracking-enabled)
+   :-site-name            (setting/get :-site-name)})
+
+(defn- index [request]
+  (-> (io/resource "frontend_client/index.html")
+      slurp
+      (stencil/render-string {:bootstrap_json (json/generate-string (index-page-vars))})
+      resp/response
+      (resp/content-type "text/html")
+      (resp/header "Last-Modified" (u/now-with-format date-format-rfc2616))))
 
 ;; Redirect naughty users who try to visit a page other than setup if setup is not yet complete
-(let [index (fn [request]
-              (-> (resp/response (stencil/render-string
-                                   (load-index)
-                                   {:bootstrap_json (cheshire/generate-string (index-page-vars))}))
-                  (resp/content-type "text/html")
-                  (resp/header "Last-Modified" (u/now-with-format date-format-rfc2616))))]
-  (defroutes routes
-    (GET "/" [] index)                                     ; ^/$           -> index.html
-    (GET "/favicon.ico" [] (resp/resource-response "frontend_client/favicon.ico"))
-    (context "/api" [] api/routes)                         ; ^/api/        -> API routes
-    (context "/app" []
-      (route/resources "/" {:root "frontend_client/app"})  ; ^/app/        -> static files under frontend_client/app
-      (route/not-found {:status 404                        ;                  return 404 for anything else starting with ^/app/ that doesn't exist
-                        :body "Not found."}))
-    (GET "*" [] index)))                                   ; Anything else (e.g. /user/edit_current) should serve up index.html; Angular app will handle the rest
+(defroutes routes
+  (GET "/" [] index)                                     ; ^/$           -> index.html
+  (GET "/favicon.ico" [] (resp/resource-response "frontend_client/favicon.ico"))
+  (context "/api" [] api/routes)                         ; ^/api/        -> API routes
+  (context "/app" []
+    (route/resources "/" {:root "frontend_client/app"})  ; ^/app/        -> static files under frontend_client/app
+    (route/not-found {:status 404                        ; return 404 for anything else starting with ^/app/ that doesn't exist
+                      :body "Not found."}))
+  (GET "*" [] index))                                    ; Anything else (e.g. /user/edit_current) should serve up index.html; Angular app will handle the rest
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)} ]}]))
diff --git a/version b/version
new file mode 100755
index 0000000000000000000000000000000000000000..c5680f048cd11dacc61aa1a19e2dd4551b0f3c0b
--- /dev/null
+++ b/version
@@ -0,0 +1,15 @@
+#! /bin/bash
+
+# Return the version string used to describe this version of Metabase.
+
+TAG=$(git describe origin/master --tags --abbrev=0)
+HASH=$(git show-ref --head --hash=7 head)            # first 7 letters of hash should be enough; that's what GitHub uses
+BRANCH=$(git symbolic-ref --short HEAD)
+
+# ./version           -> v0.11.1 (346cbe2 master)
+# ./version --short   -> v0.11.1
+if [ "$1" == "--short" ]; then
+    echo "$TAG"
+else
+    echo "$TAG ($HASH $BRANCH)"
+fi