apostrophe
Version:
Apostrophe is a user-friendly content management system. You'll need more than this core module. See apostrophenow.org to get started.
132 lines (124 loc) • 5.24 kB
JavaScript
var _ = require('lodash');
var qs = require('qs');
// Needed for A1.5 bc implementation of authentication, normally
// we go through appy's passwordHash wrapper
var crypto = require('crypto');
var passwordHash = require('password-hash');
var async = require('async');
/**
* appy
* @augments Augments the apos object with methods supporting the use of appy
* to implement logins and permissions. THIS IS NOT APPY ITSELF, see the
* appy module for that.
*/
module.exports = function(self) {
// This method integrates Apostrophe user authentication with the appy module, which
// provides a quick start for node apps.
//
// Pass the result of a call to this method as the `auth` option of appy to appy to allow people
// (as managed via the "people" module) to log in as long as they have the "login" box checked.
//
// You must pass your instance of the `pages` module as the `pages` option so that the login
// dialog can be presented.
//
// If the `adminPassword` option is set then an admin user is automatically provided
// regardless of what is in the database, with the password set as specified.
//
// This is normally set up for you by the `apostrophe-site` module.
self.appyAuth = function(options, user) {
return {
strategy: 'local',
options: {
users: self.authHardcodedUsers(options),
// A user is just a snippet page with username and password properties.
// (Yes, the password property is hashed and salted.)
collection: 'aposPages',
afterDeserializeUser: self.authAfterUnserialize,
// Render the login page
template: options.loginPage,
// This is a bit of a hack. It's here because apos.partial needs to know
// of req and res to be able to do i18n magic
passReq: true,
// Set the redirect for after login passing req.user from Appy l.~208
redirect: function(req, callback) {
if (options.redirect) {
if (options.redirect.length === 1) {
// bc
return callback(options.redirect(req.user));
}
return options.redirect(req, callback);
} else {
// This feels like overkill, because we're checking in Appy as well.
return callback('/');
}
},
extraLoginCriteria: {
// Must be an apostrophe-people person; this allows
// other types of objects to have the same email property without
// blocking logins via email
type: 'person',
trash: { $ne: true }
},
verify: function(password, hash) {
if (typeof(hash) !== 'string') {
// No hash exists yet for this user, so definitely don't
// let them log in; don't crash trying to regexp match on
// the hash if it is undefined
return false;
}
if (hash.match(/^a15/)) {
// bc with Apostrophe 1.5 hashed passwords. The salt is
// implemented differently, it's just prepended to the
// password before hashing. Whatever createHmac is doing
// in the password-hash module, it's not that. Fortunately
// it isn't hard to do directly
var components = hash.split(/\$/);
if (components.length !== 3) {
return false;
}
// Allow for a variety of algorithms coming over from A1.5
var hashType = components[0].substr(3);
var salt = components[1];
var hashed = components[2];
try {
var shasum = crypto.createHash(hashType);
shasum.update(salt + password);
var digest = shasum.digest('hex');
return (digest === hashed);
} catch (e) {
console.log(e);
return false;
}
} else {
return passwordHash.verify(password, hash);
}
}
}
};
};
// Pass this function to appy as the `beforeSignin` option to check for login privileges,
// then apply the user's permissions obtained via group membership before
// completing the login process. Normally apostrophe-site does this for you.
// TODO: migrate this code into the people module where it belongs for the most part
self.appyBeforeSignin = function(user, callback) {
return async.series({
// We just "unserialized" the user by loading their object,
// so we should call the same method that we're using as
// our deserializer for passport on later accesses
afterUnserialize: function(callback) {
return self.authAfterUnserialize(user, callback);
},
// A successful login clears any password reset URL, and also any
// account confirmation URL
deleteReset: function(callback) {
if ((!user.resetPassword) && (!user.applyConfirm)) {
return callback(null);
}
return self.pages.update({ _id: user._id }, { $unset: { resetPassword: 1, applyConfirm: 1 } }, callback);
},
joinGroups: function(callback) {
return self.authAddGroupsAndPermissions(user, callback);
}
}, callback);
};
};