Repository: couchdb-fauxton Updated Branches: refs/heads/master 422cb92c6 -> 410d2c772
Updating Auth module to use React Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/410d2c77 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/410d2c77 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/410d2c77 Branch: refs/heads/master Commit: 410d2c7729533a9bb0873a14ab72bc7a590b738e Parents: 422cb92 Author: Ben Keen <[email protected]> Authored: Thu Apr 16 16:14:27 2015 -0700 Committer: Ben Keen <[email protected]> Committed: Wed Apr 22 09:04:41 2015 -0700 ---------------------------------------------------------------------- app/addons/auth/actions.js | 117 ++++++++ app/addons/auth/actiontypes.js | 23 ++ app/addons/auth/components.react.jsx | 287 +++++++++++++++++++ app/addons/auth/resources.js | 172 ----------- app/addons/auth/routes.js | 46 +-- app/addons/auth/stores.js | 171 +++++++++++ app/addons/auth/templates/change_password.html | 29 -- app/addons/auth/templates/create_admin.html | 37 --- app/addons/auth/templates/login.html | 26 -- app/addons/auth/templates/nav_dropdown.html | 24 -- .../auth/test/auth.componentsSpec.react.jsx | 128 +++++++++ app/addons/auth/test/auth.storesSpec.js | 148 ++++++++++ app/core/routeObject.js | 11 +- app/core/tests/routeObjectSpec.js | 10 +- 14 files changed, 914 insertions(+), 315 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/actions.js ---------------------------------------------------------------------- diff --git a/app/addons/auth/actions.js b/app/addons/auth/actions.js new file mode 100644 index 0000000..ab3f50e --- /dev/null +++ b/app/addons/auth/actions.js @@ -0,0 +1,117 @@ +// 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. +define([ + 'api', + 'addons/auth/actiontypes' +], +function (FauxtonAPI, ActionTypes) { + + + var errorHandler = function (xhr, type, msg) { + msg = xhr; + if (arguments.length === 3) { + msg = xhr.responseJSON.reason; + } + + FauxtonAPI.addNotification({ + msg: msg, + type: 'error' + }); + }; + + + return { + + login: function (username, password, urlBack) { + var promise = FauxtonAPI.session.login(username, password); + + promise.then(function () { + FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.loggedIn }); + if (urlBack) { + return FauxtonAPI.navigate(urlBack); + } + FauxtonAPI.navigate('/'); + }); + promise.fail(errorHandler); + }, + + changePassword: function (password, passwordConfirm) { + var promise = FauxtonAPI.session.changePassword(password, passwordConfirm); + + promise.done(function () { + FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.changePassword }); + FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CHANGE_PWD_FIELDS }); + }); + + promise.fail(errorHandler); + }, + + updateChangePasswordField: function (value) { + FauxtonAPI.dispatch({ + type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_FIELD, + value: value + }); + }, + + updateChangePasswordConfirmField: function (value) { + FauxtonAPI.dispatch({ + type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD, + value: value + }); + }, + + createAdmin: function (username, password, loginAfter) { + var promise = FauxtonAPI.session.createAdmin(username, password, loginAfter); + + promise.then(function () { + FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.adminCreated }); + if (loginAfter) { + FauxtonAPI.navigate('/'); + } else { + FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CREATE_ADMIN_FIELDS }); + } + }); + + promise.fail(function (xhr, type, msg) { + msg = xhr; + if (arguments.length === 3) { + console.log("here...", xhr.responseJSON); + msg = xhr.responseJSON.reason; + } + errorHandler(FauxtonAPI.session.messages.adminCreationFailedPrefix + ' ' + msg); + }); + }, + + updateCreateAdminUsername: function (value) { + FauxtonAPI.dispatch({ + type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD, + value: value + }); + }, + + updateCreateAdminPassword: function (value) { + FauxtonAPI.dispatch({ + type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD, + value: value + }); + }, + + selectPage: function (page) { + FauxtonAPI.dispatch({ + type: ActionTypes.AUTH_SELECT_PAGE, + page: page + }); + } + + }; + +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/actiontypes.js ---------------------------------------------------------------------- diff --git a/app/addons/auth/actiontypes.js b/app/addons/auth/actiontypes.js new file mode 100644 index 0000000..29ce9bf --- /dev/null +++ b/app/addons/auth/actiontypes.js @@ -0,0 +1,23 @@ +// 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. + +define([], function () { + return { + 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' + }; +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/auth/components.react.jsx b/app/addons/auth/components.react.jsx new file mode 100644 index 0000000..5fd5c4c --- /dev/null +++ b/app/addons/auth/components.react.jsx @@ -0,0 +1,287 @@ +// 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. + +define([ + 'app', + 'api', + 'react', + 'addons/auth/stores', + 'addons/auth/actions' +], function (app, FauxtonAPI, React, AuthStores, AuthActions) { + + var changePasswordStore = AuthStores.changePasswordStore; + var createAdminStore = AuthStores.createAdminStore; + var createAdminSidebarStore = AuthStores.createAdminSidebarStore; + + + var LoginForm = React.createClass({ + propTypes: { + urlBack: React.PropTypes.string.isRequired + }, + + getInitialState: function () { + return { + username: '', + password: '' + }; + }, + + getDefaultProps: function () { + return { + urlBack: '' + }; + }, + + onInputChange: function (e) { + var change = (e.target.name === 'name') ? { username: e.target.value } : { password: e.target.value }; + this.setState(change); + }, + + login: function (e) { + e.preventDefault(); + AuthActions.login(this.state.username, this.state.password, this.props.urlBack); + }, + + componentDidMount: function () { + this.refs.username.getDOMNode().focus(); + }, + + render: function () { + return ( + <div className="row-fluid"> + <div className="span12"> + <form id="login" onSubmit={this.login}> + <p className="help-block"> + Login with 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" placeholder="Password" size="24" + onChange={this.onInputChange} value={this.state.password} /> + <br/> + <button id="submit" className="btn" type="submit">Login</button> + </form> + </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 () { + this.refs.password.getDOMNode().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"> + <h3>Change Password</h3> + + <form id="change-password" onSubmit={this.changePassword}> + <p> + Enter your 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= "Verify Password" + size="24" onChange={this.onChangePasswordConfirm} value={this.state.passwordConfirm} /> + + <br /> + <p> + <button type="submit" className="btn btn-primary">Change</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 () { + this.refs.username.getDOMNode().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"> + <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 validation functions. 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> + ); + } + }); + + + 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> + ); + } + }); + + return { + LoginForm: LoginForm, + ChangePasswordForm: ChangePasswordForm, + CreateAdminForm: CreateAdminForm, + CreateAdminSidebar: CreateAdminSidebar + }; + + +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/resources.js ---------------------------------------------------------------------- diff --git a/app/addons/auth/resources.js b/app/addons/auth/resources.js index 3b6d295..c3deed7 100644 --- a/app/addons/auth/resources.js +++ b/app/addons/auth/resources.js @@ -20,17 +20,6 @@ function (app, FauxtonAPI, CouchdbSession) { var Auth = new FauxtonAPI.addon(); - var errorHandler = function (xhr, type, msg) { - msg = xhr; - if (arguments.length === 3) { - msg = xhr.responseJSON.reason; - } - - FauxtonAPI.addNotification({ - msg: msg, - type: 'error' - }); - }; var Admin = Backbone.Model.extend({ url: function () { @@ -214,167 +203,6 @@ function (app, FauxtonAPI, CouchdbSession) { } }); - Auth.CreateAdminView = FauxtonAPI.View.extend({ - template: 'addons/auth/templates/create_admin', - className: "auth-page", - - initialize: function (options) { - options = options || {}; - this.login_after = options.login_after === false ? false : true; - }, - - events: { - "submit #create-admin-form": "createAdmin" - }, - - createAdmin: function (event) { - event.preventDefault(); - - var that = this, - username = this.$('#username').val(), - password = this.$('#password').val(); - - var promise = this.model.createAdmin(username, password, this.login_after); - - promise.then(function () { - FauxtonAPI.addNotification({ - msg: FauxtonAPI.session.messages.adminCreated, - }); - - if (that.login_after) { - FauxtonAPI.navigate('/'); - } else { - that.$('#username').val(''); - that.$('#password').val(''); - } - }); - - promise.fail(function (xhr, type, msg) { - msg = xhr; - if (arguments.length === 3) { - msg = xhr.responseJSON.reason; - } - msg = FauxtonAPI.session.messages.adminCreationFailedPrefix + ' ' + msg; - errorHandler(msg); - }); - }, - - afterRender: function () { - $("#username").focus(); - } - }); - - Auth.LoginView = FauxtonAPI.View.extend({ - template: 'addons/auth/templates/login', - className: "row-fluid", - initialize: function (options) { - this.urlBack = options.urlBack || ""; - }, - - events: { - "submit #login": "login" - }, - - login: function (event) { - event.preventDefault(); - - var username = this.$('#username').val(), - password = this.$('#password').val(), - urlBack = this.urlBack, - promise = this.model.login(username, password); - - promise.then(function () { - FauxtonAPI.addNotification({msg: FauxtonAPI.session.messages.loggedIn }); - - if (urlBack) { - return FauxtonAPI.navigate(urlBack); - } - - FauxtonAPI.navigate('/'); - }); - - promise.fail(errorHandler); - }, - - afterRender: function () { - $("#username").focus(); - } - }); - - Auth.ChangePassword = FauxtonAPI.View.extend({ - template: 'addons/auth/templates/change_password', - className: "auth-page", - - events: { - "submit #change-password": "changePassword" - }, - - changePassword: function (event) { - event.preventDefault(); - - var that = this, - new_password = this.$('#password').val(), - password_confirm = this.$('#password-confirm').val(); - - var promise = this.model.changePassword(new_password, password_confirm); - - promise.done(function () { - FauxtonAPI.addNotification({msg: FauxtonAPI.session.messages.changePassword}); - that.$('#password').val(''); - that.$('#password-confirm').val(''); - }); - - promise.fail(errorHandler); - }, - - afterRender: function () { - $("#password").focus(); - } - }); - - Auth.NavDropDown = FauxtonAPI.View.extend({ - template: 'addons/auth/templates/nav_dropdown', - - beforeRender: function () { - this.listenTo(this.model, 'change', this.render); - }, - - setTab: function (selectedTab) { - this.selectedTab = selectedTab; - this.$('.active').removeClass('active'); - var $tab = this.$('a[data-select="' + selectedTab + '"]'); - $tab.parent().addClass('active'); - }, - - afterRender: function () { - if (this.selectedTab) { - this.setTab(this.selectedTab); - } - }, - - serialize: function () { - return { - admin_party: this.model.isAdminParty(), - user: this.model.user() - }; - } - }); - - Auth.NoAccessView = FauxtonAPI.View.extend({ - template: "addons/auth/templates/noAccess", - className: "row-fluid", - - initialize: function (options) { - this.urlBack = options.urlBack || ""; - }, - - serialize: function () { - return { - urlBack: this.urlBack, - user: FauxtonAPI.session.user() - }; - } - }); return Auth; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/routes.js ---------------------------------------------------------------------- diff --git a/app/addons/auth/routes.js b/app/addons/auth/routes.js index b6ea57d..2632773 100644 --- a/app/addons/auth/routes.js +++ b/app/addons/auth/routes.js @@ -13,11 +13,14 @@ define([ "app", "api", - "addons/auth/resources" + 'addons/auth/resources', + 'addons/auth/actions', + 'addons/auth/components.react' ], -function (app, FauxtonAPI, Auth) { - var authRouteObject = FauxtonAPI.RouteObject.extend({ +function (app, FauxtonAPI, Auth, AuthActions, Components) { + + var AuthRouteObject = FauxtonAPI.RouteObject.extend({ layout: 'one_pane', routes: { @@ -28,33 +31,31 @@ function (app, FauxtonAPI, Auth) { }, login: function () { - var urlBack = app.getParams().urlback; - this.crumbs = [{name: 'Login', link:"#"}]; - this.setView('#dashboard-content', new Auth.LoginView({ - model: FauxtonAPI.session, - urlBack: urlBack - })); + this.crumbs = [{ name: 'Login', link: "#" }]; + + this.setComponent('#dashboard-content', Components.LoginForm, { urlBack: app.getParams().urlback }); }, logout: function () { - FauxtonAPI.addNotification({msg: 'You have been logged out.'}); + FauxtonAPI.addNotification({ msg: 'You have been logged out.' }); FauxtonAPI.session.logout().then(function () { FauxtonAPI.navigate('/'); }); }, changePassword: function () { - this.crumbs = [{name: 'Change Password', link:"#"}]; - this.setView('#dashboard-content', new Auth.ChangePassword({model: FauxtonAPI.session})); + this.crumbs = [{name: 'Change Password', link: "#" }]; + this.setComponent('#dashboard-content', Components.ChangePasswordForm); }, createAdmin: function () { this.crumbs = [{name: 'Create Admin', link:"#"}]; - this.setView('#dashboard-content', new Auth.CreateAdminView({model: FauxtonAPI.session})); + this.setComponent('#dashboard-content', Components.CreateAdminForm, { loginAfter: true }); } }); - var userRouteObject = FauxtonAPI.RouteObject.extend({ + + var UserRouteObject = FauxtonAPI.RouteObject.extend({ layout: 'with_sidebar', routes: { @@ -63,32 +64,33 @@ function (app, FauxtonAPI, Auth) { roles: ['fx_loggedIn'] }, 'addAdmin': { - roles: ['_admin'], - route: 'addAdmin' + route: 'addAdmin', + roles: ['_admin'] } }, + selectedHeader: function () { return FauxtonAPI.session.user().name; }, initialize: function () { - this.navDrop = this.setView('#sidebar-content', new Auth.NavDropDown({model: FauxtonAPI.session})); + this.setComponent('#sidebar-content', Components.CreateAdminSidebar); }, changePassword: function () { - this.navDrop.setTab('change-password'); - this.setView('#dashboard-content', new Auth.ChangePassword({model: FauxtonAPI.session})); + AuthActions.selectPage('changePassword'); + this.setComponent('#dashboard-content', Components.ChangePasswordForm); }, addAdmin: function () { - this.navDrop.setTab('add-admin'); - this.setView('#dashboard-content', new Auth.CreateAdminView({login_after: false, model: FauxtonAPI.session})); + AuthActions.selectPage('addAdmin'); + this.setComponent('#dashboard-content', Components.CreateAdminForm, { loginAfter: false }); }, crumbs: [{name: 'User Management', link: '#'}] }); - Auth.RouteObjects = [authRouteObject, userRouteObject]; + Auth.RouteObjects = [AuthRouteObject, UserRouteObject]; return Auth; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/stores.js ---------------------------------------------------------------------- diff --git a/app/addons/auth/stores.js b/app/addons/auth/stores.js new file mode 100644 index 0000000..5327574 --- /dev/null +++ b/app/addons/auth/stores.js @@ -0,0 +1,171 @@ +// 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. + +define([ + 'app', + 'api', + 'addons/auth/actiontypes' +], function (app, FauxtonAPI, 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)); + + + return { + changePasswordStore: changePasswordStore, + createAdminStore: createAdminStore, + createAdminSidebarStore: createAdminSidebarStore + }; + +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/templates/change_password.html ---------------------------------------------------------------------- diff --git a/app/addons/auth/templates/change_password.html b/app/addons/auth/templates/change_password.html deleted file mode 100644 index 8879866..0000000 --- a/app/addons/auth/templates/change_password.html +++ /dev/null @@ -1,29 +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. -*/%> - -<h3>Change Password</h3> - -<form id="change-password"> - <p> - Enter your new password. - </p> - <input id="password" type="password" name="password" placeholder="Password" size="24"> - <br/> - <input id="password-confirm" type="password" name="password_confirm" placeholder= "Verify Password" size="24"> - <br/> - - <p> - <button type="submit" class="btn btn-primary">Change</button> - </p> -</form> http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/templates/create_admin.html ---------------------------------------------------------------------- diff --git a/app/addons/auth/templates/create_admin.html b/app/addons/auth/templates/create_admin.html deleted file mode 100644 index 9da8fa9..0000000 --- a/app/addons/auth/templates/create_admin.html +++ /dev/null @@ -1,37 +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. -*/%> - -<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"> - <input id="username" type="text" name="name" placeholder="Username" size="24"> - <br/> - <input id="password" type="password" name="password" placeholder= "Password" size="24"> - <p>Non-admin users have read and write access to all databases, which - are controlled by validation functions. CouchDB can be configured to block all - access to anonymous users. - </p> - <button type="submit" href="#" id="create-admin" class="btn btn-primary">Create Admin</button> -</form> http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/templates/login.html ---------------------------------------------------------------------- diff --git a/app/addons/auth/templates/login.html b/app/addons/auth/templates/login.html deleted file mode 100644 index d5bc6d7..0000000 --- a/app/addons/auth/templates/login.html +++ /dev/null @@ -1,26 +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. ---> -<div class="span12"> - <form id="login"> - <p class="help-block"> - Login with your username and password - </p> - <input id="username" type="text" name="name" placeholder="Username" size="24" /> - <br/> - <input id="password" type="password" name="password" placeholder="Password" size="24" /> - <br/> - <button id="submit" class="btn" type="submit">Login</button> - </form> -</div> - http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/templates/nav_dropdown.html ---------------------------------------------------------------------- diff --git a/app/addons/auth/templates/nav_dropdown.html b/app/addons/auth/templates/nav_dropdown.html deleted file mode 100644 index 44acf10..0000000 --- a/app/addons/auth/templates/nav_dropdown.html +++ /dev/null @@ -1,24 +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. -*/%> - -<div class="sidenav"> -<header class="row-fluid"> - <h3><%- user.name %></h3> -</header> - -<ul class="nav nav-list"> - <li class="active" ><a data-select="change-password" id="user-change-password" href="#changePassword"> Change Password </a></li> - <li ><a data-select="add-admin" href="#addAdmin"> Create Admins </a></li> -</ul> -</div> http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/test/auth.componentsSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/auth/test/auth.componentsSpec.react.jsx b/app/addons/auth/test/auth.componentsSpec.react.jsx new file mode 100644 index 0000000..cdafb83 --- /dev/null +++ b/app/addons/auth/test/auth.componentsSpec.react.jsx @@ -0,0 +1,128 @@ +// 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. +define([ + 'api', + 'react', + 'testUtils', + 'addons/auth/components.react', + 'addons/auth/stores', + 'addons/auth/actions' +], function (FauxtonAPI, React, testUtils, Components, Stores, Actions) { + var assert = testUtils.assert; + + var TestUtils = React.addons.TestUtils; + var createAdminSidebarStore = Stores.createAdminSidebarStore; + + + describe('Auth -- Components', function () { + + describe('LoginForm', function () { + var container, loginForm; + + beforeEach(function () { + container = document.createElement('div'); + loginForm = TestUtils.renderIntoDocument(<Components.LoginForm />, container); + }); + + afterEach(function () { + React.unmountComponentAtNode(container); + }); + + it('should trigger login event when form submitted', function () { + var spy = sinon.spy(Actions, 'login'); + TestUtils.Simulate.submit($(loginForm.getDOMNode()).find('#login')[0]); + assert.ok(spy.calledOnce); + }); + }); + + describe('ChangePasswordForm', function () { + var container, changePasswordForm; + + beforeEach(function () { + container = document.createElement('div'); + changePasswordForm = TestUtils.renderIntoDocument(<Components.ChangePasswordForm />, container); + }); + + afterEach(function () { + React.unmountComponentAtNode(container); + }); + + it('should call action to update password on field change', function () { + var spy = sinon.spy(Actions, 'updateChangePasswordField'); + TestUtils.Simulate.change($(changePasswordForm.getDOMNode()).find('#password')[0], { target: { value: 'bobsyouruncle' }}); + assert.ok(spy.calledOnce); + }); + + it('should call action to update password confirm on field change', function () { + var spy = sinon.spy(Actions, 'updateChangePasswordConfirmField'); + TestUtils.Simulate.change($(changePasswordForm.getDOMNode()).find('#password-confirm')[0], { target: { value: 'hotdiggity' }}); + assert.ok(spy.calledOnce); + }); + + it('should call action to submit form', function () { + var spy = sinon.spy(Actions, 'changePassword'); + TestUtils.Simulate.submit($(changePasswordForm.getDOMNode()).find('#change-password')[0]); + assert.ok(spy.calledOnce); + }); + }); + + describe('CreateAdminForm', function () { + var container, createAdminForm; + + beforeEach(function () { + container = document.createElement('div'); + createAdminForm = TestUtils.renderIntoDocument(<Components.CreateAdminForm loginAfter={false} />, container); + }); + + afterEach(function () { + React.unmountComponentAtNode(container); + }); + + it('should call action to update username on field change', function () { + var spy = sinon.spy(Actions, 'updateCreateAdminUsername'); + TestUtils.Simulate.change($(createAdminForm.getDOMNode()).find('#username')[0], { target: { value: 'catsmeow' }}); + assert.ok(spy.calledOnce); + }); + + it('should call action to update password confirm on field change', function () { + var spy = sinon.spy(Actions, 'updateCreateAdminPassword'); + TestUtils.Simulate.change($(createAdminForm.getDOMNode()).find('#password')[0], { target: { value: 'topnotch' }}); + assert.ok(spy.calledOnce); + }); + }); + + describe('CreateAdminSidebar', function () { + var container, createAdminSidebar; + + beforeEach(function () { + createAdminSidebarStore.reset(); + container = document.createElement('div'); + createAdminSidebar = TestUtils.renderIntoDocument(<Components.CreateAdminSidebar />, container); + }); + + afterEach(function () { + React.unmountComponentAtNode(container); + }); + + it('confirm the default selected nav item is the change pwd page', function () { + assert.equal($(createAdminSidebar.getDOMNode()).find('.active').find('a').attr('href'), '#changePassword'); + }); + + it('confirm clicking a sidebar nav item selects it in the DOM', function () { + TestUtils.Simulate.click($(createAdminSidebar.getDOMNode()).find('li[data-page="addAdmin"]').find('a')[0]); + assert.equal($(createAdminSidebar.getDOMNode()).find('.active').find('a').attr('href'), '#addAdmin'); + }); + }); + + }); + +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/test/auth.storesSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/auth/test/auth.storesSpec.js b/app/addons/auth/test/auth.storesSpec.js new file mode 100644 index 0000000..fd98147 --- /dev/null +++ b/app/addons/auth/test/auth.storesSpec.js @@ -0,0 +1,148 @@ +// 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. + +define([ + 'api', + 'react', + 'testUtils', + 'addons/auth/actiontypes', + 'addons/auth/stores' +], function (FauxtonAPI, React, testUtils, ActionTypes, 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); + }); + }); + + }); + +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/core/routeObject.js ---------------------------------------------------------------------- diff --git a/app/core/routeObject.js b/app/core/routeObject.js index 0af072a..818f4eb 100644 --- a/app/core/routeObject.js +++ b/app/core/routeObject.js @@ -132,8 +132,8 @@ function (FauxtonAPI, React, Backbone) { }, renderReactComponents: function () { - _.each(this.reactComponents, function (component, selector) { - React.render(React.createElement(component, null), $(selector)[0]); + _.each(this.reactComponents, function (componentInfo, selector) { + React.render(React.createElement(componentInfo.component, componentInfo.props), $(selector)[0]); }); }, @@ -245,10 +245,13 @@ function (FauxtonAPI, React, Backbone) { return view; }, - setComponent: function (selector, component) { + setComponent: function (selector, component, props) { this.removeView(selector); this.removeComponent(selector); - this.reactComponents[selector] = component; + this.reactComponents[selector] = { + component: component, + props: props || null + }; }, removeComponent: function (selector) { http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/core/tests/routeObjectSpec.js ---------------------------------------------------------------------- diff --git a/app/core/tests/routeObjectSpec.js b/app/core/tests/routeObjectSpec.js index e267f65..fbac6b6 100644 --- a/app/core/tests/routeObjectSpec.js +++ b/app/core/tests/routeObjectSpec.js @@ -178,7 +178,15 @@ define([ var fakeSelector = '.fake-selector'; testRouteObject.setComponent(fakeSelector, fakeReactComponent); - assert.deepEqual(fakeReactComponent, testRouteObject.reactComponents[fakeSelector]); + assert.deepEqual(fakeReactComponent, testRouteObject.reactComponents[fakeSelector].component); + }); + + it('sets props for the selector', function () { + var fakeReactComponent = React.createElement('div'); + var fakeSelector = '.fake-selector'; + + testRouteObject.setComponent(fakeSelector, fakeReactComponent, {foo: 'bar baromat'}); + assert.deepEqual(fakeReactComponent, testRouteObject.reactComponents[fakeSelector].component); }); });
