Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
zemirco committed Oct 3, 2013
1 parent b2e5c66 commit f370007
Show file tree
Hide file tree
Showing 21 changed files with 808 additions and 0 deletions.
1 change: 1 addition & 0 deletions .npmignore
@@ -0,0 +1 @@
test/
8 changes: 8 additions & 0 deletions .travis.yml
@@ -0,0 +1,8 @@
language: node_js
node_js:
- "0.10"
services: couchdb
before_script:
- npm install -g grunt-cli
- curl -X PUT localhost:5984/test
- node ./test/createViews.js
32 changes: 32 additions & 0 deletions Gruntfile.js
@@ -0,0 +1,32 @@

module.exports = function(grunt) {

// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jshint: {
options: {
expr: true
},
files: ['Gruntfile.js', 'index.js', 'test/*.js']
},
mochaTest: {
test: {
options: {
reporter: 'spec'
},
src: ['test/test.js']
}
}
});

// load tasks
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-mocha-test');

// register tasks
grunt.registerTask('hint', ['jshint']);
grunt.registerTask('mocha', ['mochaTest']);
grunt.registerTask('default', ['jshint', 'mochaTest']);

};
Empty file added History.md
Empty file.
218 changes: 218 additions & 0 deletions index.js
@@ -0,0 +1,218 @@

var path = require('path');
var uuid = require('node-uuid');
var bcrypt = require('bcrypt');

module.exports = function(app, config) {

var adapter = require('lockit-' + config.db + '-adapter')(config);
var sendmail = require('lockit-sendmail')(config);

// set default route
var route = config.forgotPasswordRoute || '/forgot-password';

// GET /forgot-password
app.get(route, function(req, res) {
res.render(path.join(__dirname, 'views', 'get-forgot-password'), {
title: 'Forgot password'
});
});

// POST /forgot-password
app.post(route, function(req, response) {

var email = req.body.email;

var error = null;
// regexp from https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js#L4
var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/;

// check for valid input
if (!email || !email.match(EMAIL_REGEXP)) {
response.status(403);
response.render(path.join(__dirname, 'views', 'get-forgot-password'), {
title: 'Forgot password',
error: 'Email is invalid'
});
return;
}

// looks like given email address has the correct format

// look for user in db
adapter.find('email', email, function(err, user) {
if (err) console.log(err);

// no user found -> pretend we sent an email
if (!user) {
response.render(path.join(__dirname, 'views', 'post-forgot-password'), {
title: 'Forgot password'
});
return;
}

// user found in db
// set old pw and hash to null
// send link with setting new password page
var token = uuid.v4();
delete user.hash;
user.pwdResetToken = token;

// set expiration date for password reset token
var now = new Date();
var tomorrow = now.setTime(now.getTime() + config.forgotPasswordTokenExpiration);

user.pwdResetTokenExpires = new Date(tomorrow);

// update user in db
adapter.update(user, function(err, res) {
if (err) console.log(err);

// send email with forgot password link
sendmail.forgotPassword(user.username, user.email, token, function(err, res) {
if (err) console.log(err);

// render success message
response.render(path.join(__dirname, 'views', 'post-forgot-password'), {
title: 'Forgot password'
});

});

});

});

});

// GET /forgot-password/:token
app.get(route + '/:token', function(req, res, next) {

// get token from url
var token = req.params.token;

// verify format of token
var re = new RegExp('[0-9a-f]{22}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', 'i');

// if format is wrong no need to query the database
if (!re.test(token)) return next();

// check if we have a user with that token
adapter.find('pwdResetToken', token, function(err, user) {
if (err) console.log(err);

// if no user is found forward to error handling middleware
if (!user) return next();

// check if token has expired
if (new Date(user.pwdResetTokenExpires) < new Date()) {

// make old token invalid
delete user.pwdResetToken;
delete user.pwdResetTokenExpires;

// update user in db
adapter.update(user, function(err, user) {
if (err) console.log(err);

// tell user that link has expired
res.render(path.join(__dirname, 'views', 'link-expired'), {
title: 'Forgot password - Link expired'
});

});

return;
}

// send token as local variable for POST request to right url
res.render(path.join(__dirname, 'views', 'get-new-password'), {
token: token,
title: 'Choose a new password'
});

});

});

// POST /forgot-password/:token
app.post(route + '/:token', function(req, res, next) {

var password = req.body.password;
var token = req.params.token;

// verify format of token
var re = new RegExp('[0-9a-f]{22}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', 'i');

// if format is wrong no need to query the database
if (!re.test(token)) return next();

// check for valid input
if (!password) {
res.status(403);
res.render(path.join(__dirname, 'views', 'get-forgot-password'), {
title: 'Choose a new password',
error: 'Please enter a password',
token: token
});
return;
}

// check for token in db
adapter.find('pwdResetToken', token, function(err, user) {
if (err) console.log(err);

// if no token is found forward to error handling middleware
if (!user) return next();

// check if token has expired
if (new Date(user.pwdResetTokenExpires) < new Date()) {

// make old token invalid
delete user.pwdResetToken;
delete user.pwdResetTokenExpires;

// update user in db
adapter.update(user, function(err, user) {
if (err) console.log(err);

// tell user that link has expired
res.render(path.join(__dirname, 'views', 'link-expired'), {
title: 'Forgot password - Link expired'
});

});

return;
}

// create hash for new password
bcrypt.hash(password, 10, function(err, hash) {
if (err) console.log(err);

// update user's credentials
user.hash = hash;

// remove helper properties
delete user.pwdResetToken;
delete user.pwdResetTokenExpires;

// update user in db
adapter.update(user, function(err, user) {
if (err) console.log(err);

// render success message
res.render(path.join(__dirname, 'views', 'change-password-success'), {
title: 'Password changed'
});

});

});


});

});

};
39 changes: 39 additions & 0 deletions package.json
@@ -0,0 +1,39 @@
{
"name": "lockit-forgot-password",
"version": "0.0.1",
"description": "forgot password middleware for lockit",
"main": "index.js",
"scripts": {
"test": "grunt"
},
"author": {
"name": "Mirco Zeiss",
"email": "mirco.zeiss@gmail.com",
"twitter": "zeMirco"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/zeMirco/lockit-forgot-account"
},
"keywords": [
"lockit",
"forgot",
"password"
],
"dependencies": {
"node-uuid": "~1.4.1",
"bcrypt": "~0.7.7"
},
"devDependencies": {
"supertest": "~0.7.1",
"should": "~1.2.2",
"grunt": "~0.4.1",
"grunt-contrib-jshint": "~0.6.4",
"grunt-mocha-test": "~0.7.0",
"lockit-couchdb-adapter": "0.0.2",
"nano": "~4.1.1",
"express": "3.3.5",
"jade": "*"
}
}
73 changes: 73 additions & 0 deletions test/app.js
@@ -0,0 +1,73 @@

/**
* Module dependencies.
*/

var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');
var fs = require('fs');

// require delete account middleware
//var config = require('./config.js');
var forgotPassword = require('../index.js');

function start(config) {

config = config || require('./config.js');

var app = express();

// set basedir so views can properly extend layout.jade
app.locals.basedir = __dirname + '/views'; // comment out and error returns

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser('your secret here'));
app.use(express.cookieSession());

// set a dummy session for testing purpose
app.use(function(req, res, next) {
req.session.username = 'john';
next();
});

// use forgot password middleware with testing options
forgotPassword(app, config);

app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/users', user.list);

http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});

return app;

}

// export app for testing
if(require.main === module){
// called directly
start();
} else {
// required as a module -> from test file
module.exports = function(config) {
return start(config);
};
}

0 comments on commit f370007

Please sign in to comment.