You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2017/06/07 13:00:29 UTC

[couchdb-fauxton] branch master updated (71d3f27 -> 01ab2ac)

This is an automated email from the ASF dual-hosted git repository.

garren pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git.


    from 71d3f27  Add/update contribution guidelines, issue/PR templates for GH Issues
     new 059964b  Clean up Auth section
     new 01ab2ac  Auth improvements

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 app/addons/activetasks/components.js               |   4 +
 app/addons/auth/__tests__/base.test.js             |  83 -----
 .../components.test.js}                            |  72 ++--
 app/addons/auth/__tests__/stores.test.js           | 143 --------
 app/addons/auth/actions.js                         | 232 ++++++-------
 app/addons/auth/actiontypes.js                     |   7 -
 app/addons/auth/api.js                             |  75 ++++
 app/addons/auth/assets/less/auth.less              |  10 +-
 app/addons/auth/base.js                            | 133 +++----
 app/addons/auth/components.js                      | 384 ---------------------
 app/addons/auth/components/changepasswordform.js   |  97 ++++++
 app/addons/auth/components/createadminform.js      | 116 +++++++
 .../base.js => auth/components/index.js}           |  10 +-
 app/addons/auth/components/loginform.js            | 108 ++++++
 app/addons/auth/components/passwordmodal.js        | 107 ++++++
 app/addons/auth/layout.js                          |  53 ++-
 app/addons/auth/resources.js                       | 208 -----------
 app/addons/auth/routes.js                          | 119 -------
 app/addons/auth/routes/auth.js                     |  60 ++++
 .../verifyinstall.less => auth/routes/index.js}    |   6 +-
 app/addons/auth/routes/user.js                     |  68 ++++
 app/addons/auth/session.js                         | 110 ++++++
 app/addons/auth/stores.js                          | 165 ---------
 app/addons/cluster/cluster.actions.js              |   2 +-
 app/addons/fauxton/appwrapper.js                   |   4 +-
 app/addons/fauxton/navigation/container/NavBar.js  |   2 +-
 app/addons/fauxton/navigation/stores.js            |   7 +-
 app/addons/replication/__tests__/actions.test.js   |   9 +-
 .../replication/__tests__/newreplication.test.js   |   2 -
 app/addons/replication/api.js                      |   3 +-
 app/addons/replication/base.js                     |   7 +-
 app/addons/replication/controller.js               |   4 +-
 app/app.js                                         |   5 -
 app/core/auth.js                                   |  60 ----
 app/core/authentication.js                         |  54 +++
 app/core/base.js                                   |   1 -
 app/core/couchdbSession.js                         |  68 ----
 app/core/router.js                                 |  21 +-
 app/core/tests/couchdbSessionSpec.js               |  44 ---
 app/main.js                                        |  15 +-
 i18n.json.default.json                             |   8 +-
 41 files changed, 1070 insertions(+), 1616 deletions(-)
 delete mode 100644 app/addons/auth/__tests__/base.test.js
 rename app/addons/auth/{test/auth.componentsSpec.js => __tests__/components.test.js} (52%)
 delete mode 100644 app/addons/auth/__tests__/stores.test.js
 create mode 100644 app/addons/auth/api.js
 delete mode 100644 app/addons/auth/components.js
 create mode 100644 app/addons/auth/components/changepasswordform.js
 create mode 100644 app/addons/auth/components/createadminform.js
 copy app/addons/{components/base.js => auth/components/index.js} (68%)
 create mode 100644 app/addons/auth/components/loginform.js
 create mode 100644 app/addons/auth/components/passwordmodal.js
 delete mode 100644 app/addons/auth/resources.js
 delete mode 100644 app/addons/auth/routes.js
 create mode 100644 app/addons/auth/routes/auth.js
 copy app/addons/{verifyinstall/assets/less/verifyinstall.less => auth/routes/index.js} (86%)
 create mode 100644 app/addons/auth/routes/user.js
 create mode 100644 app/addons/auth/session.js
 delete mode 100644 app/addons/auth/stores.js
 delete mode 100644 app/core/auth.js
 create mode 100644 app/core/authentication.js
 delete mode 100644 app/core/couchdbSession.js
 delete mode 100644 app/core/tests/couchdbSessionSpec.js

-- 
To stop receiving notification emails like this one, please contact
['"commits@couchdb.apache.org" <co...@couchdb.apache.org>'].

[couchdb-fauxton] 01/02: Clean up Auth section

Posted by ga...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git

commit 059964bf54fbc5b8e145f00ab5f022626a13fa92
Author: Tim Pinington <ti...@gmail.com>
AuthorDate: Wed May 10 16:48:48 2017 +0200

    Clean up Auth section
    
    Remove backbone and clean up auth
---
 app/addons/auth/__tests__/base.test.js             |  83 -----
 app/addons/auth/actions.js                         | 223 ++++++------
 app/addons/auth/actiontypes.js                     |   2 +-
 app/addons/auth/assets/less/auth.less              |   2 +-
 app/addons/auth/base.js                            | 133 +++----
 app/addons/auth/components.js                      | 384 ---------------------
 app/addons/auth/components/changepasswordform.js   |  92 +++++
 app/addons/auth/components/createadminform.js      | 119 +++++++
 app/addons/auth/components/createadminsidebar.js   |  61 ++++
 app/addons/auth/components/index.js                |  25 ++
 app/addons/auth/components/loginform.js            | 110 ++++++
 app/addons/auth/components/passwordmodal.js        | 107 ++++++
 app/addons/auth/layout.js                          |   8 +-
 app/addons/auth/resources.js                       | 208 -----------
 app/addons/auth/routes.js                          | 119 -------
 app/addons/auth/routes/auth.js                     |  60 ++++
 app/addons/auth/routes/index.js                    |  16 +
 app/addons/auth/routes/user.js                     |  69 ++++
 app/addons/auth/stores.js                          | 165 ---------
 app/addons/auth/stores/ChangePassword.js           |  58 ++++
 app/addons/auth/stores/CreateAdminSidebarStore.js  |  35 ++
 app/addons/auth/stores/CreateAdminStore.js         |  57 +++
 app/addons/auth/stores/index.js                    |  13 +
 ...onentsSpec.js => auth.componentsSpec.react.jsx} |   6 +-
 app/addons/documents/changes/components.react.jsx  | 352 +++++++++++++++++++
 app/addons/replication/base.js                     |   8 +-
 app/app.js                                         |   6 +-
 app/core/auth.js                                   |  64 ++--
 app/core/base.js                                   |   2 +-
 app/core/couchdb/admin.js                          |  21 ++
 app/core/couchdb/index.js                          |  19 +
 app/core/couchdb/session.js                        |  32 ++
 app/core/http.js                                   |  27 ++
 app/core/session.js                                | 166 +++++++++
 34 files changed, 1654 insertions(+), 1198 deletions(-)

diff --git a/app/addons/auth/__tests__/base.test.js b/app/addons/auth/__tests__/base.test.js
deleted file mode 100644
index ae56b16..0000000
--- a/app/addons/auth/__tests__/base.test.js
+++ /dev/null
@@ -1,83 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-import FauxtonAPI from "../../../core/api";
-import Auth from "../../../core/auth";
-import Base from "../base";
-import sinon from "sinon";
-
-
-describe("Auth", function () {
-  FauxtonAPI.auth = new Auth();
-  Base.initialize();
-
-  describe("failed login", function () {
-
-    it("redirects with replace: true set", function () {
-      const navigateSpy = sinon.spy(FauxtonAPI, 'navigate');
-      FauxtonAPI.router.trigger = () => {};
-      FauxtonAPI.session.isLoggedIn = function () { return false; };
-      FauxtonAPI.auth.authDeniedCb();
-      expect(navigateSpy.withArgs('/login?urlback=', {replace: true}).calledOnce).toBeTruthy();
-      FauxtonAPI.navigate.restore();
-    });
-  });
-
-  describe('auth session change', function () {
-
-    afterEach(function () {
-      FauxtonAPI.addHeaderLink.restore && FauxtonAPI.addHeaderLink.restore();
-      FauxtonAPI.session.isLoggedIn.restore && FauxtonAPI.session.isLoggedIn.restore();
-      FauxtonAPI.session.isAdminParty.restore();
-    });
-
-    it('for admin party changes title to admin party', function () {
-      const spy = sinon.spy(FauxtonAPI, 'addHeaderLink');
-      sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(true);
-      FauxtonAPI.session.trigger('change');
-
-      expect(spy.calledOnce).toBeTruthy();
-      const args = spy.getCall(0).args[0];
-      expect(args.title).toMatch("Admin Party!");
-    });
-
-    it('for login changes title to Your Account', function () {
-      var spy = sinon.spy(FauxtonAPI, 'addHeaderLink');
-      sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
-      sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true);
-      FauxtonAPI.session.trigger('change');
-
-      expect(spy.calledOnce).toBeTruthy();
-      var args = spy.getCall(0).args[0];
-      expect(args.title).toMatch("Your Account");
-    });
-
-    it('for login adds logout link', function () {
-      var spy = sinon.spy(FauxtonAPI, 'showLogout');
-      sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
-      sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true);
-      FauxtonAPI.session.trigger('change');
-
-      expect(spy.calledOnce).toBeTruthy();
-      FauxtonAPI.showLogout.restore();
-    });
-
-    it('for logout, removes logout link', function () {
-      var spy = sinon.spy(FauxtonAPI, 'showLogin');
-      sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
-      sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(false);
-      FauxtonAPI.session.trigger('change');
-
-      expect(spy.calledOnce).toBeTruthy();
-      FauxtonAPI.showLogin.restore();
-    });
-  });
-});
diff --git a/app/addons/auth/actions.js b/app/addons/auth/actions.js
index 9375c7b..de926ba 100644
--- a/app/addons/auth/actions.js
+++ b/app/addons/auth/actions.js
@@ -10,148 +10,167 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 import FauxtonAPI from "../../core/api";
-import ActionTypes from "./actiontypes";
+import { session } from "../../core/couchdb";
 import ClusterStore from "../cluster/cluster.stores";
-
-var nodesStore = ClusterStore.nodesStore;
-
-var errorHandler = function (xhr, type, msg) {
-  msg = xhr;
-  if (arguments.length === 3) {
-    msg = xhr.responseJSON.reason;
-  }
-
+import ActionTypes from './actiontypes';
+
+const {
+  AUTH_CLEAR_CHANGE_PWD_FIELDS,
+  AUTH_UPDATE_CHANGE_PWD_FIELD,
+  AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD,
+  AUTH_CLEAR_CREATE_ADMIN_FIELDS,
+  AUTH_CREDS_VALID,
+  AUTH_CREDS_INVALID,
+  AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
+  AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD,
+  AUTH_SELECT_PAGE,
+  AUTH_SHOW_PASSWORD_MODAL,
+  AUTH_HIDE_PASSWORD_MODAL
+} = ActionTypes;
+
+const nodesStore = ClusterStore.nodesStore;
+
+function errorHandler({ message }) {
   FauxtonAPI.addNotification({
-    msg: msg,
-    type: 'error'
+    msg: message,
+    type: "error"
   });
 };
 
-
-function login (username, password, urlBack) {
-  var promise = FauxtonAPI.session.login(username, password);
-
-  promise.then(() => {
-    FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.loggedIn });
-    if (urlBack) {
-      return FauxtonAPI.navigate(urlBack);
-    }
-    FauxtonAPI.navigate('/');
-  }, errorHandler);
+export function login(username, password, urlBack) {
+  return FauxtonAPI.session.login(username, password)
+    .then(() => {
+      FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.loggedIn });
+      if (urlBack && !urlBack.includes("login")) {
+        return FauxtonAPI.navigate(urlBack);
+      }
+      FauxtonAPI.navigate("/");
+    })
+    .catch(errorHandler);
 }
 
-function changePassword (password, passwordConfirm) {
+export function changePassword(password, passwordConfirm) {
   var nodes = nodesStore.getNodes();
-  var promise = FauxtonAPI.session.changePassword(password, passwordConfirm, nodes[0].node);
-
-  promise.then(() => {
-    FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.changePassword });
-    FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CHANGE_PWD_FIELDS });
-  }, errorHandler);
+  var promise = FauxtonAPI.session.changePassword(
+    password,
+    passwordConfirm,
+    nodes[0].node
+  );
+
+  promise.then(
+    () => {
+      FauxtonAPI.addNotification({
+        msg: FauxtonAPI.session.messages.changePassword
+      });
+      FauxtonAPI.dispatch({ type: AUTH_CLEAR_CHANGE_PWD_FIELDS });
+    },
+    errorHandler
+  );
 }
 
-function updateChangePasswordField (value) {
+export function updateChangePasswordField(value) {
   FauxtonAPI.dispatch({
-    type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_FIELD,
+    type: AUTH_UPDATE_CHANGE_PWD_FIELD,
     value: value
   });
 }
 
-function updateChangePasswordConfirmField (value) {
+export function updateChangePasswordConfirmField(value) {
   FauxtonAPI.dispatch({
-    type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD,
+    type: AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD,
     value: value
   });
 }
 
-function createAdmin (username, password, loginAfter) {
-  var nodes = nodesStore.getNodes();
-  var promise = FauxtonAPI.session.createAdmin(username, password, loginAfter, nodes[0].node);
-
-  promise.then(() => {
-    FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.adminCreated });
-    if (loginAfter) {
-      FauxtonAPI.navigate('/');
-    } else {
-      FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CREATE_ADMIN_FIELDS });
+export const createAdmin = (username, password, loginAfter) => {
+  const nodes = nodesStore.getNodes();
+  FauxtonAPI.session.createAdmin(
+    username,
+    password,
+    loginAfter,
+    nodes[0].node
+  )
+  .then(
+    () => {
+      FauxtonAPI.addNotification({
+        msg: FauxtonAPI.session.messages.adminCreated
+      });
+      if (loginAfter) {
+        return FauxtonAPI.navigate("/");
+      } else {
+        FauxtonAPI.dispatch({ type: AUTH_CLEAR_CREATE_ADMIN_FIELDS });
+      }
+    },
+    (xhr, type, msg) => {
+      msg = xhr;
+      if (arguments.length === 3) {
+        msg = xhr.responseJSON.reason;
+      }
+      errorHandler(
+        `${FauxtonAPI.session.messages.adminCreationFailedPrefix} ${msg}`
+      );
     }
-  }, (xhr, type, msg) => {
-    msg = xhr;
-    if (arguments.length === 3) {
-      msg = xhr.responseJSON.reason;
-    }
-    errorHandler(FauxtonAPI.session.messages.adminCreationFailedPrefix + ' ' + msg);
-  });
-}
+  );
+};
 
 // simple authentication method - does nothing other than check creds
-function authenticate (username, password, onSuccess) {
-  $.ajax({
-    cache: false,
-    type: 'POST',
-    url: '/_session',
-    dataType: 'json',
-    data: { name: username, password: password }
-  }).then(() => {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.AUTH_CREDS_VALID,
-      options: { username: username, password: password }
-    });
-    hidePasswordModal();
-    onSuccess(username, password);
-  }, () => {
-    FauxtonAPI.addNotification({
-      msg: 'Your password is incorrect.',
-      type: 'error',
-      clear: true
-    });
-    FauxtonAPI.dispatch({
-      type: ActionTypes.AUTH_CREDS_INVALID,
-      options: { username: username, password: password }
-    });
-  });
+export function authenticate(username, password, onSuccess) {
+  session
+    .create({
+      name: username,
+      password: password
+    })
+    .then(
+      () => {
+        FauxtonAPI.dispatch({
+          type: AUTH_CREDS_VALID,
+          options: {
+            username,
+            password
+          }
+        });
+        hidePasswordModal();
+        onSuccess(username, password);
+      },
+      () => {
+        FauxtonAPI.addNotification({
+          msg: "Your password is incorrect.",
+          type: "error",
+          clear: true
+        });
+        FauxtonAPI.dispatch({
+          type: AUTH_CREDS_INVALID,
+          options: { username: username, password: password }
+        });
+      }
+    );
 }
 
-function updateCreateAdminUsername (value) {
+export function updateCreateAdminUsername(value) {
   FauxtonAPI.dispatch({
-    type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
+    type: AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
     value: value
   });
 }
 
-function updateCreateAdminPassword (value) {
+export function updateCreateAdminPassword(value) {
   FauxtonAPI.dispatch({
-    type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD,
+    type: AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD,
     value: value
   });
 }
 
-function selectPage (page) {
+export function selectPage(page) {
   FauxtonAPI.dispatch({
-    type: ActionTypes.AUTH_SELECT_PAGE,
+    type: AUTH_SELECT_PAGE,
     page: page
   });
 }
 
-function showPasswordModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.AUTH_SHOW_PASSWORD_MODAL });
+export function showPasswordModal() {
+  FauxtonAPI.dispatch({ type: AUTH_SHOW_PASSWORD_MODAL });
 }
 
-function hidePasswordModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.AUTH_HIDE_PASSWORD_MODAL });
+export function hidePasswordModal() {
+  FauxtonAPI.dispatch({ type: AUTH_HIDE_PASSWORD_MODAL });
 }
-
-
-export default {
-  login,
-  changePassword,
-  updateChangePasswordField,
-  updateChangePasswordConfirmField,
-  createAdmin,
-  authenticate,
-  updateCreateAdminUsername,
-  updateCreateAdminPassword,
-  selectPage,
-  showPasswordModal,
-  hidePasswordModal
-};
diff --git a/app/addons/auth/actiontypes.js b/app/addons/auth/actiontypes.js
index 937113a..cd7667f 100644
--- a/app/addons/auth/actiontypes.js
+++ b/app/addons/auth/actiontypes.js
@@ -1,4 +1,4 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// Licensed under the Apache License, Version 2.0 (the "License"), you may not
 // use this file except in compliance with the License. You may obtain a copy of
 // the License at
 //
diff --git a/app/addons/auth/assets/less/auth.less b/app/addons/auth/assets/less/auth.less
index 5fa1e1f..0a0d24d 100644
--- a/app/addons/auth/assets/less/auth.less
+++ b/app/addons/auth/assets/less/auth.less
@@ -13,7 +13,7 @@
 @import "../../../../../assets/less/variables.less";
 
 /*#primary-navbar .navbar nav .nav li a#user-create-admin{
-  background-color: @brandHighlight;
+  background-color: @navBG;
 }*/
 
 .sidenav header {
diff --git a/app/addons/auth/base.js b/app/addons/auth/base.js
index c7c32c7..f9c6b63 100644
--- a/app/addons/auth/base.js
+++ b/app/addons/auth/base.js
@@ -12,88 +12,67 @@
 
 import app from "../../app";
 import FauxtonAPI from "../../core/api";
-import Auth from "./routes";
+import Session from  "../../core/session";
+import RouteObjects from './routes';
 import "./assets/less/auth.less";
 
-Auth.session = new Auth.Session();
+const Auth = {
+  session: new Session()
+};
+
 FauxtonAPI.setSession(Auth.session);
 app.session = Auth.session;
 
-
-function cleanupAuthSection () {
+const cleanupAuthSection = () => {
   FauxtonAPI.removeHeaderLink({ id: 'auth', bottomNav: true });
-}
-
-Auth.initialize = function () {
-
-  Auth.session.on('change', function () {
-    const session = Auth.session;
-    let link;
-
-    if (session.isAdminParty()) {
-      link = {
-        id: 'auth',
-        title: 'Admin Party!',
-        href: '#/createAdmin',
-        icon: 'fonticon-user',
-        bottomNav: true
-      };
-
-      cleanupAuthSection();
-      FauxtonAPI.addHeaderLink(link);
-      FauxtonAPI.hideLogin();
-
-    } else if (session.isLoggedIn()) {
-      link = {
-        id: 'auth',
-        title: 'Your Account',
-        href: '#/changePassword',
-        icon: 'fonticon-user',
-        bottomNav: true
-      };
-
-      cleanupAuthSection();
-      FauxtonAPI.addHeaderLink(link);
-      FauxtonAPI.showLogout();
-    } else {
-      cleanupAuthSection();
-      FauxtonAPI.showLogin();
-    }
-
-  });
-
-  Auth.session.fetchUser().then(function () {
-    Auth.session.trigger('change');
-  });
-
-  var auth = function (session, roles) {
-    var deferred = $.Deferred();
-
-    if (session.isAdminParty()) {
-      session.trigger('authenticated');
-      deferred.resolve();
-    } else if (session.matchesRoles(roles)) {
-      session.trigger('authenticated');
-      deferred.resolve();
-    } else {
-      deferred.reject();
-    }
-
-    return [deferred];
-  };
-
-  var authDenied = function () {
-    var url = window.location.hash.replace('#', '');
-    var pattern = /login\?urlback=/g;
-
-    if (pattern.test(url)) {
-      url = url.replace('login?urlback=', '');
-    }
-    FauxtonAPI.navigate('/login?urlback=' + url, { replace: true });
-  };
-
-  FauxtonAPI.auth.registerAuth(auth);
-  FauxtonAPI.auth.registerAuthDenied(authDenied);
 };
 
-export default Auth;
+export default ({
+  initialize: () => {
+    FauxtonAPI.addHeaderLink({
+      id: 'auth',
+      title: 'Login',
+      href: '#/login',
+      icon: 'fonticon-user',
+      bottomNav: true
+    });
+
+    Auth.session.onChange(() => {
+      const session = Auth.session;
+      let link;
+
+      if (session.isAdminParty()) {
+        link = {
+          id: 'auth',
+          title: 'Admin Party!',
+          href: '#/createAdmin',
+          icon: 'fonticon-user',
+          bottomNav: true
+        };
+
+        cleanupAuthSection();
+        FauxtonAPI.addHeaderLink(link);
+        FauxtonAPI.hideLogin();
+
+      } else if (session.isLoggedIn()) {
+        link = {
+          id: 'auth',
+          title: 'Your Account',
+          href: '#/changePassword',
+          icon: 'fonticon-user',
+          bottomNav: true
+        };
+
+        cleanupAuthSection();
+        FauxtonAPI.addHeaderLink(link);
+        FauxtonAPI.showLogout();
+      } else {
+        cleanupAuthSection();
+        FauxtonAPI.showLogin();
+      }
+    });
+
+    Auth.session.fetchUser();
+  },
+  RouteObjects
+});
diff --git a/app/addons/auth/components.js b/app/addons/auth/components.js
deleted file mode 100644
index 36ac0d3..0000000
--- a/app/addons/auth/components.js
+++ /dev/null
@@ -1,384 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-import React from "react";
-import ReactDOM from "react-dom";
-import AuthStores from "./stores";
-import AuthActions from "./actions";
-import { Modal } from 'react-bootstrap';
-import Components from '../components/react-components';
-
-var changePasswordStore = AuthStores.changePasswordStore;
-var createAdminStore = AuthStores.createAdminStore;
-var createAdminSidebarStore = AuthStores.createAdminSidebarStore;
-const {ConfirmButton} = Components;
-
-
-var LoginForm = React.createClass({
-  propTypes: {
-    urlBack: React.PropTypes.string.isRequired
-  },
-
-  getInitialState: function () {
-    return {
-      username: '',
-      password: ''
-    };
-  },
-
-  getDefaultProps: function () {
-    return {
-      urlBack: '',
-
-      // for testing purposes only
-      testBlankUsername: null,
-      testBlankPassword: null
-    };
-  },
-
-  onInputChange: function (e) {
-    var change = (e.target.name === 'name') ? { username: e.target.value } : { password: e.target.value };
-    this.setState(change);
-  },
-
-  submit: function (e) {
-    e.preventDefault();
-
-    if (!this.checkUnrecognizedAutoFill()) {
-      this.login(this.state.username, this.state.password);
-    }
-  },
-
-  // Safari has a bug where autofill doesn't trigger a change event. This checks for the condition where the state
-  // and form fields have a mismatch. See: https://issues.apache.org/jira/browse/COUCHDB-2829
-  checkUnrecognizedAutoFill: function () {
-    if (this.state.username !== '' || this.state.password !== '') {
-      return false;
-    }
-    var username = (this.props.testBlankUsername) ? this.props.testBlankUsername : ReactDOM.findDOMNode(this.refs.username).value;
-    var password = (this.props.testBlankPassword) ? this.props.testBlankPassword : ReactDOM.findDOMNode(this.refs.password).value;
-    this.setState({ username: username, password: password }); // doesn't set immediately, hence separate login() call
-    this.login(username, password);
-
-    return true;
-  },
-
-  login: function (username, password) {
-    AuthActions.login(username, password, this.props.urlBack);
-  },
-
-  componentDidMount: function () {
-    ReactDOM.findDOMNode(this.refs.username).focus();
-  },
-
-  render: function () {
-    return (
-      <div className="couch-login-wrapper">
-        <div className="row-fluid">
-          <div className="span12">
-            <form id="login" onSubmit={this.submit}>
-              <p className="help-block">
-                Enter your username and password.
-              </p>
-              <input id="username" type="text" name="name" ref="username" placeholder="Username" size="24"
-                onChange={this.onInputChange} value={this.state.username} />
-              <br/>
-              <input id="password" type="password" name="password" ref="password" placeholder="Password" size="24"
-                onChange={this.onInputChange} value={this.state.password} />
-              <br/>
-              <button id="submit" className="btn btn-primary" type="submit">Log In</button>
-            </form>
-          </div>
-        </div>
-      </div>
-    );
-  }
-});
-
-
-var ChangePasswordForm = React.createClass({
-  getInitialState: function () {
-    return this.getStoreState();
-  },
-
-  getStoreState: function () {
-    return {
-      password: changePasswordStore.getChangePassword(),
-      passwordConfirm: changePasswordStore.getChangePasswordConfirm()
-    };
-  },
-
-  onChange: function () {
-    this.setState(this.getStoreState());
-  },
-
-  onChangePassword: function (e) {
-    AuthActions.updateChangePasswordField(e.target.value);
-  },
-
-  onChangePasswordConfirm: function (e) {
-    AuthActions.updateChangePasswordConfirmField(e.target.value);
-  },
-
-  componentDidMount: function () {
-    ReactDOM.findDOMNode(this.refs.password).focus();
-    changePasswordStore.on('change', this.onChange, this);
-  },
-
-  componentWillUnmount: function () {
-    changePasswordStore.off('change', this.onChange);
-  },
-
-  changePassword: function (e) {
-    e.preventDefault();
-    AuthActions.changePassword(this.state.password, this.state.passwordConfirm);
-  },
-
-  render: function () {
-    return (
-      <div className="auth-page">
-        <form id="change-password" onSubmit={this.changePassword}>
-          <p>
-            Enter and confirm a new password.
-          </p>
-
-          <input id="password" type="password" ref="password" name="password" placeholder="Password"
-            size="24" onChange={this.onChangePassword} value={this.state.password} />
-          <br />
-          <input id="password-confirm" type="password" name="password_confirm" placeholder= "Password Confirmation"
-            size="24" onChange={this.onChangePasswordConfirm} value={this.state.passwordConfirm} />
-
-          <br />
-          <p>
-            <button type="submit" className="btn btn-primary"><i className="icon icon-ok-circle" /> Change Password</button>
-          </p>
-        </form>
-      </div>
-    );
-  }
-});
-
-
-var CreateAdminForm = React.createClass({
-  propTypes: {
-    loginAfter: React.PropTypes.bool.isRequired
-  },
-
-  getInitialState: function () {
-    return this.getStoreState();
-  },
-
-  getStoreState: function () {
-    return {
-      username: createAdminStore.getUsername(),
-      password: createAdminStore.getPassword()
-    };
-  },
-
-  onChange: function () {
-    this.setState(this.getStoreState());
-  },
-
-  getDefaultProps: function () {
-    return {
-      loginAfter: ''
-    };
-  },
-
-  onChangeUsername: function (e) {
-    AuthActions.updateCreateAdminUsername(e.target.value);
-  },
-
-  onChangePassword: function (e) {
-    AuthActions.updateCreateAdminPassword(e.target.value);
-  },
-
-  componentDidMount: function () {
-    ReactDOM.findDOMNode(this.refs.username).focus();
-    createAdminStore.on('change', this.onChange, this);
-  },
-
-  componentWillUnmount: function () {
-    createAdminStore.off('change', this.onChange);
-  },
-
-  createAdmin: function (e) {
-    e.preventDefault();
-    AuthActions.createAdmin(this.state.username, this.state.password, this.props.loginAfter);
-  },
-
-  render: function () {
-    return (
-      <div className="auth-page">
-        <p>
-          Before a server admin is configured, all client connections have admin privileges. <strong>If HTTP access is open to non-trusted users, create an admin account to prevent data
-          loss.</strong>
-        </p>
-        <p>
-          Connections with Admin privileges can create and destroy databases, install and update _design documents, run
-          the test suite, and modify the CouchDB configuration.
-        </p>
-        <p>
-          Connections without Admin privileges have read and write access to all databases controlled by validation functions. CouchDB can be configured to block anonymous connections.
-        </p>
-        <form id="create-admin-form" onSubmit={this.createAdmin}>
-          <input id="username" type="text" ref="username" name="name" placeholder="Username" size="24"
-            onChange={this.onChangeUsername} />
-          <br/>
-          <input id="password" type="password" name="password" placeholder= "Password" size="24"
-            onChange={this.onChangePassword} />
-          <p>
-          <button type="submit" id="create-admin" className="btn btn-primary"><i className="icon icon-ok-circle" /> Grant Admin Privileges</button>
-          </p>
-        </form>
-      </div>
-    );
-  }
-});
-
-
-var CreateAdminSidebar = React.createClass({
-  getInitialState: function () {
-    return this.getStoreState();
-  },
-
-  getStoreState: function () {
-    return {
-      selectedPage: createAdminSidebarStore.getSelectedPage()
-    };
-  },
-
-  onChange: function () {
-    this.setState(this.getStoreState());
-  },
-
-  componentDidMount: function () {
-    createAdminSidebarStore.on('change', this.onChange, this);
-  },
-
-  componentWillUnmount: function () {
-    createAdminSidebarStore.off('change', this.onChange);
-  },
-
-  selectPage: function (e) {
-    var newPage = e.target.href.split('#')[1];
-    AuthActions.selectPage(newPage);
-  },
-
-  render: function () {
-    var user = FauxtonAPI.session.user();
-    var userName = _.isNull(user) ? '' : FauxtonAPI.session.user().name;
-
-    return (
-      <div className="sidenav">
-        <header className="row-fluid">
-          <h3>{userName}</h3>
-        </header>
-        <ul className="nav nav-list" onClick={this.selectPage}>
-          <li className={this.state.selectedPage === 'changePassword' ? 'active' : ''} data-page="changePassword">
-            <a href="#changePassword">Change Password</a>
-          </li>
-          <li className={this.state.selectedPage === 'addAdmin' ? 'active' : ''} data-page="addAdmin">
-            <a href="#addAdmin">Create Admins</a>
-          </li>
-        </ul>
-      </div>
-    );
-  }
-});
-
-
-class PasswordModal extends React.Component {
-  constructor (props) {
-    super(props);
-    this.state = {
-      password: ''
-    };
-    this.authenticate = this.authenticate.bind(this);
-    this.onKeyPress = this.onKeyPress.bind(this);
-  }
-
-  // clicking <Enter> should submit the form
-  onKeyPress (e) {
-    if (e.key === 'Enter') {
-      this.authenticate();
-    }
-  }
-
-  // default authentication function. This can be overridden via props if you want to do something different
-  authenticate () {
-    const username = app.session.get('userCtx').name; // yuck. But simplest for now until logging in publishes the user data
-    this.props.onSubmit(username, this.state.password, this.props.onSuccess);
-  }
-
-  render () {
-    const {visible, onClose, submitBtnLabel, headerTitle, modalMessage} = this.props;
-    if (!this.props.visible) {
-      return null;
-    }
-
-    return (
-      <Modal dialogClassName="enter-password-modal" show={visible} onHide={() => onClose()}>
-        <Modal.Header closeButton={true}>
-          <Modal.Title>{headerTitle}</Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
-          {modalMessage}
-          <input
-            style={{width: "385px"}}
-            type="password"
-            className="password-modal-input"
-            placeholder="Enter your password"
-            autoFocus={true}
-            value={this.state.password}
-            onChange={(e) => this.setState({ password: e.target.value })}
-            onKeyPress={this.onKeyPress}
-            />
-        </Modal.Body>
-        <Modal.Footer>
-          <a className="cancel-link" onClick={() => onClose()}>Cancel</a>
-          <ConfirmButton
-            text={submitBtnLabel}
-            onClick={this.authenticate}
-          />
-        </Modal.Footer>
-      </Modal>
-    );
-  }
-}
-PasswordModal.propTypes = {
-  visible: React.PropTypes.bool.isRequired,
-  modalMessage: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]),
-  onSubmit: React.PropTypes.func.isRequired,
-  onClose: React.PropTypes.func.isRequired,
-  submitBtnLabel: React.PropTypes.string
-};
-PasswordModal.defaultProps = {
-  headerTitle: "Enter Password",
-  visible: false,
-  modalMessage: '',
-  onClose: AuthActions.hidePasswordModal,
-  onSubmit: AuthActions.authenticate,
-  onSuccess: () => {},
-  submitBtnLabel: 'Continue'
-};
-
-
-export default {
-  LoginForm,
-  ChangePasswordForm,
-  CreateAdminForm,
-  CreateAdminSidebar,
-  PasswordModal
-};
diff --git a/app/addons/auth/components/changepasswordform.js b/app/addons/auth/components/changepasswordform.js
new file mode 100644
index 0000000..5d54917
--- /dev/null
+++ b/app/addons/auth/components/changepasswordform.js
@@ -0,0 +1,92 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+import { changePasswordStore } from "./../stores";
+import {
+  updateChangePasswordField,
+  updateChangePasswordConfirmField,
+  changePassword
+} from "./../actions";
+
+export default class ChangePasswordForm extends React.Component {
+  constructor() {
+    super();
+    this.state = this.getStoreState();
+  }
+  getStoreState() {
+    return {
+      password: changePasswordStore.getChangePassword(),
+      passwordConfirm: changePasswordStore.getChangePasswordConfirm()
+    };
+  }
+  onChange() {
+    this.setState(this.getStoreState());
+  }
+  onChangePassword(e) {
+    updateChangePasswordField(e.target.value);
+  }
+  onChangePasswordConfirm(e) {
+    updateChangePasswordConfirmField(e.target.value);
+  }
+  componentDidMount() {
+    ReactDOM.findDOMNode(this.refs.password).focus();
+    changePasswordStore.on("change", this.onChange, this);
+  }
+  componentWillUnmount() {
+    changePasswordStore.off("change", this.onChange);
+  }
+  changePassword(e) {
+    e.preventDefault();
+    changePassword(this.state.password, this.state.passwordConfirm);
+  }
+  render() {
+    return (
+      <div className="auth-page">
+        <h3>Change Password</h3>
+
+        <form id="change-password" onSubmit={this.changePassword.bind(this)}>
+          <p>
+            Enter your new password.
+          </p>
+
+          <input
+            id="password"
+            type="password"
+            ref="password"
+            name="password"
+            placeholder="Password"
+            size="24"
+            onChange={this.onChangePassword.bind(this)}
+            value={this.state.password}
+          />
+          <br />
+          <input
+            id="password-confirm"
+            type="password"
+            name="password_confirm"
+            placeholder="Verify Password"
+            size="24"
+            onChange={this.onChangePasswordConfirm.bind(this)}
+            value={this.state.passwordConfirm}
+          />
+
+          <br />
+          <p>
+            <button type="submit" className="btn btn-primary">Change</button>
+          </p>
+        </form>
+      </div>
+    );
+  }
+}
diff --git a/app/addons/auth/components/createadminform.js b/app/addons/auth/components/createadminform.js
new file mode 100644
index 0000000..47c70b5
--- /dev/null
+++ b/app/addons/auth/components/createadminform.js
@@ -0,0 +1,119 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+import { createAdminStore } from "./../stores";
+import {
+  updateCreateAdminUsername,
+  updateCreateAdminPassword,
+  createAdmin
+} from "./../actions";
+
+class CreateAdminForm extends React.Component {
+  getInitialState() {
+    return this.getStoreState();
+  }
+  getStoreState() {
+    return {
+      username: createAdminStore.getUsername(),
+      password: createAdminStore.getPassword()
+    };
+  }
+  onChange() {
+    this.setState(this.getStoreState());
+  }
+  getDefaultProps() {
+    return {
+      loginAfter: ""
+    };
+  }
+  onChangeUsername(e) {
+    updateCreateAdminUsername(e.target.value);
+  }
+  onChangePassword(e) {
+    updateCreateAdminPassword(e.target.value);
+  }
+  componentDidMount() {
+    ReactDOM.findDOMNode(this.refs.username).focus();
+    createAdminStore.on("change", this.onChange, this);
+  }
+  componentWillUnmount() {
+    createAdminStore.off("change", this.onChange);
+  }
+  createAdmin(e) {
+    e.preventDefault();
+    createAdmin(
+      this.state.username,
+      this.state.password,
+      this.props.loginAfter
+    );
+  }
+  render() {
+    return (
+      <div className="auth-page">
+        <h3>Create Admins</h3>
+
+        <p>
+          Before a server admin is configured, all clients have admin privileges. This is fine when
+
+          HTTP access is restricted to trusted users. <strong>
+            If end-users will be accessing this
+            CouchDB, you must create an admin account to prevent accidental (or malicious) data
+
+            loss.
+          </strong>
+        </p>
+        <p>
+          Server admins can create and destroy databases, install and update _design documents, run
+
+          the test suite, and edit all aspects of CouchDB configuration.
+        </p>
+
+        <form id="create-admin-form" onSubmit={this.createAdmin}>
+          <input
+            id="username"
+            type="text"
+            ref="username"
+            name="name"
+            placeholder="Username"
+            size="24"
+            onChange={this.onChangeUsername}
+          />
+          <br />
+          <input
+            id="password"
+            type="password"
+            name="password"
+            placeholder="Password"
+            size="24"
+            onChange={this.onChangePassword}
+          />
+          <p>
+            Non-admin users have read and write access to all databases, which
+            are controlled by validatio. CouchDB can be configured to block all
+            access to anonymous users.
+          </p>
+          <button type="submit" id="create-admin" className="btn btn-primary">
+            Create Admin
+          </button>
+        </form>
+      </div>
+    );
+  }
+}
+
+CreateAdminForm.propTypes = {
+  loginAfter: React.PropTypes.bool.isRequired
+};
+
+export default CreateAdminForm;
diff --git a/app/addons/auth/components/createadminsidebar.js b/app/addons/auth/components/createadminsidebar.js
new file mode 100644
index 0000000..9b954e9
--- /dev/null
+++ b/app/addons/auth/components/createadminsidebar.js
@@ -0,0 +1,61 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from 'react';
+import { selectPage } from './../actions';
+import { createAdminSidebarStore } from './../stores';
+import FauxtonAPI from "../../../core/api";
+
+export default class CreateAdminSidebar extends React.Component {
+  constructor() {
+    super();
+    this.state = this.getStoreState();
+  }
+  getStoreState() {
+    return {
+      selectedPage: createAdminSidebarStore.getSelectedPage()
+    };
+  }
+  onChange() {
+    this.setState(this.getStoreState());
+  }
+  componentDidMount() {
+    createAdminSidebarStore.on('change', this.onChange, this);
+  }
+  componentWillUnmount() {
+    createAdminSidebarStore.off('change', this.onChange);
+  }
+  selectPage(e) {
+    var newPage = e.target.href.split('#')[1];
+    selectPage(newPage);
+  }
+  render() {
+    var user = FauxtonAPI.session.user;
+    var userName = _.isNull(user) ? '' : FauxtonAPI.session.user.name;
+
+    return (
+      <div className="sidenav">
+        <header className="row-fluid">
+          <h3>{userName}</h3>
+        </header>
+        <ul className="nav nav-list" onClick={this.selectPage}>
+          <li className={this.state.selectedPage === 'changePassword' ? 'active' : ''} data-page="changePassword">
+            <a href="#changePassword">Change Password</a>
+          </li>
+          <li className={this.state.selectedPage === 'addAdmin' ? 'active' : ''} data-page="addAdmin">
+            <a href="#addAdmin">Create Admins</a>
+          </li>
+        </ul>
+      </div>
+    );
+  }
+}
diff --git a/app/addons/auth/components/index.js b/app/addons/auth/components/index.js
new file mode 100644
index 0000000..d322903
--- /dev/null
+++ b/app/addons/auth/components/index.js
@@ -0,0 +1,25 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import LoginForm from './loginform.js';
+import PasswordModal from './passwordmodal.js';
+import CreateAdminSidebar from './createadminsidebar.js';
+import CreateAdminForm from './createadminform.js';
+import ChangePasswordForm from './changepasswordform.js';
+
+export default ({
+  LoginForm,
+  PasswordModal,
+  CreateAdminSidebar,
+  CreateAdminForm,
+  ChangePasswordForm
+});
diff --git a/app/addons/auth/components/loginform.js b/app/addons/auth/components/loginform.js
new file mode 100644
index 0000000..3518225
--- /dev/null
+++ b/app/addons/auth/components/loginform.js
@@ -0,0 +1,110 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+import { login } from "./../actions";
+
+class LoginForm extends React.Component {
+  constructor() {
+    super();
+    this.state = {
+      username: "",
+      password: ""
+    };
+  }
+  onInputChange(e) {
+    let change = e.target.name === "name"
+      ? { username: e.target.value }
+      : { password: e.target.value };
+    this.setState(change);
+  }
+  submit(e) {
+    e.preventDefault();
+    if (!this.checkUnrecognizedAutoFill()) {
+      this.login(this.state.username, this.state.password);
+    }
+  }
+  // Safari has a bug where autofill doesn't trigger a change event. This checks for the condition where the state
+  // and form fields have a mismatch. See: https://issues.apache.org/jira/browse/COUCHDB-2829
+  checkUnrecognizedAutoFill() {
+    if (this.state.username !== "" || this.state.password !== "") {
+      return false;
+    }
+    var username = this.props.testBlankUsername
+      ? this.props.testBlankUsername
+      : ReactDOM.findDOMNode(this.refs.username).value;
+    var password = this.props.testBlankPassword
+      ? this.props.testBlankPassword
+      : ReactDOM.findDOMNode(this.refs.password).value;
+    this.setState({ username: username, password: password }); // doesn't set immediately, hence separate login() call
+    this.login(username, password);
+
+    return true;
+  }
+  login(username, password) {
+    login(username, password, this.props.urlBack);
+  }
+  componentDidMount() {
+    ReactDOM.findDOMNode(this.refs.username).focus();
+  }
+  render() {
+    return (
+      <div className="couch-login-wrapper">
+        <div className="row-fluid">
+          <div className="span12">
+            <form id="login" onSubmit={this.submit.bind(this)}>
+              <p className="help-block">
+                Enter your username and password.
+              </p>
+              <input
+                id="username"
+                type="text"
+                name="name"
+                ref="username"
+                placeholder="Username"
+                size="24"
+                onChange={this.onInputChange.bind(this)}
+                value={this.state.username}
+              />
+              <br />
+              <input
+                id="password"
+                type="password"
+                name="password"
+                ref="password"
+                placeholder="Password"
+                size="24"
+                onChange={this.onInputChange.bind(this)}
+                value={this.state.password}
+              />
+              <br />
+              <button id="submit" className="btn btn-success" type="submit">
+                Log In
+              </button>
+            </form>
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+
+LoginForm.defaultProps = {
+   urlBack: ""
+};
+
+LoginForm.propTypes = {
+  urlBack: React.PropTypes.string.isRequired
+};
+
+export default LoginForm;
diff --git a/app/addons/auth/components/passwordmodal.js b/app/addons/auth/components/passwordmodal.js
new file mode 100644
index 0000000..48f7784
--- /dev/null
+++ b/app/addons/auth/components/passwordmodal.js
@@ -0,0 +1,107 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import { Modal } from "react-bootstrap";
+import { hidePasswordModal, authenticate } from "./../actions";
+import Components from "../../components/react-components";
+import app from "../../../app";
+
+class PasswordModal extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      password: ""
+    };
+    this.authenticate = this.authenticate.bind(this);
+    this.onKeyPress = this.onKeyPress.bind(this);
+  }
+
+  // clicking <Enter> should submit the form
+  onKeyPress(e) {
+    if (e.key === "Enter") {
+      this.authenticate();
+    }
+  }
+  // default authentication function. This can be overridden via props if you want to do something different
+  authenticate() {
+    const username = app.session.user.name; // yuck. But simplest for now until logging in publishes the user data
+    this.props.onSubmit(username, this.state.password, this.props.onSuccess);
+  }
+  render() {
+    const {
+      visible,
+      onClose,
+      submitBtnLabel,
+      headerTitle,
+      modalMessage
+    } = this.props;
+    if (!this.props.visible) {
+      return null;
+    }
+
+    return (
+      <Modal
+        dialogClassName="enter-password-modal"
+        show={visible}
+        onHide={() => onClose()}
+      >
+        <Modal.Header closeButton={true}>
+          <Modal.Title>{headerTitle}</Modal.Title>
+        </Modal.Header>
+        <Modal.Body>
+          {modalMessage}
+          <input
+            style={{ width: "385px" }}
+            type="password"
+            className="password-modal-input"
+            placeholder="Enter your password"
+            autoFocus={true}
+            value={this.state.password}
+            onChange={e => this.setState({ password: e.target.value })}
+            onKeyPress={this.onKeyPress}
+          />
+        </Modal.Body>
+        <Modal.Footer>
+          <a className="cancel-link" onClick={() => onClose()}>Cancel</a>
+          <Components.ConfirmButton
+            text={submitBtnLabel}
+            onClick={this.authenticate}
+          />
+        </Modal.Footer>
+      </Modal>
+    );
+  }
+}
+
+PasswordModal.propTypes = {
+  visible: React.PropTypes.bool.isRequired,
+  modalMessage: React.PropTypes.oneOfType([
+    React.PropTypes.string,
+    React.PropTypes.element
+  ]),
+  onSubmit: React.PropTypes.func.isRequired,
+  onClose: React.PropTypes.func.isRequired,
+  submitBtnLabel: React.PropTypes.string
+};
+
+PasswordModal.defaultProps = {
+  headerTitle: "Enter Password",
+  visible: false,
+  modalMessage: "",
+  onClose: hidePasswordModal,
+  onSubmit: authenticate,
+  onSuccess: () => {},
+  submitBtnLabel: "Continue"
+};
+
+export default PasswordModal;
diff --git a/app/addons/auth/layout.js b/app/addons/auth/layout.js
index b6c8291..cc9a609 100644
--- a/app/addons/auth/layout.js
+++ b/app/addons/auth/layout.js
@@ -15,7 +15,11 @@ import {OnePane, OnePaneContent} from '../components/layouts';
 import {Breadcrumbs} from '../components/header-breadcrumbs';
 import Components from "./components";
 
-const { CreateAdminForm, ChangePasswordForm } = Components;
+const {
+  CreateAdminForm,
+  ChangePasswordForm,
+  CreateAdminSidebar
+} = Components;
 
 export const OnePaneHeader = ({crumbs}) => {
   return (
@@ -53,7 +57,7 @@ export const AdminLayout = ({crumbs, changePassword}) => {
       </OnePaneHeader>
       <div className="template-content flex-body flex-layout flex-row">
         <div id="sidebar-content">
-          <Components.CreateAdminSidebar />
+          <CreateAdminSidebar />
         </div>
         <div id="dashboard-content" className="flex-body">
           {content}
diff --git a/app/addons/auth/resources.js b/app/addons/auth/resources.js
deleted file mode 100644
index 5c4a921..0000000
--- a/app/addons/auth/resources.js
+++ /dev/null
@@ -1,208 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-import CouchdbSession from "../../core/couchdbSession";
-
-var Auth = new FauxtonAPI.addon();
-
-
-var Admin = Backbone.Model.extend({
-
-  initialize: function (props, options) {
-    this.node = options.node;
-  },
-
-  url: function () {
-    if (!this.node) {
-      throw new Error('no node set');
-    }
-
-    return app.host + '/_node/' + this.node + '/_config/admins/' + this.get('name');
-  },
-
-  isNew: function () { return false; },
-
-  sync: function (method, model) {
-    var params = {
-      url: model.url(),
-      contentType: 'application/json',
-      dataType: 'json',
-      data: JSON.stringify(model.get('value'))
-    };
-
-    if (method === 'delete') {
-      params.type = 'DELETE';
-    } else {
-      params.type = 'PUT';
-    }
-
-    return $.ajax(params);
-  }
-});
-
-Auth.Session = CouchdbSession.Session.extend({
-  url: app.host + '/_session',
-
-  initialize: function (options) {
-    if (!options) { options = {}; }
-
-    _.bindAll(this);
-
-    this.messages = _.extend({},  {
-        missingCredentials: 'Username or password cannot be blank.',
-        loggedIn: 'You have been logged in.',
-        adminCreated: 'CouchDB admin created',
-        changePassword: 'Your password has been updated.',
-        adminCreationFailedPrefix: 'Could not create admin.'
-      }, options.messages);
-  },
-
-  isAdminParty: function () {
-    var userCtx = this.get('userCtx');
-
-
-    if (!userCtx.name && userCtx.roles.indexOf("_admin") > -1) {
-      return true;
-    }
-
-    return false;
-  },
-
-  isLoggedIn: function () {
-    var userCtx = this.get('userCtx');
-
-    if (!userCtx) { return false;}
-    if (userCtx.name) {
-      return true;
-    }
-
-    return false;
-  },
-
-  userRoles: function () {
-    var user = this.user();
-
-    if (user && user.roles) {
-      if (user.roles.indexOf('fx_loggedIn') === -1) {
-        user.roles.push('fx_loggedIn');
-      }
-
-      return user.roles;
-    }
-
-    return [];
-  },
-
-  matchesRoles: function (roles) {
-    if (roles.length === 0) {
-      return true;
-    }
-
-    var numberMatchingRoles = _.intersection(this.userRoles(), roles).length;
-
-    if (numberMatchingRoles > 0) {
-      return true;
-    }
-
-    return false;
-  },
-
-  validateUser: function (username, password, msg) {
-    if (_.isEmpty(username) || _.isEmpty(password)) {
-      var deferred = FauxtonAPI.Deferred();
-
-      deferred.rejectWith(this, [msg]);
-      return deferred;
-    }
-  },
-
-  validatePasswords: function (password, password_confirm, msg) {
-    if (_.isEmpty(password) || _.isEmpty(password_confirm) || (password !== password_confirm)) {
-      var deferred = FauxtonAPI.Deferred();
-
-      deferred.rejectWith(this, [msg]);
-      return deferred;
-    }
-  },
-
-  createAdmin: function (username, password, login, node) {
-    var errorPromise = this.validateUser(username, password, this.messages.missingCredentials);
-
-    if (errorPromise) { return errorPromise; }
-
-    var admin = new Admin({
-      name: username,
-      value: password
-    }, {node: node});
-
-    return admin.save().then(function () {
-      if (login) {
-        return this.login(username, password);
-      }
-
-      return this.fetchUser({forceFetch: true});
-
-    }.bind(this));
-  },
-
-  login: function (username, password) {
-    var errorPromise = this.validateUser(username, password, this.messages.missingCredentials);
-
-    if (errorPromise) { return errorPromise; }
-
-    return $.ajax({
-      cache: false,
-      type: "POST",
-      url: app.host + "/_session",
-      dataType: "json",
-      data: {name: username, password: password}
-    }).then(function () {
-      return this.fetchUser({forceFetch: true});
-    }.bind(this));
-  },
-
-  logout: function () {
-    var that = this;
-
-    return $.ajax({
-      type: "DELETE",
-      url: app.host + "/_session",
-      dataType: "json",
-      username : "_",
-      password : "_"
-    }).then(function () {
-      return that.fetchUser({forceFetch: true });
-    });
-  },
-
-  changePassword: function (password, confirmedPassword, node) {
-    var errorMessage = 'Passwords do not match.';
-    var errorPromise = this.validatePasswords(password, confirmedPassword, errorMessage);
-
-    if (errorPromise) { return errorPromise; }
-
-    var userName = this.get('userCtx').name;
-    var admin = new Admin({
-      name: userName,
-      value: password
-    }, {node: node});
-
-    return admin.save().then(function () {
-      return this.login(userName, password);
-    }.bind(this));
-  }
-});
-
-
-export default Auth;
diff --git a/app/addons/auth/routes.js b/app/addons/auth/routes.js
deleted file mode 100644
index 71f4bc1..0000000
--- a/app/addons/auth/routes.js
+++ /dev/null
@@ -1,119 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import React from 'react';
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-import Auth from "./resources";
-import AuthActions from "./actions";
-import Components from "./components";
-import ClusterActions from "../cluster/cluster.actions";
-import {AuthLayout, AdminLayout} from './layout';
-
-const {LoginForm, CreateAdminForm} = Components;
-
-var AuthRouteObject = FauxtonAPI.RouteObject.extend({
-  selectedHeader: 'Login',
-
-  routes: {
-    'login?*extra': 'login',
-    'login': 'login',
-    'logout': 'logout',
-    'createAdmin': 'checkNodes',
-    'createAdmin/:node': 'createAdminForNode'
-  },
-
-  checkNodes: function () {
-    ClusterActions.navigateToNodeBasedOnNodeCount('/createAdmin/');
-  },
-
-  login: function () {
-    const crumbs = [{ name: 'Log In to CouchDB' }];
-    return <AuthLayout
-      crumbs={crumbs}
-      component={<LoginForm urlBack={app.getParams().urlback } />}
-      />;
-  },
-
-  logout: function () {
-    FauxtonAPI.addNotification({ msg: 'You have been logged out.' });
-    FauxtonAPI.session.logout().then(function () {
-      FauxtonAPI.navigate('/');
-    });
-  },
-
-  createAdminForNode: function () {
-    ClusterActions.fetchNodes();
-    const crumbs = [{ name: 'Create Admin' }];
-    return <AuthLayout
-      crumbs={crumbs}
-      component={<CreateAdminForm loginAfter={true} />}
-      />;
-  }
-});
-
-
-var UserRouteObject = FauxtonAPI.RouteObject.extend({
-  hideNotificationCenter: true,
-  hideApiBar: true,
-  selectedHeader: 'Your Account',
-
-  routes: {
-    'changePassword': {
-      route: 'checkNodesForPasswordChange',
-      roles: ['fx_loggedIn']
-    },
-    'changePassword/:node': {
-      route: 'changePassword',
-      roles: ['fx_loggedIn']
-    },
-    'addAdmin': {
-      route: 'checkNodesForAddAdmin',
-      roles: ['_admin']
-    },
-    'addAdmin/:node': {
-      route: 'addAdmin',
-      roles: ['_admin']
-    }
-  },
-
-  checkNodesForPasswordChange: function () {
-    ClusterActions.navigateToNodeBasedOnNodeCount('/changePassword/');
-  },
-
-  checkNodesForAddAdmin: function () {
-    ClusterActions.navigateToNodeBasedOnNodeCount('/addAdmin/');
-  },
-
-  changePassword: function () {
-    ClusterActions.fetchNodes();
-    AuthActions.selectPage('changePassword');
-    return <AdminLayout
-      crumbs={[{name: 'User Management'}]}
-      changePassword={true}
-      />;
-  },
-
-  addAdmin: function () {
-    ClusterActions.fetchNodes();
-    AuthActions.selectPage('addAdmin');
-    return <AdminLayout
-      crumbs={[{name: 'User Management'}]}
-      changePassword={false}
-      />;
-  },
-
-});
-
-Auth.RouteObjects = [AuthRouteObject, UserRouteObject];
-
-export default Auth;
diff --git a/app/addons/auth/routes/auth.js b/app/addons/auth/routes/auth.js
new file mode 100644
index 0000000..e72f0a1
--- /dev/null
+++ b/app/addons/auth/routes/auth.js
@@ -0,0 +1,60 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import FauxtonAPI from "../../../core/api";
+import ClusterActions from "../../cluster/cluster.actions";
+import { AuthLayout } from "./../layout";
+import app from "../../../app";
+import Components from "./../components";
+
+const {
+  LoginForm,
+  CreateAdminForm
+} = Components;
+
+const crumbs = [{ name: "Log In to CouchDB" }];
+
+export default FauxtonAPI.RouteObject.extend({
+  routes: {
+    "login?*extra": "login",
+    login: "login",
+    logout: "logout",
+    createAdmin: "checkNodes",
+    "createAdmin/:node": "createAdminForNode"
+  },
+  checkNodes() {
+    ClusterActions.navigateToNodeBasedOnNodeCount("/createAdmin/");
+  },
+  login() {
+    return (
+      <AuthLayout
+        crumbs={crumbs}
+        component={<LoginForm urlBack={app.getParams().urlback} />}
+      />
+    );
+  },
+  logout() {
+    FauxtonAPI.addNotification({ msg: "You have been logged out." });
+    FauxtonAPI.session.logout().then(() => FauxtonAPI.navigate("/"));
+  },
+  createAdminForNode() {
+    ClusterActions.fetchNodes();
+    const crumbs = [{ name: "Create Admin" }];
+    return (
+      <AuthLayout
+        crumbs={crumbs}
+        component={<CreateAdminForm loginAfter={true} />}
+      />
+    );
+  }
+});
diff --git a/app/addons/auth/routes/index.js b/app/addons/auth/routes/index.js
new file mode 100644
index 0000000..da37e64
--- /dev/null
+++ b/app/addons/auth/routes/index.js
@@ -0,0 +1,16 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import Auth from './auth';
+import User from './user';
+
+export default [Auth, User];
diff --git a/app/addons/auth/routes/user.js b/app/addons/auth/routes/user.js
new file mode 100644
index 0000000..083c578
--- /dev/null
+++ b/app/addons/auth/routes/user.js
@@ -0,0 +1,69 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import FauxtonAPI from "../../../core/api";
+import ClusterActions from "../../cluster/cluster.actions";
+import { AdminLayout } from "./../layout";
+import { selectPage } from './../actions';
+
+export default FauxtonAPI.RouteObject.extend({
+  hideNotificationCenter: true,
+  hideApiBar: true,
+  routes: {
+    changePassword: {
+      route: "checkNodesForPasswordChange",
+      roles: ["fx_loggedIn"]
+    },
+    "changePassword/:node": {
+      route: "changePassword",
+      roles: ["fx_loggedIn"]
+    },
+    addAdmin: {
+      route: "checkNodesForAddAdmin",
+      roles: ["_admin"]
+    },
+    "addAdmin/:node": {
+      route: "addAdmin",
+      roles: ["_admin"]
+    }
+  },
+  checkNodesForPasswordChange() {
+    ClusterActions.navigateToNodeBasedOnNodeCount("/changePassword/");
+  },
+  checkNodesForAddAdmin() {
+    ClusterActions.navigateToNodeBasedOnNodeCount("/addAdmin/");
+  },
+  selectedHeader() {
+    return FauxtonAPI.session.user.name;
+  },
+  changePassword() {
+    ClusterActions.fetchNodes();
+    selectPage("changePassword");
+    return (
+      <AdminLayout
+        crumbs={[{ name: "User Management" }]}
+        changePassword={true}
+      />
+    );
+  },
+  addAdmin() {
+    ClusterActions.fetchNodes();
+    selectPage("addAdmin");
+    return (
+      <AdminLayout
+        crumbs={[{ name: "User Management" }]}
+        changePassword={false}
+      />
+    );
+  }
+});
diff --git a/app/addons/auth/stores.js b/app/addons/auth/stores.js
deleted file mode 100644
index ce43e4a..0000000
--- a/app/addons/auth/stores.js
+++ /dev/null
@@ -1,165 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../core/api";
-import ActionTypes from "./actiontypes";
-
-// Not thrilled with this. The sole purpose of these next two stores is because the Create Admin + Change Password
-// forms need to clear after a successful post. Since those events occur in actions.js, we need a way to tell the
-// component to update + clear the fields. That's why all this code exists.
-
-var ChangePasswordStore = FauxtonAPI.Store.extend({
-  initialize: function () {
-    this.reset();
-  },
-
-  reset: function () {
-    this._changePassword = '';
-    this._changePasswordConfirm = '';
-  },
-
-  getChangePassword: function () {
-    return this._changePassword;
-  },
-
-  getChangePasswordConfirm: function () {
-    return this._changePasswordConfirm;
-  },
-
-  setChangePassword: function (val) {
-    this._changePassword = val;
-  },
-
-  setChangePasswordConfirm: function (val) {
-    this._changePasswordConfirm = val;
-  },
-
-  dispatch: function (action) {
-    switch (action.type) {
-
-      case ActionTypes.AUTH_CLEAR_CHANGE_PWD_FIELDS:
-        this.reset();
-        this.triggerChange();
-      break;
-
-      case ActionTypes.AUTH_UPDATE_CHANGE_PWD_FIELD:
-        this.setChangePassword(action.value);
-        this.triggerChange();
-      break;
-
-      case ActionTypes.AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD:
-        this.setChangePasswordConfirm(action.value);
-        this.triggerChange();
-      break;
-
-      default:
-      return;
-    }
-  }
-});
-
-var changePasswordStore = new ChangePasswordStore();
-changePasswordStore.dispatchToken = FauxtonAPI.dispatcher.register(changePasswordStore.dispatch.bind(changePasswordStore));
-
-
-var CreateAdminStore = FauxtonAPI.Store.extend({
-  initialize: function () {
-    this.reset();
-  },
-
-  reset: function () {
-    this._username = '';
-    this._password = '';
-  },
-
-  getUsername: function () {
-    return this._username;
-  },
-
-  getPassword: function () {
-    return this._password;
-  },
-
-  setUsername: function (val) {
-    this._username = val;
-  },
-
-  setPassword: function (val) {
-    this._password = val;
-  },
-
-  dispatch: function (action) {
-    switch (action.type) {
-      case ActionTypes.AUTH_CLEAR_CREATE_ADMIN_FIELDS:
-        this.reset();
-        this.triggerChange();
-      break;
-
-      case ActionTypes.AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD:
-        this.setUsername(action.value);
-        this.triggerChange();
-      break;
-
-      case ActionTypes.AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD:
-        this.setPassword(action.value);
-        this.triggerChange();
-      break;
-
-      default:
-      return;
-    }
-  }
-});
-
-var createAdminStore = new CreateAdminStore();
-createAdminStore.dispatchToken = FauxtonAPI.dispatcher.register(createAdminStore.dispatch.bind(createAdminStore));
-
-
-var CreateAdminSidebarStore = FauxtonAPI.Store.extend({
-  initialize: function () {
-    this.reset();
-  },
-
-  reset: function () {
-    this._selectedPage = 'changePassword';
-  },
-
-  getSelectedPage: function () {
-    return this._selectedPage;
-  },
-
-  setSelectedPage: function (val) {
-    this._selectedPage = val;
-  },
-
-  dispatch: function (action) {
-    switch (action.type) {
-      case ActionTypes.AUTH_SELECT_PAGE:
-        this.setSelectedPage(action.page);
-        this.triggerChange();
-      break;
-
-      default:
-      return;
-    }
-  }
-});
-
-var createAdminSidebarStore = new CreateAdminSidebarStore();
-createAdminSidebarStore.dispatchToken = FauxtonAPI.dispatcher.register(createAdminSidebarStore.dispatch.bind(createAdminSidebarStore));
-
-
-export default {
-  changePasswordStore: changePasswordStore,
-  createAdminStore: createAdminStore,
-  createAdminSidebarStore: createAdminSidebarStore
-};
diff --git a/app/addons/auth/stores/ChangePassword.js b/app/addons/auth/stores/ChangePassword.js
new file mode 100644
index 0000000..41d60ac
--- /dev/null
+++ b/app/addons/auth/stores/ChangePassword.js
@@ -0,0 +1,58 @@
+import FauxtonAPI from "../../../core/api";
+import ActionTypes from '../actiontypes';
+const {
+  AUTH_CLEAR_CHANGE_PWD_FIELDS,
+  AUTH_UPDATE_CHANGE_PWD_FIELD,
+  AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD
+} = ActionTypes;
+
+const ChangePasswordStore = FauxtonAPI.Store.extend({
+  initialize() {
+    this.reset();
+  },
+  reset() {
+    this._changePassword = "";
+    this._changePasswordConfirm = "";
+  },
+  getChangePassword() {
+    return this._changePassword;
+  },
+  getChangePasswordConfirm() {
+    return this._changePasswordConfirm;
+  },
+  setChangePassword(val) {
+    this._changePassword = val;
+  },
+  setChangePasswordConfirm(val) {
+    this._changePasswordConfirm = val;
+  },
+  dispatch(action) {
+    switch (action.type) {
+      case AUTH_CLEAR_CHANGE_PWD_FIELDS:
+        this.reset();
+        this.triggerChange();
+        break;
+
+      case AUTH_UPDATE_CHANGE_PWD_FIELD:
+        this.setChangePassword(action.value);
+        this.triggerChange();
+        break;
+
+      case AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD:
+        this.setChangePasswordConfirm(action.value);
+        this.triggerChange();
+        break;
+
+      default:
+        return;
+    }
+  }
+});
+
+const changePasswordStore = new ChangePasswordStore();
+
+changePasswordStore.dispatchToken = FauxtonAPI.dispatcher.register(
+  changePasswordStore.dispatch.bind(changePasswordStore)
+);
+
+export default changePasswordStore;
diff --git a/app/addons/auth/stores/CreateAdminSidebarStore.js b/app/addons/auth/stores/CreateAdminSidebarStore.js
new file mode 100644
index 0000000..220fbfc
--- /dev/null
+++ b/app/addons/auth/stores/CreateAdminSidebarStore.js
@@ -0,0 +1,35 @@
+import FauxtonAPI from "../../../core/api";
+import ActionTypes from "../actiontypes";
+
+const CreateAdminSidebarStore = FauxtonAPI.Store.extend({
+  initialize() {
+    this.reset();
+  },
+  reset() {
+    this._selectedPage = "changePassword";
+  },
+  getSelectedPage() {
+    return this._selectedPage;
+  },
+  setSelectedPage(val) {
+    this._selectedPage = val;
+  },
+  dispatch(action) {
+    switch (action.type) {
+      case ActionTypes.AUTH_SELECT_PAGE:
+        this.setSelectedPage(action.page);
+        this.triggerChange();
+        break;
+
+      default:
+        return;
+    }
+  }
+});
+
+const createAdminSidebarStore = new CreateAdminSidebarStore();
+createAdminSidebarStore.dispatchToken = FauxtonAPI.dispatcher.register(
+  createAdminSidebarStore.dispatch.bind(createAdminSidebarStore)
+);
+
+export default createAdminSidebarStore;
diff --git a/app/addons/auth/stores/CreateAdminStore.js b/app/addons/auth/stores/CreateAdminStore.js
new file mode 100644
index 0000000..f005201
--- /dev/null
+++ b/app/addons/auth/stores/CreateAdminStore.js
@@ -0,0 +1,57 @@
+import FauxtonAPI from "../../../core/api";
+import ActionTypes from "../actiontypes";
+const {
+  AUTH_CLEAR_CREATE_ADMIN_FIELDS,
+  AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
+  AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD
+} = ActionTypes;
+
+const CreateAdminStore = FauxtonAPI.Store.extend({
+  initialize() {
+    this.reset();
+  },
+  reset() {
+    this._username = "";
+    this._password = "";
+  },
+  getUsername() {
+    return this._username;
+  },
+  getPassword() {
+    return this._password;
+  },
+  setUsername(val) {
+    this._username = val;
+  },
+  setPassword(val) {
+    this._password = val;
+  },
+  dispatch(action) {
+    switch (action.type) {
+      case AUTH_CLEAR_CREATE_ADMIN_FIELDS:
+        this.reset();
+        this.triggerChange();
+        break;
+
+      case AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD:
+        this.setUsername(action.value);
+        this.triggerChange();
+        break;
+
+      case AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD:
+        this.setPassword(action.value);
+        this.triggerChange();
+        break;
+
+      default:
+        return;
+    }
+  }
+});
+
+const createAdminStore = new CreateAdminStore();
+createAdminStore.dispatchToken = FauxtonAPI.dispatcher.register(
+  createAdminStore.dispatch.bind(createAdminStore)
+);
+
+export default createAdminStore;
diff --git a/app/addons/auth/stores/index.js b/app/addons/auth/stores/index.js
new file mode 100644
index 0000000..6a6ff7d
--- /dev/null
+++ b/app/addons/auth/stores/index.js
@@ -0,0 +1,13 @@
+// Not thrilled with this. The sole purpose of these next two stores is because the Create Admin + Change Password
+// forms need to clear after a successful post. Since those events occur in actions.js, we need a way to tell the
+// component to update + clear the fields. That's why all this code exists.
+
+import changePasswordStore from './ChangePassword';
+import createAdminStore from './CreateAdminStore';
+import createAdminSidebarStore from './CreateAdminSidebarStore.js';
+
+export default ({
+  changePasswordStore,
+  createAdminStore,
+  createAdminSidebarStore
+});
diff --git a/app/addons/auth/test/auth.componentsSpec.js b/app/addons/auth/test/auth.componentsSpec.react.jsx
similarity index 97%
rename from app/addons/auth/test/auth.componentsSpec.js
rename to app/addons/auth/test/auth.componentsSpec.react.jsx
index 3b5f1d8..c537b97 100644
--- a/app/addons/auth/test/auth.componentsSpec.js
+++ b/app/addons/auth/test/auth.componentsSpec.react.jsx
@@ -13,9 +13,9 @@ import FauxtonAPI from "../../../core/api";
 import React from "react";
 import ReactDOM from "react-dom";
 import utils from "../../../../test/mocha/testUtils";
-import Components from "../components";
-import Stores from "../stores";
-import Actions from "../actions";
+import * as Components from "../components";
+import * as Stores from "../stores";
+import * as Actions from "../actions";
 import TestUtils from "react-addons-test-utils";
 import { mount } from 'enzyme';
 import sinon from "sinon";
diff --git a/app/addons/documents/changes/components.react.jsx b/app/addons/documents/changes/components.react.jsx
new file mode 100644
index 0000000..db67ee3
--- /dev/null
+++ b/app/addons/documents/changes/components.react.jsx
@@ -0,0 +1,352 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import FauxtonAPI from "../../../core/api";
+import React from "react";
+import ReactDOM from "react-dom";
+import Actions from "./actions";
+import Stores from "./stores";
+import Components from "../../fauxton/components.react";
+import ReactComponents from "../../components/react-components.react";
+import ReactCSSTransitionGroup from "react-addons-css-transition-group";
+import "../../../../assets/js/plugins/prettify";
+import uuid from 'uuid';
+
+const store = Stores.changesStore;
+const BadgeList = ReactComponents.BadgeList;
+const {Copy} = ReactComponents;
+
+class ChangesController extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = this.getStoreState();
+  }
+
+  getStoreState () {
+    return {
+      changes: store.getChanges(),
+      loaded: store.isLoaded(),
+      databaseName: store.getDatabaseName(),
+      isShowingSubset: store.isShowingSubset()
+    };
+  }
+
+  onChange () {
+    this.setState(this.getStoreState());
+  }
+
+  componentDidMount () {
+    store.on('change', this.onChange, this);
+  }
+
+  componentWillUnmount () {
+    store.off('change', this.onChange);
+  }
+
+  showingSubsetMsg () {
+    const { isShowingSubset, changes } = this.state;
+    let msg = '';
+    if (isShowingSubset) {
+      let numChanges = changes.length;
+      msg = <p className="changes-result-limit">Limiting results to latest <b>{numChanges}</b> changes.</p>;
+    }
+    return msg;
+  }
+
+  getRows () {
+    const { changes, loaded, databaseName } = this.state;
+    if (!changes.length && loaded) {
+      return (
+        <p className="no-doc-changes">
+          There are no document changes to display.
+        </p>
+      );
+    }
+
+    return changes.map((change, i) => {
+      return <ChangeRow change={change} key={i} databaseName={databaseName} />;
+    });
+  }
+
+  render () {
+    return (
+      <div>
+        <div className="js-changes-view">
+          {this.showingSubsetMsg()}
+          {this.getRows()}
+        </div>
+      </div>
+    );
+  }
+}
+
+
+class ChangesTabContent extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = this.getStoreState();
+  }
+
+  getStoreState () {
+    return {
+      filters: store.getFilters()
+    };
+  }
+
+  onChange () {
+    this.setState(this.getStoreState());
+  }
+
+  componentDidMount () {
+    store.on('change', this.onChange, this);
+  }
+
+  componentWillUnmount () {
+    store.off('change', this.onChange);
+  }
+
+  addFilter (newFilter) {
+    if (_.isEmpty(newFilter)) {
+      return;
+    }
+    Actions.addFilter(newFilter);
+  }
+
+  hasFilter (filter) {
+    return store.hasFilter(filter);
+  }
+
+  render () {
+    return (
+      <div className="changes-header">
+        <AddFilterForm tooltip={this.props.tooltip} filter={(label) => Actions.removeFilter(label)} addFilter={this.addFilter}
+          hasFilter={this.hasFilter} />
+        <BadgeList elements={this.state.filters} removeBadge={(label) => Actions.removeFilter(label)} />
+      </div>
+    );
+  }
+}
+
+
+class AddFilterForm extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = {
+      filter: '',
+      error: false
+    };
+    this.submitForm = this.submitForm.bind(this);
+  }
+
+  submitForm (e) {
+    e.preventDefault();
+    e.stopPropagation();
+
+    if (this.props.hasFilter(this.state.filter)) {
+      this.setState({ error: true });
+
+      // Yuck. This removes the class after the effect has completed so it can occur again. The
+      // other option is to use jQuery to add the flash. This seemed slightly less crumby
+      let component = this;
+      setTimeout(function () {
+        component.setState({ error: false });
+      }, 1000);
+    } else {
+      this.props.addFilter(this.state.filter);
+      this.setState({ filter: '', error: false });
+    }
+  }
+
+  componentDidMount () {
+    this.focusFilterField();
+  }
+
+  componentDidUpdate () {
+    this.focusFilterField();
+  }
+
+  focusFilterField () {
+    ReactDOM.findDOMNode(this.refs.addItem).focus();
+  }
+
+  inputClassNames () {
+    let className = 'js-changes-filter-field';
+    if (this.state.error) {
+      className += ' errorHighlight';
+    }
+    return className;
+  }
+
+  render () {
+    return (
+      <form className="form-inline js-filter-form" onSubmit={this.submitForm}>
+        <fieldset>
+          <i className="fonticon-filter" />
+          <input type="text" ref="addItem" className={this.inputClassNames()} placeholder="Sequence or ID"
+                 onChange={(e) => this.setState({ filter: e.target.value })} value={this.state.filter} />
+          <button type="submit" className="btn btn-secondary">Filter</button>
+          <div className="help-block">
+            <FilterTooltip tooltip={this.props.tooltip} />
+          </div>
+        </fieldset>
+      </form>
+    );
+  }
+}
+AddFilterForm.PropTypes = {
+  addFilter: React.PropTypes.func.isRequired,
+  hasFilter: React.PropTypes.func.isRequired,
+  tooltips: React.PropTypes.string
+};
+AddFilterForm.defaultProps = {
+  tooltip: ''
+};
+
+
+class FilterTooltip extends React.Component {
+  componentDidMount () {
+    if (this.props.tooltip) {
+      $(ReactDOM.findDOMNode(this.refs.tooltip)).tooltip();
+    }
+  }
+
+  render () {
+    if (!this.props.tooltip) {
+      return null;
+    }
+    return (
+      <i ref="tooltip" className="icon icon-question-sign js-filter-tooltip" data-toggle="tooltip"
+         data-original-title={this.props.tooltip} />
+    );
+  }
+}
+
+
+class ChangeRow extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = {
+      codeVisible: false
+    };
+  }
+
+  toggleJSON (e) {
+    e.preventDefault();
+    this.setState({ codeVisible: !this.state.codeVisible });
+  }
+
+  getChangesCode () {
+    return (this.state.codeVisible) ? <Components.CodeFormat key="changesCodeSection" code={this.getChangeCode()} /> : null;
+  }
+
+  getChangeCode () {
+    return {
+      changes: this.props.change.changes,
+      doc: this.props.change.doc
+    };
+  }
+
+  showCopiedMessage (target) {
+    let msg = 'The document ID has been copied to your clipboard.';
+    if (target === 'seq') {
+      msg = 'The document seq number has been copied to your clipboard.';
+    }
+    FauxtonAPI.addNotification({
+      msg: msg,
+      type: 'info',
+      clear: true
+    });
+  }
+
+  render () {
+    const { codeVisible } = this.state;
+    const { change, databaseName } = this.props;
+    const wrapperClass = 'change-wrapper' + (change.isNew ? ' new-change-row' : '');
+
+    return (
+      <div className={wrapperClass}>
+        <div className="change-box" data-id={change.id}>
+          <div className="row-fluid">
+            <div className="span2">seq</div>
+            <div className="span8 change-sequence">{change.seq}</div>
+            <div className="span2 text-right">
+              <Copy
+                uniqueKey={uuid.v4()}
+                text={change.seq.toString()}
+                onClipboardClick={() => this.showCopiedMessage('seq')} />
+            </div>
+          </div>
+
+          <div className="row-fluid">
+            <div className="span2">id</div>
+            <div className="span8">
+              <ChangeID id={change.id} deleted={change.deleted} databaseName={databaseName} />
+            </div>
+            <div className="span2 text-right">
+              <Copy
+                uniqueKey={uuid.v4()}
+                text={change.id}
+                onClipboardClick={() => this.showCopiedMessage('id')} />
+            </div>
+          </div>
+
+          <div className="row-fluid">
+            <div className="span2">changes</div>
+            <div className="span10">
+              <button type="button" className='btn btn-small btn-secondary' onClick={this.toggleJSON.bind(this)}>
+                {codeVisible ? 'Close JSON' : 'View JSON'}
+              </button>
+            </div>
+          </div>
+
+          <ReactCSSTransitionGroup transitionName="toggle-changes-code" component="div" className="changesCodeSectionWrapper"
+            transitionEnterTimeout={500} transitionLeaveTimeout={300}>
+            {this.getChangesCode()}
+          </ReactCSSTransitionGroup>
+
+          <div className="row-fluid">
+            <div className="span2">deleted</div>
+            <div className="span10">{change.deleted ? 'True' : 'False'}</div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+ChangeRow.PropTypes = {
+  change: React.PropTypes.object,
+  databaseName: React.PropTypes.string.isRequired
+};
+
+
+class ChangeID extends React.Component {
+  render () {
+    const { deleted, id, databaseName } = this.props;
+    if (deleted) {
+      return (
+        <span className="js-doc-id">{id}</span>
+      );
+    }
+    const link = '#' + FauxtonAPI.urls('document', 'app', databaseName, id);
+    return (
+      <a href={link} className="js-doc-link">{id}</a>
+    );
+  }
+}
+
+
+export default {
+  ChangesController,
+  ChangesTabContent,
+  ChangeRow,
+  ChangeID
+};
diff --git a/app/addons/replication/base.js b/app/addons/replication/base.js
index 02efe80..0392c67 100644
--- a/app/addons/replication/base.js
+++ b/app/addons/replication/base.js
@@ -17,12 +17,8 @@ import Actions from './actions';
 
 replication.initialize = function () {
   FauxtonAPI.addHeaderLink({ title: 'Replication', href: '#/replication', icon: 'fonticon-replicate' });
-  FauxtonAPI.session.on('authenticated', () => {
-    if (!FauxtonAPI.session.isLoggedIn()) {
-      //don't check until user is logged in
-      return;
-    }
-
+  FauxtonAPI.session.isAuthenticated().then(() => {
+    console.log('AUAUAU', FauxtonAPI.session.isLoggedIn());
     Actions.checkForNewApi();
   });
 };
diff --git a/app/app.js b/app/app.js
index cf9be21..29b5730 100644
--- a/app/app.js
+++ b/app/app.js
@@ -16,7 +16,7 @@ import "bootstrap";
 import Helpers from "./helpers";
 import Utils from "./core/utils";
 import FauxtonAPI from "./core/api";
-import Couchdb from "./core/couchdbSession";
+import Session from "./core/session";
 import "../assets/less/fauxton.less";
 
 // Make sure we have a console.log
@@ -46,10 +46,8 @@ Object.assign(app, {
   helpers: Helpers
 });
 
-FauxtonAPI.setSession(new Couchdb.Session());
+FauxtonAPI.setSession(new Session());
 
-// Define your master router on the application namespace and trigger all
-// navigation from this instance.
 FauxtonAPI.config({
   // I haven't wrapped these dispatch methods in a action
   // because I don't want to require fauxton/actions in this method.
diff --git a/app/core/auth.js b/app/core/auth.js
index e296c39..a77342b 100644
--- a/app/core/auth.js
+++ b/app/core/auth.js
@@ -11,50 +11,30 @@
 // the License.
 
 import FauxtonAPI from "./base";
-import Backbone from "backbone";
-import _ from "lodash";
 
-// This is not exposed externally as it should not need to be accessed or overridden
-var Auth = function (options) {
-  this._options = options;
-  this.initialize.apply(this, arguments);
-};
-
-// Piggy-back on Backbone's self-propagating extend function,
-Auth.extend = Backbone.Model.extend;
-
-_.extend(Auth.prototype, Backbone.Events, {
-  authDeniedCb: function () {},
-
-  initialize: function () {
-  },
-
-  authHandlerCb : function () {
-    var deferred = $.Deferred();
-    deferred.resolve();
-    return deferred;
-  },
-
-  registerAuth: function (authHandlerCb) {
-    this.authHandlerCb = authHandlerCb;
-  },
-
-  registerAuthDenied: function (authDeniedCb) {
-    this.authDeniedCb = authDeniedCb;
-  },
+function authenticate(session, roles) {
+  if (session.isAdminParty()) {
+    return true;
+  } else if (session.matchesRoles(roles)) {
+    return true;
+  } else {
+    throw new Error('Unable to authenticate');
+  }
+}
 
-  checkAccess: function (roles) {
-    var requiredRoles = roles || [],
-    that = this;
+function authenticationDenied() {
+  let url = window.location.hash
+    .replace('#', '')
+    .replace('login?urlback=', '');
 
-    if (!FauxtonAPI.session) {
-      throw new Error("Fauxton.session is not configured.");
-    }
+  FauxtonAPI.navigate(`/login?urlback=${url}`, { replace: true });
+};
 
-    return FauxtonAPI.session.fetchUser().then(function () {
-      return FauxtonAPI.when(that.authHandlerCb(FauxtonAPI.session, requiredRoles));
-    });
+export default class {
+  checkAccess(roles = []) {
+    return FauxtonAPI.session
+      .fetchUser()
+      .then(() => authenticate(FauxtonAPI.session, roles))
+      .catch(authenticationDenied);
   }
-});
-
-export default Auth;
+}
diff --git a/app/core/base.js b/app/core/base.js
index 0f8cfeb..5ab6ced 100644
--- a/app/core/base.js
+++ b/app/core/base.js
@@ -155,7 +155,7 @@ FauxtonAPI.extensions = extensions;
 
 FauxtonAPI.setSession = function (newSession) {
   FauxtonAPI.session = newSession;
-  return FauxtonAPI.session.fetchUser();
+  //return FauxtonAPI.session.fetchUser();
 };
 
 FauxtonAPI.reducers = {};
diff --git a/app/core/couchdb/admin.js b/app/core/couchdb/admin.js
new file mode 100644
index 0000000..9e800a0
--- /dev/null
+++ b/app/core/couchdb/admin.js
@@ -0,0 +1,21 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import app from './../../app';
+import { json } from '../http';
+
+export function create({name, password, node}) {
+  return json(`${app.host}/_node/${node}/_config/admins/${name}`, {
+    method: "PUT",
+    body: JSON.stringify(password)
+  });
+}
diff --git a/app/core/couchdb/index.js b/app/core/couchdb/index.js
new file mode 100644
index 0000000..fdba344
--- /dev/null
+++ b/app/core/couchdb/index.js
@@ -0,0 +1,19 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import * as session from './session';
+import * as admin from './admin';
+
+export default ({
+  session,
+  admin
+});
diff --git a/app/core/couchdb/session.js b/app/core/couchdb/session.js
new file mode 100644
index 0000000..1fac09d
--- /dev/null
+++ b/app/core/couchdb/session.js
@@ -0,0 +1,32 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import app from "./../../app";
+import { json } from "../http";
+
+export function get() {
+  return json(app.host + "/_session");
+}
+
+export function create(body) {
+  return json(app.host + "/_session", {
+    method: "POST",
+    body: JSON.stringify(body)
+  });
+}
+
+export function remove() {
+  return json(app.host + "/_session", {
+    method: "DELETE",
+    body: JSON.stringify({ username: "_", password: "_" })
+  });
+}
diff --git a/app/core/http.js b/app/core/http.js
new file mode 100644
index 0000000..f66524d
--- /dev/null
+++ b/app/core/http.js
@@ -0,0 +1,27 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import { defaultsDeep } from "lodash";
+
+export const json = (url, opts = {}) => fetch(
+  url,
+  defaultsDeep(
+    {
+      credentials: "include",
+      headers: {
+        accept: "application/json",
+        "Content-Type": "application/json"
+      }
+    },
+    opts
+  )
+).then(res => res.ok ? res.json() : { error: res.statusText });
diff --git a/app/core/session.js b/app/core/session.js
new file mode 100644
index 0000000..8867792
--- /dev/null
+++ b/app/core/session.js
@@ -0,0 +1,166 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import couchdb from "./couchdb";
+import { uniqueId, each } from "lodash";
+import Promise from 'Bluebird';
+
+const authMessages = {
+  missingCredentials: "Username or password cannot be blank.",
+  loggedIn: "You have been logged in.",
+  adminCreated: "CouchDB admin created",
+  changePassword: "Your password has been updated.",
+  adminCreationFailedPrefix: "Could not create admin.",
+  passwordsNotMatching: "Passwords do not match."
+};
+
+function isAdmin(roles = []) {
+  return roles.includes("_admin");
+}
+
+function validate(...predicates) {
+  return predicates.every(isTrue => isTrue);
+}
+
+export default class {
+
+  constructor() {
+    this._user = {
+      roles: []
+    };
+    this._onChange = {};
+    this.messages = authMessages;
+    this._authenticatedPromise = new Promise(resolve => {
+      this._authenticateResolve = resolve;
+    });
+  }
+  getUserFromSession() {
+    return couchdb.session.get().then(({ userCtx }) => {
+      return userCtx;
+    });
+  }
+
+  isAuthenticated () {
+    return this._authenticatedPromise;
+  }
+
+  setUser(user) {
+    if (this._user.name !== user.name) {
+      this._user = {
+        name: user.name,
+        roles: user.roles,
+        isAdmin: isAdmin(user.roles)
+      };
+      each(this._onChange, fn => fn(this._user));
+    }
+    return this._user;
+  }
+
+  user() {
+    return this._user;
+  }
+
+  fetchUser() {
+    return this.getUserFromSession().then(userCtx => {
+      this.setUser(userCtx);
+      console.log('HAve user', this.isLoggedIn());
+      if (this.isLoggedIn()) {
+        this._authenticateResolve(this.user());
+      }
+    });
+  }
+
+  isAdminParty() {
+    return !this._user.name && this._user.isAdmin;
+  }
+
+  isLoggedIn() {
+    return !_.isNull(this._user.name);
+  }
+
+  userRoles() {
+    const user = this._user;
+
+    console.log('UU', this._user, this.isLoggedIn());
+    //if (user && user.roles) {
+    if (this.isLoggedIn()) {
+      if (user.roles.indexOf("fx_loggedIn") === -1) {
+        user.roles.push("fx_loggedIn");
+      }
+      return user.roles;
+    }
+    return [];
+  }
+
+  onChange(fn) {
+    let uuid = uniqueId();
+    this._onChange[uuid] = fn;
+    return () => {
+      delete this._onChange[uuid];
+    };
+  }
+
+  matchesRoles(roles = []) {
+    const numberMatchingRoles = _.intersection(this.userRoles(), roles).length;
+    return numberMatchingRoles > 0;
+  }
+
+  validateUser(username, password, msg) {
+    const isValid = validate(!_.isEmpty(username), !_.isEmpty(password));
+    return isValid ? Promise.resolve() : Promise.reject(msg);
+  }
+
+  validatePasswords(password, password_confirm, msg) {
+    const isValid = validate(
+      _.isEmpty(password),
+      _.isEmpty(password_confirm),
+      password !== password_confirm
+    );
+    return isValid ? Promise.resolve() : Promise.reject(msg);
+  }
+
+  createAdmin(username, password, login, node) {
+    return this
+      .validateUser(username, password, this.messages.missingCredentials)
+      .then(() => couchdb.admin.create({ name: this._user.name, password, node }))
+      .then(
+        () =>
+          login
+            ? this.login(username, password)
+            : this.fetchUser({ forceFetch: true })
+      );
+  }
+
+  login(username, password) {
+    return this
+      .validateUser(username, password, authMessages.missingCredentials)
+      .then(() => couchdb.session.create({ name: username, password: password }))
+      .then((res) => {
+        if (res.error) throw new Error(res.error);
+      })
+      .then(() => this.fetchUser());
+  }
+
+  logout() {
+    return couchdb.session.remove().then(() => this.fetchUser());
+  }
+
+  changePassword(password, confirmedPassword, node) {
+    return this
+      .validatePasswords(
+        password,
+        confirmedPassword,
+        authMessages.passwordsNotMatching
+      )
+      .then(() => couchdb.admin.create({ name: this._user.name, password, node }))
+      .then(() => this.login(this._user.name, password));
+  }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@couchdb.apache.org" <co...@couchdb.apache.org>.

[couchdb-fauxton] 02/02: Auth improvements

Posted by ga...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git

commit 01ab2ac2482b39b86f44d7594a5aeb5ee835e4d7
Author: Garren Smith <ga...@gmail.com>
AuthorDate: Thu May 11 17:13:40 2017 +0200

    Auth improvements
    
    Makes authentication more standard. Moves the create admin and password
    section to use tabs
---
 app/addons/activetasks/components.js               |   4 +
 .../components.test.js}                            |  70 ++--
 app/addons/auth/__tests__/stores.test.js           | 143 ---------
 app/addons/auth/actions.js                         | 201 ++++++------
 app/addons/auth/actiontypes.js                     |   9 +-
 app/addons/auth/api.js                             |  75 +++++
 app/addons/auth/assets/less/auth.less              |  10 +-
 app/addons/auth/base.js                            |  18 +-
 app/addons/auth/components/changepasswordform.js   |  51 +--
 app/addons/auth/components/createadminform.js      |  61 ++--
 app/addons/auth/components/createadminsidebar.js   |  61 ----
 app/addons/auth/components/index.js                |   6 +-
 app/addons/auth/components/loginform.js            |  12 +-
 app/addons/auth/components/passwordmodal.js        |   4 +-
 app/addons/auth/layout.js                          |  51 ++-
 app/addons/auth/routes/auth.js                     |  10 +-
 app/addons/auth/routes/user.js                     |  11 +-
 app/addons/auth/session.js                         | 110 +++++++
 app/addons/auth/stores/ChangePassword.js           |  58 ----
 app/addons/auth/stores/CreateAdminSidebarStore.js  |  35 --
 app/addons/auth/stores/CreateAdminStore.js         |  57 ----
 app/addons/auth/stores/index.js                    |  13 -
 app/addons/cluster/cluster.actions.js              |   2 +-
 app/addons/documents/changes/components.react.jsx  | 352 ---------------------
 app/addons/fauxton/appwrapper.js                   |   4 +-
 app/addons/fauxton/navigation/container/NavBar.js  |   2 +-
 app/addons/fauxton/navigation/stores.js            |   7 +-
 app/addons/replication/__tests__/actions.test.js   |   9 +-
 .../replication/__tests__/newreplication.test.js   |   2 -
 app/addons/replication/api.js                      |   3 +-
 app/addons/replication/base.js                     |   1 -
 app/addons/replication/controller.js               |   4 +-
 app/app.js                                         |   3 -
 app/core/{auth.js => authentication.js}            |  42 ++-
 app/core/base.js                                   |   1 -
 app/core/couchdb/admin.js                          |  21 --
 app/core/couchdb/index.js                          |  19 --
 app/core/couchdb/session.js                        |  32 --
 app/core/couchdbSession.js                         |  68 ----
 app/core/http.js                                   |  27 --
 app/core/router.js                                 |  21 +-
 app/core/session.js                                | 166 ----------
 app/core/tests/couchdbSessionSpec.js               |  44 ---
 app/main.js                                        |  15 +-
 i18n.json.default.json                             |   8 +-
 45 files changed, 477 insertions(+), 1446 deletions(-)

diff --git a/app/addons/activetasks/components.js b/app/addons/activetasks/components.js
index 5882f95..f17b56e 100644
--- a/app/addons/activetasks/components.js
+++ b/app/addons/activetasks/components.js
@@ -384,6 +384,10 @@ export const ActiveTasksPollingWidgetController = React.createClass({
     activeTasksStore.on('change', this.onChange, this);
   },
 
+  componentWillUnmount () {
+    activeTasksStore.off('change', this.onChange, this);
+  },
+
   onChange () {
     this.setState(this.getStoreState());
   },
diff --git a/app/addons/auth/test/auth.componentsSpec.react.jsx b/app/addons/auth/__tests__/components.test.js
similarity index 53%
rename from app/addons/auth/test/auth.componentsSpec.react.jsx
rename to app/addons/auth/__tests__/components.test.js
index c537b97..1926921 100644
--- a/app/addons/auth/test/auth.componentsSpec.react.jsx
+++ b/app/addons/auth/__tests__/components.test.js
@@ -9,20 +9,18 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
-import FauxtonAPI from "../../../core/api";
+
 import React from "react";
 import ReactDOM from "react-dom";
 import utils from "../../../../test/mocha/testUtils";
-import * as Components from "../components";
-import * as Stores from "../stores";
+import LoginForm from "../components/loginform";
+import {CreateAdminForm} from "../components/createadminform";
+import {ChangePasswordForm} from '../components/changepasswordform';
 import * as Actions from "../actions";
-import TestUtils from "react-addons-test-utils";
 import { mount } from 'enzyme';
 import sinon from "sinon";
 var assert = utils.assert;
 
-var createAdminSidebarStore = Stores.createAdminSidebarStore;
-
 describe('Auth -- Components', () => {
 
   describe('LoginForm', () => {
@@ -33,12 +31,11 @@ describe('Auth -- Components', () => {
     });
 
     afterEach(() => {
-      createAdminSidebarStore.reset();
       Actions.login.restore();
     });
 
     it('should trigger login event when form submitted', () => {
-      const loginForm = mount(<Components.LoginForm/>);
+      const loginForm = mount(<LoginForm/>);
       loginForm.find('#login').simulate('submit');
       assert.ok(stub.calledOnce);
     });
@@ -48,7 +45,7 @@ describe('Auth -- Components', () => {
       const password = 'smith';
 
       const loginForm = mount(
-        <Components.LoginForm
+        <LoginForm
           testBlankUsername={username}
           testBlankPassword={password}
         />);
@@ -64,65 +61,38 @@ describe('Auth -- Components', () => {
   });
 
   describe('ChangePasswordForm', () => {
-    var container, changePasswordForm;
-
-    beforeEach(() => {
-      utils.restore(Actions.changePassword);
-    });
 
-    it('should call action to update password on field change', () => {
-      const changePasswordForm = mount(<Components.ChangePasswordForm />);
-      const spy = sinon.spy(Actions, 'updateChangePasswordField');
+    it('should update state on password change', () => {
+      const changePasswordForm = mount(<ChangePasswordForm />);
       changePasswordForm.find('#password').simulate('change', { target: { value: 'bobsyouruncle' }});
-      assert.ok(spy.calledOnce);
+      assert.deepEqual(changePasswordForm.state('password'), 'bobsyouruncle');
     });
 
-    it('should call action to update password confirm on field change', () => {
-      const changePasswordForm = mount(<Components.ChangePasswordForm />);
-      const spy = sinon.spy(Actions, 'updateChangePasswordConfirmField');
+    it('should update state on password confirm change', () => {
+      const changePasswordForm = mount(<ChangePasswordForm />);
       changePasswordForm.find('#password-confirm').simulate('change', { target: { value: 'hotdiggity' }});
-      assert.ok(spy.calledOnce);
+      assert.deepEqual(changePasswordForm.state('passwordConfirm'), 'hotdiggity');
     });
 
     it('should call action to submit form', () => {
-      const changePasswordForm = mount(<Components.ChangePasswordForm />);
-      var stub = sinon.stub(Actions, 'changePassword', () => {});
+      const spy = sinon.spy();
+      const changePasswordForm = mount(<ChangePasswordForm username={"bobsyouruncle"} changePassword={spy} />);
       changePasswordForm.find('#change-password').simulate('submit');
-      assert.ok(stub.calledOnce);
+      assert.ok(spy.calledOnce);
     });
   });
 
   describe('CreateAdminForm', () => {
-    it('should call action to update username on field change', () => {
-      const createAdminForm = mount(<Components.CreateAdminForm loginAfter={false} />);
-      var spy = sinon.spy(Actions, 'updateCreateAdminUsername');
+    it('should update username state', () => {
+      const createAdminForm = mount(<CreateAdminForm loginAfter={false} />);
       createAdminForm.find('#username').simulate('change',  { target: { value: 'catsmeow' }});
-      assert.ok(spy.calledOnce);
+      assert.deepEqual(createAdminForm.state('username'), 'catsmeow');
     });
 
     it('should call action to update password confirm on field change', () => {
-      const createAdminForm = mount(<Components.CreateAdminForm loginAfter={false} />);
-      var spy = sinon.spy(Actions, 'updateCreateAdminPassword');
+      const createAdminForm = mount(<CreateAdminForm loginAfter={false} />);
       createAdminForm.find('#password').simulate('change',  { target: { value: 'topnotch' }});
-      assert.ok(spy.calledOnce);
-    });
-  });
-
-  describe('CreateAdminSidebar', () => {
-    beforeEach(() => {
-      createAdminSidebarStore.reset();
-    });
-
-    it('confirm the default selected nav item is the change pwd page', () => {
-      const wrapper = mount(<Components.CreateAdminSidebar />)
-      assert.equal(wrapper.find('.active a').props().href, '#changePassword');
-    });
-
-    it('confirm clicking a sidebar nav item selects it in the DOM', () => {
-      const wrapper = mount(<Components.CreateAdminSidebar />)
-      wrapper.find('li[data-page="addAdmin"]').find('a').simulate('click');
-      assert.equal(wrapper.find('.active a').props().href, '#addAdmin');
+      assert.deepEqual(createAdminForm.state('password'), 'topnotch');
     });
   });
-
 });
diff --git a/app/addons/auth/__tests__/stores.test.js b/app/addons/auth/__tests__/stores.test.js
deleted file mode 100644
index c4cdbba..0000000
--- a/app/addons/auth/__tests__/stores.test.js
+++ /dev/null
@@ -1,143 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../core/api";
-import testUtils from "../../../../test/mocha/testUtils";
-import ActionTypes from "../actiontypes";
-import Stores from "../stores";
-var assert = testUtils.assert;
-
-var changePasswordStore = Stores.changePasswordStore;
-var createAdminStore = Stores.createAdminStore;
-var createAdminSidebarStore = Stores.createAdminSidebarStore;
-
-describe('Auth Stores', function () {
-
-  describe('ChangePasswordStore', function () {
-
-    it('get / set change password updates store', function () {
-      // check empty by default
-      assert.equal(changePasswordStore.getChangePassword(), '');
-
-      var newPassword = 'lets-rick-roll-mocha';
-      FauxtonAPI.dispatch({
-        type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_FIELD,
-        value: newPassword
-      });
-      assert.equal(changePasswordStore.getChangePassword(), newPassword);
-    });
-
-    it('clearing change password clears in store', function () {
-      var newPassword = 'never-gonna-give-you-up';
-      FauxtonAPI.dispatch({
-        type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_FIELD,
-        value: newPassword
-      });
-      assert.equal(changePasswordStore.getChangePassword(), newPassword);
-
-      FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CHANGE_PWD_FIELDS });
-      assert.equal(changePasswordStore.getChangePassword(), '');
-    });
-
-    it('get / set change confirm password updates store', function () {
-      // check empty by default
-      assert.equal(changePasswordStore.getChangePasswordConfirm(), '');
-
-      // check getPassword works
-      var newPassword = 'never-gonna-let-you-down';
-      FauxtonAPI.dispatch({
-        type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD,
-        value: newPassword
-      });
-      assert.equal(changePasswordStore.getChangePasswordConfirm(), newPassword);
-    });
-
-    it('clearing change confirm password clears in store', function () {
-      var newPassword = 'never-gonna-run-around-and-desert-you';
-      FauxtonAPI.dispatch({
-        type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD,
-        value: newPassword
-      });
-      assert.equal(changePasswordStore.getChangePasswordConfirm(), newPassword);
-
-      FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CHANGE_PWD_FIELDS });
-      assert.equal(changePasswordStore.getChangePasswordConfirm(), '');
-    });
-  });
-
-
-  describe('CreateAdminStore', function () {
-
-    it('get / set username updates store', function () {
-      assert.equal(createAdminStore.getUsername(), '');
-
-      var newUsername = 'never-gonna-make-you-cry';
-      FauxtonAPI.dispatch({
-        type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
-        value: newUsername
-      });
-      assert.equal(createAdminStore.getUsername(), newUsername);
-    });
-
-    it('clearing username clears in store', function () {
-      var newUsername = 'never-gonna-say-goodbye';
-      FauxtonAPI.dispatch({
-        type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
-        value: newUsername
-      });
-      assert.equal(createAdminStore.getUsername(), newUsername);
-
-      FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CREATE_ADMIN_FIELDS });
-      assert.equal(createAdminStore.getUsername(), '');
-    });
-
-    it('get / set password updates store', function () {
-      // check empty by default
-      assert.equal(createAdminStore.getPassword(), '');
-
-      // check getPassword works
-      var newPassword = 'never-gonna-tell-a-lie-and-hurt-you';
-      FauxtonAPI.dispatch({
-        type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD,
-        value: newPassword
-      });
-      assert.equal(createAdminStore.getPassword(), newPassword);
-    });
-
-    it('clearing change confirm password clears in store', function () {
-      var newPassword = 'mocha-please-consider-yourself-rickrolled';
-      FauxtonAPI.dispatch({
-        type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD,
-        value: newPassword
-      });
-      assert.equal(createAdminStore.getPassword(), newPassword);
-
-      FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CREATE_ADMIN_FIELDS });
-      assert.equal(createAdminStore.getPassword(), '');
-    });
-  });
-
-
-  describe('CreateAdminSidebarStore', function () {
-    var defaultPage = 'changePassword';
-    it('has correct default selected page', function () {
-      assert.equal(createAdminSidebarStore.getSelectedPage(), defaultPage);
-    });
-
-    it('selecting a page updates the selected page in store', function () {
-      var newPage = 'addAdmin';
-      FauxtonAPI.dispatch({ type: ActionTypes.AUTH_SELECT_PAGE, page: newPage });
-      assert.equal(createAdminSidebarStore.getSelectedPage(), newPage);
-    });
-  });
-
-});
diff --git a/app/addons/auth/actions.js b/app/addons/auth/actions.js
index de926ba..3dab391 100644
--- a/app/addons/auth/actions.js
+++ b/app/addons/auth/actions.js
@@ -10,125 +10,116 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 import FauxtonAPI from "../../core/api";
-import { session } from "../../core/couchdb";
+import app from "../../app";
 import ClusterStore from "../cluster/cluster.stores";
 import ActionTypes from './actiontypes';
+import Api from './api';
 
 const {
-  AUTH_CLEAR_CHANGE_PWD_FIELDS,
-  AUTH_UPDATE_CHANGE_PWD_FIELD,
-  AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD,
-  AUTH_CLEAR_CREATE_ADMIN_FIELDS,
-  AUTH_CREDS_VALID,
-  AUTH_CREDS_INVALID,
-  AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
-  AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD,
-  AUTH_SELECT_PAGE,
   AUTH_SHOW_PASSWORD_MODAL,
-  AUTH_HIDE_PASSWORD_MODAL
+  AUTH_HIDE_PASSWORD_MODAL,
 } = ActionTypes;
 
 const nodesStore = ClusterStore.nodesStore;
 
-function errorHandler({ message }) {
+const errorHandler = ({ message }) => {
   FauxtonAPI.addNotification({
     msg: message,
     type: "error"
   });
 };
 
-export function login(username, password, urlBack) {
-  return FauxtonAPI.session.login(username, password)
-    .then(() => {
-      FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.loggedIn });
-      if (urlBack && !urlBack.includes("login")) {
-        return FauxtonAPI.navigate(urlBack);
-      }
-      FauxtonAPI.navigate("/");
-    })
-    .catch(errorHandler);
-}
+const validate = (...predicates) => {
+  return predicates.every(isTrue => isTrue);
+};
 
-export function changePassword(password, passwordConfirm) {
-  var nodes = nodesStore.getNodes();
-  var promise = FauxtonAPI.session.changePassword(
-    password,
-    passwordConfirm,
-    nodes[0].node
+export const validateUser = (username, password) => {
+  return validate(!_.isEmpty(username), !_.isEmpty(password));
+};
+
+export const validatePasswords = (password, passwordConfirm) => {
+  return validate(
+    !_.isEmpty(password),
+    !_.isEmpty(passwordConfirm),
+    password === passwordConfirm
   );
+};
+
+export const login = (username, password, urlBack) => {
+  if (!validateUser(username, password)) {
+    return errorHandler({message: app.i18n.en_US['auth-missing-credentials']});
+  }
 
-  promise.then(
+  return Api.login({name: username, password})
+  .then(resp => {
+    if (resp.error) {
+      return errorHandler({message: resp.reason});
+    }
+
+    let msg = app.i18n.en_US['auth-logged-in'];
+    if (msg) {
+      FauxtonAPI.addNotification({msg});
+    }
+
+    if (urlBack && !urlBack.includes("login")) {
+      return FauxtonAPI.navigate(urlBack);
+    }
+    FauxtonAPI.navigate("/");
+  })
+  .catch(errorHandler);
+};
+
+export const changePassword = (username, password, passwordConfirm) => () => {
+  if (!validatePasswords(password, passwordConfirm)) {
+    return errorHandler({message: app.i18n.en_US['auth-passwords-not-matching']});
+  }
+  var nodes = nodesStore.getNodes();
+  //To change an admin's password is the same as creating an admin. So we just use the
+  //same api function call here.
+  Api.createAdmin({
+    name: username,
+    password,
+    node: nodes[0].node
+  }).then(
     () => {
       FauxtonAPI.addNotification({
-        msg: FauxtonAPI.session.messages.changePassword
+        msg: app.i18n.en_US["auth-change-password"]
       });
-      FauxtonAPI.dispatch({ type: AUTH_CLEAR_CHANGE_PWD_FIELDS });
     },
     errorHandler
   );
-}
+};
 
-export function updateChangePasswordField(value) {
-  FauxtonAPI.dispatch({
-    type: AUTH_UPDATE_CHANGE_PWD_FIELD,
-    value: value
-  });
-}
+export const createAdmin = (username, password, loginAfter) => () => {
+  const node = nodesStore.getNodes()[0].node;
+  if (!validateUser(username, password)) {
+    return errorHandler({message: app.i18n.en_US['auth-missing-credentials']});
+  }
 
-export function updateChangePasswordConfirmField(value) {
-  FauxtonAPI.dispatch({
-    type: AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD,
-    value: value
-  });
-}
+  Api.createAdmin({name: username, password, node})
+  .then(resp => {
+    if (resp.error) {
+      return errorHandler({message: `${app.i18n.en_US['auth-admin-creation-failed-prefix']} ${resp.reason}`});
+    }
 
-export const createAdmin = (username, password, loginAfter) => {
-  const nodes = nodesStore.getNodes();
-  FauxtonAPI.session.createAdmin(
-    username,
-    password,
-    loginAfter,
-    nodes[0].node
-  )
-  .then(
-    () => {
-      FauxtonAPI.addNotification({
-        msg: FauxtonAPI.session.messages.adminCreated
-      });
-      if (loginAfter) {
-        return FauxtonAPI.navigate("/");
-      } else {
-        FauxtonAPI.dispatch({ type: AUTH_CLEAR_CREATE_ADMIN_FIELDS });
-      }
-    },
-    (xhr, type, msg) => {
-      msg = xhr;
-      if (arguments.length === 3) {
-        msg = xhr.responseJSON.reason;
-      }
-      errorHandler(
-        `${FauxtonAPI.session.messages.adminCreationFailedPrefix} ${msg}`
-      );
+    FauxtonAPI.addNotification({
+      msg: app.i18n.en_US['auth-admin-created']
+    });
+
+    if (loginAfter) {
+      return FauxtonAPI.navigate("/login");
     }
-  );
+  });
 };
 
 // simple authentication method - does nothing other than check creds
-export function authenticate(username, password, onSuccess) {
-  session
-    .create({
+export const authenticate = (username, password, onSuccess) => {
+    Api.login({
       name: username,
       password: password
     })
     .then(
       () => {
-        FauxtonAPI.dispatch({
-          type: AUTH_CREDS_VALID,
-          options: {
-            username,
-            password
-          }
-        });
         hidePasswordModal();
         onSuccess(username, password);
       },
@@ -138,39 +129,23 @@ export function authenticate(username, password, onSuccess) {
           type: "error",
           clear: true
         });
-        FauxtonAPI.dispatch({
-          type: AUTH_CREDS_INVALID,
-          options: { username: username, password: password }
-        });
       }
     );
-}
-
-export function updateCreateAdminUsername(value) {
-  FauxtonAPI.dispatch({
-    type: AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
-    value: value
-  });
-}
-
-export function updateCreateAdminPassword(value) {
-  FauxtonAPI.dispatch({
-    type: AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD,
-    value: value
-  });
-}
-
-export function selectPage(page) {
-  FauxtonAPI.dispatch({
-    type: AUTH_SELECT_PAGE,
-    page: page
-  });
-}
+};
 
-export function showPasswordModal() {
+//This is used in the replication store
+export const showPasswordModal = () => {
   FauxtonAPI.dispatch({ type: AUTH_SHOW_PASSWORD_MODAL });
-}
+};
 
-export function hidePasswordModal() {
+export const hidePasswordModal = () => {
   FauxtonAPI.dispatch({ type: AUTH_HIDE_PASSWORD_MODAL });
-}
+};
+
+export const logout = () => {
+  FauxtonAPI.addNotification({ msg: "You have been logged out." });
+  Api.logout()
+  .then(Api.getSession)
+  .then(() => FauxtonAPI.navigate('/'))
+  .catch(errorHandler);
+};
diff --git a/app/addons/auth/actiontypes.js b/app/addons/auth/actiontypes.js
index cd7667f..4ab3e5b 100644
--- a/app/addons/auth/actiontypes.js
+++ b/app/addons/auth/actiontypes.js
@@ -1,4 +1,4 @@
-// Licensed under the Apache License, Version 2.0 (the "License"), you may not
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
 // use this file except in compliance with the License. You may obtain a copy of
 // the License at
 //
@@ -11,13 +11,6 @@
 // the License.
 
 export default {
-  AUTH_CLEAR_CHANGE_PWD_FIELDS: 'AUTH_CLEAR_CHANGE_PWD_FIELDS',
-  AUTH_UPDATE_CHANGE_PWD_FIELD: 'AUTH_UPDATE_CHANGE_PWD_FIELD',
-  AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD: 'AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD',
-  AUTH_CLEAR_CREATE_ADMIN_FIELDS: 'AUTH_CLEAR_CREATE_ADMIN_FIELDS',
-  AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD: 'AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD',
-  AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD: 'AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD',
-  AUTH_SELECT_PAGE: 'AUTH_SELECT_PAGE',
   AUTH_CREDS_VALID: 'AUTH_CREDS_VALID',
   AUTH_CREDS_INVALID: 'AUTH_CREDS_INVALID',
   AUTH_SHOW_PASSWORD_MODAL: 'AUTH_SHOW_PASSWORD_MODAL',
diff --git a/app/addons/auth/api.js b/app/addons/auth/api.js
new file mode 100644
index 0000000..4bad314
--- /dev/null
+++ b/app/addons/auth/api.js
@@ -0,0 +1,75 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import app from './../../app';
+import { defaultsDeep } from "lodash";
+
+export const json = (url, opts = {}) => fetch(
+  url,
+  defaultsDeep(
+    {
+      credentials: "include",
+      headers: {
+        accept: "application/json",
+        "Content-Type": "application/json"
+      }
+    },
+    opts
+  )
+).then(resp => resp.json());
+
+
+export function createAdmin({name, password, node}) {
+  return json(`${app.host}/_node/${node}/_config/admins/${name}`, {
+    method: "PUT",
+    body: JSON.stringify(password)
+  });
+}
+
+let loggedInSessionPromise;
+
+export function getSession() {
+  if (loggedInSessionPromise) {
+    return loggedInSessionPromise;
+  }
+
+  const promise = json(app.host + "/_session").then(resp => {
+    if (resp.userCtx.name) {
+      loggedInSessionPromise = promise;
+    }
+    return resp.userCtx;
+  });
+
+  return promise;
+}
+
+export function login(body) {
+  return json(app.host + "/_session", {
+    method: "POST",
+    body: JSON.stringify(body)
+  });
+}
+
+export function logout() {
+  loggedInSessionPromise = null;
+  return json(app.host + "/_session", {
+    method: "DELETE",
+    body: JSON.stringify({ username: "_", password: "_" })
+  });
+}
+
+export default {
+  createAdmin,
+  login,
+  logout,
+  getSession
+};
diff --git a/app/addons/auth/assets/less/auth.less b/app/addons/auth/assets/less/auth.less
index 0a0d24d..bf6ddcc 100644
--- a/app/addons/auth/assets/less/auth.less
+++ b/app/addons/auth/assets/less/auth.less
@@ -12,19 +12,15 @@
 
 @import "../../../../../assets/less/variables.less";
 
-/*#primary-navbar .navbar nav .nav li a#user-create-admin{
-  background-color: @navBG;
-}*/
-
 .sidenav header {
-  padding-left: 24px;
+  padding-left: 24px !important;
   h3 {
     margin: 8px 0 4px;
   }
 }
 
-#dashboard-content .auth-page {
-  padding: 20px;
+.faux__auth-page {
+  padding: 20px !important;
   h3 {
     margin-top: 0;
   }
diff --git a/app/addons/auth/base.js b/app/addons/auth/base.js
index f9c6b63..0fce5f8 100644
--- a/app/addons/auth/base.js
+++ b/app/addons/auth/base.js
@@ -10,23 +10,17 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import app from "../../app";
 import FauxtonAPI from "../../core/api";
-import Session from  "../../core/session";
 import RouteObjects from './routes';
 import "./assets/less/auth.less";
-
-const Auth = {
-  session: new Session()
-};
-
-FauxtonAPI.setSession(Auth.session);
-app.session = Auth.session;
+import Session from './session';
 
 const cleanupAuthSection = () => {
   FauxtonAPI.removeHeaderLink({ id: 'auth', bottomNav: true });
 };
 
+FauxtonAPI.setSession(new Session({allowAdminParty: true}));
+
 export default ({
   initialize: () => {
     FauxtonAPI.addHeaderLink({
@@ -37,8 +31,8 @@ export default ({
       bottomNav: true
     });
 
-    Auth.session.onChange(() => {
-      const session = Auth.session;
+    FauxtonAPI.session.onChange(() => {
+      const session = FauxtonAPI.session;
       let link;
 
       if (session.isAdminParty()) {
@@ -71,8 +65,6 @@ export default ({
         FauxtonAPI.showLogin();
       }
     });
-
-    Auth.session.fetchUser();
   },
   RouteObjects
 });
diff --git a/app/addons/auth/components/changepasswordform.js b/app/addons/auth/components/changepasswordform.js
index 5d54917..da77375 100644
--- a/app/addons/auth/components/changepasswordform.js
+++ b/app/addons/auth/components/changepasswordform.js
@@ -12,47 +12,43 @@
 
 import React from "react";
 import ReactDOM from "react-dom";
-import { changePasswordStore } from "./../stores";
+import FauxtonAPI from '../../../core/api';
 import {
-  updateChangePasswordField,
-  updateChangePasswordConfirmField,
   changePassword
 } from "./../actions";
 
-export default class ChangePasswordForm extends React.Component {
-  constructor() {
-    super();
-    this.state = this.getStoreState();
-  }
-  getStoreState() {
-    return {
-      password: changePasswordStore.getChangePassword(),
-      passwordConfirm: changePasswordStore.getChangePasswordConfirm()
+import {connect} from 'react-redux';
+
+export class ChangePasswordForm extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      password: '',
+      passwordConfirm: ''
     };
   }
-  onChange() {
-    this.setState(this.getStoreState());
-  }
+
   onChangePassword(e) {
-    updateChangePasswordField(e.target.value);
+    this.setState({password: e.target.value});
   }
+
   onChangePasswordConfirm(e) {
-    updateChangePasswordConfirmField(e.target.value);
+    this.setState({passwordConfirm: e.target.value});
   }
+
   componentDidMount() {
     ReactDOM.findDOMNode(this.refs.password).focus();
-    changePasswordStore.on("change", this.onChange, this);
-  }
-  componentWillUnmount() {
-    changePasswordStore.off("change", this.onChange);
   }
+
   changePassword(e) {
     e.preventDefault();
-    changePassword(this.state.password, this.state.passwordConfirm);
+    this.props.changePassword(this.props.username, this.state.password, this.state.passwordConfirm);
   }
+
   render() {
     return (
-      <div className="auth-page">
+      <div className="faux__auth-page">
         <h3>Change Password</h3>
 
         <form id="change-password" onSubmit={this.changePassword.bind(this)}>
@@ -90,3 +86,12 @@ export default class ChangePasswordForm extends React.Component {
     );
   }
 }
+
+export default connect(
+  () => {
+    return {
+      username: FauxtonAPI.session.user().name
+    };
+  },
+  {changePassword}
+)(ChangePasswordForm);
diff --git a/app/addons/auth/components/createadminform.js b/app/addons/auth/components/createadminform.js
index 47c70b5..2a2847d 100644
--- a/app/addons/auth/components/createadminform.js
+++ b/app/addons/auth/components/createadminform.js
@@ -12,55 +12,44 @@
 
 import React from "react";
 import ReactDOM from "react-dom";
-import { createAdminStore } from "./../stores";
 import {
-  updateCreateAdminUsername,
-  updateCreateAdminPassword,
   createAdmin
 } from "./../actions";
+import {connect} from 'react-redux';
 
-class CreateAdminForm extends React.Component {
-  getInitialState() {
-    return this.getStoreState();
-  }
-  getStoreState() {
-    return {
-      username: createAdminStore.getUsername(),
-      password: createAdminStore.getPassword()
-    };
-  }
-  onChange() {
-    this.setState(this.getStoreState());
-  }
-  getDefaultProps() {
-    return {
-      loginAfter: ""
+export class CreateAdminForm extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      username: '',
+      password: ''
     };
   }
+
   onChangeUsername(e) {
-    updateCreateAdminUsername(e.target.value);
+    this.setState({username: e.target.value});
   }
+
   onChangePassword(e) {
-    updateCreateAdminPassword(e.target.value);
+    this.setState({password: e.target.value});
   }
+
   componentDidMount() {
     ReactDOM.findDOMNode(this.refs.username).focus();
-    createAdminStore.on("change", this.onChange, this);
-  }
-  componentWillUnmount() {
-    createAdminStore.off("change", this.onChange);
   }
+
   createAdmin(e) {
     e.preventDefault();
-    createAdmin(
+    this.props.createAdmin(
       this.state.username,
       this.state.password,
       this.props.loginAfter
     );
   }
+
   render() {
     return (
-      <div className="auth-page">
+      <div className="faux__auth-page">
         <h3>Create Admins</h3>
 
         <p>
@@ -79,7 +68,7 @@ class CreateAdminForm extends React.Component {
           the test suite, and edit all aspects of CouchDB configuration.
         </p>
 
-        <form id="create-admin-form" onSubmit={this.createAdmin}>
+        <form id="create-admin-form" onSubmit={this.createAdmin.bind(this)}>
           <input
             id="username"
             type="text"
@@ -87,7 +76,7 @@ class CreateAdminForm extends React.Component {
             name="name"
             placeholder="Username"
             size="24"
-            onChange={this.onChangeUsername}
+            onChange={this.onChangeUsername.bind(this)}
           />
           <br />
           <input
@@ -96,11 +85,11 @@ class CreateAdminForm extends React.Component {
             name="password"
             placeholder="Password"
             size="24"
-            onChange={this.onChangePassword}
+            onChange={this.onChangePassword.bind(this)}
           />
           <p>
             Non-admin users have read and write access to all databases, which
-            are controlled by validatio. CouchDB can be configured to block all
+            are controlled by validation. CouchDB can be configured to block all
             access to anonymous users.
           </p>
           <button type="submit" id="create-admin" className="btn btn-primary">
@@ -116,4 +105,12 @@ CreateAdminForm.propTypes = {
   loginAfter: React.PropTypes.bool.isRequired
 };
 
-export default CreateAdminForm;
+CreateAdminForm.defaultProps = {
+  loginAfter: false
+};
+
+
+export default connect(
+  null,
+  {createAdmin}
+)(CreateAdminForm);
diff --git a/app/addons/auth/components/createadminsidebar.js b/app/addons/auth/components/createadminsidebar.js
deleted file mode 100644
index 9b954e9..0000000
--- a/app/addons/auth/components/createadminsidebar.js
+++ /dev/null
@@ -1,61 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import React from 'react';
-import { selectPage } from './../actions';
-import { createAdminSidebarStore } from './../stores';
-import FauxtonAPI from "../../../core/api";
-
-export default class CreateAdminSidebar extends React.Component {
-  constructor() {
-    super();
-    this.state = this.getStoreState();
-  }
-  getStoreState() {
-    return {
-      selectedPage: createAdminSidebarStore.getSelectedPage()
-    };
-  }
-  onChange() {
-    this.setState(this.getStoreState());
-  }
-  componentDidMount() {
-    createAdminSidebarStore.on('change', this.onChange, this);
-  }
-  componentWillUnmount() {
-    createAdminSidebarStore.off('change', this.onChange);
-  }
-  selectPage(e) {
-    var newPage = e.target.href.split('#')[1];
-    selectPage(newPage);
-  }
-  render() {
-    var user = FauxtonAPI.session.user;
-    var userName = _.isNull(user) ? '' : FauxtonAPI.session.user.name;
-
-    return (
-      <div className="sidenav">
-        <header className="row-fluid">
-          <h3>{userName}</h3>
-        </header>
-        <ul className="nav nav-list" onClick={this.selectPage}>
-          <li className={this.state.selectedPage === 'changePassword' ? 'active' : ''} data-page="changePassword">
-            <a href="#changePassword">Change Password</a>
-          </li>
-          <li className={this.state.selectedPage === 'addAdmin' ? 'active' : ''} data-page="addAdmin">
-            <a href="#addAdmin">Create Admins</a>
-          </li>
-        </ul>
-      </div>
-    );
-  }
-}
diff --git a/app/addons/auth/components/index.js b/app/addons/auth/components/index.js
index d322903..2c38e5a 100644
--- a/app/addons/auth/components/index.js
+++ b/app/addons/auth/components/index.js
@@ -12,14 +12,12 @@
 
 import LoginForm from './loginform.js';
 import PasswordModal from './passwordmodal.js';
-import CreateAdminSidebar from './createadminsidebar.js';
 import CreateAdminForm from './createadminform.js';
 import ChangePasswordForm from './changepasswordform.js';
 
-export default ({
+export default {
   LoginForm,
   PasswordModal,
-  CreateAdminSidebar,
   CreateAdminForm,
   ChangePasswordForm
-});
+};
diff --git a/app/addons/auth/components/loginform.js b/app/addons/auth/components/loginform.js
index 3518225..e72705a 100644
--- a/app/addons/auth/components/loginform.js
+++ b/app/addons/auth/components/loginform.js
@@ -23,10 +23,8 @@ class LoginForm extends React.Component {
     };
   }
   onInputChange(e) {
-    let change = e.target.name === "name"
-      ? { username: e.target.value }
-      : { password: e.target.value };
-    this.setState(change);
+    this.state[e.target.name] = e.target.value;
+    this.setState(this.state);
   }
   submit(e) {
     e.preventDefault();
@@ -40,10 +38,10 @@ class LoginForm extends React.Component {
     if (this.state.username !== "" || this.state.password !== "") {
       return false;
     }
-    var username = this.props.testBlankUsername
+    let username = this.props.testBlankUsername
       ? this.props.testBlankUsername
       : ReactDOM.findDOMNode(this.refs.username).value;
-    var password = this.props.testBlankPassword
+    let password = this.props.testBlankPassword
       ? this.props.testBlankPassword
       : ReactDOM.findDOMNode(this.refs.password).value;
     this.setState({ username: username, password: password }); // doesn't set immediately, hence separate login() call
@@ -69,7 +67,7 @@ class LoginForm extends React.Component {
               <input
                 id="username"
                 type="text"
-                name="name"
+                name="username"
                 ref="username"
                 placeholder="Username"
                 size="24"
diff --git a/app/addons/auth/components/passwordmodal.js b/app/addons/auth/components/passwordmodal.js
index 48f7784..6b7ea13 100644
--- a/app/addons/auth/components/passwordmodal.js
+++ b/app/addons/auth/components/passwordmodal.js
@@ -14,7 +14,7 @@ import React from "react";
 import { Modal } from "react-bootstrap";
 import { hidePasswordModal, authenticate } from "./../actions";
 import Components from "../../components/react-components";
-import app from "../../../app";
+import FauxtonAPI from "../../../core/api";
 
 class PasswordModal extends React.Component {
   constructor(props) {
@@ -34,7 +34,7 @@ class PasswordModal extends React.Component {
   }
   // default authentication function. This can be overridden via props if you want to do something different
   authenticate() {
-    const username = app.session.user.name; // yuck. But simplest for now until logging in publishes the user data
+    const username = FauxtonAPI.session.user().name;
     this.props.onSubmit(username, this.state.password, this.props.onSuccess);
   }
   render() {
diff --git a/app/addons/auth/layout.js b/app/addons/auth/layout.js
index cc9a609..8efc722 100644
--- a/app/addons/auth/layout.js
+++ b/app/addons/auth/layout.js
@@ -14,11 +14,12 @@ import React from 'react';
 import {OnePane, OnePaneContent} from '../components/layouts';
 import {Breadcrumbs} from '../components/header-breadcrumbs';
 import Components from "./components";
+import {TabElementWrapper, TabElement} from '../components/components/tabelement';
+import FauxtonAPI from "../../core/api";
 
 const {
   CreateAdminForm,
-  ChangePasswordForm,
-  CreateAdminSidebar
+  ChangePasswordForm
 } = Components;
 
 export const OnePaneHeader = ({crumbs}) => {
@@ -47,23 +48,43 @@ export const AuthLayout = ({crumbs, component}) => {
   );
 };
 
+const Tabs = ({changePassword}) => {
+  return (
+    <TabElementWrapper>
+      <TabElement
+        key={1}
+        selected={changePassword}
+        text={"Change Password"}
+        onChange={() => {
+          FauxtonAPI.navigate('#changePassword');
+        }}
+        />
+      <TabElement
+        key={2}
+        selected={!changePassword}
+        text={"Create Server Admin"}
+        onChange={() => {
+          FauxtonAPI.navigate('#addAdmin');
+        }}
+        />
+    </TabElementWrapper>
+  );
+};
+
 export const AdminLayout = ({crumbs, changePassword}) => {
   let content = changePassword ? <ChangePasswordForm /> : <CreateAdminForm loginAfter={false} />;
   return (
-    <div id="dashboard" className="template-with-sidebar flex-layout flex-col">
-      <OnePaneHeader
-        crumbs={crumbs}
-      >
-      </OnePaneHeader>
-      <div className="template-content flex-body flex-layout flex-row">
-        <div id="sidebar-content">
-          <CreateAdminSidebar />
-        </div>
-        <div id="dashboard-content" className="flex-body">
-          {content}
+    <OnePane>
+      <OnePaneHeader crumbs={crumbs}/>
+      <OnePaneContent>
+        <div className="template-content flex-body flex-layout flex-col">
+          <Tabs changePassword={changePassword} />
+          <div id="dashboard-conten1t" className="flex-layout flex-col">
+            {content}
+          </div>
         </div>
-      </div>
-    </div>
+      </OnePaneContent>
+    </OnePane>
 
   );
 
diff --git a/app/addons/auth/routes/auth.js b/app/addons/auth/routes/auth.js
index e72f0a1..d2ebd64 100644
--- a/app/addons/auth/routes/auth.js
+++ b/app/addons/auth/routes/auth.js
@@ -16,6 +16,7 @@ import ClusterActions from "../../cluster/cluster.actions";
 import { AuthLayout } from "./../layout";
 import app from "../../../app";
 import Components from "./../components";
+import {logout} from '../actions';
 
 const {
   LoginForm,
@@ -27,9 +28,9 @@ const crumbs = [{ name: "Log In to CouchDB" }];
 export default FauxtonAPI.RouteObject.extend({
   routes: {
     "login?*extra": "login",
-    login: "login",
-    logout: "logout",
-    createAdmin: "checkNodes",
+    "login": "login",
+    "logout": "logout",
+    "createAdmin": "checkNodes",
     "createAdmin/:node": "createAdminForNode"
   },
   checkNodes() {
@@ -44,8 +45,7 @@ export default FauxtonAPI.RouteObject.extend({
     );
   },
   logout() {
-    FauxtonAPI.addNotification({ msg: "You have been logged out." });
-    FauxtonAPI.session.logout().then(() => FauxtonAPI.navigate("/"));
+    logout();
   },
   createAdminForNode() {
     ClusterActions.fetchNodes();
diff --git a/app/addons/auth/routes/user.js b/app/addons/auth/routes/user.js
index 083c578..1934b44 100644
--- a/app/addons/auth/routes/user.js
+++ b/app/addons/auth/routes/user.js
@@ -14,11 +14,12 @@ import React from "react";
 import FauxtonAPI from "../../../core/api";
 import ClusterActions from "../../cluster/cluster.actions";
 import { AdminLayout } from "./../layout";
-import { selectPage } from './../actions';
 
 export default FauxtonAPI.RouteObject.extend({
   hideNotificationCenter: true,
   hideApiBar: true,
+  selectedHeader: 'Your Account',
+
   routes: {
     changePassword: {
       route: "checkNodesForPasswordChange",
@@ -37,18 +38,17 @@ export default FauxtonAPI.RouteObject.extend({
       roles: ["_admin"]
     }
   },
+
   checkNodesForPasswordChange() {
     ClusterActions.navigateToNodeBasedOnNodeCount("/changePassword/");
   },
+
   checkNodesForAddAdmin() {
     ClusterActions.navigateToNodeBasedOnNodeCount("/addAdmin/");
   },
-  selectedHeader() {
-    return FauxtonAPI.session.user.name;
-  },
+
   changePassword() {
     ClusterActions.fetchNodes();
-    selectPage("changePassword");
     return (
       <AdminLayout
         crumbs={[{ name: "User Management" }]}
@@ -58,7 +58,6 @@ export default FauxtonAPI.RouteObject.extend({
   },
   addAdmin() {
     ClusterActions.fetchNodes();
-    selectPage("addAdmin");
     return (
       <AdminLayout
         crumbs={[{ name: "User Management" }]}
diff --git a/app/addons/auth/session.js b/app/addons/auth/session.js
new file mode 100644
index 0000000..20e72fc
--- /dev/null
+++ b/app/addons/auth/session.js
@@ -0,0 +1,110 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import { uniqueId, each, includes } from "lodash";
+import Api from './api';
+import Promise from 'bluebird';
+
+function isAdmin(roles = []) {
+  return includes(roles, "_admin");
+}
+
+export default class {
+
+  constructor(opts) {
+    this._user = {
+      name: false,
+      roles: []
+    };
+    this._onChange = {};
+    this._authenticatedPromise = new Promise((resolve, reject) => {
+      this._authenticateResolve = resolve;
+      this._authenticateReject = reject;
+    });
+
+    this._allowAdminParty = opts.allowAdminParty;
+  }
+
+  isAuthenticated () {
+    return this._authenticatedPromise;
+  }
+
+  setUser(user) {
+    if (this._user.name !== user.name) {
+      this._user = {
+        name: user.name,
+        roles: user.roles,
+        isAdmin: isAdmin(user.roles)
+      };
+      each(this._onChange, fn => fn(this._user));
+    }
+    return this._user;
+  }
+
+  user() {
+    return this._user;
+  }
+
+  getSession() {
+    return Api.getSession()
+      .then(userCtx => {
+        this.setUser(userCtx);
+        if (this.isLoggedIn()) {
+          this._authenticateResolve(this.user());
+        }
+      })
+      .catch(err => {
+        this._authenticateReject(err);
+      });
+  }
+
+  isAdminParty() {
+    if (!this._allowAdminParty) {
+      return false;
+    }
+
+    return !this._user.name && this._user.isAdmin;
+  }
+
+  isLoggedIn() {
+    return !_.isNull(this._user.name);
+  }
+
+  userRoles() {
+    const user = this._user;
+
+    if (this.isLoggedIn()) {
+      if (user.roles.indexOf("fx_loggedIn") === -1) {
+        user.roles.push("fx_loggedIn");
+      }
+      return user.roles;
+    }
+    return [];
+  }
+
+  onChange(fn) {
+    let uuid = uniqueId();
+    this._onChange[uuid] = fn;
+    return () => {
+      delete this._onChange[uuid];
+    };
+  }
+
+  matchesRoles(roles = []) {
+    if (roles.length === 0) {
+      return true;
+    }
+
+    const numberMatchingRoles = _.intersection(this.userRoles(), roles).length;
+    return numberMatchingRoles > 0;
+  }
+}
diff --git a/app/addons/auth/stores/ChangePassword.js b/app/addons/auth/stores/ChangePassword.js
deleted file mode 100644
index 41d60ac..0000000
--- a/app/addons/auth/stores/ChangePassword.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import FauxtonAPI from "../../../core/api";
-import ActionTypes from '../actiontypes';
-const {
-  AUTH_CLEAR_CHANGE_PWD_FIELDS,
-  AUTH_UPDATE_CHANGE_PWD_FIELD,
-  AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD
-} = ActionTypes;
-
-const ChangePasswordStore = FauxtonAPI.Store.extend({
-  initialize() {
-    this.reset();
-  },
-  reset() {
-    this._changePassword = "";
-    this._changePasswordConfirm = "";
-  },
-  getChangePassword() {
-    return this._changePassword;
-  },
-  getChangePasswordConfirm() {
-    return this._changePasswordConfirm;
-  },
-  setChangePassword(val) {
-    this._changePassword = val;
-  },
-  setChangePasswordConfirm(val) {
-    this._changePasswordConfirm = val;
-  },
-  dispatch(action) {
-    switch (action.type) {
-      case AUTH_CLEAR_CHANGE_PWD_FIELDS:
-        this.reset();
-        this.triggerChange();
-        break;
-
-      case AUTH_UPDATE_CHANGE_PWD_FIELD:
-        this.setChangePassword(action.value);
-        this.triggerChange();
-        break;
-
-      case AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD:
-        this.setChangePasswordConfirm(action.value);
-        this.triggerChange();
-        break;
-
-      default:
-        return;
-    }
-  }
-});
-
-const changePasswordStore = new ChangePasswordStore();
-
-changePasswordStore.dispatchToken = FauxtonAPI.dispatcher.register(
-  changePasswordStore.dispatch.bind(changePasswordStore)
-);
-
-export default changePasswordStore;
diff --git a/app/addons/auth/stores/CreateAdminSidebarStore.js b/app/addons/auth/stores/CreateAdminSidebarStore.js
deleted file mode 100644
index 220fbfc..0000000
--- a/app/addons/auth/stores/CreateAdminSidebarStore.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import FauxtonAPI from "../../../core/api";
-import ActionTypes from "../actiontypes";
-
-const CreateAdminSidebarStore = FauxtonAPI.Store.extend({
-  initialize() {
-    this.reset();
-  },
-  reset() {
-    this._selectedPage = "changePassword";
-  },
-  getSelectedPage() {
-    return this._selectedPage;
-  },
-  setSelectedPage(val) {
-    this._selectedPage = val;
-  },
-  dispatch(action) {
-    switch (action.type) {
-      case ActionTypes.AUTH_SELECT_PAGE:
-        this.setSelectedPage(action.page);
-        this.triggerChange();
-        break;
-
-      default:
-        return;
-    }
-  }
-});
-
-const createAdminSidebarStore = new CreateAdminSidebarStore();
-createAdminSidebarStore.dispatchToken = FauxtonAPI.dispatcher.register(
-  createAdminSidebarStore.dispatch.bind(createAdminSidebarStore)
-);
-
-export default createAdminSidebarStore;
diff --git a/app/addons/auth/stores/CreateAdminStore.js b/app/addons/auth/stores/CreateAdminStore.js
deleted file mode 100644
index f005201..0000000
--- a/app/addons/auth/stores/CreateAdminStore.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import FauxtonAPI from "../../../core/api";
-import ActionTypes from "../actiontypes";
-const {
-  AUTH_CLEAR_CREATE_ADMIN_FIELDS,
-  AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
-  AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD
-} = ActionTypes;
-
-const CreateAdminStore = FauxtonAPI.Store.extend({
-  initialize() {
-    this.reset();
-  },
-  reset() {
-    this._username = "";
-    this._password = "";
-  },
-  getUsername() {
-    return this._username;
-  },
-  getPassword() {
-    return this._password;
-  },
-  setUsername(val) {
-    this._username = val;
-  },
-  setPassword(val) {
-    this._password = val;
-  },
-  dispatch(action) {
-    switch (action.type) {
-      case AUTH_CLEAR_CREATE_ADMIN_FIELDS:
-        this.reset();
-        this.triggerChange();
-        break;
-
-      case AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD:
-        this.setUsername(action.value);
-        this.triggerChange();
-        break;
-
-      case AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD:
-        this.setPassword(action.value);
-        this.triggerChange();
-        break;
-
-      default:
-        return;
-    }
-  }
-});
-
-const createAdminStore = new CreateAdminStore();
-createAdminStore.dispatchToken = FauxtonAPI.dispatcher.register(
-  createAdminStore.dispatch.bind(createAdminStore)
-);
-
-export default createAdminStore;
diff --git a/app/addons/auth/stores/index.js b/app/addons/auth/stores/index.js
deleted file mode 100644
index 6a6ff7d..0000000
--- a/app/addons/auth/stores/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// Not thrilled with this. The sole purpose of these next two stores is because the Create Admin + Change Password
-// forms need to clear after a successful post. Since those events occur in actions.js, we need a way to tell the
-// component to update + clear the fields. That's why all this code exists.
-
-import changePasswordStore from './ChangePassword';
-import createAdminStore from './CreateAdminStore';
-import createAdminSidebarStore from './CreateAdminSidebarStore.js';
-
-export default ({
-  changePasswordStore,
-  createAdminStore,
-  createAdminSidebarStore
-});
diff --git a/app/addons/cluster/cluster.actions.js b/app/addons/cluster/cluster.actions.js
index 47dcf0c..b0ca2a5 100644
--- a/app/addons/cluster/cluster.actions.js
+++ b/app/addons/cluster/cluster.actions.js
@@ -36,7 +36,7 @@ export default {
     var memberships = new ClusterResources.ClusterNodes();
 
     memberships.fetch().then(function () {
-      var nodes = memberships.get('all_nodes');
+      const nodes = memberships.get('all_nodes');
 
       if (nodes.length === 1) {
         return FauxtonAPI.navigate(successtarget + nodes[0]);
diff --git a/app/addons/documents/changes/components.react.jsx b/app/addons/documents/changes/components.react.jsx
deleted file mode 100644
index db67ee3..0000000
--- a/app/addons/documents/changes/components.react.jsx
+++ /dev/null
@@ -1,352 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../core/api";
-import React from "react";
-import ReactDOM from "react-dom";
-import Actions from "./actions";
-import Stores from "./stores";
-import Components from "../../fauxton/components.react";
-import ReactComponents from "../../components/react-components.react";
-import ReactCSSTransitionGroup from "react-addons-css-transition-group";
-import "../../../../assets/js/plugins/prettify";
-import uuid from 'uuid';
-
-const store = Stores.changesStore;
-const BadgeList = ReactComponents.BadgeList;
-const {Copy} = ReactComponents;
-
-class ChangesController extends React.Component {
-  constructor (props) {
-    super(props);
-    this.state = this.getStoreState();
-  }
-
-  getStoreState () {
-    return {
-      changes: store.getChanges(),
-      loaded: store.isLoaded(),
-      databaseName: store.getDatabaseName(),
-      isShowingSubset: store.isShowingSubset()
-    };
-  }
-
-  onChange () {
-    this.setState(this.getStoreState());
-  }
-
-  componentDidMount () {
-    store.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount () {
-    store.off('change', this.onChange);
-  }
-
-  showingSubsetMsg () {
-    const { isShowingSubset, changes } = this.state;
-    let msg = '';
-    if (isShowingSubset) {
-      let numChanges = changes.length;
-      msg = <p className="changes-result-limit">Limiting results to latest <b>{numChanges}</b> changes.</p>;
-    }
-    return msg;
-  }
-
-  getRows () {
-    const { changes, loaded, databaseName } = this.state;
-    if (!changes.length && loaded) {
-      return (
-        <p className="no-doc-changes">
-          There are no document changes to display.
-        </p>
-      );
-    }
-
-    return changes.map((change, i) => {
-      return <ChangeRow change={change} key={i} databaseName={databaseName} />;
-    });
-  }
-
-  render () {
-    return (
-      <div>
-        <div className="js-changes-view">
-          {this.showingSubsetMsg()}
-          {this.getRows()}
-        </div>
-      </div>
-    );
-  }
-}
-
-
-class ChangesTabContent extends React.Component {
-  constructor (props) {
-    super(props);
-    this.state = this.getStoreState();
-  }
-
-  getStoreState () {
-    return {
-      filters: store.getFilters()
-    };
-  }
-
-  onChange () {
-    this.setState(this.getStoreState());
-  }
-
-  componentDidMount () {
-    store.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount () {
-    store.off('change', this.onChange);
-  }
-
-  addFilter (newFilter) {
-    if (_.isEmpty(newFilter)) {
-      return;
-    }
-    Actions.addFilter(newFilter);
-  }
-
-  hasFilter (filter) {
-    return store.hasFilter(filter);
-  }
-
-  render () {
-    return (
-      <div className="changes-header">
-        <AddFilterForm tooltip={this.props.tooltip} filter={(label) => Actions.removeFilter(label)} addFilter={this.addFilter}
-          hasFilter={this.hasFilter} />
-        <BadgeList elements={this.state.filters} removeBadge={(label) => Actions.removeFilter(label)} />
-      </div>
-    );
-  }
-}
-
-
-class AddFilterForm extends React.Component {
-  constructor (props) {
-    super(props);
-    this.state = {
-      filter: '',
-      error: false
-    };
-    this.submitForm = this.submitForm.bind(this);
-  }
-
-  submitForm (e) {
-    e.preventDefault();
-    e.stopPropagation();
-
-    if (this.props.hasFilter(this.state.filter)) {
-      this.setState({ error: true });
-
-      // Yuck. This removes the class after the effect has completed so it can occur again. The
-      // other option is to use jQuery to add the flash. This seemed slightly less crumby
-      let component = this;
-      setTimeout(function () {
-        component.setState({ error: false });
-      }, 1000);
-    } else {
-      this.props.addFilter(this.state.filter);
-      this.setState({ filter: '', error: false });
-    }
-  }
-
-  componentDidMount () {
-    this.focusFilterField();
-  }
-
-  componentDidUpdate () {
-    this.focusFilterField();
-  }
-
-  focusFilterField () {
-    ReactDOM.findDOMNode(this.refs.addItem).focus();
-  }
-
-  inputClassNames () {
-    let className = 'js-changes-filter-field';
-    if (this.state.error) {
-      className += ' errorHighlight';
-    }
-    return className;
-  }
-
-  render () {
-    return (
-      <form className="form-inline js-filter-form" onSubmit={this.submitForm}>
-        <fieldset>
-          <i className="fonticon-filter" />
-          <input type="text" ref="addItem" className={this.inputClassNames()} placeholder="Sequence or ID"
-                 onChange={(e) => this.setState({ filter: e.target.value })} value={this.state.filter} />
-          <button type="submit" className="btn btn-secondary">Filter</button>
-          <div className="help-block">
-            <FilterTooltip tooltip={this.props.tooltip} />
-          </div>
-        </fieldset>
-      </form>
-    );
-  }
-}
-AddFilterForm.PropTypes = {
-  addFilter: React.PropTypes.func.isRequired,
-  hasFilter: React.PropTypes.func.isRequired,
-  tooltips: React.PropTypes.string
-};
-AddFilterForm.defaultProps = {
-  tooltip: ''
-};
-
-
-class FilterTooltip extends React.Component {
-  componentDidMount () {
-    if (this.props.tooltip) {
-      $(ReactDOM.findDOMNode(this.refs.tooltip)).tooltip();
-    }
-  }
-
-  render () {
-    if (!this.props.tooltip) {
-      return null;
-    }
-    return (
-      <i ref="tooltip" className="icon icon-question-sign js-filter-tooltip" data-toggle="tooltip"
-         data-original-title={this.props.tooltip} />
-    );
-  }
-}
-
-
-class ChangeRow extends React.Component {
-  constructor (props) {
-    super(props);
-    this.state = {
-      codeVisible: false
-    };
-  }
-
-  toggleJSON (e) {
-    e.preventDefault();
-    this.setState({ codeVisible: !this.state.codeVisible });
-  }
-
-  getChangesCode () {
-    return (this.state.codeVisible) ? <Components.CodeFormat key="changesCodeSection" code={this.getChangeCode()} /> : null;
-  }
-
-  getChangeCode () {
-    return {
-      changes: this.props.change.changes,
-      doc: this.props.change.doc
-    };
-  }
-
-  showCopiedMessage (target) {
-    let msg = 'The document ID has been copied to your clipboard.';
-    if (target === 'seq') {
-      msg = 'The document seq number has been copied to your clipboard.';
-    }
-    FauxtonAPI.addNotification({
-      msg: msg,
-      type: 'info',
-      clear: true
-    });
-  }
-
-  render () {
-    const { codeVisible } = this.state;
-    const { change, databaseName } = this.props;
-    const wrapperClass = 'change-wrapper' + (change.isNew ? ' new-change-row' : '');
-
-    return (
-      <div className={wrapperClass}>
-        <div className="change-box" data-id={change.id}>
-          <div className="row-fluid">
-            <div className="span2">seq</div>
-            <div className="span8 change-sequence">{change.seq}</div>
-            <div className="span2 text-right">
-              <Copy
-                uniqueKey={uuid.v4()}
-                text={change.seq.toString()}
-                onClipboardClick={() => this.showCopiedMessage('seq')} />
-            </div>
-          </div>
-
-          <div className="row-fluid">
-            <div className="span2">id</div>
-            <div className="span8">
-              <ChangeID id={change.id} deleted={change.deleted} databaseName={databaseName} />
-            </div>
-            <div className="span2 text-right">
-              <Copy
-                uniqueKey={uuid.v4()}
-                text={change.id}
-                onClipboardClick={() => this.showCopiedMessage('id')} />
-            </div>
-          </div>
-
-          <div className="row-fluid">
-            <div className="span2">changes</div>
-            <div className="span10">
-              <button type="button" className='btn btn-small btn-secondary' onClick={this.toggleJSON.bind(this)}>
-                {codeVisible ? 'Close JSON' : 'View JSON'}
-              </button>
-            </div>
-          </div>
-
-          <ReactCSSTransitionGroup transitionName="toggle-changes-code" component="div" className="changesCodeSectionWrapper"
-            transitionEnterTimeout={500} transitionLeaveTimeout={300}>
-            {this.getChangesCode()}
-          </ReactCSSTransitionGroup>
-
-          <div className="row-fluid">
-            <div className="span2">deleted</div>
-            <div className="span10">{change.deleted ? 'True' : 'False'}</div>
-          </div>
-        </div>
-      </div>
-    );
-  }
-}
-ChangeRow.PropTypes = {
-  change: React.PropTypes.object,
-  databaseName: React.PropTypes.string.isRequired
-};
-
-
-class ChangeID extends React.Component {
-  render () {
-    const { deleted, id, databaseName } = this.props;
-    if (deleted) {
-      return (
-        <span className="js-doc-id">{id}</span>
-      );
-    }
-    const link = '#' + FauxtonAPI.urls('document', 'app', databaseName, id);
-    return (
-      <a href={link} className="js-doc-link">{id}</a>
-    );
-  }
-}
-
-
-export default {
-  ChangesController,
-  ChangesTabContent,
-  ChangeRow,
-  ChangeID
-};
diff --git a/app/addons/fauxton/appwrapper.js b/app/addons/fauxton/appwrapper.js
index ab3ddd4..2074c77 100644
--- a/app/addons/fauxton/appwrapper.js
+++ b/app/addons/fauxton/appwrapper.js
@@ -53,7 +53,7 @@ class ContentWrapper extends React.Component {
   }
 }
 
-class App extends React.Component {
+export default class App extends React.Component {
   constructor (props) {
     super(props);
     this.state = this.getStoreState();
@@ -100,5 +100,3 @@ class App extends React.Component {
     );
   }
 };
-
-export default App;
diff --git a/app/addons/fauxton/navigation/container/NavBar.js b/app/addons/fauxton/navigation/container/NavBar.js
index 537f0da..c96fcfa 100644
--- a/app/addons/fauxton/navigation/container/NavBar.js
+++ b/app/addons/fauxton/navigation/container/NavBar.js
@@ -56,7 +56,7 @@ const NavBarContainer = React.createClass({
   render () {
     const user = FauxtonAPI.session.user();
 
-    const username =  user ? user.name : '';
+    const username =  (user && user.name) ? user.name : '';
     return (
       <NavBar {...this.state} username={username} />
     );
diff --git a/app/addons/fauxton/navigation/stores.js b/app/addons/fauxton/navigation/stores.js
index 777f097..be52c57 100644
--- a/app/addons/fauxton/navigation/stores.js
+++ b/app/addons/fauxton/navigation/stores.js
@@ -12,6 +12,7 @@
 
 import FauxtonAPI from "../../../core/api";
 import ActionTypes from "./actiontypes";
+import {findIndex} from 'lodash';
 
 const Stores = {};
 
@@ -76,18 +77,16 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({
 
   removeLink (removeLink) {
     const links = this.getLinkSection(removeLink);
-    let indexOf = 0;
 
-    const res = links.filter((link) => {
+    const indexOf = findIndex(links, link => {
       if (link.id === removeLink.id) {
         return true;
       }
 
-      indexOf++;
       return false;
     });
 
-    if (!res.length) { return; }
+    if (indexOf === -1) { return; }
 
     links.splice(indexOf, 1);
   },
diff --git a/app/addons/replication/__tests__/actions.test.js b/app/addons/replication/__tests__/actions.test.js
index b99c94e..f12851a 100644
--- a/app/addons/replication/__tests__/actions.test.js
+++ b/app/addons/replication/__tests__/actions.test.js
@@ -13,12 +13,13 @@ import utils from '../../../../test/mocha/testUtils';
 import {replicate, getReplicationStateFrom, deleteDocs} from '../actions';
 import ActionTypes from '../actiontypes';
 import fetchMock from 'fetch-mock';
-import app from '../../../app';
 import FauxtonAPI from '../../../core/api';
 
-app.session = {
-  get () {
-    return 'test-user-name';
+FauxtonAPI.session = {
+  user () {
+    return {
+      name: 'test-user-name'
+    };
   }
 };
 
diff --git a/app/addons/replication/__tests__/newreplication.test.js b/app/addons/replication/__tests__/newreplication.test.js
index 2128b12..19c56e4 100644
--- a/app/addons/replication/__tests__/newreplication.test.js
+++ b/app/addons/replication/__tests__/newreplication.test.js
@@ -21,8 +21,6 @@ const {assert, restore}  = utils;
 
 describe('New Replication Component', () => {
 
-  FauxtonAPI.session.triggerError = () => {};
-
   describe('validation', () => {
 
     afterEach(() => {
diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js
index b0f08ba..6731828 100644
--- a/app/addons/replication/api.js
+++ b/app/addons/replication/api.js
@@ -12,7 +12,6 @@
 
 import 'url-polyfill';
 import Constants from './constants';
-import app from '../../app';
 import FauxtonAPI from '../../core/api';
 import base64 from 'base-64';
 import _ from 'lodash';
@@ -54,7 +53,7 @@ export const decodeFullUrl = (fullUrl) => {
 };
 
 export const getUsername = () => {
-  return app.session.get('userCtx').name;
+  return FauxtonAPI.session.user().name;
 };
 
 export const getAuthHeaders = (username, password) => {
diff --git a/app/addons/replication/base.js b/app/addons/replication/base.js
index 0392c67..0b2cc19 100644
--- a/app/addons/replication/base.js
+++ b/app/addons/replication/base.js
@@ -18,7 +18,6 @@ import Actions from './actions';
 replication.initialize = function () {
   FauxtonAPI.addHeaderLink({ title: 'Replication', href: '#/replication', icon: 'fonticon-replicate' });
   FauxtonAPI.session.isAuthenticated().then(() => {
-    console.log('AUAUAU', FauxtonAPI.session.isLoggedIn());
     Actions.checkForNewApi();
   });
 };
diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js
index 62d95f2..819b17a 100644
--- a/app/addons/replication/controller.js
+++ b/app/addons/replication/controller.js
@@ -12,7 +12,7 @@
 import React from 'react';
 import Stores from './stores';
 import Actions from './actions';
-import AuthActions from '../auth/actions';
+import {showPasswordModal} from '../auth/actions';
 import Components from '../components/react-components';
 import NewReplication from './components/newreplication';
 import Activity from './components/activity';
@@ -134,7 +134,7 @@ export default class ReplicationController extends React.Component {
         localSourceKnown={localSourceKnown}
         clearReplicationForm={Actions.clearReplicationForm}
         replicate={Actions.replicate}
-        showPasswordModal={AuthActions.showPasswordModal}
+        showPasswordModal={showPasswordModal}
         replicationSource={replicationSource}
         replicationTarget={replicationTarget}
         replicationType={replicationType}
diff --git a/app/app.js b/app/app.js
index 29b5730..212ccc2 100644
--- a/app/app.js
+++ b/app/app.js
@@ -16,7 +16,6 @@ import "bootstrap";
 import Helpers from "./helpers";
 import Utils from "./core/utils";
 import FauxtonAPI from "./core/api";
-import Session from "./core/session";
 import "../assets/less/fauxton.less";
 
 // Make sure we have a console.log
@@ -46,8 +45,6 @@ Object.assign(app, {
   helpers: Helpers
 });
 
-FauxtonAPI.setSession(new Session());
-
 FauxtonAPI.config({
   // I haven't wrapped these dispatch methods in a action
   // because I don't want to require fauxton/actions in this method.
diff --git a/app/core/auth.js b/app/core/authentication.js
similarity index 53%
rename from app/core/auth.js
rename to app/core/authentication.js
index a77342b..c933aff 100644
--- a/app/core/auth.js
+++ b/app/core/authentication.js
@@ -11,30 +11,44 @@
 // the License.
 
 import FauxtonAPI from "./base";
+import Promise from 'bluebird';
 
-function authenticate(session, roles) {
+export const authenticate = (session, roles) => {
   if (session.isAdminParty()) {
     return true;
   } else if (session.matchesRoles(roles)) {
     return true;
-  } else {
-    throw new Error('Unable to authenticate');
   }
-}
 
-function authenticationDenied() {
+  throw new Error('Unable to authenticate');
+};
+
+export const authenticationDenied = () => {
   let url = window.location.hash
     .replace('#', '')
     .replace('login?urlback=', '');
 
-  FauxtonAPI.navigate(`/login?urlback=${url}`, { replace: true });
+  if (url) {
+    FauxtonAPI.navigate(`/login?urlback=${url}`, { replace: true });
+  }
+
+  FauxtonAPI.navigate(`/login`, { replace: true });
 };
 
-export default class {
-  checkAccess(roles = []) {
-    return FauxtonAPI.session
-      .fetchUser()
-      .then(() => authenticate(FauxtonAPI.session, roles))
-      .catch(authenticationDenied);
-  }
-}
+export const checkAccess = (roles = []) => {
+  return new Promise((resolve, reject) => {
+    return FauxtonAPI.session.getSession()
+      .then(() => {
+        if (authenticate(FauxtonAPI.session, roles)) {
+          resolve();
+          return;
+        }
+
+        reject();
+      })
+      .catch(err => {
+        reject(err);
+        authenticationDenied();
+      });
+  });
+};
diff --git a/app/core/base.js b/app/core/base.js
index 5ab6ced..8e49be3 100644
--- a/app/core/base.js
+++ b/app/core/base.js
@@ -155,7 +155,6 @@ FauxtonAPI.extensions = extensions;
 
 FauxtonAPI.setSession = function (newSession) {
   FauxtonAPI.session = newSession;
-  //return FauxtonAPI.session.fetchUser();
 };
 
 FauxtonAPI.reducers = {};
diff --git a/app/core/couchdb/admin.js b/app/core/couchdb/admin.js
deleted file mode 100644
index 9e800a0..0000000
--- a/app/core/couchdb/admin.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import app from './../../app';
-import { json } from '../http';
-
-export function create({name, password, node}) {
-  return json(`${app.host}/_node/${node}/_config/admins/${name}`, {
-    method: "PUT",
-    body: JSON.stringify(password)
-  });
-}
diff --git a/app/core/couchdb/index.js b/app/core/couchdb/index.js
deleted file mode 100644
index fdba344..0000000
--- a/app/core/couchdb/index.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import * as session from './session';
-import * as admin from './admin';
-
-export default ({
-  session,
-  admin
-});
diff --git a/app/core/couchdb/session.js b/app/core/couchdb/session.js
deleted file mode 100644
index 1fac09d..0000000
--- a/app/core/couchdb/session.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import app from "./../../app";
-import { json } from "../http";
-
-export function get() {
-  return json(app.host + "/_session");
-}
-
-export function create(body) {
-  return json(app.host + "/_session", {
-    method: "POST",
-    body: JSON.stringify(body)
-  });
-}
-
-export function remove() {
-  return json(app.host + "/_session", {
-    method: "DELETE",
-    body: JSON.stringify({ username: "_", password: "_" })
-  });
-}
diff --git a/app/core/couchdbSession.js b/app/core/couchdbSession.js
deleted file mode 100644
index da3aa14..0000000
--- a/app/core/couchdbSession.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "./base";
-import _ from "lodash";
-
-var CouchdbSession = {
-  Session: FauxtonAPI.Model.extend({
-    url: '/_session',
-
-    user: function () {
-      var userCtx = this.get('userCtx');
-
-      if (!userCtx || !userCtx.name) { return null; }
-
-      return {
-        name: userCtx.name,
-        roles: userCtx.roles
-      };
-    },
-
-    isAdmin: function () {
-      var userCtx = this.get('userCtx');
-      return userCtx.roles.indexOf('_admin') !== -1;
-    },
-
-    fetchUser: function (opt) {
-      var options = opt || {},
-          currentUser = this.user(),
-          fetch = _.bind(this.fetchOnce, this);
-
-      if (options.forceFetch) {
-        fetch = _.bind(this.fetch, this);
-      }
-
-      return fetch(opt).then(function () {
-        var user = this.user();
-
-        // Notify anyone listening on these events that either a user has changed
-        // or current user is the same
-        if (currentUser !== user) {
-          this.trigger('session:userChanged');
-        } else {
-          this.trigger('session:userFetched');
-        }
-
-        // this will return the user as a value to all function that calls done on this
-        // eg. session.fetchUser().done(user) { .. do something with user ..}
-        return user;
-      }.bind(this), this.triggerError.bind(this));
-    },
-
-    triggerError: function (xhr, type, message) {
-      this.trigger('session:error', xhr, type, message);
-    }
-  })
-};
-
-export default CouchdbSession;
diff --git a/app/core/http.js b/app/core/http.js
deleted file mode 100644
index f66524d..0000000
--- a/app/core/http.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import { defaultsDeep } from "lodash";
-
-export const json = (url, opts = {}) => fetch(
-  url,
-  defaultsDeep(
-    {
-      credentials: "include",
-      headers: {
-        accept: "application/json",
-        "Content-Type": "application/json"
-      }
-    },
-    opts
-  )
-).then(res => res.ok ? res.json() : { error: res.statusText });
diff --git a/app/core/router.js b/app/core/router.js
index fac7d83..3ce3e7e 100644
--- a/app/core/router.js
+++ b/app/core/router.js
@@ -10,8 +10,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import FauxtonAPI from "./base";
-import Auth from "./auth";
+import {checkAccess} from "./authentication";
 import Backbone from "backbone";
 import _ from "lodash";
 
@@ -55,9 +54,8 @@ export default Backbone.Router.extend({
     routeUrls.forEach(route => {
       this.route(route, route.toString(), (...args) => {
         const roles = RouteObject.prototype.getRouteRoles(route);
-        const authPromise = FauxtonAPI.auth.checkAccess(roles);
 
-        authPromise.then(() => {
+        checkAccess(roles).then(() => {
           if (!that.activeRouteObject || !that.activeRouteObject.hasRoute(route)) {
             that.activeRouteObject = new RouteObject(route, args);
           }
@@ -71,10 +69,7 @@ export default Backbone.Router.extend({
             route: route.toString()
           };
           that.trigger('new-component', this.currentRouteOptions);
-        }, () => {
-          FauxtonAPI.auth.authDeniedCb();
-        });
-
+        }, () => {/* do nothing on reject*/ });
       });
     });
   },
@@ -93,7 +88,6 @@ export default Backbone.Router.extend({
 
   initialize: function (addons) {
     this.addons = addons;
-    this.auth = FauxtonAPI.auth = new Auth();
     // NOTE: This must be below creation of the layout
     // FauxtonAPI header links and others depend on existence of the layout
     this.setModuleRoutes(addons);
@@ -106,14 +100,5 @@ export default Backbone.Router.extend({
         this.lastPages.shift();
       }
     }, this);
-  },
-
-  triggerRouteEvent: function (event, args) {
-    if (this.activeRouteObject) {
-      var eventArgs = [event].concat(args);
-
-      this.activeRouteObject.trigger.apply(this.activeRouteObject, eventArgs);
-      this.activeRouteObject.renderWith(eventArgs, FauxtonAPI.masterLayout, args);
-    }
   }
 });
diff --git a/app/core/session.js b/app/core/session.js
deleted file mode 100644
index 8867792..0000000
--- a/app/core/session.js
+++ /dev/null
@@ -1,166 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-import couchdb from "./couchdb";
-import { uniqueId, each } from "lodash";
-import Promise from 'Bluebird';
-
-const authMessages = {
-  missingCredentials: "Username or password cannot be blank.",
-  loggedIn: "You have been logged in.",
-  adminCreated: "CouchDB admin created",
-  changePassword: "Your password has been updated.",
-  adminCreationFailedPrefix: "Could not create admin.",
-  passwordsNotMatching: "Passwords do not match."
-};
-
-function isAdmin(roles = []) {
-  return roles.includes("_admin");
-}
-
-function validate(...predicates) {
-  return predicates.every(isTrue => isTrue);
-}
-
-export default class {
-
-  constructor() {
-    this._user = {
-      roles: []
-    };
-    this._onChange = {};
-    this.messages = authMessages;
-    this._authenticatedPromise = new Promise(resolve => {
-      this._authenticateResolve = resolve;
-    });
-  }
-  getUserFromSession() {
-    return couchdb.session.get().then(({ userCtx }) => {
-      return userCtx;
-    });
-  }
-
-  isAuthenticated () {
-    return this._authenticatedPromise;
-  }
-
-  setUser(user) {
-    if (this._user.name !== user.name) {
-      this._user = {
-        name: user.name,
-        roles: user.roles,
-        isAdmin: isAdmin(user.roles)
-      };
-      each(this._onChange, fn => fn(this._user));
-    }
-    return this._user;
-  }
-
-  user() {
-    return this._user;
-  }
-
-  fetchUser() {
-    return this.getUserFromSession().then(userCtx => {
-      this.setUser(userCtx);
-      console.log('HAve user', this.isLoggedIn());
-      if (this.isLoggedIn()) {
-        this._authenticateResolve(this.user());
-      }
-    });
-  }
-
-  isAdminParty() {
-    return !this._user.name && this._user.isAdmin;
-  }
-
-  isLoggedIn() {
-    return !_.isNull(this._user.name);
-  }
-
-  userRoles() {
-    const user = this._user;
-
-    console.log('UU', this._user, this.isLoggedIn());
-    //if (user && user.roles) {
-    if (this.isLoggedIn()) {
-      if (user.roles.indexOf("fx_loggedIn") === -1) {
-        user.roles.push("fx_loggedIn");
-      }
-      return user.roles;
-    }
-    return [];
-  }
-
-  onChange(fn) {
-    let uuid = uniqueId();
-    this._onChange[uuid] = fn;
-    return () => {
-      delete this._onChange[uuid];
-    };
-  }
-
-  matchesRoles(roles = []) {
-    const numberMatchingRoles = _.intersection(this.userRoles(), roles).length;
-    return numberMatchingRoles > 0;
-  }
-
-  validateUser(username, password, msg) {
-    const isValid = validate(!_.isEmpty(username), !_.isEmpty(password));
-    return isValid ? Promise.resolve() : Promise.reject(msg);
-  }
-
-  validatePasswords(password, password_confirm, msg) {
-    const isValid = validate(
-      _.isEmpty(password),
-      _.isEmpty(password_confirm),
-      password !== password_confirm
-    );
-    return isValid ? Promise.resolve() : Promise.reject(msg);
-  }
-
-  createAdmin(username, password, login, node) {
-    return this
-      .validateUser(username, password, this.messages.missingCredentials)
-      .then(() => couchdb.admin.create({ name: this._user.name, password, node }))
-      .then(
-        () =>
-          login
-            ? this.login(username, password)
-            : this.fetchUser({ forceFetch: true })
-      );
-  }
-
-  login(username, password) {
-    return this
-      .validateUser(username, password, authMessages.missingCredentials)
-      .then(() => couchdb.session.create({ name: username, password: password }))
-      .then((res) => {
-        if (res.error) throw new Error(res.error);
-      })
-      .then(() => this.fetchUser());
-  }
-
-  logout() {
-    return couchdb.session.remove().then(() => this.fetchUser());
-  }
-
-  changePassword(password, confirmedPassword, node) {
-    return this
-      .validatePasswords(
-        password,
-        confirmedPassword,
-        authMessages.passwordsNotMatching
-      )
-      .then(() => couchdb.admin.create({ name: this._user.name, password, node }))
-      .then(() => this.login(this._user.name, password));
-  }
-}
diff --git a/app/core/tests/couchdbSessionSpec.js b/app/core/tests/couchdbSessionSpec.js
deleted file mode 100644
index 7203b36..0000000
--- a/app/core/tests/couchdbSessionSpec.js
+++ /dev/null
@@ -1,44 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-import FauxtonAPI from "../api";
-import testUtils from "../../../test/mocha/testUtils";
-import sinon from "sinon";
-
-describe('CouchDBSession', function () {
-
-  before(function (done) {
-    sinon.stub(FauxtonAPI.session, 'fetch', function () {
-      var promise = FauxtonAPI.Deferred();
-      promise.reject();
-      return promise;
-    });
-
-    done();
-  });
-
-  after(function (done) {
-    testUtils.restore(FauxtonAPI.session.fetch);
-    testUtils.restore(FauxtonAPI.session.triggerError);
-
-    done();
-  });
-
-  it('triggers error on failed fetch', function (done) {
-
-    sinon.stub(FauxtonAPI.session, 'triggerError', function () {
-      done();
-    });
-
-    FauxtonAPI.session.fetchUser();
-  });
-
-});
diff --git a/app/main.js b/app/main.js
index d14e974..27bad1e 100644
--- a/app/main.js
+++ b/app/main.js
@@ -23,6 +23,13 @@ import { createStore, applyMiddleware, combineReducers } from 'redux';
 import thunk from 'redux-thunk';
 import { Provider } from 'react-redux';
 
+const middlewares = [thunk];
+
+const store = createStore(
+  combineReducers(FauxtonAPI.reducers),
+  applyMiddleware(...middlewares)
+);
+
 app.addons = LoadAddons;
 FauxtonAPI.router = app.router = new FauxtonAPI.Router(app.addons);
 // Trigger the initial route and enable HTML5 History API support, set the
@@ -34,7 +41,6 @@ if ('ActiveXObject' in window) {
   $.ajaxSetup({ cache: false });
 }
 
-
 // All navigation that is relative should be passed through the navigate
 // method, to be processed by the router. If the link has a `data-bypass`
 // attribute, bypass the delegation completely.
@@ -57,13 +63,6 @@ $(document).on("click", "a:not([data-bypass])", function (evt) {
   }
 });
 
-const middlewares = [thunk];
-
-const store = createStore(
-  combineReducers(FauxtonAPI.reducers),
-  applyMiddleware(...middlewares)
-);
-
 ReactDOM.render(
   <Provider store={store}>
     <AppWrapper router={app.router}/>
diff --git a/i18n.json.default.json b/i18n.json.default.json
index d1e4971..f5baf56 100644
--- a/i18n.json.default.json
+++ b/i18n.json.default.json
@@ -12,6 +12,12 @@
     "cors-disable-cors-prompt": "Are you sure? Disabling CORS will overwrite your specific origin domains.",
     "cors-notice": "Cross-Origin Resource Sharing (CORS) lets you connect to remote servers directly from the browser, so you can host browser-based apps on static pages and talk directly with CouchDB to load your data.",
     "replication-password-modal-header": "Enter Account Password.",
-    "replication-password-modal-text": "Replication requires authentication on your credentials."
+    "replication-password-modal-text": "Replication requires authentication on your credentials.",
+    "auth-missing-credentials": "Username or password cannot be blank.",
+    "auth-logged-in": "You have been logged in.",
+    "auth-admin-created": "CouchDB admin created",
+    "auth-change-password": "Your password has been updated.",
+    "auth-admin-creation-failed-prefix": "Could not create admin.",
+    "auth-passwords-not-matching": "Passwords do not match."
   }
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@couchdb.apache.org" <co...@couchdb.apache.org>.