* Setting up redis.
*/
-var songsdb = redisurl.createClient(config.songsdburl);
-var usersdb = redisurl.createClient(config.usersdburl);
+var songsdb = redisurl.createClient(config.songsdburl)
+ , usersdb = redisurl.createClient(config.usersdburl);
songsdb.on('error', function(err) {
console.log(err.message);
// Routes
site.use({db:songsdb,rooms:config.rooms});
-user.use({db:usersdb,rooms:config.rooms});
+user.use({db:usersdb,rooms:config.rooms,sendgrid:config.sendgrid});
app.get('/', site.index);
app.get('/artworks', site.artworks);
app.get('/logout', user.logout);
app.get('/signup', site.signup);
app.post('/signup', user.validateSignUp, user.userExists, user.emailExists, user.createAccount);
+app.get('/recoverpasswd', site.recoverPasswd);
+app.post('/recoverpasswd', user.validateRecoverPasswd, user.sendEmail);
+app.get('/resetpasswd', site.resetPasswd);
+app.post('/resetpasswd', user.resetPasswd);
app.get('/:room', site.room);
app.get('/user/*', user.profile);
}
});
socket.on('joinanonymously', function(nickname, roomname) {
- if (!socket.nickname && typeof nickname === 'string' && nickname !== '' &&
+ if (!socket.nickname && typeof nickname === 'string' && nickname !== '' &&
typeof roomname === 'string' && config.rooms.indexOf(roomname) !== -1) {
rooms[roomname].setNickName(socket, nickname);
}
"songsdburl": "",
"usersdburl": "",
"sessionsecret": "",
+ "sendgrid": {
+ "user": "",
+ "pass": ""
+ },
"songsinarun": 15,
"gameswithnorepeats": 3,
"allowederrors": 2,
--- /dev/null
+/**
+ * Module dependencies.
+ */
+
+var fs = require('fs')
+ , jade = require('jade')
+ , nodemailer = require('nodemailer')
+ , jadetemplate = fs.readFileSync(__dirname + '/template.jade')
+ , texttemplate = fs.readFileSync(__dirname + '/template.txt', 'utf-8')
+ , transport;
+
+/**
+ * Generate the HTML version of the message.
+ */
+
+var HTMLMessage = jade.compile(jadetemplate);
+
+/**
+ * Generate the plaintext version of the message.
+ */
+
+var plaintextMessage = function(token) {
+ return texttemplate.replace(/<token>/, token);
+};
+
+/**
+ * Send the reset password email.
+ */
+
+exports.sendEmail = function(to, token, callback) {
+ transport.sendMail({
+ from: 'binb <no-reply@binb.nodejitsu.com>',
+ to: to,
+ subject: 'binb password recovery',
+ html: HTMLMessage({token:token}),
+ text: plaintextMessage(token)
+ }, function(err, response) {
+ if(err) {
+ return callback(err);
+ }
+ callback(null, response);
+ });
+};
+
+/**
+ * Create a reusable transport method.
+ */
+
+exports.setTransport = function(sendgrid) {
+ transport = nodemailer.createTransport ('SMTP', {
+ service: 'SendGrid',
+ auth: {
+ user: sendgrid.user,
+ pass: sendgrid.pass
+ }
+ });
+};
--- /dev/null
+doctype html
+html
+ head
+ meta(charset="UTF-8")
+ title binb password recovery
+ body(style="font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;")
+ div(style="width:600px;margin:0 auto;border:3px solid #CCC;border-radius:5px;background-color:#F0F0F0;")
+ div(style="padding:16px 16px 0;font-size:15px;color:#505050;")
+ img(alt="logo",src="https://dl.dropbox.com/u/58444696/binb-logo.png")
+ h2 Password recovery
+ p To initiate the password reset process for your binb account,
+ | click the link below:
+ a(style="color:#0088CC;text-decoration:none;",
+ href="http://binb.nodejitsu.com/resetpasswd?token=#{token}")
+ | http://bind.nodejitsu.com/resetpasswd?token=#{token}
+ p If you can't click it, please copy and paste the URL in a new tab/window.
+ p If you haven't requested a password reset, you can disregard this message,
+ | because it's likely that another user entered your email address
+ | by mistake while trying to reset a password.
+ p.note(style="font-size:13px;")
+ b Note:
+ | This email cannot accept replies.
--- /dev/null
+
+
+============================================================
+binb password recovery
+============================================================
+
+To initiate the password reset process for your binb
+account, click the link below:
+
+
+http://binb.nodejitsu.com/resetpasswd?token=<token>
+
+
+If you can't click it, please copy and paste the URL in a
+new tab or window.
+
+If you haven't requested a password reset, you can
+disregard this message, because it's likely that another
+user enteredyour email address by mistake while trying to
+reset a password.
+
+============================================================
+============================================================
+
+Note: This email cannot accept replies.
"connect-redis": "1.4.x",
"express": "2.5.x",
"jade": "0.26.x",
+ "nodemailer": "0.3.x",
"redis-url": "0.1.x",
"socket.io": "0.9.x"
},
"engines": {
"node": "0.6.x"
},
- "version": "0.3.1-7"
-}
\ No newline at end of file
+ "version": "0.3.2"
+}
margin-left: 120px;
margin-top: 9px;
}
+.forgot-passwd {
+ display: inline-block;
+ vertical-align: top;
+ margin: 14px 0 0 15px;
+}
#captcha-input {
width: 126px;
}
float: right;
}
.thumbnails {
- margin-bottom: 0px;
+ margin-bottom: 0;
}
.thumbnails > li {
margin: 0 0 18px 80px;
width: 24px;
height:24px;
top: 49px;
- background: url('/static/images/sprites.png') no-repeat 0px -32px;
+ background: url('/static/images/sprites.png') no-repeat 0 -32px;
}
#wheel-left {
left:51px;
-webkit-border-radius:4px;
-moz-border-radius:4px;
border-radius:4px;
- -webkit-box-shadow: inset 0px 1px 2px 0px #333;
- -moz-box-shadow: inset 0px 1px 2px 0px #333;
- box-shadow: inset 0px 1px 2px 0px #333;
+ -webkit-box-shadow: inset 0 1px 2px 0 #333;
+ -moz-box-shadow: inset 0 1px 2px 0 #333;
+ box-shadow: inset 0 1px 2px 0 #333;
}
#progress-bar {
position:absolute;
}
#progress {
background-color: #6184b7;
- width:0px;
+ width:0;
}
#touch-backdrop {
width:220px;
}
#volume-button .button:hover {
border-color:#999999;
- -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
- -moz-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
- box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
+ -webkit-box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.25);
+ -moz-box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.25);
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.25);
}
#volume-button .button:active {
border-color:#999999 #AAAAAA #CCCCCC;
- -webkit-box-shadow: 0px 1px 2px 0px #aaa inset;
- -moz-box-shadow: 0px 1px 2px 0px #aaa inset;
- box-shadow: 0px 1px 2px 0px #aaa inset;
+ -webkit-box-shadow: 0 1px 2px 0 #aaa inset;
+ -moz-box-shadow: 0 1px 2px 0 #aaa inset;
+ box-shadow: 0 1px 2px 0 #aaa inset;
filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#e6e6e6,EndColorStr=#dcdcdc);
background-image:-moz-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
background-image:-ms-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
border-radius: 2px;
}
#volume-total {
- -webkit-box-shadow: inset 0px 0px 3px 0px #333;
- -moz-box-shadow: inset 0px 0px 3px 0px #333;
- box-shadow: inset 0px 0px 3px 0px #333;
+ -webkit-box-shadow: inset 0 0 3px 0 #333;
+ -moz-box-shadow: inset 0 0 3px 0 #333;
+ box-shadow: inset 0 0 3px 0 #333;
}
#volume-current {
- -webkit-box-shadow: inset 0px 0px 1px 0px #000;
- -moz-box-shadow: inset 0px 0px 1px 0px #000;
- box-shadow: inset 0px 0px 1px 0px #000;
+ -webkit-box-shadow: inset 0 0 1px 0 #000;
+ -moz-box-shadow: inset 0 0 1px 0 #000;
+ box-shadow: inset 0 0 1px 0 #000;
background-color: #6184b7;
}
#volume-handle {
.registered, .round-rank {
height: 16px;
width: 16px;
- margin: 1px 2px 0px 0px;
+ margin: 1px 2px 0 0;
}
.registered {
- background: url('/static/images/sprites.png') no-repeat 0px -16px;
+ background: url('/static/images/sprites.png') no-repeat 0 -16px;
}
.registered:hover {
background: url('/static/images/sprites.png') no-repeat -16px -16px;
if (!req.session.user) {
return res.redirect('/login?followup=/changepasswd');
}
- res.render('changepasswd', {followup:req.query['followup'],loggedin:req.session.user});
+ res.render('changepasswd', {followup:req.query.followup,loggedin:req.session.user});
};
exports.index = function(req, res) {
};
exports.login = function(req, res) {
- res.render('login', {followup:req.query['followup']});
+ res.render('login', {followup:req.query.followup});
+};
+
+exports.recoverPasswd = function(req, res) {
+ var captcha = new Captcha();
+ req.session.captchacode = captcha.getCode();
+ res.render('recoverpasswd', {captchaurl:captcha.toDataURL(),followup:req.query.followup});
+};
+
+exports.resetPasswd = function(req, res) {
+ res.render('resetpasswd', {token:req.query.token});
};
exports.room = function(req, res) {
exports.signup = function(req, res) {
var captcha = new Captcha();
req.session.captchacode = captcha.getCode();
- res.render('signup', {captchaurl:captcha.toDataURL(),followup:req.query['followup']});
+ res.render('signup', {captchaurl:captcha.toDataURL(),followup:req.query.followup});
};
var crypto = require('crypto')
, db
, followupurls = []
+ , mailer = require('../lib/email/mailer')
, User = require('../lib/user');
/**
var obj = {
pointsleaderboard: [],
timesleaderboard: []
- }
+ };
for (var i=0; i<pointsresults.length; i+=2) {
obj.pointsleaderboard.push({
username: pointsresults[i],
exports.use = function(options) {
db = options.db;
rooms = options.rooms;
+ mailer.setTransport(options.sendgrid);
// Populate the whitelist of follow-up URLs
followupurls.push('/');
followupurls.push('/changepasswd');
*/
exports.validateChangePasswd = function(req, res, next) {
- if (!req.session.user || !req.body.oldpassword || !req.body.newpassword) {
- return res.send('Missing data');
+ if (!req.session.user || req.body.oldpassword === undefined ||
+ req.body.newpassword === undefined) {
+ return res.send(412);
}
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";
}
};
exports.changePasswd = function(req, res) {
- var followup = (safeFollowup(req.query['followup'])) ? req.query['followup'] : '/'
+ var followup = (safeFollowup(req.query.followup)) ? req.query.followup : '/'
, user = req.session.user
, key = 'user:'+user
, salt = crypto.randomBytes(6).toString('base64')
*/
exports.validateLogin = function(req, res, next) {
- if (!req.body.username || !req.body.password) {
- return res.send('Missing data');
+ if (req.body.username === undefined || req.body.password === undefined) {
+ return res.send(412);
}
var errors = {};
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'] : '/';
+ 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
*/
exports.validateSignUp = function(req, res, next) {
- if (!req.body.username || !req.body.email || !req.body.password || !req.body.captcha) {
- return res.send('Missing data');
+ if (req.body.username === undefined || req.body.email === undefined ||
+ req.body.password === undefined || req.body.captcha === undefined) {
+ return res.send(412);
}
var errors = {};
exports.userExists = function(req, res, next) {
var key = 'user:'+req.body.username;
db.exists(key, function(err, data) {
- if (data === 1) {
+ if (data === 1) {
// User already exists
req.session.errors = {alert: 'A user with that name already exists.'};
return res.redirect(req.url);
exports.emailExists = function(req, res, next) {
var key = 'email:'+req.body.email;
db.exists(key, function(err, data) {
- if (data === 1) {
+ if (data === 1) {
// Email already exists
req.session.errors = {alert: 'A user with that email already exists.'};
return res.redirect(req.url);
// 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', {followup:req.query['followup'],success:msg});
+ res.render('login', {followup:req.query.followup,success:msg});
+};
+
+/**
+ * Recover password middlewares.
+ */
+
+exports.validateRecoverPasswd = function(req, res, next) {
+ if (req.body.email === undefined || req.body.captcha === undefined) {
+ return res.send(412);
+ }
+
+ var errors = {};
+
+ if (!req.body.email.isEmail()) {
+ errors.email = 'is not an email address';
+ }
+ if (req.body.captcha !== req.session.captchacode) {
+ errors.captcha = 'no match';
+ }
+
+ req.session.oldvalues = {email: req.body.email};
+
+ if (errors.email || errors.captcha) {
+ req.session.errors = errors;
+ return res.redirect(req.url);
+ }
+
+ next();
+};
+
+exports.sendEmail = function(req, res) {
+ var key = 'email:'+req.body.email;
+ db.get(key, function(err, data) {
+ if (data !== null) {
+ // Email exists, generate a secure random token
+ delete req.session.captchacode;
+ var token = crypto.randomBytes(48).toString('hex');
+ // Token expires after 4 hours
+ db.setex('token:'+token, 14400, data, function(err, reply) {
+ mailer.sendEmail(req.body.email, token, function(err, response) {
+ if (err) {
+ console.log('reset password error: '+err.message);
+ }
+ });
+ });
+ return res.render('recoverpasswd', {followup:req.query.followup,success:true});
+ }
+ req.session.errors = {alert: 'The email address you specified could not be found'};
+ res.redirect(req.url);
+ });
+};
+
+/**
+ * Reset user password.
+ */
+
+exports.resetPasswd = function(req, res) {
+ if (req.body.password === undefined) {
+ return res.send(412);
+ }
+
+ var errors = {};
+
+ // Validate new password
+ if (!req.body.password.match(/^[A-Za-z0-9]{6,15}$/)) {
+ errors.password = '6 to 15 alphanumeric characters required';
+ }
+ // Check token availability
+ if (!req.query.token) {
+ errors.alert = 'Missing token.';
+ }
+
+ if (errors.password || errors.alert) {
+ req.session.errors = errors;
+ return res.redirect(req.url);
+ }
+
+ var key = 'token:'+req.query.token;
+ db.get(key, function(err, user) {
+ if (user !== null) {
+ // Delete the token
+ db.del(key);
+ // Update password
+ var salt = crypto.randomBytes(6).toString('base64');
+ var password = crypto.createHash('sha256').update(salt+req.body.password).digest('hex');
+ db.hmset(user, 'salt', salt, 'password', password, function(err, data) {
+ res.render('login', {success:'You can now login with your new password.'});
+ });
+ return;
+ }
+ req.session.errors = {alert: 'Invalid or expired token.'};
+ res.redirect(req.url);
+ });
};
/**
input#password(type="password",name="newpassword",
placeholder="enter your new password...")
button.submit-button.btn.btn-primary(type="submit")
- i.icon-lock.icon-white
+ i.icon-edit.icon-white
| Update
include footer
button.submit-button.btn.btn-primary(type="submit")
i.icon-lock.icon-white
| Login
+ a.forgot-passwd(href="/recoverpasswd#{followup}")
+ | Forgot your password?
include footer
--- /dev/null
+followup = (typeof(followup) !== "undefined") ? '?followup='+followup : '?followup=/'
+doctype html
+html
+ include header
+ title binb :: Recover 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
+ a(href="/signup#{followup}") Sign up
+ li
+ a(href="/login#{followup}") Login
+ .container
+ section
+ .row
+ .span12.offset2
+ if (typeof(success) !== "undefined")
+ .alert.alert-block.alert-success
+ h4.alert-heading Success!
+ | An email has been sent to you.<br>To start the password reset
+ | process, open this email and follow the given instructions.<br>
+ | If you don't receive it in a reasonable amount of time, please
+ | use the support form on the left.
+ else
+ if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
+ .alert.alert-error
+ a.close(data-dismiss="alert") ×
+ strong Oh snap!
+ | #{errors.alert}
+ form.form-horizontal.well(method="post",
+ action="/recoverpasswd#{followup}")
+ fieldset
+ if (typeof(errors) !== "undefined")
+ if (typeof(errors.email) !== "undefined")
+ .control-group.error
+ label.control-label(for="email") Email
+ .controls
+ input#oldpassword(type="text",name="email",
+ value="#{oldvalues.email}")
+ span.help-inline #{errors.email}
+ else
+ .control-group
+ label.control-label(for="email") Email
+ .controls
+ input#username(type="text",name="email",
+ value="#{oldvalues.email}")
+ if (typeof(errors.captcha) !== 'undefined')
+ .control-group.error
+ label.control-label(for="captcha-input")
+ | Are you human?
+ .controls
+ img#captcha(src="#{captchaurl}")
+ input#captcha-input(type="text",name="captcha")
+ span.help-inline #{errors.captcha}
+ else
+ .control-group
+ label.control-label(for="captcha-input")
+ | Are you human?
+ .controls
+ img#captcha(src="#{captchaurl}")
+ input#captcha-input(type="text",name="captcha",
+ placeholder="type what you see...")
+ else
+ .control-group
+ label.control-label(for="email") Email
+ .controls
+ input#email(type="text",name="email",
+ placeholder="type the email you used to sign up...")
+ .control-group
+ label.control-label(for="captcha-input") Are you human?
+ .controls
+ img#captcha(src="#{captchaurl}")
+ input#captcha-input(type="text",name="captcha",
+ placeholder="type what you see...")
+ button.submit-button.btn.btn-primary(type="submit")
+ i.icon-envelope.icon-white
+ | Send password reset link
+ include footer
--- /dev/null
+token = (typeof(token) !== "undefined") ? '?token='+token : ''
+doctype html
+html
+ include header
+ title binb :: Reset password
+ script(src="/static/js/bootstrap.min.js")
+ body
+ include uv.jade
+ .navbar.navbar-fixed-top
+ .navbar-inner
+ .container
+ a.brand(href="#")
+ .logo #{motto}
+ .container
+ section
+ .row
+ .span12.offset2
+ if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
+ .alert.alert-error
+ a.close(data-dismiss="alert") ×
+ strong Oh snap!
+ | #{errors.alert}
+ form.form-horizontal.well(method="post",action="/resetpasswd#{token}")
+ fieldset
+ if (typeof(errors) !== "undefined")
+ if (typeof(errors.password) !== "undefined")
+ .control-group.error
+ label.control-label(for="password") New password
+ .controls
+ input#oldpassword(type="password",name="password")
+ span.help-inline #{errors.password}
+ else
+ .control-group
+ label.control-label(for="password") New password
+ .controls
+ input#username(type="password",name="password",
+ placeholder="enter your new password...")
+ else
+ .control-group
+ label.control-label(for="oldpassword") New password
+ .controls
+ input#username(type="password",name="password",
+ placeholder="enter your new password...")
+ button.submit-button.btn.btn-primary(type="submit")
+ i.icon-edit.icon-white
+ | Update
+ include footer