Skip to content

Instantly share code, notes, and snippets.

@sorensen
Created December 8, 2011 23:07
Show Gist options
  • Save sorensen/1449152 to your computer and use it in GitHub Desktop.
Save sorensen/1449152 to your computer and use it in GitHub Desktop.
DNode/Express Mongoose Authentication
// DNode Session
// =============
var connect = require('connect')
module.exports = function(opt) {
var key = opt.key || 'connect.sid'
, store = opt.store
, interval = opt.interval || 120000
return function(client, conn) {
var self = this
, iid
, sid
, sess
, cookies
conn.on('ready', function() {
var id = conn.stream.socketio.id
, request = conn.stream.socketio.manager.handshaken[id]
if (!request.headers.cookie) return
cookies = connect.utils.parseCookie(request.headers.cookie)
sid = cookies[key]
store.load(sid, function(err, sess) {
if (!err) sess = sess
})
iid = setInterval(function() {
if (!sess) return
sess.reload(function() {
sess.touch().save()
}, interval)
})
})
conn.on('end', function() {
iid && clearInterval(iid)
})
this.session = function(fn) {
if ('function' !== typeof fn) return
store.load(sid, function(err, sess) {
if (!err) conn.session = sess
fn(err, sess)
})
}
this.cookies = function(fn) {
if ('function' !== typeof fn) return
fn(cookies)
}
}
}
// Authentication middleware
// -------------------------
// Shim for DNode's current version of socket.io,
// which cannot pass `null` references, and turning
// the mongoose model to a pure JSON object
function shim(err, doc, fn) {
if (!err) err = 0
if (doc) doc = JSON.parse(JSON.stringify(doc))
return fn(err, doc)
}
module.exports = function(options) {
var database = options.database
, Store = options.store
return function(client, conn) {
var self = this
, User = database.model('user')
, Token = database.model('token')
, Session = database.model('session')
this.authenticate = function(data, fn) {
User.authenticate(data.username, data.password, function(err, doc) {
self.session(function(err, sess) {
if (err) return
sess._user = doc._id
sess.save()
})
shim(err, doc, fn)
})
}
this.register = function(data, fn) {
User.register(data, function(err, doc) {
self.session(function(err, sess) {
if (err) return
sess._user = doc._id
sess.save()
})
shim(err, doc, fn)
})
}
this.logout = function(fn) {
console.log('logout')
self.session(function(err, sess) {
if (!err) {
delete sess._user
sess.save()
}
fn && fn()
})
}
}
}
// Application Server
// ==================
// Dependencies
// ------------
var express = require('express')
, Mongo = require('mongodb')
, Session = require('connect-mongo')
, Mongoose = require('mongoose')
, DNode = require('dnode')
, dnodeSession = require(__dirname + '/custom-dnode-session')
, Schemas = require(__dirname + '/schemas')
, auth = require(__dirname + '/backbone-auth')
, app = module.exports = express.createServer()
// Session
// -------
var store = new Session({
db: 'db'
})
// Server configuration
// --------------------
app.configure(function() {
app.use(express.cookieParser())
app.use(express.session({
cookie: {maxAge: 8000000}
, secret: 'secret'
, store: store
}))
})
// Schemas
// -------
var User
, Token
Schemas.defineModels(Mongoose, function() {
database = Mongoose.connect(config.mongo.host)
Token = database.model('token')
User = database.model('user')
})
// Routes
// ------
function authFromToken(req, res, next) {
var cookie = JSON.parse(req.cookies.logintoken);
if (cookies && cookies.token) {
Token
.findOne({
_user: cookie._user
, series: cookie.series
, token: cookie.token
})
.populate('_user')
.run(function(err, token) {
console.log('authFromToken', token._user)
if (!token || !token._user) {
next()
return
}
req.session._user = token._user._id
req.user = token._user
token.token = token.randomToken()
token.save(function() {
res.cookie('logintoken', token.cookieValue, {
expires: new Date(Date.now() + 2 * 604800000)
, path: '/'
})
next()
})
})
}
}
function loadUser(req, res, next) {
if (req.session._user) {
User.findById(req.session._user, function(err, user) {
if (user) {
req.user = user
}
next()
})
} else if (req.cookies.logintoken) {
authFromToken(req, res, next)
} else {
next()
}
}
function roomsAndProfiles(req, res, next) {
Room.find(function(err, rooms) {
req.rooms = rooms
Profile.find(function(err, profiles) {
req.profiles = profiles
next()
})
})
}
app.get('/', loadUser, function(req, res) {
res.render('index.jade', {
locals: {
user: JSON.stringify(req.user || null)
}
})
})
// Initialize
// ----------
app.listen(8000)
DNode()
.use(dnodeSession({
store: store
}))
.use(auth({
database: database
, store: store
}))
.listen(app)
// Schemas
// =======
var UserSchema
, TokenSchema
// Dependencies
// ------------
var bcrypt = require('bcrypt')
, _ = require('underscore')._
// Helpers
// -------
function emptyToSparse(str) {
return (!str || !str.length) ? undefined : str
}
function encryptPassword(model, next) {
if (model._password && !model.hash) {
bcrypt.gen_salt(10, function(err, salt) {
if (err) return next(err)
bcrypt.encrypt(model._password, salt, function(err, hash) {
if (err) return next(err)
model.hash = hash
next()
})
})
} else {
next()
}
}
function createdAndModified(schema, options) {
schema.add({
modified: { type: Date },
created: { type: Date, default: Date.now }
})
schema.pre('save', function (next) {
this.modified = new Date
next()
})
}
function defineModels(mongoose, next) {
var Schema = mongoose.Schema
, ObjectId = Schema.ObjectId
// User
// ----
UserSchema = new Schema({
first_name: { type: String, trim: true }
, last_name: { type: String, trim: true }
, username: {
type: String
, lowercase: true
, required: true
, index: { unique: true }
}
, email: {
type: String
, lowercase: true
, set: emptyToSparse
, index: { unique: true, sparse: true }
}
, hash: { type: String }
})
UserSchema
.virtual('password')
.get(function() {
return this.hash
})
.set(function(password) {
this._password = password
})
UserSchema.plugin(createdAndModified)
UserSchema.path('created').index(true)
UserSchema
.pre('save', function(next) {
encryptPassword(this, function(err) {
next()
})
})
UserSchema
.method('authenticate', function(password, next) {
bcrypt.compare(password, this.hash, next)
})
UserSchema
.static('authenticate', function(username, password, next) {
this.findOne({
username: username
}
, function(err, user) {
if (err) return next(err)
if (!user) return next('User does not exist')
user.authenticate(password, function(err, valid) {
if (err) return next(err)
if (valid) return next(null, user)
return next('Invalid password', null)
})
})
})
UserSchema
.static('register', function(attr, next) {
this.create(attr, function(err, user) {
if (err) {
console.log(err)
if (/duplicate key/.test(err)) {
return next('Username taken')
}
return next(err)
}
return next(null, user)
})
})
// Token
// -----
TokenSchema = new Schema({
_user: { type: ObjectId, ref: UserSchema, index: true }
, series: { type: String, index: true }
, token: { type: String, index: true }
})
TokenSchema
.method('randomToken', function() {
return Math.round((new Date().valueOf() * Math.random())) + ''
})
TokenSchema
.pre('save', function(next) {
this.token = this.randomToken()
if (this.isNew) {
this.series = this.randomToken()
}
next()
})
TokenSchema
.virtual('id')
.get(function() {
return this._id.toHexString()
})
TokenSchema
.virtual('cookieValue')
.get(function() {
return JSON.stringify({
email: this.email
, token: this.token
, series: this.series
})
})
// Set models to mongoose
mongoose.model('user', UserSchema)
mongoose.model('token', TokenSchema)
next && next()
}
exports.defineModels = defineModels
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment