Initial commit
This commit is contained in:
92
modules/authentication.js
Normal file
92
modules/authentication.js
Normal file
@@ -0,0 +1,92 @@
|
||||
var crypto = require('crypto');
|
||||
|
||||
// larger numbers mean better security, less
|
||||
var config = {
|
||||
digest: 'sha512',
|
||||
// size of the generated hash
|
||||
hashBytes: 32,
|
||||
// larger salt means hashed passwords are more resistant to rainbow table, but
|
||||
// you get diminishing returns pretty fast
|
||||
saltBytes: 24,
|
||||
// more iterations means an attacker has to take longer to brute force an
|
||||
// individual password, so larger is better. however, larger also means longer
|
||||
// to hash the password. tune so that hashing the password takes about a
|
||||
// second
|
||||
iterations: 233335
|
||||
};
|
||||
|
||||
/**
|
||||
* Hash a password using Node's asynchronous pbkdf2 (key derivation) function.
|
||||
*
|
||||
* Returns a self-contained buffer which can be arbitrarily encoded for storage
|
||||
* that contains all the data needed to verify a password.
|
||||
*
|
||||
* @param {!String} password
|
||||
* @param {!function(?Error, ?Buffer=)} callback
|
||||
*/
|
||||
function hashPassword (password, callback) {
|
||||
// generate a salt for pbkdf2
|
||||
crypto.randomBytes(config.saltBytes, function (err, salt) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
crypto.pbkdf2(password, salt, config.iterations, config.hashBytes, config.digest,
|
||||
function (err, hash) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var combined = Buffer.alloc((hash.length + salt.length + 8));
|
||||
|
||||
// include the size of the salt so that we can, during verification,
|
||||
// figure out how much of the hash is salt
|
||||
combined.writeUInt32BE(salt.length, 0, true);
|
||||
// similarly, include the iteration count
|
||||
combined.writeUInt32BE(config.iterations, 4, true);
|
||||
|
||||
salt.copy(combined, 8);
|
||||
hash.copy(combined, salt.length + 8);
|
||||
callback(null, combined);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a password using Node's asynchronous pbkdf2 (key derivation) function.
|
||||
*
|
||||
* Accepts a hash and salt generated by hashPassword, and returns whether the
|
||||
* hash matched the password (as a boolean).
|
||||
*
|
||||
* @param {!String} password
|
||||
* @param {!Buffer} combined Buffer containing hash and salt as generated by
|
||||
* hashPassword.
|
||||
* @param {!function(?Error, !boolean)}
|
||||
*/
|
||||
function verifyPassword (password, combined, callback) {
|
||||
// extract the salt and hash from the combined buffer
|
||||
var saltBytes = combined.readUInt32BE(0);
|
||||
var hashBytes = combined.length - saltBytes - 8;
|
||||
var iterations = combined.readUInt32BE(4);
|
||||
var salt = combined.slice(8, saltBytes + 8);
|
||||
var hash = combined.toString('binary', saltBytes + 8);
|
||||
|
||||
// verify the salt and hash against the password
|
||||
crypto.pbkdf2(password, salt, iterations, hashBytes, config.digest, function(err, verify) {
|
||||
if (err && typeof callback === 'function') {
|
||||
return callback(err, false);
|
||||
} else if (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback(null, verify.toString('binary') === hash);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports.hashPassword = hashPassword;
|
||||
exports.verifyPassword = verifyPassword;
|
||||
49
modules/geocoder.js
Normal file
49
modules/geocoder.js
Normal file
@@ -0,0 +1,49 @@
|
||||
var NodeGeocoder = require('node-geocoder');
|
||||
|
||||
var options = {
|
||||
provider: 'google',
|
||||
|
||||
// Optional depending on the providers
|
||||
httpAdapter: 'https', // Default
|
||||
apiKey: 'AIzaSyCvpBGztvxtRUNigOW9f0GXVRWlukJZsps', // for Mapquest, OpenCage, Google Premier
|
||||
formatter: null // 'gpx', 'string', ...
|
||||
};
|
||||
|
||||
var geocoder = NodeGeocoder(options);
|
||||
|
||||
|
||||
exports.geocoder = geocoder;
|
||||
|
||||
|
||||
// Using callback
|
||||
// geocoder.geocode('29 champs elysée paris', function(err, res) {
|
||||
// console.log(res);
|
||||
// });
|
||||
//
|
||||
// // Or using Promise
|
||||
// geocoder.geocode('29 champs elysée paris')
|
||||
// .then(function(res) {
|
||||
// console.log(res);
|
||||
// })
|
||||
// .catch(function(err) {
|
||||
// console.log(err);
|
||||
// });
|
||||
|
||||
// output :
|
||||
// [{
|
||||
// latitude: 48.8698679,
|
||||
// longitude: 2.3072976,
|
||||
// country: 'France',
|
||||
// countryCode: 'FR',
|
||||
// city: 'Paris',
|
||||
// zipcode: '75008',
|
||||
// streetName: 'Champs-Élysées',
|
||||
// streetNumber: '29',
|
||||
// administrativeLevels: {
|
||||
// level1long: 'Île-de-France',
|
||||
// level1short: 'IDF',
|
||||
// level2long: 'Paris',
|
||||
// level2short: '75'
|
||||
// },
|
||||
// provider: 'google'
|
||||
// }]
|
||||
147
modules/token.js
Normal file
147
modules/token.js
Normal file
@@ -0,0 +1,147 @@
|
||||
const JWT = require('jsonwebtoken');
|
||||
const Roles = require('../models/role');
|
||||
|
||||
const KEY = 'Th1s is THE s3cr3t kEy. It secures the t0ken!';
|
||||
|
||||
const Token = {
|
||||
create: (payload, callback) => {
|
||||
JWT.sign(payload, KEY, { expiresIn: '1h' }, callback);
|
||||
},
|
||||
verify: (token, callback) => {
|
||||
JWT.verify(token, KEY, callback);
|
||||
}
|
||||
};
|
||||
|
||||
function createAnonymousToken (e) {
|
||||
Token.create({ user: null, permission: 0 }, (err, token) => {
|
||||
if (err) {
|
||||
e.emit('token:create', err, null);
|
||||
}
|
||||
|
||||
if (token) {
|
||||
e.emit('token:create', null, token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createHmac (e, options) {
|
||||
|
||||
}
|
||||
|
||||
function createAuthenticatedToken (e, user, event = 'token:create') {
|
||||
Token.create({ user: user.userName, permission: user.permission._id, uid: user.uid }, (err, token) => {
|
||||
if (err) {
|
||||
e.emit(event, err, null);
|
||||
}
|
||||
|
||||
if (token) {
|
||||
e.emit(event, null, token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function refreshToken (e, token) {
|
||||
Token.verify(e, token, (err, decoded) => {
|
||||
if (err) {
|
||||
e.emit('token:refresh', err, null);
|
||||
}
|
||||
|
||||
if (decoded) {
|
||||
createAuthenticatedToken(
|
||||
e,
|
||||
{ user: decoded.user, permission: decoded.permission },
|
||||
'token:refresh'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateToken (e, token, callback) {
|
||||
if (token) {
|
||||
token = token.replace(/(bearer|basic)\s/i, '');
|
||||
|
||||
Token.verify(token, (err, decoded) => {
|
||||
var result = { valid: !!decoded, data: decoded };
|
||||
|
||||
if (e) {
|
||||
if (err || !result.valid) {
|
||||
e.emit('token:validate', (err || result), null);
|
||||
}
|
||||
|
||||
e.emit('token:validate', null, result);
|
||||
}
|
||||
|
||||
else if (typeof callback === 'function') {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
}
|
||||
|
||||
callback(null, result);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (e) {
|
||||
e.emit('token:validate', 'No session token passed.', null);
|
||||
}
|
||||
|
||||
else if (typeof callback === 'function') {
|
||||
callback('No session token passed.', null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function verifyTokenAndUserThen (token, minimumPermission, callback) {
|
||||
validateToken(null, token, (err, decoded) => {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
}
|
||||
|
||||
if (decoded && decoded.valid && decoded.data.permission >= minimumPermission) {
|
||||
callback(null, decoded);
|
||||
} else {
|
||||
callback('User role does not have permission', null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function verifyTokenAndRoleThen (token, action, callback, log = false) {
|
||||
if (log) console.log('verifyTokenAndRoleThen', { token: token, action: action });
|
||||
validateToken(null, token, (err, decoded) => {
|
||||
if (log) console.log('verifyTokenAndRoleThen::validateToken', { err: err, decoded: decoded.data });
|
||||
|
||||
if (err) {
|
||||
callback('Session could not be validated.', null);
|
||||
}
|
||||
|
||||
let [initial, canElevateTo = false] = Array.isArray(action) ? action : [ action ];
|
||||
|
||||
if (log) {
|
||||
console.log('Roles.canRole[' + initial + ']', Roles.canRole(null, decoded.data.permission, initial));
|
||||
console.log('Roles.canRole[' + canElevateTo + ']', Roles.canRole(null, decoded.data.permission, canElevateTo));
|
||||
}
|
||||
|
||||
if (decoded && decoded.valid) {
|
||||
Roles.canRole(null, decoded.data.permission, action, (err, result) => {
|
||||
if (err) {
|
||||
callback('There was an error verifying the role permissions.', null);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
decoded.hasPermission = result.hasPermission;
|
||||
decoded.canElevate = result.canElevate;
|
||||
callback(null, decoded);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
anonymous: createAnonymousToken,
|
||||
create: createAuthenticatedToken,
|
||||
refresh: refreshToken,
|
||||
validate: validateToken,
|
||||
verifyRoleThen: verifyTokenAndRoleThen,
|
||||
verifyThen: verifyTokenAndRoleThen
|
||||
};
|
||||
Reference in New Issue
Block a user