diff --git a/.gitignore b/.gitignore index 58df632..911b342 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ docs/test.html npm-debug.log node_modules/ +*.swp diff --git a/files/seed_users.js b/files/seed_users.js new file mode 100644 index 0000000..986cfbf --- /dev/null +++ b/files/seed_users.js @@ -0,0 +1,22 @@ +var mongoose = require('mongoose'), + config = require('config'), + User = require('../lib/models/user'); + +mongoose.connect(process.env.DATABASE || config.app.database); +var db = mongoose.connection; +db.on('error', function(){ + app.log.error('Could not connect to Mongo :/') +}); +db.once('open', function(){ + console.log('Mongo connected!'); + + var user = new User({ username: 'bob', email: 'bob@example.com', password: 'secret' }); + user.save(function(err) { + if(err) { + console.log(err); + } else { + console.log('user: ' + user.username + " saved."); + } + mongoose.disconnect(); + }); +}); diff --git a/lib/app.js b/lib/app.js index 78969a8..854bda8 100644 --- a/lib/app.js +++ b/lib/app.js @@ -1,6 +1,9 @@ // module dependencies var express = require('express') - , config = require('config'); + , path = require('path') + , config = require('config') + , passport = require('passport') + , flash = require('connect-flash'); var app = express(); @@ -12,10 +15,16 @@ app.use(express.favicon(__dirname + '/../public/favicon.ico')); app.use(express.compress()); app.use(express.bodyParser()); app.use(express.methodOverride()); +app.use(express.cookieParser()); +app.use(express.session({ secret: 'keyboard cat' })); +app.use(flash()); +app.use(passport.initialize()); +app.use(passport.session()); app.use(app.router); app.use(express.static(__dirname + '/../public')); app.log = require('./utils/logger')(); + // development only var dev = 'production' != app.get('env');// good enough if (dev) { @@ -26,6 +35,8 @@ if (dev) { require('./routes/index')(app); require('./routes/search')(app); require('./routes/info')(app); +require('./routes/login')(app); +require('./routes/signup')(app); var mongoose = require('mongoose'); mongoose.connect(process.env.DATABASE || config.app.database); diff --git a/lib/authn/index.js b/lib/authn/index.js new file mode 100644 index 0000000..8bb7f73 --- /dev/null +++ b/lib/authn/index.js @@ -0,0 +1,34 @@ +var User = require('../models/user'), + passport = require('passport'), + LocalStrategy = require('passport-local').Strategy, + flash = require('connect-flash'); + +passport.use(new LocalStrategy(function(username, password, done) { + User.findOne({ username: username }, function(err, user) { + if (err) { + console.log('there was an error: ', err); + return done(err); + } + if (!user) { + return done(null, false, { message: 'Unknown user ' + username }); + } + user.comparePassword(password, function(err, isMatch) { + if (err) return done(err); + if(isMatch) { + return done(null, user); + } else { + return done(null, false, { message: 'Invalid password' }); + } + }); + }); +})); + +passport.serializeUser(function(user, done) { + done(null, user.id); +}); + +passport.deserializeUser(function(id, done) { + User.findById(id, function (err, user) { + done(err, user); + }); +}); diff --git a/lib/models/user.js b/lib/models/user.js new file mode 100644 index 0000000..adb35c3 --- /dev/null +++ b/lib/models/user.js @@ -0,0 +1,37 @@ +var mongoose = require('mongoose'), + bcrypt = require('bcrypt'), + SALT_WORK_FACTOR = 10; + +// User Schema +var userSchema = mongoose.Schema({ + username: { type: String, required: true, unique: true }, + email: { type: String, required: true, unique: true }, + password: { type: String, required: true}, +}); + +// Bcrypt middleware +userSchema.pre('save', function(next) { + var user = this; + + if(!user.isModified('password')) return next(); + + bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) { + if(err) return next(err); + + bcrypt.hash(user.password, salt, function(err, hash) { + if(err) return next(err); + user.password = hash; + next(); + }); + }); +}); + +// Password verification +userSchema.methods.comparePassword = function(candidatePassword, cb) { + bcrypt.compare(candidatePassword, this.password, function(err, isMatch) { + if(err) return cb(err); + cb(null, isMatch); + }); +}; + +module.exports = mongoose.model('User', userSchema); diff --git a/lib/routes/login.js b/lib/routes/login.js new file mode 100644 index 0000000..67da235 --- /dev/null +++ b/lib/routes/login.js @@ -0,0 +1,29 @@ +var passport = require('passport'), + pass = require('../authn'); + +module.exports = function(app) { + app.get('/login', function(req, res) { + res.render('login', { user: req.user, message: req.flash('error')}); + }); + + app.get('/logout', function(req, res){ + req.logout(); + res.redirect('/'); + }); + + app.post('/login', passport.authenticate('local', + { + successRedirect: '/profile', + failureRedirect: '/login', + failureFlash: true + })); + + app.get('/profile', ensureAuthenticated, function(req, res){ + res.render('profile', { username: req.user.username}); + }); + + function ensureAuthenticated(req, res, next) { + if (req.isAuthenticated()) { return next(); } + res.redirect('/login') + } +} diff --git a/lib/routes/signup.js b/lib/routes/signup.js new file mode 100644 index 0000000..c6abe0c --- /dev/null +++ b/lib/routes/signup.js @@ -0,0 +1,24 @@ +var User = require('../models/user'); + +module.exports = function(app) { + + app.get('/signup', function(req, res) { + res.render('signup'); + }); + + app.post('/signup', function(req, res){ + var user = new User({ username: req.body.username, email: req.body.email, + password: req.body.password }); + user.save(function(err) { + if(err) { + console.log(err); + //TODO (Jose) make this error more user friendly + res.render('signup', { user: req.user, message: err }); + } else { + console.log('user: ' + user.username + " saved."); + res.render('profile', { username: user.username }); + } + }); + }); + +} diff --git a/lib/views/login.jade b/lib/views/login.jade new file mode 100644 index 0000000..2b0c505 --- /dev/null +++ b/lib/views/login.jade @@ -0,0 +1,13 @@ +div + if message + div= message + form(action='/login',method='post') + div + fieldset + label(for='username') Username: + input(name='username',type='text',value='',placeholder='username') + label(for='password') Password: + input(name='password',type='password',value='',placeholder='password') + div + input(type='submit',value='Login') + diff --git a/lib/views/profile.jade b/lib/views/profile.jade new file mode 100644 index 0000000..fdd973e --- /dev/null +++ b/lib/views/profile.jade @@ -0,0 +1,7 @@ +a(href='/') home +&& +a(href='/logout') logout +div + p + | This is the profile for: + span= username diff --git a/lib/views/signup.jade b/lib/views/signup.jade new file mode 100644 index 0000000..e85c550 --- /dev/null +++ b/lib/views/signup.jade @@ -0,0 +1,10 @@ +if message + div= message +form(id="signup", method="POST", action="/signup") + label(for='username') Username + input#email(type='text', name='username') + label(for='email') Email + input#email(type='text', name='email') + label(for='password') Password + input#password(type='password', name='password') + input(type="submit", value="SignUp") diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0a3b412..63666cb 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -4,84 +4,86 @@ "dependencies": { "express": { "version": "3.2.4", - "from": "express@3.2.4", + "from": "https://registry.npmjs.org/express/-/express-3.2.4.tgz", "resolved": "https://registry.npmjs.org/express/-/express-3.2.4.tgz", "dependencies": { "connect": { "version": "2.7.9", - "from": "connect@2.7.9", + "from": "https://registry.npmjs.org/connect/-/connect-2.7.9.tgz", "resolved": "https://registry.npmjs.org/connect/-/connect-2.7.9.tgz", "dependencies": { "qs": { "version": "0.6.4", - "from": "qs@0.6.4", + "from": "https://registry.npmjs.org/qs/-/qs-0.6.4.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.4.tgz" }, "formidable": { "version": "1.0.13", - "from": "formidable@1.0.13" + "from": "formidable@1.0.13", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.13.tgz" }, "bytes": { "version": "0.2.0", - "from": "bytes@0.2.0", + "from": "https://registry.npmjs.org/bytes/-/bytes-0.2.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-0.2.0.tgz" }, "pause": { "version": "0.0.1", - "from": "pause@0.0.1", + "from": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz" } } }, "commander": { "version": "0.6.1", - "from": "commander@0.6.1", + "from": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz" }, "range-parser": { "version": "0.0.4", - "from": "range-parser@0.0.4", + "from": "https://registry.npmjs.org/range-parser/-/range-parser-0.0.4.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-0.0.4.tgz" }, "mkdirp": { "version": "0.3.4", - "from": "mkdirp@0.3.4", + "from": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.4.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.4.tgz" }, "cookie": { "version": "0.0.5", - "from": "cookie@0.0.5", + "from": "https://registry.npmjs.org/cookie/-/cookie-0.0.5.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.0.5.tgz" }, "buffer-crc32": { "version": "0.2.1", - "from": "buffer-crc32@0.2.1", + "from": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.1.tgz", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.1.tgz" }, "fresh": { "version": "0.1.0", - "from": "fresh@0.1.0", + "from": "https://registry.npmjs.org/fresh/-/fresh-0.1.0.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.1.0.tgz" }, "methods": { "version": "0.0.1", - "from": "methods@0.0.1", + "from": "https://registry.npmjs.org/methods/-/methods-0.0.1.tgz", "resolved": "https://registry.npmjs.org/methods/-/methods-0.0.1.tgz" }, "send": { "version": "0.1.0", - "from": "send@0.1.0", + "from": "https://registry.npmjs.org/send/-/send-0.1.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.1.0.tgz", "dependencies": { "mime": { "version": "1.2.6", - "from": "mime@1.2.6" + "from": "mime@1.2.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.6.tgz" } } }, "cookie-signature": { "version": "1.0.1", - "from": "cookie-signature@1.0.1", + "from": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.1.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.1.tgz" }, "debug": { @@ -94,54 +96,55 @@ "jade": { "version": "0.31.1", "from": "jade@0.31.1", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.31.1.tgz", "dependencies": { "commander": { "version": "1.1.1", - "from": "commander@1.1.1", + "from": "https://registry.npmjs.org/commander/-/commander-1.1.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-1.1.1.tgz", "dependencies": { "keypress": { "version": "0.1.0", - "from": "keypress@0.1.0", + "from": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz", "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz" } } }, "mkdirp": { "version": "0.3.5", - "from": "mkdirp@0.3.5", + "from": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" }, "transformers": { "version": "2.0.1", - "from": "transformers@2.0.1", + "from": "https://registry.npmjs.org/transformers/-/transformers-2.0.1.tgz", "resolved": "https://registry.npmjs.org/transformers/-/transformers-2.0.1.tgz", "dependencies": { "promise": { "version": "2.0.0", - "from": "promise@2.0.0", + "from": "https://registry.npmjs.org/promise/-/promise-2.0.0.tgz", "resolved": "https://registry.npmjs.org/promise/-/promise-2.0.0.tgz", "dependencies": { "is-promise": { "version": "1.0.0", - "from": "is-promise@1.0.0", + "from": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.0.tgz" } } }, "css": { "version": "1.0.8", - "from": "css@1.0.8", + "from": "https://registry.npmjs.org/css/-/css-1.0.8.tgz", "resolved": "https://registry.npmjs.org/css/-/css-1.0.8.tgz", "dependencies": { "css-parse": { "version": "1.0.4", - "from": "css-parse@1.0.4", + "from": "https://registry.npmjs.org/css-parse/-/css-parse-1.0.4.tgz", "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.0.4.tgz" }, "css-stringify": { "version": "1.0.5", - "from": "css-stringify@1.0.5", + "from": "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz", "resolved": "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz" } } @@ -149,26 +152,28 @@ "uglify-js": { "version": "2.2.5", "from": "uglify-js@2.2.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", "dependencies": { "source-map": { "version": "0.1.22", - "from": "source-map@0.1.22", + "from": "https://registry.npmjs.org/source-map/-/source-map-0.1.22.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.22.tgz", "dependencies": { "amdefine": { "version": "0.0.5", - "from": "amdefine@0.0.5" + "from": "amdefine@0.0.5", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-0.0.5.tgz" } } }, "optimist": { "version": "0.3.7", - "from": "optimist@0.3.7", + "from": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", "dependencies": { "wordwrap": { "version": "0.0.2", - "from": "wordwrap@0.0.2", + "from": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" } } @@ -179,30 +184,33 @@ }, "character-parser": { "version": "1.0.2", - "from": "character-parser@1.0.2" + "from": "character-parser@1.0.2", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-1.0.2.tgz" }, "monocle": { "version": "0.1.48", - "from": "monocle@0.1.48", + "from": "https://registry.npmjs.org/monocle/-/monocle-0.1.48.tgz", "resolved": "https://registry.npmjs.org/monocle/-/monocle-0.1.48.tgz", "dependencies": { "readdirp": { "version": "0.2.4", "from": "readdirp@0.2.4", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-0.2.4.tgz", "dependencies": { "minimatch": { "version": "0.2.12", - "from": "minimatch@0.2.12", + "from": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.12.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.12.tgz", "dependencies": { "lru-cache": { "version": "2.3.0", - "from": "lru-cache@2.3.0", + "from": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.3.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.3.0.tgz" }, "sigmund": { "version": "1.0.0", - "from": "sigmund@1.0.0" + "from": "sigmund@1.0.0", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.0.tgz" } } } @@ -212,7 +220,7 @@ }, "with": { "version": "1.0.4", - "from": "with@1.0.4", + "from": "https://registry.npmjs.org/with/-/with-1.0.4.tgz", "resolved": "https://registry.npmjs.org/with/-/with-1.0.4.tgz", "dependencies": { "lexical-scope": { @@ -222,12 +230,13 @@ "dependencies": { "astw": { "version": "0.0.0", - "from": "astw@0.0.0", + "from": "https://registry.npmjs.org/astw/-/astw-0.0.0.tgz", "resolved": "https://registry.npmjs.org/astw/-/astw-0.0.0.tgz", "dependencies": { "esprima": { "version": "1.0.2", - "from": "esprima@1.0.2" + "from": "esprima@1.0.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.2.tgz" } } } @@ -459,11 +468,66 @@ }, "config": { "version": "0.4.27", - "from": "config@" + "from": "config@0.4.27", + "resolved": "https://registry.npmjs.org/config/-/config-0.4.27.tgz" }, "yaml": { "version": "0.2.3", - "from": "yaml@" + "from": "yaml@0.2.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-0.2.3.tgz" + }, + "passport-local": { + "version": "0.1.6", + "from": "passport-local@", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-0.1.6.tgz", + "dependencies": { + "pkginfo": { + "version": "0.2.3", + "from": "pkginfo@0.2.x" + }, + "passport": { + "version": "0.1.17", + "from": "passport@~0.1.1", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.1.17.tgz", + "dependencies": { + "pause": { + "version": "0.0.1", + "from": "pause@0.0.1" + } + } + } + } + }, + "passport": { + "version": "0.1.17", + "from": "passport@", + "dependencies": { + "pkginfo": { + "version": "0.2.3", + "from": "pkginfo@0.2.x" + }, + "pause": { + "version": "0.0.1", + "from": "pause@0.0.1" + } + } + }, + "bcrypt": { + "version": "0.7.6", + "from": "bcrypt@", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-0.7.6.tgz", + "dependencies": { + "bindings": { + "version": "1.0.0", + "from": "bindings@1.0.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.0.0.tgz" + } + } + }, + "connect-flash": { + "version": "0.1.1", + "from": "connect-flash@", + "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz" } } } diff --git a/package.json b/package.json index 2406658..edeba78 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,11 @@ "geocoder": "0.1.x", "winston": "~0.7.1", "config": "~0.4.27", - "yaml": "~0.2.3" + "yaml": "~0.2.3", + "passport-local": "~0.1.6", + "passport": "~0.1.17", + "bcrypt": "~0.7.6", + "connect-flash": "~0.1.1" }, "devDependencies": { "mocha": "~1.9.0",