]> git.example.dev Git - binbsis50.git/commitdiff
added ability for registered users to change their own password
authorLuigi Pinca <luigipinca@gmail.com>
Tue, 26 Jun 2012 14:29:03 +0000 (16:29 +0200)
committerLuigi Pinca <luigipinca@gmail.com>
Tue, 26 Jun 2012 14:29:03 +0000 (16:29 +0200)
app.js
package.json
public/static/css/style.css
routes/site.js
routes/user.js
views/changepasswd.jade [new file with mode: 0644]
views/index.jade
views/leaderboards.jade [moved from views/leaderboard.jade with 98% similarity]
views/login.jade
views/room.jade
views/signup.jade

diff --git a/app.js b/app.js
index c47b4ef051860ed28356795369d5d1e8777a5de9..e092453213fdc86da6572fa764ce95acb6861d7d 100644 (file)
--- a/app.js
+++ b/app.js
@@ -18,11 +18,11 @@ var songsdb = redisurl.createClient(config.songsdburl);
 var usersdb = redisurl.createClient(config.usersdburl);
 
 songsdb.on('error', function(err) {
-    console.log(err.toString());
+    console.log(err.message);
 });
 
 usersdb.on('error', function(err) {
-    console.log(err.toString());
+    console.log(err.message);
 });
 
 /**
@@ -56,11 +56,13 @@ app.dynamicHelpers({
 
 // Routes
 site.use({db:songsdb,rooms:config.rooms});
-user.use({db:usersdb});
+user.use({db:usersdb,rooms:config.rooms});
 
 app.get('/', site.index);
 app.get('/artworks', site.artworks);
-app.get('/leaderboard', user.leaderboard);
+app.get('/changepasswd', site.changePasswd);
+app.post('/changepasswd', user.validateChangePasswd, user.checkOldPasswd, user.changePasswd);
+app.get('/leaderboards', user.leaderboards);
 app.get('/login', site.login);
 app.post('/login', user.validateLogin, user.checkUser, user.authenticate);
 app.get('/logout', user.logout);
@@ -99,7 +101,7 @@ io.set('authorization', function(data, accept) {
     var cookie = parseCookie(data.headers.cookie);
     sessionstore.get(cookie['connect.sid'], function(err, session) {
         if (err) {
-            return accept(err.toString(), false);
+            return accept(err.message, false);
         }
         else if (!session) {
             return accept('session not found', false);
index 104b50b74186e5d26465af86afce3cc421691e65..e9ac03f092a310e918ca749cef8a6823d69ac65b 100644 (file)
@@ -3,8 +3,8 @@
   "dependencies": {
     "async": "0.1.x",
     "canvas": "0.12.x",
-    "connect": "1.8.x",
-    "connect-redis": "1.3.x",
+    "connect": "1.9.x",
+    "connect-redis": "1.4.x",
     "express": "2.5.x",
     "jade": "0.26.x",
     "redis-url": "0.1.x",
@@ -17,5 +17,5 @@
   "engines": {
     "node": "0.6.x"
   },
-  "version": "0.3.1-4"
+  "version": "0.3.1-7"
 }
\ No newline at end of file
index e5af1c9c0a5e25087784a15cb68925a08ea023f3..aec9cb97ea8d0ccb87e0f0082949b8b25212c58f 100644 (file)
@@ -36,10 +36,6 @@ section {
     font-size: 34px;
     color: #0088CC;
 }
-.navbar .navbar-text {
-    line-height:19px;
-    padding: 9px 10px 11px;
-}
 .form-horizontal .control-group {
     margin-bottom: 10px;
 }
@@ -59,7 +55,7 @@ form .clearfix {
 .alert {
     margin-bottom: 9px;
 }
-#signup-button {
+.submit-button {
     margin-left: 120px;
     margin-top: 9px;
 }
index b806d2142629f7113fa55143c17b4a2052ea4d3e..5f8b8d86be081bb98f36eb8333346509520e7a99 100644 (file)
@@ -28,13 +28,6 @@ exports.use = function(options) {
     rooms = options.rooms;
 };
 
-exports.index = function(req, res) {
-    if (req.session.user) {
-        res.local('loggedin', req.session.user);
-    }
-    res.render('index', {rooms:rooms});
-};
-
 /**
  * Extract at random in each room, some album covers and return the result as a JSON.
  */
@@ -55,25 +48,32 @@ exports.artworks = function(req, res) {
     });
 };
 
-exports.login = function(req, res) {
-    res.render('login');
+exports.changePasswd = function(req, res) {
+    if (!req.session.user) {
+        return res.redirect('/login?followup=/changepasswd');
+    }
+    res.render('changepasswd', {followup:req.query['followup'],loggedin:req.session.user});
 };
 
-exports.signup = function(req, res) {
-    var captcha = new Captcha();
-    req.session.captchacode = captcha.getCode();
-    res.render('signup', {captchaurl:captcha.toDataURL()});
+exports.index = function(req, res) {
+    res.render('index', {loggedin:req.session.user,rooms:rooms});
 };
 
+exports.login = function(req, res) {
+    res.render('login', {followup:req.query['followup']});
+};
 
 exports.room = function(req, res) {
     if (rooms.indexOf(req.params.room) !== -1) {
-        if (req.session.user) {
-            res.local('loggedin', req.session.user);
-        }
-        res.render('room', {roomname:req.params.room,rooms:rooms});
+        res.render('room', {loggedin:req.session.user,roomname:req.params.room,rooms:rooms});
     }
     else {
         res.send(404);
     }
 };
+
+exports.signup = function(req, res) {
+    var captcha = new Captcha();
+    req.session.captchacode = captcha.getCode();
+    res.render('signup', {captchaurl:captcha.toDataURL(),followup:req.query['followup']});
+};
index e1ca5863d61c86255448570392cd19df0d08dae6..e26cfc68e11e2f8ec00f942b7ed3962f8859f750 100644 (file)
@@ -4,6 +4,7 @@
 
 var crypto = require('crypto')
     , db
+    , followupurls = []
     , User = require('../lib/user');
     
 /**
@@ -18,6 +19,17 @@ String.prototype.isEmail = function() {
     return this.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/);
 };
 
+/**
+ * Check if a URL is in the whitelist of follow-up URLs.
+ */
+
+var safeFollowup = function(url) {
+    if (followupurls.indexOf(url) !== -1) {
+        return true;
+    }
+    return false;
+};
+
 /**
  * Parameters to get users ordered by best guess time.
  */
@@ -36,11 +48,11 @@ var sortparams = [
 ];
 
 /**
- * Leaderboard helper function.
+ * Helper function used to build leaderboards.
  * Rearrange database results in an object.
  */
 
-var buildLeaderboard = function(pointsresults, timesresults) {
+var buildLeaderboards = function(pointsresults, timesresults) {
     var obj = {
         pointsleaderboard: [],
         timesleaderboard: []
@@ -64,17 +76,83 @@ var buildLeaderboard = function(pointsresults, timesresults) {
 
 exports.use = function(options) {
     db = options.db;
+    rooms = options.rooms;
+    // Populate the whitelist of follow-up URLs
+    followupurls.push('/');
+    followupurls.push('/changepasswd');
+    for (var i=0; i<rooms.length; i++) {
+        followupurls.push('/'+rooms[i]);
+    }
 };
 
 /**
- * Show a list of users ordered by points and best guess time (limit set to 30).
+ * Show two lists of users, one ordered by points and one by best guess time (limit set to 30).
  */
 
-exports.leaderboard = function(req, res) {
+exports.leaderboards = function(req, res) {
     db.zrevrange('users', 0, 29, 'withscores', function(err, pointsresults) {
         db.sort(sortparams, function (e, timesresults) {
-            var leaderboard = buildLeaderboard(pointsresults, timesresults);
-            res.render('leaderboard', leaderboard);
+            var leaderboards = buildLeaderboards(pointsresults, timesresults);
+            res.render('leaderboards', leaderboards);
+        });
+    });
+};
+
+/**
+ * Change password middlewares.
+ */
+exports.validateChangePasswd = function(req, res, next) {
+    if (!req.session.user || !req.body.oldpassword || !req.body.newpassword) {
+        return res.send('Missing data');
+    }
+    
+    var errors = {};
+    
+    req.body.oldpassword = req.body.oldpassword.trim();
+    req.body.newpassword = req.body.newpassword.trim();
+    if (req.body.oldpassword === '') {
+        errors.oldpassword = "can't be empty";
+    }
+    if (!req.body.newpassword.match(/^[A-Za-z0-9]{6,15}$/)) {
+        errors.newpassword = '6 to 15 alphanumeric characters required';
+    }
+    else if(req.body.newpassword === req.body.oldpassword) {
+        errors.newpassword = "can't be changed to the old one";
+    }
+    
+    if (errors.oldpassword || errors.newpassword) {
+        req.session.errors = errors;
+        return res.redirect(req.url);
+    }
+    
+    next();
+};
+
+exports.checkOldPasswd = function(req, res, next) {
+    var key = 'user:'+req.session.user;
+    db.hmget(key, 'salt', 'password', function(err, data) {
+        var hash = crypto.createHash('sha256').update(data[0]+req.body.oldpassword).digest('hex');
+        if (hash !== data[1]) {
+            req.session.errors = {oldpassword: 'is incorrect'};
+            return res.redirect(req.url);
+        }
+        next();
+    });
+};
+
+exports.changePasswd = function(req, res) {
+    var followup = (safeFollowup(req.query['followup'])) ? req.query['followup'] : '/'
+        , user = req.session.user
+        , key = 'user:'+user
+        , salt = crypto.randomBytes(6).toString('base64')
+        , password = crypto.createHash('sha256').update(salt+req.body.newpassword).digest('hex');
+    db.hmset(key, 'salt', salt, 'password', password, function(err, data) {
+        // Regenerate the session
+        req.session.regenerate(function() {
+            req.session.cookie.maxAge = 604800000; // One week
+            req.session.user = user;
+            res.redirect(followup);
         });
     });
 };
@@ -84,6 +162,10 @@ exports.leaderboard = function(req, res) {
  */
 
 exports.validateLogin = function(req, res, next) {
+    if (!req.body.username || !req.body.password) {
+        return res.send('Missing data');
+    }
+
     var errors = {};
     
     req.body.username = req.body.username.trim(); // Username sanitization
@@ -98,9 +180,8 @@ exports.validateLogin = function(req, res, next) {
     req.session.oldvalues = {username: req.body.username};
     if (errors.username || errors.password) {
         req.session.errors = errors;
-        return res.redirect('/login');
+        return res.redirect(req.url);
     }
-    
     next();
 };
 
@@ -112,7 +193,7 @@ exports.checkUser = function(req, res, next) {
             return next();
         }
         req.session.errors = {alert: 'The username you specified does not exists.'};
-        res.redirect('/login');
+        res.redirect(req.url);
     });
 };
 
@@ -121,16 +202,17 @@ exports.authenticate = function(req, res) {
     db.hmget(key, 'salt', 'password', function(err, data) {
         var hash = crypto.createHash('sha256').update(data[0]+req.body.password).digest('hex');
         if (hash === data[1]) {
+            var followup = (safeFollowup(req.query['followup'])) ? req.query['followup'] : '/';
             // Authentication succeeded, regenerate the session
             req.session.regenerate(function() {
                 req.session.cookie.maxAge = 604800000; // One week
                 req.session.user = req.body.username;
-                res.redirect('/');
+                res.redirect(followup);
             });
             return;
         }
         req.session.errors = {alert: 'The password you specified is not correct.'};
-        res.redirect('/login');
+        res.redirect(req.url);
     });
 };
 
@@ -150,6 +232,10 @@ exports.logout = function(req, res) {
  */
 
 exports.validateSignUp = function(req, res, next) {
+    if (!req.body.username || !req.body.email || !req.body.password || !req.body.captcha) {
+        return res.send('Missing data');
+    }
+
     var errors = {};
     
     req.body.username = req.body.username.trim(); // Username sanitization
@@ -177,7 +263,7 @@ exports.validateSignUp = function(req, res, next) {
     
     if (errors.username || errors.email || errors.password || errors.captcha) {
         req.session.errors = errors;
-        return res.redirect('/signup');
+        return res.redirect(req.url);
     }
     
     next();
@@ -189,7 +275,7 @@ exports.userExists = function(req, res, next) {
         if (data === 1) { 
             // User already exists
             req.session.errors = {alert: 'A user with that name already exists.'};
-            return res.redirect('/signup');
+            return res.redirect(req.url);
         }
         next();
     });
@@ -201,7 +287,7 @@ exports.emailExists = function(req, res, next) {
         if (data === 1) { 
             // Email already exists
             req.session.errors = {alert: 'A user with that email already exists.'};
-            return res.redirect('/signup');
+            return res.redirect(req.url);
         }
         next();
     });
@@ -233,7 +319,7 @@ exports.createAccount = function(req, res) {
     // Delete old fields values (we don't want these to be available in login view)
     delete req.session.oldvalues;
     var msg = 'You successfully created your account. You are now ready to login.';
-    res.render('login', {success:msg});
+    res.render('login', {followup:req.query['followup'],success:msg});
 };
 
 /**
diff --git a/views/changepasswd.jade b/views/changepasswd.jade
new file mode 100644 (file)
index 0000000..e761bc4
--- /dev/null
@@ -0,0 +1,72 @@
+followup = (typeof(followup) !== "undefined") ? '?followup='+followup : '?followup=/'
+doctype html
+html
+    include header
+        title binb :: Change password
+        script(src="/static/js/bootstrap.min.js")
+    body
+        include uv.jade
+        .navbar.navbar-fixed-top
+            .navbar-inner
+                .container
+                    a.brand(href="/")
+                        .logo #{motto}
+                    ul.nav.pull-right
+                        li
+                            a(href="/") Home
+                        li.dropdown
+                            a.dropdown-toggle(data-toggle="dropdown",
+                                href="#") Logged in as #{loggedin.replace(/&/g, '&amp;')} 
+                                span.caret
+                            ul.dropdown-menu
+                                li
+                                    a(href="/user/#{encodeURIComponent(loggedin)}",
+                                        target="_blank") Profile
+                                li
+                                    a(href="/logout") Logout
+        .container
+            section
+                .row
+                    .span12.offset2
+                        form.form-horizontal.well(method="post",action="/changepasswd#{followup}")
+                            fieldset
+                                if (typeof(errors) !== "undefined")
+                                    if (typeof(errors.oldpassword) !== "undefined")
+                                        .control-group.error
+                                            label.control-label(for="oldpassword") Old password
+                                            .controls
+                                                input#oldpassword(type="password",name="oldpassword")
+                                                span.help-inline #{errors.oldpassword}
+                                    else
+                                        .control-group
+                                            label.control-label(for="oldpassword") Old password
+                                            .controls
+                                                input#username(type="password",name="oldpassword",
+                                                    placeholder="enter your current password...")
+                                    if (typeof(errors.newpassword) !== 'undefined')
+                                        .control-group.error
+                                            label.control-label(for="newpassword") New password
+                                            .controls
+                                                input#password(type="password",name="newpassword")
+                                                span.help-inline #{errors.newpassword}
+                                    else
+                                        .control-group
+                                            label.control-label(for="newpassword") New password
+                                            .controls
+                                                input#password(type="password",name="newpassword",
+                                                    placeholder="enter your new password...")
+                                else
+                                    .control-group
+                                        label.control-label(for="oldpassword") Old password
+                                        .controls
+                                            input#username(type="password",name="oldpassword",
+                                                placeholder="enter your current password...")
+                                    .control-group
+                                        label.control-label(for="password") New password
+                                        .controls
+                                            input#password(type="password",name="newpassword",
+                                                placeholder="enter your new password...")
+                                button.submit-button.btn.btn-primary(type="submit")
+                                    i.icon-lock.icon-white
+                                    |  Update
+            include footer
index b354e2e39350a2d47593e8f56480436dd9de524c..d7149845346e9c0c38337fcb071ecbba3031b902 100644 (file)
@@ -3,6 +3,7 @@ html
     include header
         title binb
         script(src="/socket.io/socket.io.js")
+        script(src="/static/js/bootstrap.min.js")
         script(src="/static/js/home.js")
     body
         include uv.jade
@@ -15,16 +16,22 @@ html
                         li.active
                             a(href="/") Home
                         li
-                            a(target="_blank", href="/leaderboard")
+                            a(target="_blank", href="/leaderboards")
                                 i.icon-list-alt.icon-white
                                 |  Leaderboards
                         if (typeof(loggedin) !== "undefined")
-                            li
-                                p.navbar-text Logged in as 
-                                    a#loggedin(href="/user/#{encodeURIComponent(loggedin)}",
-                                        target="_blank") #{loggedin.replace(/&/g, '&amp;')}
-                            li
-                                a(href="/logout") Logout
+                            li.dropdown
+                                a.dropdown-toggle(data-toggle="dropdown",
+                                    href="#") Logged in as #{loggedin.replace(/&/g, '&amp;')} 
+                                    span.caret
+                                ul.dropdown-menu
+                                    li
+                                        a(href="/user/#{encodeURIComponent(loggedin)}",
+                                            target="_blank") Profile
+                                    li
+                                        a(href="/changepasswd") Change password
+                                    li
+                                        a(href="/logout") Logout
                         else
                             li
                                 a(href="/signup") Sign up
similarity index 98%
rename from views/leaderboard.jade
rename to views/leaderboards.jade
index 632e29e5ecb0acc83f6e9dd54441bd99c4d21c90..061c6297f158b18613893dc3a6a6961bce48e762 100644 (file)
@@ -1,7 +1,7 @@
 doctype html
 html
     include header
-        title binb :: Leaderboard
+        title binb :: Leaderboards
     body
         include uv.jade
         .navbar.navbar-fixed-top
index ca0ee1bb9593c356eb18b8e18ead725f4189bfde..7461c1c4856a8e2f103ba14afe68128481fe5b8c 100644 (file)
@@ -1,3 +1,4 @@
+followup = (typeof(followup) !== "undefined") ? '?followup='+followup : '?followup=/'
 doctype html
 html
     include header
@@ -14,15 +15,15 @@ html
                         li
                             a(href="/") Home
                         li
-                            a(href="/signup") Sign up
+                            a(href="/signup#{followup}") Sign up
                         li.active
-                            a(href="/login") Login
+                            a(href="/login#{followup}") Login
         .container
             section
                 .row
                     .span3
                         h3 New user?
-                        a(href="/signup") Click here to create an account.
+                        a(href="/signup#{followup}") Click here to create an account.
                     .span13
                         if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
                             .alert.alert-error
@@ -34,7 +35,7 @@ html
                                 a.close(data-dismiss="alert") &times;
                                 strong Well done!
                                 |  #{success}
-                        form.form-horizontal.well(method="post",action="/login")
+                        form.form-horizontal.well(method="post",action="/login#{followup}")
                             fieldset
                                 if (typeof(errors) !== "undefined")
                                     if (typeof(errors.username) !== "undefined")
@@ -73,7 +74,7 @@ html
                                         .controls
                                             input#password(type="password",name="password",
                                                 placeholder="enter your password...")
-                                button#signup-button.btn.btn-primary(type="submit")
-                                    i.icon-user.icon-white
+                                button.submit-button.btn.btn-primary(type="submit")
+                                    i.icon-lock.icon-white
                                     |  Login
             include footer
index 561fc75e5bc2598c5064ddde07f7e9755d826599..6f303b5f76eb20a618175e1826f449dce1757f12 100644 (file)
@@ -17,7 +17,7 @@ html
                         li
                             a(href="/") Home
                         li
-                            a(target="_blank", href="/leaderboard")
+                            a(target="_blank", href="/leaderboards")
                                 i.icon-list-alt.icon-white
                                 |  Leaderboards
                         li.active.dropdown
@@ -29,17 +29,24 @@ html
                                         li
                                             a(href="/#{item}") #{item}
                         if (typeof(loggedin) !== "undefined")
-                            li
-                                p.navbar-text Logged in as 
-                                    a#loggedin(href="/user/#{encodeURIComponent(loggedin)}",
-                                        target="_blank") #{loggedin.replace(/&/g, '&amp;')}
-                            li
-                                a(href="/logout") Logout
+                            li.dropdown
+                                a.dropdown-toggle(data-toggle="dropdown",
+                                    href="#") Logged in as #{loggedin.replace(/&/g, '&amp;')} 
+                                    span.caret
+                                ul.dropdown-menu
+                                    li
+                                        a(href="/user/#{encodeURIComponent(loggedin)}",
+                                            target="_blank") Profile
+                                    li
+                                        a(href="/changepasswd?followup=/#{roomname}")
+                                            | Change password
+                                    li
+                                        a(href="/logout") Logout
                         else
                             li
-                                a(href="/signup") Sign up
+                                a(href="/signup?followup=/#{roomname}") Sign up
                             li
-                                a(href="/login") Login
+                                a(href="/login?followup=/#{roomname}") Login
         #player
         #modal.modal.fade
         .container
index a32c652dacc698e5ffb106bdd4b7c90d75e7ec50..bca03237f956b5b883c064945ec8613b84d0c943 100644 (file)
@@ -1,3 +1,4 @@
+followup = (typeof(followup) !== "undefined") ? '?followup='+followup : '?followup=/'
 doctype html
 html
     include header
@@ -14,15 +15,15 @@ html
                         li
                             a(href="/") Home
                         li.active
-                            a(href="/signup") Sign up
+                            a(href="/signup#{followup}") Sign up
                         li
-                            a(href="/login") Login
+                            a(href="/login#{followup}") Login
         .container
             section
                 .row
                     .span3
                         h3 Not a new user?
-                        a(href="/login") Click here to log in.
+                        a(href="/login#{followup}") Click here to log in.
                     .span13
                         h3 Why sign up?
                         p Registration is optional, but if you are a regular user consider creating 
@@ -36,7 +37,7 @@ html
                                 a.close(data-dismiss="alert") &times;
                                 strong Oh snap!
                                 |  #{errors.alert}
-                        form.form-horizontal.well(method="post",action="/signup")
+                        form.form-horizontal.well(method="post",action="/signup#{followup}")
                             fieldset
                                 if (typeof(errors) !== "undefined")
                                     if (typeof(errors.username) !== "undefined")
@@ -113,7 +114,7 @@ html
                                             img#captcha(src="#{captchaurl}")
                                             input#captcha-input(type="text",name="captcha",
                                                 placeholder="type what you see...")
-                                button#signup-button.btn.btn-success(type="submit")
+                                button.submit-button.btn.btn-success(type="submit")
                                     i.icon-user.icon-white
                                     |  Sign up!
             include footer