Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
808 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
test/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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' | ||
}); | ||
|
||
}); | ||
|
||
}); | ||
|
||
|
||
}); | ||
|
||
}); | ||
|
||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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": "*" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}; | ||
} |
Oops, something went wrong.