diff options
Diffstat (limited to 'server/lib')
| -rw-r--r-- | server/lib/auth.js | 201 | ||||
| -rw-r--r-- | server/lib/auth/index.js | 331 | ||||
| -rw-r--r-- | server/lib/auth/mail.js | 67 | ||||
| -rw-r--r-- | server/lib/auth/views.js | 33 | ||||
| -rw-r--r-- | server/lib/middleware.js | 1 | ||||
| -rw-r--r-- | server/lib/schemas/User.js | 33 | ||||
| -rw-r--r-- | server/lib/upload.js | 4 | ||||
| -rw-r--r-- | server/lib/util.js | 6 |
8 files changed, 466 insertions, 210 deletions
diff --git a/server/lib/auth.js b/server/lib/auth.js deleted file mode 100644 index 018c9ad..0000000 --- a/server/lib/auth.js +++ /dev/null @@ -1,201 +0,0 @@ -/* jshint node: true */ - -var passport = require('passport'), - FacebookStrategy = require('passport-facebook').Strategy, - TwitterStrategy = require('passport-twitter').Strategy, - LocalStrategy = require('passport-local').Strategy, - passportSocketIo = require("passport.socketio"), - cookieParser = require('express').cookieParser, - crypto = require('crypto'), - _ = require('lodash'), - util = require('./util'), - config = require('../../config.json'), - User = require('./schemas/User'); - -var auth = { - - init: function () { - passport.serializeUser(auth.serializeUser); - passport.deserializeUser(auth.deserializeUser); - - passport.use(new LocalStrategy(auth.verifyLocalUser)) - - passport.use(new TwitterStrategy({ - consumerKey: process.env.VVALLS_TWITTER_KEY || '0L5blfBIapqhpons8bCXdIoGM', - consumerSecret: process.env.VVALLS_TWITTER_SECRET || '5EKW7m7inoODqYSKbp7cadBKFp1FghBl4MBDoXNcUjKtodZfuP', - callbackURL: 'http://' + config.host + '/auth/twitter/callback' - }, auth.insertTwitterUser)); - - passport.use(new FacebookStrategy({ - clientID: process.env.VVALLS_FACEBOOK_KEY || '719828821410310', - clientSecret: process.env.VVALLS_FACEBOOK_SECRET || 'f9aba78e08f37f621eadb88b1409d48c', - callbackURL: 'http://' + config.host + '/auth/facebook/callback', - enableProof: false, - }, auth.insertFacebookUser)); - }, - - initSockets: function (io, express, SessionStore) { - io.set('authorization', passportSocketIo.authorize({ - cookieParser: cookieParser, - passport: passport, - key: 'vvalls.sid', // the name of the cookie where express/connect stores its session_id - secret: 'flibbertigibbet', // the session_secret to parse the cookie - store: SessionStore, // we NEED to use a sessionstore. no memorystore please - success: auth.socketSuccess, - fail: auth.socketFail, - })); - }, - - socketSuccess: function (data, accept) { - // console.error('successful connection to socket.io'); - accept(null, true); - }, - - socketFail: function (data, message, error, accept){ - if (error) { - throw new Error(message); - } - // console.log(data) - console.error('failed connection to socket.io:', message); - - // We use this callback to log all of our failed connections. - accept(null, false); - }, - - // technically these return the login middleware - login: function (strategy) { - return passport.authenticate(strategy); - }, - - loggedIn: function (strategy) { - return passport.authenticate(strategy, { - successReturnToOrRedirect: '/', - failureRedirect: '/login' - }); - }, - - loggedInLocal: function (req, res, next) { - passport.authenticate("local", function(err, user, info){ - if (err) { - return res.json({ error: err }); - } - if (! user) { - return info ? res.json(info) : res.redirect("/login"); - } - req.logIn(user, function(err) { - if (err) { return next(err); } - return res.json({ status: "OK" }) - }); - })(req, res, next); - }, - - logout: function (req, res) { - req.logout(); - res.redirect('/'); - }, - - serializeUser: function (user, done) { - done(null, user._id); - }, - - deserializeUser: function (id, done) { - User.findOne({ _id: id }, "_id displayName username photo isStaff", function (err, user) { - done(err, user); - }); - }, - - signup: function (req, res){ - var username = util.trim(req.body.username) - var password = req.body.password - var email = util.trim(req.body.email) - - var shasum = crypto.createHash('sha1') - shasum.update(password) - password = shasum.digest('hex'); - - User.findOne({ username: username }, "_id username", function (err, user) { - if (user) { - res.json({ error: { errors: { username: { message: "Username has been taken" } } } }) - return - } - var data = { - username: username, - displayName: username, - password: password, - email: email, - created_ip: util.ip2num(req.connection.remoteAddress), - last_ip: util.ip2num(req.connection.remoteAddress), - created_at: new Date () - } - new User(data).save(function(err, user){ - if (err || ! data) { return res.json({ error: err }) } - - req.login(user, function(){ - res.json({ status: "OK", payload: user }) - }) - }) - }) - }, - - verifyLocalUser: function (username, password, done) { - User.findOne({ username: username }, function(err, user){ - if (err) { return done(err); } - if (!user) { - return done(null, false, { error: { errors: { username: { message: 'Incorrect username.' } }}}) - } - if (! user.validPassword(password)) { - return done(null, false, { error: { errors: { password: { message: 'Incorrect password.' } }}}) - } - return done(null, user); - }); - }, - - insertTwitterUser: function (accessToken, refreshToken, profile, done) { - process.nextTick(function () { - var userData = { - twitter_id: profile.id, - username: profile.username, - displayName: profile.displayName, - photo: profile.photos[0].value, - twitterName: profile.username, - }; - - User.findOne({twitter_id: profile.id}, function(err, data){ - if (! err && data) { - return done(err, data); - } - new User(userData).save(function(err, data){ - if (err) { console.error(err) } - return done(err, data) - }) - }); - - }); - }, - - insertFacebookUser: function (accessToken, refreshToken, profile, done) { - process.nextTick(function () { - var userData = { - facebook_id: profile.id, - username: profile.username || profile.displayName.toLowerCase().replace(/ /g,'-'), - displayName: profile.displayName, - photo: "http://graph.facebook.com/" + profile.id + "/picture?type=large", - facebookUrl: profile.username ? "https://facebook.com/" + profile.username : "" - }; - - User.findOne({facebook_id: profile.id}, function(err, data){ - if (! err && data) { - return done(err, data); - } - new User(userData).save(function(err, data){ - if (err) { console.error(err) } - return done(err, data) - }) - }); - - }); - }, - -} - -module.exports = auth diff --git a/server/lib/auth/index.js b/server/lib/auth/index.js new file mode 100644 index 0000000..99af9b5 --- /dev/null +++ b/server/lib/auth/index.js @@ -0,0 +1,331 @@ +/* jshint node: true */ + +var passport = require('passport'), + FacebookStrategy = require('passport-facebook').Strategy, + TwitterStrategy = require('passport-twitter').Strategy, + LocalStrategy = require('passport-local').Strategy, + passportSocketIo = require("passport.socketio"), + cookieParser = require('express').cookieParser, + crypto = require('crypto'), + express = require('express'), + _ = require('lodash'), + mongoose = require('mongoose'), + util = require('../util'), + config = require('../../../config.json'), + User = require('../schemas/User'); + +var auth = { + views: require('./views'), + mail: require('./mail'), + + init: function () { + passport.serializeUser(auth.serializeUser); + passport.deserializeUser(auth.deserializeUser); + + passport.use(new LocalStrategy(auth.verifyLocalUser)) + + passport.use(new TwitterStrategy({ + consumerKey: process.env.VVALLS_TWITTER_KEY || 'brI5VqBak5yrhCcxU56lj5L3v', + consumerSecret: process.env.VVALLS_TWITTER_SECRET || 'ThzaEVWUgkmfzqOs3qcrdonGzgDBjDHTVzPkfY0wFJxjUH6JWZ', + callbackURL: 'http://' + config.host + '/auth/twitter/callback' + }, auth.insertTwitterUser)); + + /* + passport.use(new FacebookStrategy({ + clientID: process.env.VVALLS_FACEBOOK_KEY || '719828821410310', + clientSecret: process.env.VVALLS_FACEBOOK_SECRET || 'f9aba78e08f37f621eadb88b1409d48c', + callbackURL: 'http://' + config.host + '/auth/facebook/callback', + enableProof: false, + }, auth.insertFacebookUser)); + */ + auth.mail.init() + }, + + initSockets: function (io, SessionStore) { + io.set('authorization', passportSocketIo.authorize({ + cookieParser: express.cookieParser, + passport: passport, + key: 'vvalls.sid', // the name of the cookie where express/connect stores its session_id + secret: 'flibbertigibbet', // the session_secret to parse the cookie + store: SessionStore, // we NEED to use a sessionstore. no memorystore please + success: auth.socketSuccess, + fail: auth.socketFail, + })); + }, + + socketSuccess: function (data, accept) { + // console.error('successful connection to socket.io'); + accept(null, true); + }, + + socketFail: function (data, message, error, accept){ + if (error) { + throw new Error(message); + } + // console.log(data) + console.error('failed connection to socket.io:', message); + + // We use this callback to log all of our failed connections. + accept(null, false); + }, + + // technically these return the login middleware + login: function (strategy) { + return passport.authenticate(strategy); + }, + + loggedIn: function (strategy) { + return function (req, res, next) { + console.log("attempting to use", strategy) + + passport.authenticate(strategy, function(err, user, info){ + if (err) { + return next(err); + } + if (! user) { + req.session.userData = info + return res.redirect('/auth/usernameTaken'); + } + if (! user.created_ip) { + user.created_ip = util.ip2num( req.ip ) + } + user.last_ip = util.ip2num( req.ip ) + user.save(function(err, data){ if (err) console.err('error setting ip for user') }) + + req.logIn(user, function(err) { + if (err) { return next(err); } + var returnTo = req.session.returnTo + delete req.session.returnTo + return res.redirect( returnTo || "/profile" ); + }); + })(req, res, next); + } + }, + + loggedInLocal: function (req, res, next) { + passport.authenticate("local", function(err, user, info){ + if (err) { + return res.json({ error: err }); + } + if (! user) { + return info ? res.json(info) : res.redirect("/login"); + } + + user.last_ip = util.ip2num( req.ip ) + user.save(function(err, data){ if (err) console.err('error setting ip for user') }) + + req.logIn(user, function(err) { + if (err) { return next(err); } + var returnTo = req.session.returnTo + delete req.session.returnTo + return res.json({ status: "OK", returnTo: returnTo || "/profile" }) + }); + })(req, res, next); + }, + + logout: function (req, res) { + req.logout(); + res.redirect('/'); + }, + + serializeUser: function (user, done) { + done(null, user._id); + }, + + deserializeUser: function (id, done) { + try { + var _id = mongoose.Types.ObjectId(id) + User.findOne({ _id: _id }, "_id displayName username photo isStaff", function (err, user) { + done(err, user); + }); + } + catch (e) { + User.findOne({ twitter_id: id }, "_id displayName username photo isStaff", function (err, user) { + done(err, user); + }); + } + }, + + signup: function (req, res){ + var username = util.trim(req.body.username) + var password = req.body.password + var email = util.trim(req.body.email) + + var shasum = crypto.createHash('sha1') + shasum.update(password) + password = shasum.digest('hex'); + + User.findByUsername(username, function (err, user) { + if (user) { + res.json({ error: { errors: { username: { message: "Username has been taken" } } } }) + return + } + + User.findByEmail(email, function (err, user) { + if (user) { + res.json({ error: { errors: { username: { message: "Email has already been used" } } } }) + return + } + var data = { + username: username, + displayName: username, + password: password, + email: email, + created_ip: util.ip2num( req.ip ), + last_ip: util.ip2num( req.ip ), + created_at: new Date () + } + new User(data).save(function(err, user){ + if (err || ! data) { return res.json({ error: err }) } + req.login(user, function(){ + auth.mail.welcome(user, function(){ + res.json({ status: "OK", payload: user }) + }) + }) + }) + }) + }) + }, + + verifyLocalUser: function (username, password, done) { + User.findByUsername(username, function(err, user){ + if (err) { return done(err); } + if (! user) { + return done(null, false, { error: { errors: { username: { message: 'No such username.' } }}}) + } + if (! user.validPassword(password)) { + return done(null, false, { error: { errors: { password: { message: 'Incorrect password.' } }}}) + } + return done(null, user); + }); + }, + + insertTwitterUser: function (accessToken, refreshToken, profile, done) { + process.nextTick(function () { + var userData = { + twitter_id: profile.id, + username: profile.username, + displayName: profile.displayName, + photo: profile.photos[0].value, + twitterName: profile.username, + }; + User.findOne({twitter_id: profile.id}, function(err, data){ + if (! err && data) { + return done(err, data); + } + User.findByUsername(profile.username, function(err, data){ + if (data) { + return done(null, false, userData) + } + new User(userData).save(function(err, data){ + if (err) { + console.error(err) + } + return done(err, data) + }) + }) + }); + + }); + }, + + insertFacebookUser: function (accessToken, refreshToken, profile, done) { + process.nextTick(function () { + var userData = { + facebook_id: profile.id, + username: profile.username || profile.displayName.toLowerCase().replace(/ /g,'-'), + displayName: profile.displayName, + photo: "http://graph.facebook.com/" + profile.id + "/picture?type=large", + facebookUrl: profile.username ? "https://facebook.com/" + profile.username : "" + }; + + User.findOne({twitter_id: profile.id}, function(err, data){ + if (! err && data) { + return done(err, data); + } + User.findByUsername(profile.username, function(err, data){ + if (data) { + return done(null, false, userData) + } + new User(userData).save(function(err, data){ + if (err) { + console.error(err) + } + return done(err, data) + }) + }) + }); + + }); + }, + + usernameFixed: function (req, res) { + + var userData = req.session.userData + if (! userData) { + return res.redirect("/") + } + if (req.isAuthenticated()) { + delete req.session.userData + return res.redirect("/") + } + + var username = util.sanitize(req.body.username) + + User.findByUsername(username, function(err, doc){ + if (err || doc) { + res.json({ error: { errors: { username: { message: "Username has been taken" } } } }) + return + } + userData.username = username + + new User(userData).save(function(err, user){ + req.logIn(user, function(err) { + if (err) { return res.json(err); } + var returnTo = req.session.returnTo + delete req.session.returnTo + return res.json({ status: "OK", returnTo: returnTo || "/profile" }) + }); + }) + }) + }, + + forgotPassword: function (req, res) { + User.findByEmail(req.body.email, function(err, user){ + if (err || ! user) { + res.json({ error: { errors: { email: { message: "That email address was not found." } } } }) + return + } + User.resetPasswordNonce(user, function(err){ + auth.mail.forgotPassword(user, function(){ + res.json({ success: 'OK' }) + }) + }) + }) + }, + + resetPassword: function (req, res) { + var password = req.body.password + + var shasum = crypto.createHash('sha1') + shasum.update(password) + password = shasum.digest('hex'); + + User.findOne({ passwordNonce: req.body.nonce }, function (err, user){ + if (err || ! user) { + res.json({ error: { errors: { email: { message: "That reset token has already been used." } } } }) + return + } + user.password = password + user.passwordNonce = "" + user.save(function(){ + req.login(user, function(){ + res.json({ status: "OK", payload: user }) + }) + }) + }) + }, + +} + +module.exports = auth diff --git a/server/lib/auth/mail.js b/server/lib/auth/mail.js new file mode 100644 index 0000000..a4abccd --- /dev/null +++ b/server/lib/auth/mail.js @@ -0,0 +1,67 @@ + +var email = require("emailjs"), + ejs = require("ejs"), + fs = require("fs"), + util = require("../util"); + +var mail = { + + from: 'Vvalls <info@vvalls.com>', + templates: {}, + + init: function(){ + var names = ["welcome","password"].forEach(function(name){ + mail.templates[name] = {}; + var types = ["text","html"].forEach(function(type){ + fs.readFile("views/mail/" + name + "." + type + ".ejs", function(err, data){ + mail.templates[name][type] = ejs.compile(data.toString()) + }) + }) + }) + }, + + connect: function(){ + var server = email.server.connect({ + user: process.env.OKFOCUS_EMAIL_USERNAME, + password: process.env.OKFOCUS_EMAIL_PASSWORD, + host: "smtp.sendgrid.net", + ssl: true + }) + return server + }, + + send: function(msg, cb){ + var server = mail.connect() + server.send(msg, cb) + }, + + welcome: function(user, cb){ + var message = { + text: mail.templates.welcome.text(user), + from: mail.from, + to: user.email, + subject: "Welcome to Vvalls", + attachment: [ + { data: mail.templates.welcome.html(user), alternative: true }, + ] + }; + mail.send(message, cb) + console.log("sent welcome email to", user.email) + }, + + forgotPassword: function(user, cb){ + var message = { + text: mail.templates.password.text(user), + from: mail.from, + to: user.email, + subject: "Recover your password", + attachment: [ + { data: mail.templates.password.html(user), alternative: true }, + ] + } + mail.send(message, cb) + console.log("sent password email to", user.email) + }, +} + +module.exports = mail diff --git a/server/lib/auth/views.js b/server/lib/auth/views.js new file mode 100644 index 0000000..591b06a --- /dev/null +++ b/server/lib/auth/views.js @@ -0,0 +1,33 @@ + +var util = require('../util'), + User = require("../schemas/User"); + + +var views = {} + +views.usernameTaken = function (req, res) { + var userData = req.session.userData + if (! userData) { + return res.redirect("/") + } + if (req.isAuthenticated()) { + delete req.session.userData + return res.redirect("/") + } + res.render("modal", { opt: { username: util.sanitize(userData.username) } }) +} + +views.resetPassword = function (req, res) { + var nonce = util.sanitize(req.query.nonce) + if (! nonce.length) { + return res.redirect("/") + } + User.findOne({ passwordNonce: nonce }, function (err, user){ + if (err || ! user) { + return res.redirect("/") + } + res.render("reset-password", { username: user.username, nonce: user.passwordNonce }) + }) +} + +module.exports = views diff --git a/server/lib/middleware.js b/server/lib/middleware.js index 0bc3f7a..aec54ad 100644 --- a/server/lib/middleware.js +++ b/server/lib/middleware.js @@ -39,6 +39,7 @@ var middleware = { res.locals.user = req.user || {} res.locals.config = config res.locals.profile = null + res.locals.opt = {} next() }, diff --git a/server/lib/schemas/User.js b/server/lib/schemas/User.js index 77e2e02..b64f8fc 100644 --- a/server/lib/schemas/User.js +++ b/server/lib/schemas/User.js @@ -41,10 +41,8 @@ var UserSchema = new mongoose.Schema({ }, "{PATH} is not an acceptable name"] }, email: { type: String, default: "" }, - emailVerified: { - type: Boolean, - default: false, - }, + emailVerified: { type: Boolean, default: false, }, + emailOptout: { type: Boolean, default: false, }, password: { type: String, validate: [function (val) { @@ -52,6 +50,11 @@ var UserSchema = new mongoose.Schema({ return true }, "{PATH} is not an acceptable password"] }, + passwordNonce: { + type: String, + default: "", + }, + location: { type: String, default: "" }, photo: { type: String, default: "" }, bio: { type: String, default: "" }, @@ -72,6 +75,26 @@ UserSchema.methods.validPassword = function (pw) { shasum.update(pw) return this.password === shasum.digest('hex'); } - +UserSchema.statics.findByUsername = function (username, cb) { + this.findOne({ username: new RegExp("^" + username + "$", "i") }, cb) +} +UserSchema.statics.findByEmail = function (email, cb) { + email = util.escapeRegExp(email) + this.findOne({ email: new RegExp("^" + email + "$", "i") }, cb) +} +UserSchema.statics.findByIP = function (ip, cb) { + ip = util.ip2num(ip) + this.findOne({ $or: [{ created_ip: ip }, { last_ip: ip }] }, cb) +} +UserSchema.statics.resetPasswordNonce = function(user, cb){ + crypto.pseudoRandomBytes(256, function (err, buf){ + var shasum = crypto.createHash('sha1') + shasum.update(buf) + user.passwordNonce = shasum.digest('hex') + user.save(function(err, doc){ + cb() + }) + }) +} module.exports = exports = mongoose.model('user', UserSchema); exports.schema = UserSchema; diff --git a/server/lib/upload.js b/server/lib/upload.js index 0f6c624..e206f7c 100644 --- a/server/lib/upload.js +++ b/server/lib/upload.js @@ -60,7 +60,9 @@ module.exports.put = function (key, file, opt) { }, function(err, s3res) { if (err || s3res.statusCode !== 200) { console.error(err); - s3res.resume() + if (s3res && s3res.resume) { + s3res.resume() + } return; } diff --git a/server/lib/util.js b/server/lib/util.js index aaa8274..6604abe 100644 --- a/server/lib/util.js +++ b/server/lib/util.js @@ -16,18 +16,18 @@ util.trim = function (s){ return (s || "").replace(whitespaceHead,"").replace(wh util.slugify = function (s){ return (s || "").toLowerCase().replace(whitespace,"-").replace(nonAlphanumerics, '-').replace(consecutiveDashes,"-") } - util.sanitize = function (s){ return (s || "").replace(entities, "") } - util.capitalize = function (s) { return (s || "").split(" ").map(util.capitalizeWord).join(" "); } - util.capitalizeWord = function (s) { return s.charAt(0).toUpperCase() + s.slice(1); } +util.escapeRegExp: function (s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") +} util.cleanQuery = function (query) { |
