Skip to content

Commit

Permalink
Add mongoDB backend for User Authentication
Browse files Browse the repository at this point in the history
Signed-off-by: Ross Gallagher <ross_gallagher@rossgallagher.co.uk>
  • Loading branch information
rgallagher27 committed Jun 9, 2013
1 parent 2cd072a commit ff1cd44
Show file tree
Hide file tree
Showing 676 changed files with 114,556 additions and 129 deletions.
Binary file modified .DS_Store
Binary file not shown.
Binary file added Configs/.DS_Store
Binary file not shown.
46 changes: 46 additions & 0 deletions Configs/config.js
@@ -0,0 +1,46 @@
/**
* Environment dependent configuration properties
*/
module.exports = {
development: {
root: require('path').normalize(__dirname + '/..'),
app: {
name: 'Nodejs Restify Oauth Mongoose Demo'
},
host: 'localhost',
port: '8090',
db_url: 'mongodb://localhost:27017/restify_test',
session_timeout: 20 * 60 * 10, // defaults to 20 minutes, in ms (20 * 60 * 1000)
socket_loglevel: '1', // 0 - error, 1 - warn, 2 - info, 3 - debug
mailSettings : {
mailFrom: 'test@gmail.com',
mailService: "Gmail",
mailAuth: {user: "test@gmail.com", pass: "testpass"},
sendEmail: false,
browserPreview: true
},
version: '1.0.0'
},
test: {
root: require('path').normalize(__dirname + '/..'),
app: {
name: 'Nodejs Restify Oauth Mongoose Demo'
},
host: 'http://enigmatic-anchorage-1633.herokuapp.com',
port: process.env.PORT,
db_url: process.env.MONGOLAB_URI || process.env.MONGOHQ_URL,
session_timeout: 20 * 60 * 10, // defaults to 20 minutes, in ms (20 * 60 * 10)
socket_loglevel: '1', // 0 - error, 1 - warn, 2 - info, 3 - debug
mailSettings : {
mailFrom: 'test@gmail.com',
mailService: "Gmail",
mailAuth: {user: "test@gmail.com", pass: "testpass"},
sendEmail: false,
browserPreview: true
},
version: '1.0.0'
},
production: {

}
}
89 changes: 89 additions & 0 deletions Configs/hooks.js
@@ -0,0 +1,89 @@
"use strict";

var mongoose = require('mongoose');
var _ = require("underscore");
var crypto = require("crypto");
var Client = mongoose.model('ClientKey');
var Token = mongoose.model('AuthToken');
var User = mongoose.model('User');

var database = {
clients: {
officialApiClient: { secret: "C0FFEE" },
unofficialClient: { secret: "DECAF" }
},
users: {
AzureDiamond: { password: "hunter2" },
Cthon98: { password: "*********" }
},
tokensToUsernames: {}
};

function generateToken(data)
{
var random = Math.floor(Math.random() * 100001);
var timestamp = (new Date()).getTime();
var sha256 = crypto.createHmac("sha256", random + "WOO" + timestamp);

return sha256.update(data).digest("base64");
}

exports.validateClient = function (clientId, clientSecret, cb)
{
// Call back with `true` to signal that the client is valid, and `false` otherwise.
// Call back with an error if you encounter an internal server error situation while trying to validate.

Client.findOne({ client: clientId, secret: clientSecret }, function (err, client) {
if(err){
cb(null, false);
}else {
if( client === null ) {
cb(null, false);
} else {
cb(null, true);
}
}
});
};

exports.grantUserToken = function (username, password, cb)
{
var query = User.where( 'username', new RegExp('^' + username + '$', 'i') );

query.findOne(function (err, user) {
if (err) {
cb(null, false);
} else if (!user) {
cb(null, false);
} else if (user.authenticate(password)) {
// If the user authenticates, generate a token for them and store it to the database so
// we can look it up later.

var token = generateToken(username + ":" + password);
var newToken = new Token({ username: username, token: token });
newToken.save();

// Call back with the token so Restify-OAuth2 can pass it on to the client.
return cb(null, token);
} else {
cb(null, false);
}
});
};

exports.authenticateToken = function (token, cb)
{
Token.findOne({ token: token }, function (err, authToken) {
if(err){
cb(null, false);
}else {
if( authToken === null ) {
cb(null, false);
} else {
// If the token authenticates, call back with the corresponding username. Restify-OAuth2 will put it in the
// request's `username` property.
return cb(null, authToken.username);
}
}
});
};
49 changes: 49 additions & 0 deletions Models/authkey.js
@@ -0,0 +1,49 @@
/**
* Module dependencies.
*/
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var restify = require('restify');

/*
* Client Key Schema
*/
var TokenSchema = new Schema(
{
id: ObjectId,
username: { type: String, trim: true },
token: { type: String, trim: true }
})

/**
* Validations
*/
var validatePresenceOf = function (value)
{
return value && value.length
}

/**
* Pre-save hook
*/
TokenSchema.pre('save', function(next)
{
if (!validatePresenceOf(this.username)) {
next(new restify.MissingParameterError('Username cannot be blank'));
}
if (!validatePresenceOf(this.token)) {
next(new restify.MissingParameterError('Token cannot be blank'));
}
next();
})

/**
* Methods
*/

TokenSchema.methods = {

}

mongoose.model('AuthToken', TokenSchema)
49 changes: 49 additions & 0 deletions Models/clientkey.js
@@ -0,0 +1,49 @@
/**
* Module dependencies.
*/
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var restify = require('restify');

/*
* Client Key Schema
*/
var ClientSchema = new Schema(
{
id: ObjectId,
client: { type: String, trim: true },
secret: { type: String, trim: true }
})

/**
* Validations
*/
var validatePresenceOf = function (value)
{
return value && value.length
}

/**
* Pre-save hook
*/
ClientSchema.pre('save', function(next)
{
if (!validatePresenceOf(this.client)) {
next(new restify.MissingParameterError('Client cannot be blank'));
}
if (!validatePresenceOf(this.secret)) {
next(new restify.MissingParameterError('Secret cannot be blank'));
}
next();
})

/**
* Methods
*/

ClientSchema.methods = {

}

mongoose.model('ClientKey', ClientSchema)
126 changes: 126 additions & 0 deletions Models/user.js
@@ -0,0 +1,126 @@

/**
* Module dependencies.
*/
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
var restify = require('restify');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

/**
* User Schema
*/
var UserSchema = new Schema({
id: ObjectId,
name: { type: String, trim: true },
email: { type: String, trim: true },
newEmail: { type: String, trim: true, default: '' },
emailValidatedFlag: { type: Boolean, default: false },
username: { type: String, trim: true },
role: { type: String, enum: ['User', 'Subscriber', 'Admin'], default: 'User' },
hashed_password: { type: String, trim: true },
tempPasswordFlag: { type: Boolean, default: false }
})


/**
* Virtuals
*/
UserSchema
.virtual('password')
.set(function(password) {
this._password = password
this.hashed_password = this.encryptPassword(password)
})
.get(function() { return this._password })

/**
* Validations
*/
var validatePresenceOf = function (value) {
return value && value.length
}

// tried these formats, always get the generic message
//UserSchema.path('name').validate(function (name) {
// return validatePresenceOf(name)
//}, 'Name cannot be blank')

/**
* Pre-save hook
*/
UserSchema.pre('save', function(next) {
if (!validatePresenceOf(this.username)) {
next(new restify.MissingParameterError('Username cannot be blank'));
}
if (!validatePresenceOf(this.name)) {
next(new restify.MissingParameterError('Name cannot be blank'));
}
if (!validatePresenceOf(this.role)) {
next(new restify.MissingParameterError('Role cannot be blank'));
}
if (!validatePresenceOf(this.email)) {
next(new restify.MissingParameterError('Email cannot be blank'));
}
if (this.email.indexOf('@') <= 0) {
// next(new restify.MissingParameterError('Email address must be valid'));
}

// password not blank when creating, otherwise skip
if (!this.isNew) return next();
if (!validatePresenceOf(this.password)) {
next(new restify.MissingParameterError('Invalid password'));
}
next();
})

/**
* Methods
*/

UserSchema.methods = {

/**
* Authenticate - check if the passwords are the same
*
* @param {String} plainText
* @return {Boolean}
* @api public
*/
authenticate: function(plainText) {
return bcrypt.compareSync(plainText, this.hashed_password);
},

/**
* Encrypt password
*
* @param {String} password
* @return {String}
* @api public
*/
encryptPassword: function(password) {
if (!password) return ''

var salt = bcrypt.genSaltSync(10);
var hash = bcrypt.hashSync(password, salt);

return hash;
},

/**
* allowAccess
*
* @param {String} role
* @return {Boolean}
* @api public
*/
allowAccess: function(role) {
if (this.role == 'Admin') return true; // Admin can access everything
if (role == 'Subscriber' && this.role == 'Subscriber') return true; // Subscriber can access Subscriber and User
if (role == 'User' && (this.role == 'User' || this.role == 'Subscriber')) return true; // user is at the bottom of special access
return false; // should only happen if checking access for an anonymous user
}
}

mongoose.model('User', UserSchema)

0 comments on commit ff1cd44

Please sign in to comment.