You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by se...@apache.org on 2015/08/10 18:00:54 UTC

[12/34] incubator-ignite git commit: IGNITE-843 Implemented password reset.

IGNITE-843 Implemented password reset.


Project: http://git-wip-us.apache.org/repos/asf/incubator-ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ignite/commit/8406e7c0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ignite/tree/8406e7c0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ignite/diff/8406e7c0

Branch: refs/heads/ignite-1201
Commit: 8406e7c0df285bc939d969895835b10038a70589
Parents: 2aa1ce8
Author: AKuznetsov <ak...@gridgain.com>
Authored: Thu Aug 6 16:55:24 2015 +0700
Committer: AKuznetsov <ak...@gridgain.com>
Committed: Thu Aug 6 16:55:24 2015 +0700

----------------------------------------------------------------------
 modules/control-center-web/src/main/js/app.js   |  10 +-
 .../js/controllers/cache-viewer-controller.js   |  77 ------------
 .../src/main/js/controllers/common-module.js    |  56 ++++++---
 modules/control-center-web/src/main/js/db.js    |   5 +-
 .../src/main/js/routes/agent.js                 |   2 +-
 .../src/main/js/routes/public.js                | 120 ++++++++++---------
 .../src/main/js/views/includes/header.jade      |   4 +-
 .../src/main/js/views/login.jade                |  55 ---------
 .../src/main/js/views/loginModal.jade           |  55 +++++++++
 .../src/main/js/views/reset.jade                |   3 +-
 .../src/main/js/views/resetModal.jade           |  12 +-
 11 files changed, 185 insertions(+), 214 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/app.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/app.js b/modules/control-center-web/src/main/js/app.js
index 7741822..413d92c 100644
--- a/modules/control-center-web/src/main/js/app.js
+++ b/modules/control-center-web/src/main/js/app.js
@@ -100,8 +100,14 @@ app.all('/configuration/*', mustAuthenticated);
 app.all('*', function(req, res, next) {
     var becomeUsed = req.session.viewedUser && req.user.admin;
 
-    res.locals.user = becomeUsed ? req.session.viewedUser : req.user;
-    res.locals.becomeUsed = becomeUsed;
+    if (req.url == '/reset') {
+        res.locals.user = null;
+        res.locals.becomeUsed = false;
+    }
+    else {
+        res.locals.user = becomeUsed ? req.session.viewedUser : req.user;
+        res.locals.becomeUsed = becomeUsed;
+    }
 
     req.currentUserId = function() {
         if (!req.user)

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/controllers/cache-viewer-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/cache-viewer-controller.js b/modules/control-center-web/src/main/js/controllers/cache-viewer-controller.js
deleted file mode 100644
index 6e0c130..0000000
--- a/modules/control-center-web/src/main/js/controllers/cache-viewer-controller.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
-
-var demoResults = [
-    {
-        id: 256,
-        s: 'com.foo.User@3213',
-        fields: {
-            id: 256,
-            firstName: 'Ivan',
-            lastName: 'Ivanov',
-            old: 23
-        }
-    },
-
-    {
-        id: 384,
-        s: 'com.foo.User@23214',
-        fields: {
-            id: 384,
-            firstName: 'Sergey',
-            lastName: 'Petrov',
-            old: 28
-        }
-    },
-
-    {
-        id: 923,
-        s: 'com.foo.User@93494',
-        fields: {
-            id: 923,
-            firstName: 'Andrey',
-            lastName: 'Sidorov',
-            old: 28
-        }
-    }
-];
-
-var demoCaches = ['Users', 'Organizations', 'Cities'];
-
-controlCenterModule.controller('cacheViewerController', ['$scope', '$http', '$common', function ($scope, $http, $common) {
-    $scope.results = demoResults;
-
-    $scope.caches = demoCaches;
-
-    $scope.defCache = $scope.caches.length > 0 ? $scope.caches[0] : null;
-
-    var sqlEditor = ace.edit('querySql');
-
-    sqlEditor.setOptions({
-        highlightActiveLine: false,
-        showPrintMargin: false,
-        showGutter: true,
-        theme: "ace/theme/chrome",
-        mode: "ace/mode/sql",
-        fontSize: 14
-    });
-
-    sqlEditor.setValue("select u.id from User u where u.name like 'aaaa';");
-
-    sqlEditor.selection.clearSelection()
-
-}]);

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/controllers/common-module.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/common-module.js b/modules/control-center-web/src/main/js/controllers/common-module.js
index 05186a9..60e02fc 100644
--- a/modules/control-center-web/src/main/js/controllers/common-module.js
+++ b/modules/control-center-web/src/main/js/controllers/common-module.js
@@ -82,11 +82,11 @@ controlCenterModule.service('$common', ['$alert', function ($alert) {
         return errMsg ? errMsg : 'Internal server error.';
     }
 
-    function showError(msg) {
+    function showError(msg, placement, container) {
         if (msgModal)
             msgModal.hide();
 
-        msgModal = $alert({title: errorMessage(msg)});
+        msgModal = $alert({title: errorMessage(msg), placement: placement ? placement : 'top-right', container: container ? container : 'body'});
 
         return false;
     }
@@ -596,14 +596,11 @@ controlCenterModule.controller('activeLink', [
 
 // Login popup controller.
 controlCenterModule.controller('auth', [
-    '$scope', '$modal', '$alert', '$http', '$window', '$common', '$focus',
-    function ($scope, $modal, $alert, $http, $window, $common, $focus) {
-        $scope.errorMessage = $common.errorMessage;
-
+    '$scope', '$modal', '$http', '$window', '$timeout', '$common', '$focus',
+    function ($scope, $modal, $http, $window, $timeout, $common, $focus) {
+        $scope.showResetModal = false;
         $scope.action = 'login';
 
-        $scope.valid = false;
-
         $scope.userDropdown = [{text: 'Profile', href: '/profile'}];
 
         if (!$scope.becomeUsed) {
@@ -614,36 +611,65 @@ controlCenterModule.controller('auth', [
         }
 
         // Pre-fetch modal dialogs.
-        var authModal = $modal({scope: $scope, templateUrl: '/login', show: false});
+        var loginModal = $modal({scope: $scope, templateUrl: '/loginModal', show: false});
         var resetModal = $modal({scope: $scope, templateUrl: '/resetModal', show: false});
 
+        // Show reset modal if needed.
+        $timeout(function () {
+            if ($scope.showResetModal)
+                $scope.reset()
+        });
+
+        // Show login modal.
         $scope.login = function () {
-            authModal.$promise.then(function () {
-                authModal.show();
+            loginModal.$promise.then(function () {
+                loginModal.show();
 
                 $focus('user_email');
             });
         };
 
+        // Show reset password modal.
         $scope.reset = function () {
             resetModal.$promise.then(function () {
                 resetModal.show();
 
-                $focus('user_password');
+                $focus('user_token');
             });
         };
 
+        // Try to authorize user with provided credentials.
         $scope.auth = function (action, user_info) {
             $http.post('/' + action, user_info)
                 .success(function () {
-                    authModal.hide();
+                    loginModal.hide();
 
                     $window.location = '/configuration/clusters';
                 })
-                .error(function (data) {
-                    $alert({placement: 'top', container: '#errors-container', title: $scope.errorMessage(data)});
+                .error(function (data, status) {
+                    if (status == 403) {
+                        loginModal.hide();
+
+                        $window.location = '/reset';
+                    }
+                    else
+                        $common.showError(data, 'top', '#errors-container');
                 });
         };
+
+        // Try to reset user password for provided token.
+        $scope.resetPassword = function (user_info) {
+            $http.post('/reset_password', user_info)
+                .success(function (data) {
+                    resetModal.hide();
+
+                    $scope.user_info = {email: data};
+                    $scope.login();
+                })
+                .error(function (data) {
+                    $common.showError(data, 'top', '#errors-container');
+                });
+        }
     }]);
 
 // Navigation bar controller.

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/db.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/db.js b/modules/control-center-web/src/main/js/db.js
index 401beff..f815266 100644
--- a/modules/control-center-web/src/main/js/db.js
+++ b/modules/control-center-web/src/main/js/db.js
@@ -33,7 +33,8 @@ var AccountSchema = new Schema({
     username: String,
     email: String,
     lastLogin: Date,
-    admin: Boolean
+    admin: Boolean,
+    resetPasswordToken: String
 });
 
 AccountSchema.plugin(passportLocalMongoose, {usernameField: 'email', limitAttempts: true, lastLoginField: 'lastLogin',
@@ -379,4 +380,4 @@ exports.upsert = function (model, data, cb) {
         new model(data).save(cb);
 };
 
-exports.mongoose = mongoose;
\ No newline at end of file
+exports.mongoose = mongoose;

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/routes/agent.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/agent.js b/modules/control-center-web/src/main/js/routes/agent.js
index 45cd0ef..c92c575 100644
--- a/modules/control-center-web/src/main/js/routes/agent.js
+++ b/modules/control-center-web/src/main/js/routes/agent.js
@@ -72,7 +72,7 @@ router.post('/next_page', function(req, res) {
     var cache = client.ignite().cache(req.body.cacheName);
 
     var cmd = cache._createCommand("qryfetch").addParam("qryId", req.body.queryId).
-        addParam("psz", req.body.pageSize);
+        addParam("pageSize", req.body.pageSize);
 
     cache.__createPromise(cmd).then(function (page) {
         res.json({rows: page["items"], last: page === null || page["last"]});

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/routes/public.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/public.js b/modules/control-center-web/src/main/js/routes/public.js
index 817c93b..3f770a1 100644
--- a/modules/control-center-web/src/main/js/routes/public.js
+++ b/modules/control-center-web/src/main/js/routes/public.js
@@ -42,8 +42,8 @@ router.get('/copy', function (req, res) {
 });
 
 /* GET login dialog. */
-router.get('/login', function (req, res) {
-    res.render('login');
+router.get('/loginModal', function (req, res) {
+    res.render('loginModal');
 });
 
 /* GET reset password page. */
@@ -85,19 +85,52 @@ router.post('/register', function (req, res) {
     });
 });
 
-router.post('/restore', function(req, res) {
+/**
+ * Login in exist account.
+ */
+router.post('/login', function (req, res, next) {
+    passport.authenticate('local', function (err, user) {
+        if (err)
+            return res.status(401).send(err.message);
+
+        if (!user)
+            return res.status(401).send('Invalid email or password');
+
+        req.logIn(user, {}, function (err) {
+            if (err)
+                return res.status(401).send(err.message);
+
+            res.redirect('/configuration/clusters');
+        });
+    })(req, res, next);
+});
+
+/**
+ * Logout.
+ */
+router.get('/logout', function (req, res) {
+    req.logout();
+
+    res.redirect('/');
+});
+
+/**
+ * Request for password reset and send e-mail to user with reset token. */
+router.post('/request_password_reset', function(req, res) {
     var token = crypto.randomBytes(20).toString('hex');
 
     db.Account.findOne({ email: req.body.email }, function(err, user) {
-        if (err)
+        if (!user)
             return res.status(401).send('No account with that email address exists!');
 
+        if (err)
+            return res.status(401).send(err);
+
         user.resetPasswordToken = token;
-        user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
 
         user.save(function(err) {
             if (err)
-                return res.status(401).send('Failed to send e-mail with reset link!');
+                return res.status(401).send(err);
 
             var transporter  = nodemailer.createTransport({
                 service: 'gmail',
@@ -108,39 +141,44 @@ router.post('/restore', function(req, res) {
             });
 
             var mailOptions = {
-                from: 'passwordreset@YOUR.DOMAIN',
+                from: '!!! YOUR USERNAME !!!',
                 to: user.email,
                 subject: 'Password Reset',
                 text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
                 'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
-                'http://' + req.headers.host + '/reset/' + token + '\n\n' +
-                'If you did not request this, please ignore this email and your password will remain unchanged.\n' +
-                'Link will be valid for one hour.\n'
+                'http://' + req.headers.host + '/reset\n' +
+                'And enter this reset token: ' + token + '\n\n' +
+                'If you did not request this, please ignore this email and your password will remain unchanged.\n\n' +
+                '--------------\n' +
+                'Apache Ignite Web Control Center\n'
             };
 
-            transporter.sendMail(mailOptions, function(err, info){
+            transporter.sendMail(mailOptions, function(err){
                 if (err)
                     return res.status(401).send('Failed to send e-mail with reset link!');
 
-                console.log('Message sent: ' + info.response);
-
-                return res.status(500).send('An e-mail has been sent with further instructions.');
+                return res.status(403).send('An e-mail has been sent with further instructions.');
             });
         });
     });
 });
 
-router.post('/reset/:token', function(req, res) {
-    db.Account.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) {
+/**
+ * Reset password with given token.
+ */
+router.post('/reset_password', function(req, res) {
+    db.Account.findOne({ resetPasswordToken: req.body.token }, function(err, user) {
+        if (!user)
+            return res.status(500).send('Invalid token for password reset!');
+
         if (err)
             return res.status(500).send(err);
 
-        user.setPassword(newPassword, function (err, updatedUser) {
+        user.setPassword(req.body.password, function (err, updatedUser) {
             if (err)
                 return res.status(500).send(err.message);
 
-            user.resetPasswordToken = undefined;
-            user.resetPasswordExpires = undefined;
+            updatedUser.resetPasswordToken = undefined;
 
             updatedUser.save(function (err) {
                 if (err)
@@ -155,55 +193,27 @@ router.post('/reset/:token', function(req, res) {
                 });
 
                 var mailOptions = {
-                    from: 'passwordreset@YOUR.DOMAIN',
+                    from: '!!! YOUR USERNAME !!!',
                     to: user.email,
                     subject: 'Your password has been changed',
                     text: 'Hello,\n\n' +
-                    'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n'
+                    'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n\n' +
+                    'Now you can login: http://' + req.headers.host + '\n\n' +
+                    '--------------\n' +
+                    'Apache Ignite Web Control Center\n'
                 };
 
-                transporter.sendMail(mailOptions, function(err, info){
+                transporter.sendMail(mailOptions, function(err){
                     if (err)
-                        return res.status(401).send('Failed to send e-mail with reset link!');
-
-                    console.log('Message sent: ' + info.response);
+                        return res.status(401).send('Failed to send password reset confirmation e-mail!');
 
-                    res.redirect('/login');
+                    return res.status(200).send(user.email);
                 });
             });
         });
     });
 });
 
-/**
- * Login in exist account.
- */
-router.post('/login', function (req, res, next) {
-    passport.authenticate('local', function (err, user) {
-        if (err)
-            return res.status(401).send(err.message);
-
-        if (!user)
-            return res.status(401).send('Invalid email or password');
-
-        req.logIn(user, {}, function (err) {
-            if (err)
-                return res.status(401).send(err.message);
-
-            res.redirect('/configuration/clusters');
-        });
-    })(req, res, next);
-});
-
-/**
- * Logout.
- */
-router.get('/logout', function (req, res) {
-    req.logout();
-
-    res.redirect('/');
-});
-
 /* GET home page. */
 router.get('/', function (req, res) {
     if (req.isAuthenticated())

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/views/includes/header.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/includes/header.jade b/modules/control-center-web/src/main/js/views/includes/header.jade
index 49ea592..b690ba2 100644
--- a/modules/control-center-web/src/main/js/views/includes/header.jade
+++ b/modules/control-center-web/src/main/js/views/includes/header.jade
@@ -17,7 +17,7 @@ mixin header-item(active, ref, txt)
     li
         a(ng-class='{active: isActive("#{active}")}' href='#{ref}') #{txt}
 
-header.header(id='header')
+header#header.header
     .viewedUser(ng-show='becomeUsed') Currently assuming "
         strong {{user.username}}
         | ",&nbsp;&nbsp;
@@ -39,4 +39,4 @@ header.header(id='header')
                     a.dropdown-toggle(data-toggle='dropdown' bs-dropdown='userDropdown' data-placement='bottom-right' data-ng-bind='::user.username')
                         span.caret
                 li.nav-login(ng-if='!user')
-                    a(ng-click='login()' href='#') Log In
\ No newline at end of file
+                    a(ng-click='login()') Log In

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/views/login.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/login.jade b/modules/control-center-web/src/main/js/views/login.jade
deleted file mode 100644
index 47bb2c5..0000000
--- a/modules/control-center-web/src/main/js/views/login.jade
+++ /dev/null
@@ -1,55 +0,0 @@
-//-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You 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.
-
-mixin lbl(txt)
-    label.col-sm-3.required #{txt}
-
-.modal.center(role='dialog')
-    .modal-dialog
-        .modal-content
-            #errors-container.modal-header.header
-                button.close(type='button' ng-click='$hide()' aria-hidden='true') &times;
-                h1.navbar-brand
-                    a(href='/') Apache Ignite Web Configurator
-                h4.modal-title(style='padding-right: 55px') Authentication
-                p(style='padding-right: 55px') Log in or register in order to collaborate
-            form.form-horizontal(name='loginForm' ng-init='action == "login"')
-                .modal-body.row
-                    .col-sm-9.col-sm-offset-1
-                        .details-row(ng-show='action == "register"')
-                            +lbl('Full Name:')
-                            .col-sm-9
-                                input#user_name.form-control(enter-focus-next enter-focus-next-id='user_email' type='text' ng-model='user_info.username' placeholder='John Smith' ng-required='action=="register"')
-                        .details-row
-                            +lbl('Email:')
-                            .col-sm-9
-                                input#user_email.form-control(enter-focus-next enter-focus-next-id='user_password' type='email' ng-model='user_info.email' placeholder='you@domain.com' required)
-                        .details-row(ng-show='action != "restore"')
-                            +lbl('Password:')
-                            .col-sm-9
-                                input#user_password.form-control(enter-focus-next enter-focus-next-id='user_confirm' type='password' ng-model='user_info.password' placeholder='Password' ng-required='action != "restore"' on-enter='action == "login" && auth(action, user_info)')
-                        .details-row(ng-if='action == "register"')
-                            +lbl('Confirm:')
-                            .col-sm-9
-                                input#user_confirm.form-control(type='password' ng-model='user_info.confirm' match='user_info.password' placeholder='Confirm password' ng-required='action == "register"' on-enter='auth(action, user_info)')
-            .modal-footer
-                a.labelField(ng-show='action != "restore"' ng-click='action = "restore"' event-focus='click' event-focus-id='user_email') Forgot password?
-                a.labelField(ng-show='action == "restore"' ng-click='action = "login"' event-focus='click' event-focus-id='user_email') Log In
-                a.labelLogin(ng-show='action == "register"' event-focus='click' event-focus-id='user_email' ng-click='action = "login";') Log In
-                a.labelLogin(ng-show='action == "login"' event-focus='click' event-focus-id='user_name' ng-click='action = "register"') Sign Up
-                button.btn.btn-primary(ng-show='action == "login"' ng-disabled='loginForm.$invalid' ng-click='auth(action, user_info)') Log In
-                button.btn.btn-primary(ng-show='action == "register"' ng-disabled='loginForm.$invalid' ng-click='auth(action, user_info)') Sign Up
-                button.btn.btn-primary(ng-show='action == "restore"' ng-disabled='loginForm.$invalid' ng-click='auth(action, user_info)') Reset Password

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/views/loginModal.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/loginModal.jade b/modules/control-center-web/src/main/js/views/loginModal.jade
new file mode 100644
index 0000000..8b32aa1
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/loginModal.jade
@@ -0,0 +1,55 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+
+mixin lbl(txt)
+    label.col-sm-3.required #{txt}
+
+.modal.center(role='dialog')
+    .modal-dialog
+        .modal-content
+            #errors-container.modal-header.header
+                button.close(type='button' ng-click='$hide()' aria-hidden='true') &times;
+                h1.navbar-brand
+                    a(href='/') Apache Ignite Web Configurator
+                h4.modal-title(style='padding-right: 55px') Authentication
+                p(style='padding-right: 55px') Log in or register in order to collaborate
+            form.form-horizontal(name='loginForm' ng-init='action == "login"')
+                .modal-body.row
+                    .col-sm-9.col-sm-offset-1
+                        .details-row(ng-show='action == "register"')
+                            +lbl('Full Name:')
+                            .col-sm-9
+                                input#user_name.form-control(enter-focus-next enter-focus-next-id='user_email' type='text' ng-model='user_info.username' placeholder='John Smith' ng-required='action=="register"')
+                        .details-row
+                            +lbl('Email:')
+                            .col-sm-9
+                                input#user_email.form-control(enter-focus-next enter-focus-next-id='user_password' type='email' ng-model='user_info.email' placeholder='you@domain.com' required on-enter='action == "request_password_reset" && loginForm.$valid && auth(action, user_info)')
+                        .details-row(ng-show='action != "request_password_reset"')
+                            +lbl('Password:')
+                            .col-sm-9
+                                input#user_password.form-control(enter-focus-next enter-focus-next-id='user_confirm' type='password' ng-model='user_info.password' placeholder='Password' ng-required='action != "request_password_reset"' on-enter='action == "login" && loginForm.$valid && auth(action, user_info)')
+                        .details-row(ng-if='action == "register"')
+                            +lbl('Confirm:')
+                            .col-sm-9
+                                input#user_confirm.form-control(type='password' ng-model='user_info.confirm' match='user_info.password' placeholder='Confirm password' ng-required='action == "register"' on-enter='loginForm.$valid && auth(action, user_info)')
+            .modal-footer
+                a.labelField(ng-show='action != "request_password_reset"' ng-click='action = "request_password_reset"' event-focus='click' event-focus-id='user_email') Forgot password?
+                a.labelField(ng-show='action == "request_password_reset"' ng-click='action = "login"' event-focus='click' event-focus-id='user_email') Log In
+                a.labelLogin(ng-show='action == "register"' event-focus='click' event-focus-id='user_email' ng-click='action = "login";') Log In
+                a.labelLogin(ng-show='action == "login"' event-focus='click' event-focus-id='user_name' ng-click='action = "register"') Sign Up
+                button.btn.btn-primary(ng-show='action == "login"' ng-disabled='loginForm.$invalid' ng-click='auth(action, user_info)') Log In
+                button.btn.btn-primary(ng-show='action == "register"' ng-disabled='loginForm.$invalid' ng-click='auth(action, user_info)') Sign Up
+                button.btn.btn-primary(ng-show='action == "request_password_reset"' ng-disabled='loginForm.$invalid' ng-click='auth(action, user_info)') Reset Password

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/views/reset.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/reset.jade b/modules/control-center-web/src/main/js/views/reset.jade
index c9c4d88..950f1a7 100644
--- a/modules/control-center-web/src/main/js/views/reset.jade
+++ b/modules/control-center-web/src/main/js/views/reset.jade
@@ -18,5 +18,6 @@ extends templates/layout
 
 block container
     .row
-        .text-center(ng-controller='auth')
+        .text-center(ng-controller='auth' ng-init='showResetModal=true')
+            p Further instructions for password reset have been sent to your e-mail address.
             button.btn.btn-primary(ng-click='reset()' href='#') Reset Password

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8406e7c0/modules/control-center-web/src/main/js/views/resetModal.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/resetModal.jade b/modules/control-center-web/src/main/js/views/resetModal.jade
index 0878525..df3dec8 100644
--- a/modules/control-center-web/src/main/js/views/resetModal.jade
+++ b/modules/control-center-web/src/main/js/views/resetModal.jade
@@ -14,21 +14,25 @@
 
 .modal.center(role='dialog')
     .modal-dialog
-        .modal-content
-            #errors-container.modal-header.header
+        #errors-container.modal-content
+            .modal-header.header
                 button.close(type='button' ng-click='$hide()' aria-hidden='true') &times;
                 h4.modal-title(style='padding-right: 55px') Reset password
-            form.form-horizontal(name='loginForm' ng-init='action == "login"')
+            form.form-horizontal(name='resetForm' ng-init='action == "login"')
                 .modal-body.row
                     .col-sm-9.col-sm-offset-1
                         .details-row
+                            label.col-sm-4.required Token:
+                            .col-sm-8
+                                input#user_token.form-control(enter-focus-next enter-focus-next-id='user_password' type='text' ng-model='user_info.token' placeholder='Reset token' required)
+                        .details-row
                             label.col-sm-4.required New password:
                             .col-sm-8
                                 input#user_password.form-control(enter-focus-next enter-focus-next-id='user_confirm' type='password' ng-model='user_info.password' placeholder='New password' required)
                         .details-row
                             label.col-sm-4.required Confirm:
                             .col-sm-8
-                                input#user_confirm.form-control(type='password' ng-model='user_info.confirm' match='user_info.password' placeholder='Confirm new password' ng-required='action == "register"' on-enter='resetPassword(user_info)')
+                                input#user_confirm.form-control(type='password' ng-model='user_info.confirm' match='user_info.password' placeholder='Confirm new password' required on-enter='resetPassword(user_info)')
             .modal-footer
                 button.btn.btn-primary(ng-disabled='resetForm.$invalid' ng-click='resetPassword(user_info)') Reset Password