diff --git a/server.js b/server.js index 61ba4a14dc..31cd296181 100755 --- a/server.js +++ b/server.js @@ -1,25 +1,38 @@ var express = require('express'), - mongoose = require('mongoose'), + http = require('http'), + path = require('path'), fs = require('fs'), + mongoose = require('mongoose'), + passport = require('passport'), config = require('./conf'); +var User = require('./server/models/User'); var db = mongoose.connect(config.db); var app = express(); - -/** - * Express Settings - */ app.set('port', process.env.PORT || 3000); +app.set('views', __dirname + '/client/views'); +app.set('view engine', 'jade'); app.use(express.favicon()); app.use(express.logger('dev')); -app.use(express.json()); -app.use(express.urlencoded()); +app.use(express.cookieParser()); +app.use(express.bodyParser()); app.use(express.methodOverride()); +app.use(express.cookieSession({ secret: process.env.COOKIE_SECRET || 'secret' })); +app.use(passport.initialize()); +app.use(passport.session()); app.use(app.router); app.use(express.static(config.root + '/public')); +passport.use(User.localStrategy); +passport.use(User.twitterStrategy()); +passport.use(User.facebookStrategy()); +passport.use(User.googleStrategy()); +passport.use(User.linkedInStrategy()); +passport.serializeUser(User.serializeUser); +passport.deserializeUser(User.deserializeUser); + /** * API Controllers @@ -28,13 +41,6 @@ var articles = require('./controllers/articles'); var users = require('./controllers/users'); -/** - * API Models - */ -var Article = require('./models/article'); -var User = require('./models/user'); - - /** * API Routes */ diff --git a/server/models/User.js b/server/models/User.js index 6be42e463b..936133c291 100755 --- a/server/models/User.js +++ b/server/models/User.js @@ -1,175 +1,199 @@ -var User - , _ = require('underscore') - , passport = require('passport') - , LocalStrategy = require('passport-local').Strategy - , TwitterStrategy = require('passport-twitter').Strategy - , FacebookStrategy = require('passport-facebook').Strategy - , GoogleStrategy = require('passport-google').Strategy - , LinkedInStrategy = require('passport-linkedin').Strategy - , check = require('validator').check - , userRoles = require('../../client/js/routingConfig').userRoles; +var _ = require('underscore'), + mongoose = require('mongoose'), + crypto = require('crypto'), + passport = require('passport'), + LocalStrategy = require('passport-local').Strategy, + TwitterStrategy = require('passport-twitter').Strategy, + FacebookStrategy = require('passport-facebook').Strategy, + GoogleStrategy = require('passport-google').Strategy, + LinkedInStrategy = require('passport-linkedin').Strategy, + check = require('validator').check, + userRoles = require('../../client/js/routingConfig').userRoles; + +var UserSchema = new mongoose.Schema({ + name: String, + email: String, + username: { + type: String, + unique: true + }, + provider: String, + hashed_password: String, + salt: String, + facebook: {}, + twitter: {}, + google: {} +}); + +var User = mongoose.model('User', UserSchema); var users = [ - { - id: 1, - username: "user", - password: "123", - role: userRoles.user - }, - { - id: 2, - username: "admin", - password: "123", - role: userRoles.admin - } + { + id: 1, + username: "user", + password: "123", + role: userRoles.user + }, + { + id: 2, + username: "admin", + password: "123", + role: userRoles.admin + } ]; module.exports = { - addUser: function(username, password, role, callback) { - if(this.findByUsername(username) !== undefined) return callback("UserAlreadyExists"); - - // Clean up when 500 users reached - if(users.length > 500) { - users = users.slice(0, 2); - } - - var user = { - id: _.max(users, function(user) { return user.id; }).id + 1, - username: username, - password: password, - role: role - }; - users.push(user); - callback(null, user); - }, - - findOrCreateOauthUser: function(provider, providerId) { - var user = module.exports.findByProviderId(provider, providerId); - if(!user) { - user = { - id: _.max(users, function(user) { return user.id; }).id + 1, - username: provider + '_user', // Should keep Oauth users anonymous on demo site - role: userRoles.user, - provider: provider - }; - user[provider] = providerId; - users.push(user); - } - - return user; - }, - - findAll: function() { - return _.map(users, function(user) { return _.clone(user); }); - }, - - findById: function(id) { - return _.clone(_.find(users, function(user) { return user.id === id })); - }, - - findByUsername: function(username) { - return _.clone(_.find(users, function(user) { return user.username === username; })); - }, - - findByProviderId: function(provider, id) { - return _.find(users, function(user) { return user[provider] === id; }); - }, - - validate: function(user) { - check(user.username, 'Username must be 1-20 characters long').len(1, 20); - check(user.password, 'Password must be 5-60 characters long').len(5, 60); - check(user.username, 'Invalid username').not(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/); - - // TODO: Seems node-validator's isIn function doesn't handle Number arrays very well... - // Till this is rectified Number arrays must be converted to string arrays - // https://github.com/chriso/node-validator/issues/185 - var stringArr = _.map(_.values(userRoles), function(val) { return val.toString() }); - check(user.role, 'Invalid user role given').isIn(stringArr); - }, - - localStrategy: new LocalStrategy( - function(username, password, done) { - - var user = module.exports.findByUsername(username); - - if(!user) { - done(null, false, { message: 'Incorrect username.' }); - } - else if(user.password != password) { - done(null, false, { message: 'Incorrect username.' }); - } - else { - return done(null, user); - } - - } - ), - - twitterStrategy: function() { - if(!process.env.TWITTER_CONSUMER_KEY) throw new Error('A Twitter Consumer Key is required if you want to enable login via Twitter.'); - if(!process.env.TWITTER_CONSUMER_SECRET) throw new Error('A Twitter Consumer Secret is required if you want to enable login via Twitter.'); - - return new TwitterStrategy({ - consumerKey: process.env.TWITTER_CONSUMER_KEY, - consumerSecret: process.env.TWITTER_CONSUMER_SECRET, - callbackURL: process.env.TWITTER_CALLBACK_URL || 'http://localhost:8000/auth/twitter/callback' - }, - function(token, tokenSecret, profile, done) { - var user = module.exports.findOrCreateOauthUser(profile.provider, profile.id); - done(null, user); - }); - }, - - facebookStrategy: function() { - if(!process.env.FACEBOOK_APP_ID) throw new Error('A Facebook App ID is required if you want to enable login via Facebook.'); - if(!process.env.FACEBOOK_APP_SECRET) throw new Error('A Facebook App Secret is required if you want to enable login via Facebook.'); - - return new FacebookStrategy({ - clientID: process.env.FACEBOOK_APP_ID, - clientSecret: process.env.FACEBOOK_APP_SECRET, - callbackURL: process.env.FACEBOOK_CALLBACK_URL || "http://localhost:8000/auth/facebook/callback" - }, - function(accessToken, refreshToken, profile, done) { - var user = module.exports.findOrCreateOauthUser(profile.provider, profile.id); - done(null, user); - }); - }, - - googleStrategy: function() { - - return new GoogleStrategy({ - returnURL: process.env.GOOGLE_RETURN_URL || "http://localhost:8000/auth/google/return", - realm: process.env.GOOGLE_REALM || "http://localhost:8000/" - }, - function(identifier, profile, done) { - var user = module.exports.findOrCreateOauthUser('google', identifier); - done(null, user); - }); - }, - - linkedInStrategy: function() { - if(!process.env.LINKED_IN_KEY) throw new Error('A LinkedIn App Key is required if you want to enable login via LinkedIn.'); - if(!process.env.LINKED_IN_SECRET) throw new Error('A LinkedIn App Secret is required if you want to enable login via LinkedIn.'); - - return new LinkedInStrategy({ - consumerKey: process.env.LINKED_IN_KEY, - consumerSecret: process.env.LINKED_IN_SECRET, - callbackURL: process.env.LINKED_IN_CALLBACK_URL || "http://localhost:8000/auth/linkedin/callback" - }, - function(token, tokenSecret, profile, done) { - var user = module.exports.findOrCreateOauthUser('linkedin', profile.id); - done(null,user); - } - ); - }, - serializeUser: function(user, done) { - done(null, user.id); - }, - - deserializeUser: function(id, done) { - var user = module.exports.findById(id); - - if(user) { done(null, user); } - else { done(null, false); } + addUser: function(username, password, role, callback) { + if (this.findByUsername(username) !== undefined) { + return callback("UserAlreadyExists"); } + + // Clean up when 500 users reached + if(users.length > 500) { + users = users.slice(0, 2); + } + + var user = new User({ + id: _.max(users, function(user) { return user.id; }).id + 1, + username: username, + password: password, + role: role + }); + users.push(user); + callback(null, user); + }, + + findOrCreateOauthUser: function(provider, providerId) { + var user = module.exports.findByProviderId(provider, providerId); + if(!user) { + user = { + id: _.max(users, function(user) { return user.id; }).id + 1, + username: provider + '_user', // Should keep Oauth users anonymous on demo site + role: userRoles.user, + provider: provider + }; + user[provider] = providerId; + users.push(user); + } + + return user; + }, + + findAll: function() { + return _.map(users, function(user) { return _.clone(user); }); + }, + + findById: function(id) { + return _.clone(_.find(users, function(user) { return user.id === id })); + }, + + findByUsername: function(username) { + User.findOne({ username: username }, function(err, user) { + if (user) { + return user; + } + }); + }, + + findByProviderId: function(provider, id) { + return _.find(users, function(user) { return user[provider] === id; }); + }, + + validate: function(user) { + check(user.username, 'Username must be 1-20 characters long').len(1, 20); + check(user.password, 'Password must be 5-60 characters long').len(5, 60); + check(user.username, 'Invalid username').not(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/); + + // TODO: Seems node-validator's isIn function doesn't handle Number arrays very well... + // Till this is rectified Number arrays must be converted to string arrays + // https://github.com/chriso/node-validator/issues/185 + var stringArr = _.map(_.values(userRoles), function(val) { return val.toString() }); + check(user.role, 'Invalid user role given').isIn(stringArr); + }, + + localStrategy: new LocalStrategy( + function(username, password, done) { + + var user = module.exports.findByUsername(username); + + if(!user) { + done(null, false, { message: 'Incorrect username.' }); + } + else if(user.password != password) { + done(null, false, { message: 'Incorrect username.' }); + } + else { + return done(null, user); + } + + } + ), + + twitterStrategy: function() { + if(!process.env.TWITTER_CONSUMER_KEY) throw new Error('A Twitter Consumer Key is required if you want to enable login via Twitter.'); + if(!process.env.TWITTER_CONSUMER_SECRET) throw new Error('A Twitter Consumer Secret is required if you want to enable login via Twitter.'); + + return new TwitterStrategy({ + consumerKey: process.env.TWITTER_CONSUMER_KEY, + consumerSecret: process.env.TWITTER_CONSUMER_SECRET, + callbackURL: process.env.TWITTER_CALLBACK_URL || 'http://localhost:8000/auth/twitter/callback' + }, + function(token, tokenSecret, profile, done) { + var user = module.exports.findOrCreateOauthUser(profile.provider, profile.id); + done(null, user); + }); + }, + + facebookStrategy: function() { + if(!process.env.FACEBOOK_APP_ID) throw new Error('A Facebook App ID is required if you want to enable login via Facebook.'); + if(!process.env.FACEBOOK_APP_SECRET) throw new Error('A Facebook App Secret is required if you want to enable login via Facebook.'); + + return new FacebookStrategy({ + clientID: process.env.FACEBOOK_APP_ID, + clientSecret: process.env.FACEBOOK_APP_SECRET, + callbackURL: process.env.FACEBOOK_CALLBACK_URL || "http://localhost:8000/auth/facebook/callback" + }, + function(accessToken, refreshToken, profile, done) { + var user = module.exports.findOrCreateOauthUser(profile.provider, profile.id); + done(null, user); + }); + }, + + googleStrategy: function() { + + return new GoogleStrategy({ + returnURL: process.env.GOOGLE_RETURN_URL || "http://localhost:8000/auth/google/return", + realm: process.env.GOOGLE_REALM || "http://localhost:8000/" + }, + function(identifier, profile, done) { + var user = module.exports.findOrCreateOauthUser('google', identifier); + done(null, user); + }); + }, + + linkedInStrategy: function() { + if(!process.env.LINKED_IN_KEY) throw new Error('A LinkedIn App Key is required if you want to enable login via LinkedIn.'); + if(!process.env.LINKED_IN_SECRET) throw new Error('A LinkedIn App Secret is required if you want to enable login via LinkedIn.'); + + return new LinkedInStrategy({ + consumerKey: process.env.LINKED_IN_KEY, + consumerSecret: process.env.LINKED_IN_SECRET, + callbackURL: process.env.LINKED_IN_CALLBACK_URL || "http://localhost:8000/auth/linkedin/callback" + }, + function(token, tokenSecret, profile, done) { + var user = module.exports.findOrCreateOauthUser('linkedin', profile.id); + done(null,user); + } + ); + }, + serializeUser: function(user, done) { + done(null, user.id); + }, + + deserializeUser: function(id, done) { + var user = module.exports.findById(id); + + if(user) { done(null, user); } + else { done(null, false); } + } }; \ No newline at end of file