diff --git a/.eslintrc.js b/.eslintrc.js
index 34d3af270c..6cd0e1015e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -13,6 +13,7 @@ module.exports = {
     plugins: [
       "react",
       "flowtype",
+      "babel"
     ],
     env: {
         es6: true,
@@ -23,6 +24,11 @@ module.exports = {
         }
     },
     rules: {
+        // eslint's built in no-invalid-this rule breaks with class properties
+        "no-invalid-this": "off",
+        // so we replace it with a version that is class property aware
+        "babel/no-invalid-this": "error",
+
         /** react **/
         // This just uses the react plugin to help eslint known when
         // variables have been used in JSX
diff --git a/package.json b/package.json
index 6e7013fb93..a07e2236aa 100644
--- a/package.json
+++ b/package.json
@@ -90,6 +90,7 @@
     "babel-preset-react": "^6.11.1",
     "eslint": "^3.13.1",
     "eslint-config-google": "^0.7.1",
+    "eslint-plugin-babel": "^4.0.1",
     "eslint-plugin-flowtype": "^2.30.0",
     "eslint-plugin-react": "^6.9.0",
     "expect": "^1.16.0",
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 87a2878e37..d5683b35a0 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -1,5 +1,6 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -24,6 +25,8 @@ import Presence from './Presence';
 import dis from './dispatcher';
 import DMRoomMap from './utils/DMRoomMap';
 import RtsClient from './RtsClient';
+import Modal from './Modal';
+import sdk from './index';
 
 /**
  * Called at startup, to attempt to build a logged-in Matrix session. It tries
@@ -109,16 +112,17 @@ export function loadSession(opts) {
         return q();
     }
 
-    if (_restoreFromLocalStorage()) {
-        return q();
-    }
+    return _restoreFromLocalStorage().then((success) => {
+        if (success) {
+            return;
+        }
 
-    if (enableGuest) {
-        return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
-    }
+        if (enableGuest) {
+            return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
+        }
 
-    // fall back to login screen
-    return q();
+        // fall back to login screen
+    });
 }
 
 function _loginWithToken(queryParams, defaultDeviceDisplayName) {
@@ -178,10 +182,11 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
     });
 }
 
-// returns true if a session is found in localstorage
+// returns a promise which resolves to true if a session is found in
+// localstorage
 function _restoreFromLocalStorage() {
     if (!localStorage) {
-        return false;
+        return q(false);
     }
     const hs_url = localStorage.getItem("mx_hs_url");
     const is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
@@ -208,28 +213,55 @@ function _restoreFromLocalStorage() {
                 identityServerUrl: is_url,
                 guest: is_guest,
             });
-            return true;
+            return q(true);
         } catch (e) {
-            console.log("Unable to restore session", e);
-
-            var msg = e.message;
-            if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
-                msg = "You need to log back in to generate end-to-end encryption keys "
-                    + "for this device and submit the public key to your homeserver. "
-                    + "This is a once off; sorry for the inconvenience.";
-            }
-
-            // don't leak things into the new session
-            _clearLocalStorage();
-
-            throw new Error("Unable to restore previous session: " + msg);
+            return _handleRestoreFailure(e);
         }
     } else {
         console.log("No previous session found.");
-        return false;
+        return q(false);
     }
 }
 
+function _handleRestoreFailure(e) {
+    console.log("Unable to restore session", e);
+
+    let msg = e.message;
+    if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
+        msg = "You need to log back in to generate end-to-end encryption keys "
+            + "for this device and submit the public key to your homeserver. "
+            + "This is a once off; sorry for the inconvenience.";
+
+        _clearLocalStorage();
+
+        return q.reject(new Error(
+            "Unable to restore previous session: " + msg,
+        ));
+    }
+
+    const def = q.defer();
+    const SessionRestoreErrorDialog =
+          sdk.getComponent('views.dialogs.SessionRestoreErrorDialog');
+
+    Modal.createDialog(SessionRestoreErrorDialog, {
+        error: msg,
+        onFinished: (success) => {
+            def.resolve(success);
+        },
+    });
+
+    return def.promise.then((success) => {
+        if (success) {
+            // user clicked continue.
+            _clearLocalStorage();
+            return false;
+        }
+
+        // try, try again
+        return _restoreFromLocalStorage();
+    });
+}
+
 let rtsClient = null;
 export function initRtsClient(url) {
     rtsClient = new RtsClient(url);
diff --git a/src/RtsClient.js b/src/RtsClient.js
index 5cf2e811ad..8c3ce54b37 100644
--- a/src/RtsClient.js
+++ b/src/RtsClient.js
@@ -50,18 +50,18 @@ export default class RtsClient {
      * Track a referral with the Riot Team Server. This should be called once a referred
      * user has been successfully registered.
      * @param {string} referrer the user ID of one who referred the user to Riot.
-     * @param {string} userId the user ID of the user being referred.
-     * @param {string} userEmail the email address linked to `userId`.
+     * @param {string} sid the sign-up identity server session ID .
+     * @param {string} clientSecret the sign-up client secret.
      * @returns {Promise} a promise that resolves to { team_token: 'sometoken' } upon
      * success.
      */
-    trackReferral(referrer, userId, userEmail) {
+    trackReferral(referrer, sid, clientSecret) {
         return request(this._url + '/register',
             {
                 body: {
                     referrer: referrer,
-                    user_id: userId,
-                    user_email: userEmail,
+                    session_id: sid,
+                    client_secret: clientSecret,
                 },
                 method: 'POST',
             }
diff --git a/src/SignupStages.js b/src/SignupStages.js
index cdb9d5989b..1441682c85 100644
--- a/src/SignupStages.js
+++ b/src/SignupStages.js
@@ -149,6 +149,7 @@ class EmailIdentityStage extends Stage {
             nextLink
         ).then(function(response) {
             self.sid = response.sid;
+            self.signupInstance.setIdSid(self.sid);
             return self._completeVerify();
         }).then(function(request) {
             request.poll_for_success = true;
diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js
index d7d3e7bc7a..66a872958c 100644
--- a/src/UserSettingsStore.js
+++ b/src/UserSettingsStore.js
@@ -26,7 +26,7 @@ var Notifier = require("./Notifier");
 module.exports = {
     LABS_FEATURES: [
         {
-            name: 'Rich Text Editor',
+            name: 'New Composer & Autocomplete',
             id: 'rich_text_editor',
             default: false,
         },
diff --git a/src/component-index.js b/src/component-index.js
index 5b28be0627..c705150e12 100644
--- a/src/component-index.js
+++ b/src/component-index.js
@@ -31,6 +31,8 @@ import structures$CreateRoom from './components/structures/CreateRoom';
 structures$CreateRoom && (module.exports.components['structures.CreateRoom'] = structures$CreateRoom);
 import structures$FilePanel from './components/structures/FilePanel';
 structures$FilePanel && (module.exports.components['structures.FilePanel'] = structures$FilePanel);
+import structures$InteractiveAuth from './components/structures/InteractiveAuth';
+structures$InteractiveAuth && (module.exports.components['structures.InteractiveAuth'] = structures$InteractiveAuth);
 import structures$LoggedInView from './components/structures/LoggedInView';
 structures$LoggedInView && (module.exports.components['structures.LoggedInView'] = structures$LoggedInView);
 import structures$MatrixChat from './components/structures/MatrixChat';
@@ -75,6 +77,8 @@ import views$dialogs$BaseDialog from './components/views/dialogs/BaseDialog';
 views$dialogs$BaseDialog && (module.exports.components['views.dialogs.BaseDialog'] = views$dialogs$BaseDialog);
 import views$dialogs$ChatInviteDialog from './components/views/dialogs/ChatInviteDialog';
 views$dialogs$ChatInviteDialog && (module.exports.components['views.dialogs.ChatInviteDialog'] = views$dialogs$ChatInviteDialog);
+import views$dialogs$ConfirmUserActionDialog from './components/views/dialogs/ConfirmUserActionDialog';
+views$dialogs$ConfirmUserActionDialog && (module.exports.components['views.dialogs.ConfirmUserActionDialog'] = views$dialogs$ConfirmUserActionDialog);
 import views$dialogs$DeactivateAccountDialog from './components/views/dialogs/DeactivateAccountDialog';
 views$dialogs$DeactivateAccountDialog && (module.exports.components['views.dialogs.DeactivateAccountDialog'] = views$dialogs$DeactivateAccountDialog);
 import views$dialogs$ErrorDialog from './components/views/dialogs/ErrorDialog';
@@ -85,6 +89,8 @@ import views$dialogs$NeedToRegisterDialog from './components/views/dialogs/NeedT
 views$dialogs$NeedToRegisterDialog && (module.exports.components['views.dialogs.NeedToRegisterDialog'] = views$dialogs$NeedToRegisterDialog);
 import views$dialogs$QuestionDialog from './components/views/dialogs/QuestionDialog';
 views$dialogs$QuestionDialog && (module.exports.components['views.dialogs.QuestionDialog'] = views$dialogs$QuestionDialog);
+import views$dialogs$SessionRestoreErrorDialog from './components/views/dialogs/SessionRestoreErrorDialog';
+views$dialogs$SessionRestoreErrorDialog && (module.exports.components['views.dialogs.SessionRestoreErrorDialog'] = views$dialogs$SessionRestoreErrorDialog);
 import views$dialogs$SetDisplayNameDialog from './components/views/dialogs/SetDisplayNameDialog';
 views$dialogs$SetDisplayNameDialog && (module.exports.components['views.dialogs.SetDisplayNameDialog'] = views$dialogs$SetDisplayNameDialog);
 import views$dialogs$TextInputDialog from './components/views/dialogs/TextInputDialog';
diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js
new file mode 100644
index 0000000000..70b3c2e306
--- /dev/null
+++ b/src/components/structures/InteractiveAuth.js
@@ -0,0 +1,152 @@
+/*
+Copyright 2017 Vector Creations Ltd.
+
+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 Matrix from 'matrix-js-sdk';
+const InteractiveAuth = Matrix.InteractiveAuth;
+
+import React from 'react';
+
+import sdk from '../../index';
+
+import {getEntryComponentForLoginType} from '../views/login/InteractiveAuthEntryComponents';
+
+export default React.createClass({
+    displayName: 'InteractiveAuth',
+
+    propTypes: {
+        // response from initial request. If not supplied, will do a request on
+        // mount.
+        authData: React.PropTypes.shape({
+            flows: React.PropTypes.array,
+            params: React.PropTypes.object,
+            session: React.PropTypes.string,
+        }),
+
+        // callback
+        makeRequest: React.PropTypes.func.isRequired,
+
+        // callback called when the auth process has finished
+        // @param {bool} status True if the operation requiring
+        //     auth was completed sucessfully, false if canceled.
+        // @param result The result of the authenticated call
+        onFinished: React.PropTypes.func.isRequired,
+    },
+
+    getInitialState: function() {
+        return {
+            authStage: null,
+            busy: false,
+            errorText: null,
+            stageErrorText: null,
+            submitButtonEnabled: false,
+        };
+    },
+
+    componentWillMount: function() {
+        this._unmounted = false;
+        this._authLogic = new InteractiveAuth({
+            authData: this.props.authData,
+            doRequest: this._requestCallback,
+            startAuthStage: this._startAuthStage,
+        });
+
+        this._authLogic.attemptAuth().then((result) => {
+            this.props.onFinished(true, result);
+        }).catch((error) => {
+            console.error("Error during user-interactive auth:", error);
+            if (this._unmounted) {
+                return;
+            }
+
+            const msg = error.message || error.toString();
+            this.setState({
+                errorText: msg
+            });
+        }).done();
+    },
+
+    componentWillUnmount: function() {
+        this._unmounted = true;
+    },
+
+    _startAuthStage: function(stageType, error) {
+        this.setState({
+            authStage: stageType,
+            errorText: error ? error.error : null,
+        }, this._setFocus);
+    },
+
+    _requestCallback: function(auth) {
+        this.setState({
+            busy: true,
+            errorText: null,
+            stageErrorText: null,
+        });
+        return this.props.makeRequest(auth).finally(() => {
+            if (this._unmounted) {
+                return;
+            }
+            this.setState({
+                busy: false,
+            });
+        });
+    },
+
+    _setFocus: function() {
+        if (this.refs.stageComponent && this.refs.stageComponent.focus) {
+            this.refs.stageComponent.focus();
+        }
+    },
+
+    _submitAuthDict: function(authData) {
+        this._authLogic.submitAuthDict(authData);
+    },
+
+    _renderCurrentStage: function() {
+        const stage = this.state.authStage;
+        var StageComponent = getEntryComponentForLoginType(stage);
+        return (
+            <StageComponent ref="stageComponent"
+                loginType={stage}
+                authSessionId={this._authLogic.getSessionId()}
+                stageParams={this._authLogic.getStageParams(stage)}
+                submitAuthDict={this._submitAuthDict}
+                errorText={this.state.stageErrorText}
+                busy={this.state.busy}
+            />
+        );
+    },
+
+    render: function() {
+        let error = null;
+        if (this.state.errorText) {
+            error = (
+                <div className="error">
+                    {this.state.errorText}
+                </div>
+            );
+        }
+
+        return (
+            <div>
+                <div>
+                    {this._renderCurrentStage()}
+                    {error}
+                </div>
+            </div>
+        );
+    },
+});
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 8fdcf15e1b..72680a3eac 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -191,6 +191,17 @@ module.exports = React.createClass({
             MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
         }
 
+        // To enable things like riot.im/geektime in a nicer way than rewriting the URL
+        // and appending a team token query parameter, use the first path segment to
+        // indicate a team, with "public" team tokens stored in the config teamTokenMap.
+        let routedTeamToken = null;
+        if (this.props.config.teamTokenMap) {
+            const teamName = window.location.pathname.split('/')[1];
+            if (teamName && this.props.config.teamTokenMap.hasOwnProperty(teamName)) {
+                routedTeamToken = this.props.config.teamTokenMap[teamName];
+            }
+        }
+
         // Persist the team token across refreshes using sessionStorage. A new window or
         // tab will not persist sessionStorage, but refreshes will.
         if (this.props.startingFragmentQueryParams.team_token) {
@@ -202,8 +213,19 @@ module.exports = React.createClass({
 
         // Use the locally-stored team token first, then as a fall-back, check to see if
         // a referral link was used, which will contain a query parameter `team_token`.
-        this._teamToken = window.localStorage.getItem('mx_team_token') ||
+        this._teamToken = routedTeamToken ||
+            window.localStorage.getItem('mx_team_token') ||
             window.sessionStorage.getItem('mx_team_token');
+
+        // Some users have ended up with "undefined" as their local storage team token,
+        // treat that as undefined.
+        if (this._teamToken === "undefined") {
+            this._teamToken = undefined;
+        }
+
+        if (this._teamToken) {
+            console.info(`Team token set to ${this._teamToken}`);
+        }
     },
 
     componentDidMount: function() {
@@ -888,14 +910,6 @@ module.exports = React.createClass({
     onUserClick: function(event, userId) {
         event.preventDefault();
 
-        // var MemberInfo = sdk.getComponent('rooms.MemberInfo');
-        // var member = new Matrix.RoomMember(null, userId);
-        // ContextualMenu.createMenu(MemberInfo, {
-        //     member: member,
-        //     right: window.innerWidth - event.pageX,
-        //     top: event.pageY
-        // });
-
         var member = new Matrix.RoomMember(null, userId);
         if (!member) { return; }
         dis.dispatch({
@@ -975,6 +989,11 @@ module.exports = React.createClass({
         this._setPage(PageTypes.UserSettings);
     },
 
+    onTeamMemberRegistered: function(teamToken) {
+        this._teamToken = teamToken;
+        this._setPage(PageTypes.HomePage);
+    },
+
     onFinishPostRegistration: function() {
         // Don't confuse this with "PageType" which is the middle window to show
         this.setState({
@@ -1103,6 +1122,7 @@ module.exports = React.createClass({
                     customIsUrl={this.getCurrentIsUrl()}
                     registrationUrl={this.props.registrationUrl}
                     defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
+                    onTeamMemberRegistered={this.onTeamMemberRegistered}
                     onLoggedIn={this.onRegistered}
                     onLoginClick={this.onLoginClick}
                     onRegisterClick={this.onRegisterClick}
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index 288ca0b974..ca50e1071a 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -223,8 +223,7 @@ module.exports = React.createClass({
             users = users.slice(0, limit - 1);
         }
 
-        let avatars = users.map((u, index) => {
-            let showInitial = othersCount === 0 && index === users.length - 1;
+        const avatars = users.map((u) => {
             return (
                 <MemberAvatar
                     key={u.userId}
@@ -232,7 +231,6 @@ module.exports = React.createClass({
                     width={24}
                     height={24}
                     resizeMethod="crop"
-                    defaultToInitialLetter={showInitial}
                 />
             );
         });
diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js
index efe7dae723..db1147a5d2 100644
--- a/src/components/structures/login/Registration.js
+++ b/src/components/structures/login/Registration.js
@@ -58,6 +58,7 @@ module.exports = React.createClass({
             teamServerURL: React.PropTypes.string.isRequired,
         }),
         teamSelected: React.PropTypes.object,
+        onTeamMemberRegistered: React.PropTypes.func.isRequired,
 
         defaultDeviceDisplayName: React.PropTypes.string,
 
@@ -213,20 +214,22 @@ module.exports = React.createClass({
                 accessToken: response.access_token
             });
 
-            if (
-                self._rtsClient &&
-                self.props.referrer &&
-                self.state.teamSelected
-            ) {
-                // Track referral, get team_token in order to retrieve team config
+            // Done regardless of `teamSelected`. People registering with non-team emails
+            // will just nop. The point of this being we might not have the email address
+            // that the user registered with at this stage (depending on whether this
+            // is the client they initiated registration).
+            if (self._rtsClient) {
+                // Track referral if self.props.referrer set, get team_token in order to
+                // retrieve team config and see welcome page etc.
                 self._rtsClient.trackReferral(
-                    self.props.referrer,
-                    response.user_id,
-                    self.state.formVals.email
+                    self.props.referrer || '', // Default to empty string = not referred
+                    self.registerLogic.params.idSid,
+                    self.registerLogic.params.clientSecret
                 ).then((data) => {
                     const teamToken = data.team_token;
                     // Store for use /w welcome pages
                     window.localStorage.setItem('mx_team_token', teamToken);
+                    self.props.onTeamMemberRegistered(teamToken);
 
                     self._rtsClient.getTeam(teamToken).then((team) => {
                         console.log(
@@ -426,7 +429,12 @@ module.exports = React.createClass({
         return (
             <div className="mx_Login">
                 <div className="mx_Login_box">
-                    <LoginHeader icon={this.state.teamSelected ? this.state.teamSelected.icon : null}/>
+                    <LoginHeader
+                        icon={this.state.teamSelected ?
+                            this.props.teamServerConfig.teamServerURL + "/static/common/" +
+                            this.state.teamSelected.domain + "/icon.png" :
+                            null}
+                    />
                     {this._getRegisterContentJsx()}
                     <LoginFooter />
                 </div>
diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js
index 2b3980c536..e83403ef7c 100644
--- a/src/components/views/dialogs/BaseDialog.js
+++ b/src/components/views/dialogs/BaseDialog.js
@@ -17,6 +17,7 @@ limitations under the License.
 import React from 'react';
 
 import * as KeyCode from '../../../KeyCode';
+import AccessibleButton from '../elements/AccessibleButton';
 
 /**
  * Basic container for modal dialogs.
@@ -59,9 +60,21 @@ export default React.createClass({
         }
     },
 
+    _onCancelClick: function(e) {
+        this.props.onFinished();
+    },
+
     render: function() {
         return (
             <div onKeyDown={this._onKeyDown} className={this.props.className}>
+                <AccessibleButton onClick={this._onCancelClick}
+                    className="mx_Dialog_cancelButton"
+                >
+                    <img
+                        src="img/cancel.svg" width="18" height="18"
+                        alt="Cancel" title="Cancel"
+                    />
+                </AccessibleButton>
                 <div className='mx_Dialog_title'>
                     { this.props.title }
                 </div>
diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.js b/src/components/views/dialogs/ConfirmUserActionDialog.js
new file mode 100644
index 0000000000..fbe719710b
--- /dev/null
+++ b/src/components/views/dialogs/ConfirmUserActionDialog.js
@@ -0,0 +1,83 @@
+/*
+Copyright 2017 Vector Creations Ltd
+
+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 sdk from '../../../index';
+import classnames from 'classnames';
+
+/*
+ * A dialog for confirming an operation on another user.
+ * Takes a user ID and a verb, displays the target user prominently
+ * such that it should be easy to confirm that the operation is being
+ * performed on the right person, and displays the operation prominently
+ * to make it obvious what is going to happen.
+ * Also tweaks the style for 'dangerous' actions (albeit only with colour)
+ */
+export default React.createClass({
+    displayName: 'ConfirmUserActionDialog',
+    propTypes: {
+        member: React.PropTypes.object.isRequired, // matrix-js-sdk member object
+        action: React.PropTypes.string.isRequired, // eg. 'Ban'
+        danger: React.PropTypes.bool,
+        onFinished: React.PropTypes.func.isRequired,
+    },
+
+    defaultProps: {
+        danger: false,
+    },
+
+    onOk: function() {
+        this.props.onFinished(true);
+    },
+
+    onCancel: function() {
+        this.props.onFinished(false);
+    },
+
+    render: function() {
+        const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
+        const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
+
+        const title = this.props.action + " this person?";
+        const confirmButtonClass = classnames({
+            'mx_Dialog_primary': true,
+            'danger': this.props.danger,
+        });
+        return (
+            <BaseDialog className="mx_UserActionConfirmDialog" onFinished={this.props.onFinished}
+                onEnterPressed={ this.onOk }
+                title={title}
+            >
+                <div className="mx_Dialog_content">
+                    <div className="mx_ConfirmUserActionDialog_avatar">
+                        <MemberAvatar member={this.props.member} width={72} height={72} />
+                    </div>
+                    <div className="mx_ConfirmUserActionDialog_name">{this.props.member.name}</div>
+                    <div className="mx_ConfirmUserActionDialog_userId">{this.props.member.userId}</div>
+                </div>
+                <div className="mx_Dialog_buttons">
+                    <button className={confirmButtonClass} onClick={this.onOk} autoFocus={true}>
+                        {this.props.action}
+                    </button>
+
+                    <button onClick={this.onCancel}>
+                        Cancel
+                    </button>
+                </div>
+            </BaseDialog>
+        );
+    },
+});
diff --git a/src/components/views/dialogs/InteractiveAuthDialog.js b/src/components/views/dialogs/InteractiveAuthDialog.js
index a4abbb17d9..66b662b23d 100644
--- a/src/components/views/dialogs/InteractiveAuthDialog.js
+++ b/src/components/views/dialogs/InteractiveAuthDialog.js
@@ -1,5 +1,6 @@
 /*
 Copyright 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -15,13 +16,12 @@ limitations under the License.
 */
 
 import Matrix from 'matrix-js-sdk';
-const InteractiveAuth = Matrix.InteractiveAuth;
 
 import React from 'react';
 
 import sdk from '../../../index';
 
-import {getEntryComponentForLoginType} from '../login/InteractiveAuthEntryComponents';
+import AccessibleButton from '../elements/AccessibleButton';
 
 export default React.createClass({
     displayName: 'InteractiveAuthDialog',
@@ -41,168 +41,29 @@ export default React.createClass({
         onFinished: React.PropTypes.func.isRequired,
 
         title: React.PropTypes.string,
-        submitButtonLabel: React.PropTypes.string,
     },
 
     getDefaultProps: function() {
         return {
             title: "Authentication",
-            submitButtonLabel: "Submit",
         };
     },
 
-    getInitialState: function() {
-        return {
-            authStage: null,
-            busy: false,
-            errorText: null,
-            stageErrorText: null,
-            submitButtonEnabled: false,
-        };
-    },
-
-    componentWillMount: function() {
-        this._unmounted = false;
-        this._authLogic = new InteractiveAuth({
-            authData: this.props.authData,
-            doRequest: this._requestCallback,
-            startAuthStage: this._startAuthStage,
-        });
-
-        this._authLogic.attemptAuth().then((result) => {
-            this.props.onFinished(true, result);
-        }).catch((error) => {
-            console.error("Error during user-interactive auth:", error);
-            if (this._unmounted) {
-                return;
-            }
-
-            const msg = error.message || error.toString();
-            this.setState({
-                errorText: msg
-            });
-        }).done();
-    },
-
-    componentWillUnmount: function() {
-        this._unmounted = true;
-    },
-
-    _startAuthStage: function(stageType, error) {
-        this.setState({
-            authStage: stageType,
-            errorText: error ? error.error : null,
-        }, this._setFocus);
-    },
-
-    _requestCallback: function(auth) {
-        this.setState({
-            busy: true,
-            errorText: null,
-            stageErrorText: null,
-        });
-        return this.props.makeRequest(auth).finally(() => {
-            if (this._unmounted) {
-                return;
-            }
-            this.setState({
-                busy: false,
-            });
-        });
-    },
-
-    _onEnterPressed: function(e) {
-        if (this.state.submitButtonEnabled && !this.state.busy) {
-            this._onSubmit();
-        }
-    },
-
-    _onSubmit: function() {
-        if (this.refs.stageComponent && this.refs.stageComponent.onSubmitClick) {
-            this.refs.stageComponent.onSubmitClick();
-        }
-    },
-
-    _setFocus: function() {
-        if (this.refs.stageComponent && this.refs.stageComponent.focus) {
-            this.refs.stageComponent.focus();
-        }
-    },
-
-    _onCancel: function() {
-        this.props.onFinished(false);
-    },
-
-    _setSubmitButtonEnabled: function(enabled) {
-        this.setState({
-            submitButtonEnabled: enabled,
-        });
-    },
-
-    _submitAuthDict: function(authData) {
-        this._authLogic.submitAuthDict(authData);
-    },
-
-    _renderCurrentStage: function() {
-        const stage = this.state.authStage;
-        var StageComponent = getEntryComponentForLoginType(stage);
-        return (
-            <StageComponent ref="stageComponent"
-                loginType={stage}
-                authSessionId={this._authLogic.getSessionId()}
-                stageParams={this._authLogic.getStageParams(stage)}
-                submitAuthDict={this._submitAuthDict}
-                setSubmitButtonEnabled={this._setSubmitButtonEnabled}
-                errorText={this.state.stageErrorText}
-            />
-        );
-    },
-
     render: function() {
-        const Loader = sdk.getComponent("elements.Spinner");
+        const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth");
         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
 
-        let error = null;
-        if (this.state.errorText) {
-            error = (
-                <div className="error">
-                    {this.state.errorText}
-                </div>
-            );
-        }
-
-        const submitLabel = this.state.busy ? <Loader /> : this.props.submitButtonLabel;
-        const submitEnabled = this.state.submitButtonEnabled && !this.state.busy;
-
-        const submitButton = (
-            <button className="mx_Dialog_primary"
-                onClick={this._onSubmit}
-                disabled={!submitEnabled}
-            >
-                {submitLabel}
-            </button>
-        );
-
-        const cancelButton = (
-            <button onClick={this._onCancel}>
-                Cancel
-            </button>
-        );
-
         return (
             <BaseDialog className="mx_InteractiveAuthDialog"
-                onEnterPressed={this._onEnterPressed}
                 onFinished={this.props.onFinished}
                 title={this.props.title}
             >
-                <div className="mx_Dialog_content">
-                    <p>This operation requires additional authentication.</p>
-                    {this._renderCurrentStage()}
-                    {error}
-                </div>
-                <div className="mx_Dialog_buttons">
-                    {submitButton}
-                    {cancelButton}
+                <div>
+                    <InteractiveAuth ref={this._collectInteractiveAuth}
+                        authData={this.props.authData}
+                        makeRequest={this.props.makeRequest}
+                        onFinished={this.props.onFinished}
+                    />
                 </div>
             </BaseDialog>
         );
diff --git a/src/components/views/dialogs/SessionRestoreErrorDialog.js b/src/components/views/dialogs/SessionRestoreErrorDialog.js
new file mode 100644
index 0000000000..358bbf1fec
--- /dev/null
+++ b/src/components/views/dialogs/SessionRestoreErrorDialog.js
@@ -0,0 +1,74 @@
+/*
+Copyright 2017 Vector Creations Ltd
+
+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 sdk from '../../../index';
+import SdkConfig from '../../../SdkConfig';
+import Modal from '../../../Modal';
+
+
+export default React.createClass({
+    displayName: 'SessionRestoreErrorDialog',
+
+    propTypes: {
+        error: React.PropTypes.string.isRequired,
+        onFinished: React.PropTypes.func.isRequired,
+    },
+
+    _sendBugReport: function() {
+        const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
+        Modal.createDialog(BugReportDialog, {});
+    },
+
+    _continueClicked: function() {
+        this.props.onFinished(true);
+    },
+
+    render: function() {
+        const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
+        let bugreport;
+
+        if (SdkConfig.get().bug_report_endpoint_url) {
+            bugreport = (
+                <p>Otherwise, <a onClick={this._sendBugReport} href='#'>
+                   click here</a> to send a bug report.
+                </p>
+            );
+        }
+
+        return (
+            <BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
+                    title='Unable to restore session'>
+                <div className="mx_Dialog_content">
+                    <p>We encountered an error trying to restore your previous session. If
+                    you continue, you will need to log in again, and encrypted chat
+                    history will be unreadable.</p>
+
+                    <p>If you have previously used a more recent version of Riot, your session
+                    may be incompatible with this version. Close this window and return
+                    to the more recent version.</p>
+
+                    {bugreport}
+                </div>
+                <div className="mx_Dialog_buttons">
+                    <button className="mx_Dialog_primary" onClick={this._continueClicked}>
+                        Continue anyway
+                    </button>
+                </div>
+            </BaseDialog>
+        );
+    },
+});
diff --git a/src/components/views/login/InteractiveAuthEntryComponents.js b/src/components/views/login/InteractiveAuthEntryComponents.js
index ec184ca09f..e18e60d7bc 100644
--- a/src/components/views/login/InteractiveAuthEntryComponents.js
+++ b/src/components/views/login/InteractiveAuthEntryComponents.js
@@ -1,5 +1,6 @@
 /*
 Copyright 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -20,7 +21,7 @@ import sdk from '../../../index';
 import MatrixClientPeg from '../../../MatrixClientPeg';
 
 /* This file contains a collection of components which are used by the
- * InteractiveAuthDialog to prompt the user to enter the information needed
+ * InteractiveAuth to prompt the user to enter the information needed
  * for an auth stage. (The intention is that they could also be used for other
  * components, such as the registration flow).
  *
@@ -32,10 +33,10 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
  * stageParams:            params from the server for the stage being attempted
  * errorText:              error message from a previous attempt to authenticate
  * submitAuthDict:         a function which will be called with the new auth dict
- * setSubmitButtonEnabled: a function which will enable/disable the 'submit' button
+ * busy:                   a boolean indicating whether the auth logic is doing something
+ *                         the user needs to wait for.
  *
  * Each component may also provide the following functions (beyond the standard React ones):
- *    onSubmitClick: handle a 'submit' button click
  *    focus: set the input focus appropriately in the form.
  */
 
@@ -48,12 +49,16 @@ export const PasswordAuthEntry = React.createClass({
 
     propTypes: {
         submitAuthDict: React.PropTypes.func.isRequired,
-        setSubmitButtonEnabled: React.PropTypes.func.isRequired,
         errorText: React.PropTypes.string,
+        // is the auth logic currently waiting for something to
+        // happen?
+        busy: React.PropTypes.bool,
     },
 
-    componentWillMount: function() {
-        this.props.setSubmitButtonEnabled(false);
+    getInitialState: function() {
+        return {
+            passwordValid: false,
+        };
     },
 
     focus: function() {
@@ -62,7 +67,10 @@ export const PasswordAuthEntry = React.createClass({
         }
     },
 
-    onSubmitClick: function() {
+    _onSubmit: function(e) {
+        e.preventDefault();
+        if (this.props.busy) return;
+
         this.props.submitAuthDict({
             type: PasswordAuthEntry.LOGIN_TYPE,
             user: MatrixClientPeg.get().credentials.userId,
@@ -72,7 +80,9 @@ export const PasswordAuthEntry = React.createClass({
 
     _onPasswordFieldChange: function(ev) {
         // enable the submit button iff the password is non-empty
-        this.props.setSubmitButtonEnabled(Boolean(ev.target.value));
+        this.setState({
+            passwordValid: Boolean(this.refs.passwordField.value),
+        });
     },
 
     render: function() {
@@ -82,16 +92,34 @@ export const PasswordAuthEntry = React.createClass({
             passwordBoxClass = 'error';
         }
 
+        let submitButtonOrSpinner;
+        if (this.props.busy) {
+            const Loader = sdk.getComponent("elements.Spinner");
+            submitButtonOrSpinner = <Loader />;
+        } else {
+            submitButtonOrSpinner = (
+                <input type="submit"
+                    className="mx_Dialog_primary"
+                    disabled={!this.state.passwordValid}
+                />
+            );
+        }
+
         return (
             <div>
                 <p>To continue, please enter your password.</p>
                 <p>Password:</p>
-                <input
-                    ref="passwordField"
-                    className={passwordBoxClass}
-                    onChange={this._onPasswordFieldChange}
-                    type="password"
-                />
+                <form onSubmit={this._onSubmit}>
+                    <input
+                        ref="passwordField"
+                        className={passwordBoxClass}
+                        onChange={this._onPasswordFieldChange}
+                        type="password"
+                    />
+                    <div className="mx_button_row">
+                        {submitButtonOrSpinner}
+                    </div>
+                </form>
                 <div className="error">
                     {this.props.errorText}
                 </div>
@@ -110,14 +138,9 @@ export const RecaptchaAuthEntry = React.createClass({
     propTypes: {
         submitAuthDict: React.PropTypes.func.isRequired,
         stageParams: React.PropTypes.object.isRequired,
-        setSubmitButtonEnabled: React.PropTypes.func.isRequired,
         errorText: React.PropTypes.string,
     },
 
-    componentWillMount: function() {
-        this.props.setSubmitButtonEnabled(false);
-    },
-
     _onCaptchaResponse: function(response) {
         this.props.submitAuthDict({
             type: RecaptchaAuthEntry.LOGIN_TYPE,
@@ -148,7 +171,6 @@ export const FallbackAuthEntry = React.createClass({
         authSessionId: React.PropTypes.string.isRequired,
         loginType: React.PropTypes.string.isRequired,
         submitAuthDict: React.PropTypes.func.isRequired,
-        setSubmitButtonEnabled: React.PropTypes.func.isRequired,
         errorText: React.PropTypes.string,
     },
 
@@ -156,7 +178,6 @@ export const FallbackAuthEntry = React.createClass({
         // we have to make the user click a button, as browsers will block
         // the popup if we open it immediately.
         this._popupWindow = null;
-        this.props.setSubmitButtonEnabled(true);
         window.addEventListener("message", this._onReceiveMessage);
     },
 
@@ -167,13 +188,12 @@ export const FallbackAuthEntry = React.createClass({
         }
     },
 
-    onSubmitClick: function() {
+    _onShowFallbackClick: function() {
         var url = MatrixClientPeg.get().getFallbackAuthUrl(
             this.props.loginType,
             this.props.authSessionId
         );
         this._popupWindow = window.open(url);
-        this.props.setSubmitButtonEnabled(false);
     },
 
     _onReceiveMessage: function(event) {
@@ -188,7 +208,7 @@ export const FallbackAuthEntry = React.createClass({
     render: function() {
         return (
             <div>
-                Click "Submit" to authenticate
+                <a onClick={this._onShowFallbackClick}>Start authentication</a>
                 <div className="error">
                     {this.props.errorText}
                 </div>
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index fd26ae58da..a625e63062 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -222,7 +222,8 @@ module.exports = React.createClass({
                 title: "Add an Integration",
                 description:
                     <div>
-                        You are about to taken to a third-party site so you can authenticate your account for use with {integrationsUrl}.<br/>
+                        You are about to be taken to a third-party site so you can
+                        authenticate your account for use with {integrationsUrl}.<br/>
                         Do you wish to continue?
                     </div>,
                 button: "Continue",
diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js
index d33b8f3524..699ee8a3a2 100644
--- a/src/components/views/rooms/MemberInfo.js
+++ b/src/components/views/rooms/MemberInfo.js
@@ -1,5 +1,6 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -25,16 +26,16 @@ limitations under the License.
  * 'muted': boolean,
  * 'isTargetMod': boolean
  */
-var React = require('react');
-var classNames = require('classnames');
-var dis = require("../../../dispatcher");
-var Modal = require("../../../Modal");
-var sdk = require('../../../index');
-var createRoom = require('../../../createRoom');
-var DMRoomMap = require('../../../utils/DMRoomMap');
-var Unread = require('../../../Unread');
-var Receipt = require('../../../utils/Receipt');
-var WithMatrixClient = require('../../../wrappers/WithMatrixClient');
+import React from 'react';
+import classNames from 'classnames';
+import dis from '../../../dispatcher';
+import Modal from '../../../Modal';
+import sdk from '../../../index';
+import createRoom from '../../../createRoom';
+import DMRoomMap from '../../../utils/DMRoomMap';
+import Unread from '../../../Unread';
+import { findReadReceiptFromUserId } from '../../../utils/Receipt';
+import WithMatrixClient from '../../../wrappers/WithMatrixClient';
 import AccessibleButton from '../elements/AccessibleButton';
 
 module.exports = WithMatrixClient(React.createClass({
@@ -43,13 +44,6 @@ module.exports = WithMatrixClient(React.createClass({
     propTypes: {
         matrixClient: React.PropTypes.object.isRequired,
         member: React.PropTypes.object.isRequired,
-        onFinished: React.PropTypes.func,
-    },
-
-    getDefaultProps: function() {
-        return {
-            onFinished: function() {}
-        };
     },
 
     getInitialState: function() {
@@ -164,7 +158,7 @@ module.exports = WithMatrixClient(React.createClass({
     onRoomReceipt: function(receiptEvent, room) {
         // because if we read a notification, it will affect notification count
         // only bother updating if there's a receipt from us
-        if (Receipt.findReadReceiptFromUserId(receiptEvent, this.props.matrixClient.credentials.userId)) {
+        if (findReadReceiptFromUserId(receiptEvent, this.props.matrixClient.credentials.userId)) {
             this.forceUpdate();
         }
     },
@@ -224,46 +218,72 @@ module.exports = WithMatrixClient(React.createClass({
     },
 
     onKick: function() {
-        var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
-        var roomId = this.props.member.roomId;
-        var target = this.props.member.userId;
-        this.setState({ updating: this.state.updating + 1 });
-        this.props.matrixClient.kick(roomId, target).then(function() {
-                // NO-OP; rely on the m.room.member event coming down else we could
-                // get out of sync if we force setState here!
-                console.log("Kick success");
-            }, function(err) {
-                Modal.createDialog(ErrorDialog, {
-                    title: "Kick error",
-                    description: err.message
+        const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
+        Modal.createDialog(ConfirmUserActionDialog, {
+            member: this.props.member,
+            action: 'Kick',
+            danger: true,
+            onFinished: (proceed) => {
+                if (!proceed) return;
+
+                this.setState({ updating: this.state.updating + 1 });
+                this.props.matrixClient.kick(
+                    this.props.member.roomId, this.props.member.userId,
+                ).then(function() {
+                        // NO-OP; rely on the m.room.member event coming down else we could
+                        // get out of sync if we force setState here!
+                        console.log("Kick success");
+                    }, function(err) {
+                        const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                        Modal.createDialog(ErrorDialog, {
+                            title: "Kick error",
+                            description: err.message
+                        });
+                    }
+                ).finally(()=>{
+                    this.setState({ updating: this.state.updating - 1 });
                 });
             }
-        ).finally(()=>{
-            this.setState({ updating: this.state.updating - 1 });
         });
-        this.props.onFinished();
     },
 
-    onBan: function() {
-        var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
-        var roomId = this.props.member.roomId;
-        var target = this.props.member.userId;
-        this.setState({ updating: this.state.updating + 1 });
-        this.props.matrixClient.ban(roomId, target).then(
-            function() {
-                // NO-OP; rely on the m.room.member event coming down else we could
-                // get out of sync if we force setState here!
-                console.log("Ban success");
-            }, function(err) {
-                Modal.createDialog(ErrorDialog, {
-                    title: "Ban error",
-                    description: err.message
+    onBanOrUnban: function() {
+        const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
+        Modal.createDialog(ConfirmUserActionDialog, {
+            member: this.props.member,
+            action: this.props.member.membership == 'ban' ? 'Unban' : 'Ban',
+            danger: this.props.member.membership != 'ban',
+            onFinished: (proceed) => {
+                if (!proceed) return;
+
+                this.setState({ updating: this.state.updating + 1 });
+                let promise;
+                if (this.props.member.membership == 'ban') {
+                    promise = this.props.matrixClient.unban(
+                        this.props.member.roomId, this.props.member.userId,
+                    );
+                } else {
+                    promise = this.props.matrixClient.ban(
+                        this.props.member.roomId, this.props.member.userId,
+                    );
+                }
+                promise.then(
+                    function() {
+                        // NO-OP; rely on the m.room.member event coming down else we could
+                        // get out of sync if we force setState here!
+                        console.log("Ban success");
+                    }, function(err) {
+                        const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                        Modal.createDialog(ErrorDialog, {
+                            title: "Ban error",
+                            description: err.message,
+                        });
+                    }
+                ).finally(()=>{
+                    this.setState({ updating: this.state.updating - 1 });
                 });
-            }
-        ).finally(()=>{
-            this.setState({ updating: this.state.updating - 1 });
+            },
         });
-        this.props.onFinished();
     },
 
     onMuteToggle: function() {
@@ -272,14 +292,12 @@ module.exports = WithMatrixClient(React.createClass({
         var target = this.props.member.userId;
         var room = this.props.matrixClient.getRoom(roomId);
         if (!room) {
-            this.props.onFinished();
             return;
         }
         var powerLevelEvent = room.currentState.getStateEvents(
             "m.room.power_levels", ""
         );
         if (!powerLevelEvent) {
-            this.props.onFinished();
             return;
         }
         var isMuted = this.state.muted;
@@ -314,7 +332,6 @@ module.exports = WithMatrixClient(React.createClass({
                 this.setState({ updating: this.state.updating - 1 });
             });
         }
-        this.props.onFinished();
     },
 
     onModToggle: function() {
@@ -323,19 +340,16 @@ module.exports = WithMatrixClient(React.createClass({
         var target = this.props.member.userId;
         var room = this.props.matrixClient.getRoom(roomId);
         if (!room) {
-            this.props.onFinished();
             return;
         }
         var powerLevelEvent = room.currentState.getStateEvents(
             "m.room.power_levels", ""
         );
         if (!powerLevelEvent) {
-            this.props.onFinished();
             return;
         }
         var me = room.getMember(this.props.matrixClient.credentials.userId);
         if (!me) {
-            this.props.onFinished();
             return;
         }
         var defaultLevel = powerLevelEvent.getContent().users_default;
@@ -366,7 +380,6 @@ module.exports = WithMatrixClient(React.createClass({
         ).finally(()=>{
             this.setState({ updating: this.state.updating - 1 });
         });
-        this.props.onFinished();
     },
 
     _applyPowerChange: function(roomId, target, powerLevel, powerLevelEvent) {
@@ -386,7 +399,6 @@ module.exports = WithMatrixClient(React.createClass({
         ).finally(()=>{
             this.setState({ updating: this.state.updating - 1 });
         }).done();
-        this.props.onFinished();
     },
 
     onPowerChange: function(powerLevel) {
@@ -396,14 +408,12 @@ module.exports = WithMatrixClient(React.createClass({
         var room = this.props.matrixClient.getRoom(roomId);
         var self = this;
         if (!room) {
-            this.props.onFinished();
             return;
         }
         var powerLevelEvent = room.currentState.getStateEvents(
             "m.room.power_levels", ""
         );
         if (!powerLevelEvent) {
-            this.props.onFinished();
             return;
         }
         if (powerLevelEvent.getContent().users) {
@@ -422,9 +432,6 @@ module.exports = WithMatrixClient(React.createClass({
                         if (confirmed) {
                             self._applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
                         }
-                        else {
-                            self.props.onFinished();
-                        }
                     },
                 });
             }
@@ -440,7 +447,6 @@ module.exports = WithMatrixClient(React.createClass({
     onNewDMClick: function() {
         this.setState({ updating: this.state.updating + 1 });
         createRoom({dmUserId: this.props.member.userId}).finally(() => {
-            this.props.onFinished();
             this.setState({ updating: this.state.updating - 1 });
         }).done();
     },
@@ -450,30 +456,29 @@ module.exports = WithMatrixClient(React.createClass({
             action: 'leave_room',
             room_id: this.props.member.roomId,
         });
-        this.props.onFinished();
     },
 
     _calculateOpsPermissions: function(member) {
-        var defaultPerms = {
+        const defaultPerms = {
             can: {},
             muted: false,
             modifyLevel: false
         };
-        var room = this.props.matrixClient.getRoom(member.roomId);
+        const room = this.props.matrixClient.getRoom(member.roomId);
         if (!room) {
             return defaultPerms;
         }
-        var powerLevels = room.currentState.getStateEvents(
+        const powerLevels = room.currentState.getStateEvents(
             "m.room.power_levels", ""
         );
         if (!powerLevels) {
             return defaultPerms;
         }
-        var me = room.getMember(this.props.matrixClient.credentials.userId);
+        const me = room.getMember(this.props.matrixClient.credentials.userId);
         if (!me) {
             return defaultPerms;
         }
-        var them = member;
+        const them = member;
         return {
             can: this._calculateCanPermissions(
                 me, them, powerLevels.getContent()
@@ -484,22 +489,22 @@ module.exports = WithMatrixClient(React.createClass({
     },
 
     _calculateCanPermissions: function(me, them, powerLevels) {
-        var can = {
+        const can = {
             kick: false,
             ban: false,
             mute: false,
             modifyLevel: false
         };
-        var canAffectUser = them.powerLevel < me.powerLevel;
+        const canAffectUser = them.powerLevel < me.powerLevel;
         if (!canAffectUser) {
             //console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
             return can;
         }
-        var editPowerLevel = (
+        const editPowerLevel = (
             (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
             powerLevels.state_default
         );
-        var levelToSend = (
+        const levelToSend = (
             (powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
             powerLevels.events_default
         );
@@ -646,10 +651,14 @@ module.exports = WithMatrixClient(React.createClass({
             );
         }
         if (this.state.can.ban) {
+            let label = 'Ban';
+            if (this.props.member.membership == 'ban') {
+                label = 'Unban';
+            }
             banButton = (
                 <AccessibleButton className="mx_MemberInfo_field"
-                        onClick={this.onBan}>
-                    Ban
+                        onClick={this.onBanOrUnban}>
+                    {label}
                 </AccessibleButton>
             );
         }
diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js
index a23368f5e8..4d1285678b 100644
--- a/src/components/views/rooms/RoomSettings.js
+++ b/src/components/views/rooms/RoomSettings.js
@@ -14,17 +14,18 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-var q = require("q");
-var React = require('react');
-var MatrixClientPeg = require('../../../MatrixClientPeg');
-var SdkConfig = require('../../../SdkConfig');
-var sdk = require('../../../index');
-var Modal = require('../../../Modal');
-var ObjectUtils = require("../../../ObjectUtils");
-var dis = require("../../../dispatcher");
-var ScalarAuthClient = require("../../../ScalarAuthClient");
-var ScalarMessaging = require('../../../ScalarMessaging');
-var UserSettingsStore = require('../../../UserSettingsStore');
+import q from 'q';
+import React from 'react';
+import MatrixClientPeg from '../../../MatrixClientPeg';
+import SdkConfig from '../../../SdkConfig';
+import sdk from '../../../index';
+import Modal from '../../../Modal';
+import ObjectUtils from '../../../ObjectUtils';
+import dis from '../../../dispatcher';
+import ScalarAuthClient from '../../../ScalarAuthClient';
+import ScalarMessaging from '../../../ScalarMessaging';
+import UserSettingsStore from '../../../UserSettingsStore';
+import AccessibleButton from '../elements/AccessibleButton';
 
 
 // parse a string as an integer; if the input is undefined, or cannot be parsed
@@ -635,16 +636,16 @@ module.exports = React.createClass({
         if (myMember) {
             if (myMember.membership === "join") {
                 leaveButton = (
-                    <div className="mx_RoomSettings_leaveButton" onClick={ this.onLeaveClick }>
+                    <AccessibleButton className="mx_RoomSettings_leaveButton" onClick={ this.onLeaveClick }>
                         Leave room
-                    </div>
+                    </AccessibleButton>
                 );
             }
             else if (myMember.membership === "leave") {
                 leaveButton = (
-                    <div className="mx_RoomSettings_leaveButton" onClick={ this.onForgetClick }>
+                    <AccessibleButton className="mx_RoomSettings_leaveButton" onClick={ this.onForgetClick }>
                         Forget room
-                    </div>
+                    </AccessibleButton>
                 );
             }
         }
diff --git a/test/components/views/dialogs/InteractiveAuthDialog-test.js b/test/components/views/dialogs/InteractiveAuthDialog-test.js
index 35daace0f8..80f027ab44 100644
--- a/test/components/views/dialogs/InteractiveAuthDialog-test.js
+++ b/test/components/views/dialogs/InteractiveAuthDialog-test.js
@@ -67,16 +67,24 @@ describe('InteractiveAuthDialog', function () {
                 onFinished={onFinished}
             />, parentDiv);
 
-        // at this point there should be a password box
-        const passwordNode = ReactTestUtils.findRenderedDOMComponentWithTag(
+        // at this point there should be a password box and a submit button
+        const formNode = ReactTestUtils.findRenderedDOMComponentWithTag(dlg, "form");
+        const inputNodes = ReactTestUtils.scryRenderedDOMComponentsWithTag(
             dlg, "input"
         );
-        expect(passwordNode.type).toEqual("password");
+        let passwordNode;
+        let submitNode;
+        for (const node of inputNodes) {
+            if (node.type == 'password') {
+                passwordNode = node;
+            } else if (node.type == 'submit') {
+                submitNode = node;
+            }
+        }
+        expect(passwordNode).toExist();
+        expect(submitNode).toExist();
 
         // submit should be disabled
-        const submitNode = ReactTestUtils.findRenderedDOMComponentWithClass(
-            dlg, "mx_Dialog_primary"
-        );
         expect(submitNode.disabled).toBe(true);
 
         // put something in the password box, and hit enter; that should
@@ -84,9 +92,7 @@ describe('InteractiveAuthDialog', function () {
         passwordNode.value = "s3kr3t";
         ReactTestUtils.Simulate.change(passwordNode);
         expect(submitNode.disabled).toBe(false);
-        ReactTestUtils.Simulate.keyDown(passwordNode, {
-            key: "Enter", keyCode: 13, which: 13,
-        });
+        ReactTestUtils.Simulate.submit(formNode, {});
 
         expect(doRequest.callCount).toEqual(1);
         expect(doRequest.calledWithExactly({
@@ -96,8 +102,10 @@ describe('InteractiveAuthDialog', function () {
             user: "@user:id",
         })).toBe(true);
 
-        // the submit button should now be disabled (and be a spinner)
-        expect(submitNode.disabled).toBe(true);
+        // there should now be a spinner
+        ReactTestUtils.findRenderedComponentWithType(
+            dlg, sdk.getComponent('elements.Spinner'),
+        );
 
         // let the request complete
         q.delay(1).then(() => {