UNPKG

apostrophe

Version:

Apostrophe is a user-friendly content management system. This core module of Apostrophe provides rich content editing and essential facilities to integrate Apostrophe into your Express project. Apostrophe also includes simple facilities for storing your r

131 lines (123 loc) 5.21 kB
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' }, 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); }; };