Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into connect-middleware
Conflicts: lib/validation.js
- Loading branch information
Showing
6 changed files
with
376 additions
and
71 deletions.
There are no files selected for viewing
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,3 @@ | ||
Simple example of a basic REST api using nohm. This is in no way secure and has no authentication checks. | ||
|
||
It requires express |
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,176 @@ | ||
var nohm = require(__dirname+'/../../lib/nohm.js').Nohm, | ||
crypto = require('crypto'); | ||
|
||
/** | ||
* Given a password and salt this creates an SHA512 hash. | ||
*/ | ||
var hasher = function hasher (password, salt) { | ||
var hash = crypto.createHash('sha512'); | ||
hash.update(password); | ||
hash.update(salt); | ||
return hash.digest('base64'); | ||
}; | ||
|
||
/** | ||
* Create a random id | ||
*/ | ||
var uid = function uid () { | ||
return ((Date.now() & 0x7fff).toString(32) + (0x100000000 * Math.random()).toString(32)); | ||
}; | ||
|
||
|
||
var password_minlength = 6; // we use this multiple times and store it here to only have one place where it needs to be configured | ||
|
||
/** | ||
* Model definition of a simple user | ||
*/ | ||
var userModel = module.exports = nohm.model('User', { | ||
idGenerator: 'increment', | ||
properties: { | ||
name: { | ||
type: 'string', | ||
unique: true, | ||
validations: [ | ||
'notEmpty', | ||
['minLength', 4] | ||
] | ||
}, | ||
email: { | ||
type: 'string', | ||
validations: [ | ||
['email', true] // this means only values that pass the email regexp are accepted. BUT it is also optional, thus a falsy value is accepted as well. | ||
] | ||
}, | ||
password: { | ||
load_pure: true, // this ensures that there is no typecasting when loading from the db. | ||
type: function (value, key, old) { // because when typecasting, we create a new salt and hash the pw. | ||
var pwd, salt, | ||
valueDefined = value && typeof(value.length) !== 'undefined'; | ||
if ( valueDefined && value.length >= password_minlength) { | ||
pwd = hasher(value, this.p('salt')); | ||
if (pwd !== old) { | ||
// if the password was changed, we change the salt as well, just to be sure. | ||
salt = uid(); | ||
this.p('salt', salt); | ||
pwd = hasher(value, salt); | ||
} | ||
return pwd; | ||
} else { | ||
return value; | ||
} | ||
}, | ||
validations: [ | ||
'notEmpty', | ||
['minLength', password_minlength] | ||
] | ||
}, | ||
salt: { | ||
// we store the salt so we can check the hashed passwords. | ||
// this is done so that if someone gets access to the database, they can't just use the same salt for every password. this increases the time they need for the decryption and thus makes it less likely that they'll succeed. | ||
// Note: this is not very secure. There should also be an application salt and some other techniques to make password decryption more difficult | ||
defaultValue: uid() | ||
} | ||
}, | ||
methods: { | ||
// custom methods we define here to make handling this model easier. | ||
|
||
/** | ||
* Check a given username/password combination for validity. | ||
*/ | ||
login: function (name, password, callback) { | ||
var self = this; | ||
if (!name || name === '' || !password || password === '') { | ||
callback(false); | ||
return; | ||
} | ||
this.find({name: name}, function (err, ids) { | ||
if (ids.length === 0) { | ||
callback(false); | ||
} else { | ||
self.load(ids[0], function (err) { | ||
if (!err && self.p('password') === hasher(password, self.p('salt'))) { | ||
callback(true); | ||
} else { | ||
callback(false); | ||
} | ||
}); | ||
} | ||
}); | ||
}, | ||
|
||
/** | ||
* This function makes dealing with user input a little easier, since we don't want the user to be able to do things on certain fields, like the salt. | ||
* You can specify a data array that might come from the user and an array containing the fields that should be used from used from the data. | ||
* Optionally you can specify a function that gets called on every field/data pair to do a dynamic check if the data should be included. | ||
* The principle of this might make it into core nohm at some point. | ||
*/ | ||
fill: function (data, fields, fieldCheck) { | ||
var props = {}, | ||
passwordInField, | ||
passwordChanged = false, | ||
self = this, | ||
doFieldCheck = typeof(fieldCheck) === 'function'; | ||
|
||
fields = Array.isArray(fields) ? fields : Object.keys(data); | ||
|
||
fields.forEach(function (i) { | ||
var fieldCheckResult; | ||
|
||
if (i === 'salt' || // make sure the salt isn't overwritten | ||
! self.properties.hasOwnProperty(i)) | ||
return; | ||
|
||
if (doFieldCheck) | ||
fieldCheckResult = fieldCheck(i, data[i]); | ||
|
||
if (doFieldCheck && fieldCheckResult === false) | ||
return; | ||
else if (doFieldCheck && typeof (fieldCheckResult) !== 'undefined' && | ||
fieldCheckResult !== true) | ||
return (props[i] = fieldCheckResult); | ||
|
||
|
||
props[i] = data[i]; | ||
}); | ||
|
||
this.p(props); | ||
return props; | ||
}, | ||
|
||
/** | ||
* This is a wrapper around fill and save. | ||
* It also makes sure that if there are validation errors, the salt field is not included in there. (although we don't have validations for the salt, an empty entry for it would be created in the errors object) | ||
*/ | ||
store: function (data, callback) { | ||
var self = this; | ||
|
||
this.fill(data); | ||
this.save(function () { | ||
delete self.errors.salt; | ||
callback.apply(self, Array.prototype.slice.call(arguments, 0)); | ||
}); | ||
}, | ||
|
||
/** | ||
* Wrapper around fill and valid. | ||
* This makes it easier to check user input. | ||
*/ | ||
checkProperties: function (data, fields, callback) { | ||
var self = this; | ||
callback = typeof(fields) === 'function' ? fields : callback; | ||
|
||
this.fill(data, fields); | ||
this.valid(false, false, callback); | ||
}, | ||
|
||
/** | ||
* Overwrites nohms allProperties() to make sure password and salt are not given out. | ||
*/ | ||
allProperties: function (stringify) { | ||
var props = this._super_allProperties.call(this); | ||
delete props.password; | ||
delete props.salt; | ||
return stringify ? JSON.stringify(props) : props; | ||
} | ||
} | ||
}); |
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,71 @@ | ||
var express = require(__dirname+'/../../../stack/node_modules/express'); | ||
var Nohm = require(__dirname+'/../../lib/nohm.js').Nohm; | ||
var UserModel = require(__dirname+'/UserModel.js'); | ||
var redis = require('redis'); | ||
|
||
var redisClient = redis.createClient(); | ||
|
||
Nohm.setPrefix('rest-user-server-example'); | ||
Nohm.setClient(redisClient); | ||
|
||
var server = express.createServer(); | ||
|
||
server.get('/User/list', function (req, res, next) { | ||
UserModel.find(function (err, ids) { | ||
if (err) { | ||
return next(err); | ||
} | ||
var users = []; | ||
var len = ids.length; | ||
var count = 0; | ||
if (len === 0) { | ||
return res.json(users); | ||
} | ||
ids.forEach(function (id) { | ||
var user = new UserModel(); | ||
user.load(id, function (err, props) { | ||
if (err) { | ||
return next(err); | ||
} | ||
users.push({id: this.id, name: props.name}); | ||
if (++count === len) { | ||
res.json(users); | ||
} | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
server.get('/User/create', function (req, res, next) { | ||
var data = { | ||
name: req.param('name'), | ||
password: req.param('password'), | ||
email: req.param('email') | ||
}; | ||
|
||
var user = new UserModel(); | ||
user.store(data, function (err) { | ||
if (err === 'invalid') { | ||
next(user.errors); | ||
} else if (err) { | ||
next(err); | ||
} else { | ||
res.json({result: 'success', data: user.allProperties()}); | ||
} | ||
}); | ||
}); | ||
|
||
server.use(Nohm.getConnectValidationMiddleware([{ | ||
model: UserModel, | ||
blacklist: ['salt'] | ||
}])); | ||
|
||
server.use(function (err, req, res, next) { | ||
if (err instanceof Error) { | ||
err = err.message; | ||
} | ||
res.json({result: 'error', data: err}); | ||
}); | ||
|
||
server.listen(3000); | ||
console.log('listening on 3000'); |
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
Oops, something went wrong.