Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add mongoDB backend for User Authentication
Signed-off-by: Ross Gallagher <ross_gallagher@rossgallagher.co.uk>
- Loading branch information
1 parent
2cd072a
commit ff1cd44
Showing
676 changed files
with
114,556 additions
and
129 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: { | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
Oops, something went wrong.