Search code examples
node.jsexpresspromisesequelize.jspassport.js

Yet another Promise question - nested Sequelize calls?


I have read dozens of articles on Promises, and for the most part, I get them and have been using them for a couple of years now. But I have a case that I'm struggling with and haven't found an exact match in all of my reading - unless I haven't recognized the match. My case is this:

I have both client and server applications. The server app is an API for the client. The user makes a login request and after a successful call to Active Directory where the user is found there, a Sequelize 'Users.findOne' call is made and returns the user data to the client in the .then which follows. There is a .catch for any errors.

I want to add a check and another Sequelize call once the findOne is successful wherein if any of three data points from Active Directory (office, company, department) doesn't match what is stored in the Users table, I update it with a Users.update call. I do not need to return the results of this call to the client, nor notify it of any failures. Logging both to the server is good enough. I use winston for my logging. Code without and with the addition follows. Both work, but I'm not sure the longer one is right.

I'm including the Passport call to AD, and also the alternative login via AD Group, for completeness, but you can ignore those bits. I'm adding a row of asterisks to the original code to indicate where I inserted the additional code, and in the second listing, I'm surrounding the code with rows of asterisks so you can find it easily.

So am I doing this right, or what?

Note: for brevity, you can ignore everything after the return statement following the asterisks. That's less than half the code that way. I just wanted you to see the rest if you think it might be relevant.

api.post('/login', passport.authenticate('ldapauth', {session: true, failureFlash: 'Invalid username or password.', failWithError: true}),
    (req, res, next) => {
      // this is only called if authentication was successful. this is called after the serializer.
      // the authenticated user is found in req.user.
      let adGroups = req.user.memberOf;
      let office = req.user.physicalDeliveryOfficeName;
      let company = req.user.company;
      let department = req.user.department;
      // console.log('Member Of', adGroups);

      if (typeof req.user.sessionID !== 'undefined') {
        winston.info(`\"Login Successful for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
          {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
      } else {
        winston.info(`\"Login Successful for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
          {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
      }
      // console.log('Req user: ', req.user);
      // console.log('right here 1')
      let tempAbilities = [];
      let userAbilities = [];
      let allAbilities = displayAbilities();
      let include = [{
        model: Roles,
        where: {
          active: 1
        },
        include: [ RolesPerms ]
      }];
      let options = {
        where: {
          username: req.user.sAMAccountName
        },
        include: include
      };
      let userID = null;
      // TODO check if found user is 'active === 0' and if so, fail the authentication; use where clause "active != 0"
      // console.log('right here 2')

      Users.findOne(options).then(userResult => {
        // console.log('user result', userResult);
        if (userResult === null) {
          if (typeof req.user.sessionID !== 'undefined') {
            winston.info(`\"Login user not found - looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
          } else {
            winston.info(`\"Login user not found - looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
          }
          throw new Error('User not in SutterNow DB.')
        }
        userID = userResult.dataValues.id;
        userResult.roles.forEach(rElement => {
          rElement.roles_perms.forEach(rpElement => {
            // console.log('rpElement', rpElement);
            // if (abilities.findIndex(x => x.prop=="propVal")) --> an example of using an object property to find index
            if (tempAbilities.indexOf(rpElement.dataValues.permission_name) === -1) {
              tempAbilities.push(rpElement.dataValues.permission_name);
              userAbilities.push(allAbilities[allAbilities.findIndex(x => x.name === rpElement.dataValues.permission_name)]);
            }
          })
        });
        req.session.rules = userAbilities;
        let location = {
          office: office,
          company: company,
          department: department
        }
        req.session.location = location
/****************************************************************************************************/
        return res.json({id: userID, rules: userAbilities, location: location, "Message": "Login Successful"});
      }).catch(err => {
        // console.log('ah crap')
        if (typeof req.user.sessionID !== 'undefined') {
          winston.info(`\"Looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
            {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
        } else {
          winston.info(`\"Looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
            {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
        }
        const promA = Groups.findAll({
          where: {
            active: 1
          },
          include: [{
            model: Roles,
            where: {
              active: 1
            },
            include: [ RolesPerms ]
          }],
          order: ['displayName']
        });
        const promB = GroupsRoles.findAll();
        // console.error(err);
        // user not found in our DB, but they're in AD; let's check if they belong to an approved group
        Promise.all([promA, promB]).then(responses => {
          // console.log('Response 1', responses[0]);
          // console.log('Response 2', responses[1]);

          if (typeof req.user.sessionID !== 'undefined') {
            winston.info(`\"Found group results for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
          } else {
            winston.info(`\"Found group results for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
          }

          let foundGroup = ''
          let foundRoles = []
          let foundPerms = []

          responses[0].forEach(el => {
            // console.log('our el', el)
            // console.log('our group', el.dataValues)
            for (let j = 0; j < adGroups.length; j++) {
              // console.log('adGroups j', adGroups[j])
              if (adGroups[j].match(el.dataValues.username)) {
                // console.log('found it', el.dataValues)
                userID = el.id
                foundGroup = el.dataValues.username;
                foundRoles = el.dataValues;
                break // TODO allow for membership in multiple groups, like I do multiple roles, below
              }
            }
          });

          foundRoles.roles.forEach(role => {
            // console.log('roles_perms things', role.roles_perms);
            role.roles_perms.forEach(roleP => {
              if (foundPerms.indexOf(roleP.dataValues.permission_name) === -1) {
                foundPerms.push(roleP.dataValues.permission_name);
                userAbilities.push(allAbilities[allAbilities.findIndex(x => x.name === roleP.dataValues.permission_name)]);
              }
            })
          });
          req.session.rules = userAbilities;
          let location = {
            office: office,
            company: company,
            department: department
          }
          req.session.location = location

          return res.json({id: 0, rules: userAbilities, location: location, "Message": "Login Successful via group membership"});
        }).catch(err => {
          console.error('the error ' + err);
          return res.json({"Message": "Login failed. You neither had a user account in SutterNow, nor did you belong to a valid AD Group."})
        });
      });

      return null;
      // res.json({"Message":"Login successful"});
  },
    (err, req, res, next) => {
      /* failWithErrors: true, above, makes this section get called to handle the error.
         We don't handle the logging nor the json return here; instead, we setup the error object and pass it on
         to the error handler which does those things. */
      console.log('Authentication failed; passing error on to error handler...');
      err.route = 'Authentication';
      err.statusCode = 401;
      err.status = 401;
      err.shouldRedirect = req.headers['user-agent'].indexOf('Postman') > -1;
      if (typeof req.flash === 'function' && typeof req.flash('error') !== 'undefined' && req.flash('error').length !== 0) {
        err.message = req.flash('error').slice(-1)[0];
        console.log('Flash error ' + req.flash('error').slice(-1)[0]);
        res.statusMessage = req.flash('error').slice(-1)[0];
      }
      if (app.get('env') === 'production') {
        console.log('stack redacted');
        err.stack = '';// We want to obscure any data the user shouldn't see.
      }
      next(err);
      // return res.json({"Message": "Login failed. " + err.message});
      // return null;
    }
  );
api.post('/login', passport.authenticate('ldapauth', {session: true, failureFlash: 'Invalid username or password.', failWithError: true}),
    (req, res, next) => {
      // this is only called if authentication was successful. this is called after the serializer.
      // the authenticated user is found in req.user.
      let adGroups = req.user.memberOf;
      let office = req.user.physicalDeliveryOfficeName;
      let company = req.user.company;
      let department = req.user.department;
      // console.log('Member Of', adGroups);

      if (typeof req.user.sessionID !== 'undefined') {
        winston.info(`\"Login Successful for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
          {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
      } else {
        winston.info(`\"Login Successful for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
          {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
      }
      // console.log('Req user: ', req.user);
      // console.log('right here 1')
      let tempAbilities = [];
      let userAbilities = [];
      let allAbilities = displayAbilities();
      let include = [{
        model: Roles,
        where: {
          active: 1
        },
        include: [ RolesPerms ]
      }];
      let options = {
        where: {
          username: req.user.sAMAccountName
        },
        include: include
      };
      let userID = null;
      // TODO check if found user is 'active === 0' and if so, fail the authentication; use where clause "active != 0"
      // console.log('right here 2')

      Users.findOne(options).then(userResult => {
        // console.log('user result', userResult);
        if (userResult === null) {
          if (typeof req.user.sessionID !== 'undefined') {
            winston.info(`\"Login user not found - looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
          } else {
            winston.info(`\"Login user not found - looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
          }
          throw new Error('User not in SutterNow DB.')
        }
        userID = userResult.dataValues.id;
        userResult.roles.forEach(rElement => {
          rElement.roles_perms.forEach(rpElement => {
            // console.log('rpElement', rpElement);
            // if (abilities.findIndex(x => x.prop=="propVal")) --> an example of using an object property to find index
            if (tempAbilities.indexOf(rpElement.dataValues.permission_name) === -1) {
              tempAbilities.push(rpElement.dataValues.permission_name);
              userAbilities.push(allAbilities[allAbilities.findIndex(x => x.name === rpElement.dataValues.permission_name)]);
            }
          })
        });
        req.session.rules = userAbilities;
        let location = {
          office: office,
          company: company,
          department: department
        }
        req.session.location = location
/****************************************************************************************************/
        // TODO is this supposed to be another .then chained between this one and the catch (or before this one)? if so, combine the catch?
        if (userResult.dataValues.office !== office || userResult.dataValues.department !== department || userResult.dataValues.company !== company) {
          // update the database with the new AD values
          console.log('updating user')
          Users.update({
            // values
            office: office,
            department: department,
            company: company
          }, {
            // options
            where: {
              id: userID
            }
          }).then(numAffected => {
            winston.info(`\"Updated ${numAffected} user for ${req.user.dataValues.username}\" - SessionID: ${req.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.dataValues.username, sessionID: req.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'User Admin'});
            return;
          }).catch((err) => {
            if (err) {
              // Not sure how to get an error here. ensureAuthenticated handles invalid users attempting this PUT.
              // console.log(err);
              err.route = 'User Admin';
              err.statusCode = 'UPDATE_USER_AT_LOGIN_BY_ID_ERROR';
              err.status = 'UPDATE USER AT LOGIN BY ID ERROR';
              err.shouldRedirect = req.headers['user-agent'].indexOf('Postman') > -1;
              if (app.get('env') === 'production') {
                console.log('stack redacted');
                err.stack = '';// We want to obscure any data the user shouldn't see.
              }
              next(err);
            }
          });
        }
/****************************************************************************************************/
        return res.json({id: userID, rules: userAbilities, location: location, "Message": "Login Successful"});
      }).catch(err => {
        // console.log('ah crap')
        if (typeof req.user.sessionID !== 'undefined') {
          winston.info(`\"Looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
            {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
        } else {
          winston.info(`\"Looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
            {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
        }
        const promA = Groups.findAll({
          where: {
            active: 1
          },
          include: [{
            model: Roles,
            where: {
              active: 1
            },
            include: [ RolesPerms ]
          }],
          order: ['displayName']
        });
        const promB = GroupsRoles.findAll();
        // console.error(err);
        // user not found in our DB, but they're in AD; let's check if they belong to an approved group
        Promise.all([promA, promB]).then(responses => {
          // console.log('Response 1', responses[0]);
          // console.log('Response 2', responses[1]);

          if (typeof req.user.sessionID !== 'undefined') {
            winston.info(`\"Found group results for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
          } else {
            winston.info(`\"Found group results for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
          }

          let foundGroup = ''
          let foundRoles = []
          let foundPerms = []

          responses[0].forEach(el => {
            // console.log('our el', el)
            // console.log('our group', el.dataValues)
            for (let j = 0; j < adGroups.length; j++) {
              // console.log('adGroups j', adGroups[j])
              if (adGroups[j].match(el.dataValues.username)) {
                // console.log('found it', el.dataValues)
                userID = el.id
                foundGroup = el.dataValues.username;
                foundRoles = el.dataValues;
                break // TODO allow for membership in multiple groups, like I do multiple roles, below
              }
            }
          });

          foundRoles.roles.forEach(role => {
            // console.log('roles_perms things', role.roles_perms);
            role.roles_perms.forEach(roleP => {
              if (foundPerms.indexOf(roleP.dataValues.permission_name) === -1) {
                foundPerms.push(roleP.dataValues.permission_name);
                userAbilities.push(allAbilities[allAbilities.findIndex(x => x.name === roleP.dataValues.permission_name)]);
              }
            })
          });
          req.session.rules = userAbilities;
          let location = {
            office: office,
            company: company,
            department: department
          }
          req.session.location = location

          return res.json({id: 0, rules: userAbilities, location: location, "Message": "Login Successful via group membership"});
        }).catch(err => {
          console.error('the error ' + err);
          return res.json({"Message": "Login failed. You neither had a user account in SutterNow, nor did you belong to a valid AD Group."})
        });
      });

      return null;
      // res.json({"Message":"Login successful"});
  },
    (err, req, res, next) => {
      /* failWithErrors: true, above, makes this section get called to handle the error.
         We don't handle the logging nor the json return here; instead, we setup the error object and pass it on
         to the error handler which does those things. */
      console.log('Authentication failed; passing error on to error handler...');
      err.route = 'Authentication';
      err.statusCode = 401;
      err.status = 401;
      err.shouldRedirect = req.headers['user-agent'].indexOf('Postman') > -1;
      if (typeof req.flash === 'function' && typeof req.flash('error') !== 'undefined' && req.flash('error').length !== 0) {
        err.message = req.flash('error').slice(-1)[0];
        console.log('Flash error ' + req.flash('error').slice(-1)[0]);
        res.statusMessage = req.flash('error').slice(-1)[0];
      }
      if (app.get('env') === 'production') {
        console.log('stack redacted');
        err.stack = '';// We want to obscure any data the user shouldn't see.
      }
      next(err);
      // return res.json({"Message": "Login failed. " + err.message});
      // return null;
    }
  );

Solution

  • I think I figured it out. I moved my added code to a separate .then, and made sure the return for the one before it doesn't use res.json, but just returns the object. I then return that object in the new .then using res.json whether the user table update succeeds or fails, thus:

    api.post('/login', passport.authenticate('ldapauth', {session: true, failureFlash: 'Invalid username or password.', failWithError: true}),
        (req, res, next) => {
          // this is only called if authentication was successful. this is called after the serializer.
          // the authenticated user is found in req.user.
          let adGroups = req.user.memberOf;
          let office = req.user.physicalDeliveryOfficeName;
          let company = req.user.company;
          let department = req.user.department;
          // console.log('Member Of', adGroups);
    
          if (typeof req.user.sessionID !== 'undefined') {
            winston.info(`\"Login Successful for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
          } else {
            winston.info(`\"Login Successful for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
              {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
          }
          // console.log('Req user: ', req.user);
          // console.log('right here 1')
          let tempAbilities = [];
          let userAbilities = [];
          let allAbilities = displayAbilities();
          let include = [{
            model: Roles,
            where: {
              active: 1
            },
            include: [ RolesPerms ]
          }];
          let options = {
            where: {
              username: req.user.sAMAccountName
            },
            include: include
          };
          let userID = null;
          // TODO check if found user is 'active === 0' and if so, fail the authentication; use where clause "active != 0"
          // console.log('right here 2')
    
          Users.findOne(options).then(userResult => {
            // console.log('user result', userResult);
            if (userResult === null) {
              if (typeof req.user.sessionID !== 'undefined') {
                winston.info(`\"Login user not found - looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
                  {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
              } else {
                winston.info(`\"Login user not found - looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
                  {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
              }
              throw new Error('User not in SutterNow DB.')
            }
            userID = userResult.dataValues.id;
            userResult.roles.forEach(rElement => {
              rElement.roles_perms.forEach(rpElement => {
                // console.log('rpElement', rpElement);
                // if (abilities.findIndex(x => x.prop=="propVal")) --> an example of using an object property to find index
                if (tempAbilities.indexOf(rpElement.dataValues.permission_name) === -1) {
                  tempAbilities.push(rpElement.dataValues.permission_name);
                  userAbilities.push(allAbilities[allAbilities.findIndex(x => x.name === rpElement.dataValues.permission_name)]);
                }
              })
            });
            req.session.rules = userAbilities;
            let location = {
              office: office,
              company: company,
              department: department
            }
            req.session.location = location
            let adLocation = {
              office: userResult.dataValues.office,
              company: userResult.dataValues.company,
              department: userResult.dataValues.department
            }
    
            return {id: userID, rules: userAbilities, location: location, adLocation: adLocation, "Message": "Login Successful"};
          }).then(result => {
            if (result.adLocation.office !== result.location.office || result.adLocation.department !== result.location.department || result.adLocation.company !== result.location.company) {
              // update the database with the new AD values
              Users.update({
                // values
                office: office,
                department: department,
                company: company
              }, {
                // options
                where: {
                  id: userID
                }
              }).then(numAffected => {
                winston.info(`\"Updated ${numAffected} user for ${req.user.dataValues.username}\" - SessionID: ${req.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
                  {username: req.user.dataValues.username, sessionID: req.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'User Admin'});
                return res.json(result);
              }).catch((err) => {
                if (err) {
                  // Not sure how to get an error here. ensureAuthenticated handles invalid users attempting this PUT.
                  // console.log(err);
                  err.route = 'User Admin';
                  err.statusCode = 'UPDATE_USER_AT_LOGIN_BY_ID_ERROR';
                  err.status = 'UPDATE USER AT LOGIN BY ID ERROR';
                  err.shouldRedirect = req.headers['user-agent'].indexOf('Postman') > -1;
                  if (app.get('env') === 'production') {
                    console.log('stack redacted');
                    err.stack = '';// We want to obscure any data the user shouldn't see.
                  }
                  next(err);
                }
              });
            } else {
              return res.json(result);
            }
          }).catch(err => {
            // console.log('ah crap')
            if (typeof req.user.sessionID !== 'undefined') {
              winston.info(`\"Looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
                {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
            } else {
              winston.info(`\"Looking for group for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
                {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
            }
            const promA = Groups.findAll({
              where: {
                active: 1
              },
              include: [{
                model: Roles,
                where: {
                  active: 1
                },
                include: [ RolesPerms ]
              }],
              order: ['displayName']
            });
            const promB = GroupsRoles.findAll();
            // console.error(err);
            // user not found in our DB, but they're in AD; let's check if they belong to an approved group
            Promise.all([promA, promB]).then(responses => {
              // console.log('Response 1', responses[0]);
              // console.log('Response 2', responses[1]);
    
              if (typeof req.user.sessionID !== 'undefined') {
                winston.info(`\"Found group results for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.sessionID} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
                  {username: req.user.sAMAccountName, sessionID: req.user.sessionID, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
              } else {
                winston.info(`\"Found group results for \"${req.user.displayName}\" (${req.user.sAMAccountName})\" - SessionID: ${req.user.dataValues.sid} ${req.ip} \"${req.method} ${req.originalUrl} HTTP/${req.httpVersion}\" \"${req.headers['referer']}\" \"${req.headers['user-agent']}\" \"${req.headers['content-length']}\"`,
                  {username: req.user.sAMAccountName, sessionID: req.user.dataValues.sid, ip: req.ip, referrer: req.headers['referer'], url: req.originalUrl, query: req.method, route: 'Authentication'});
              }
    
              let foundGroup = ''
              let foundRoles = []
              let foundPerms = []
    
              responses[0].forEach(el => {
                // console.log('our el', el)
                // console.log('our group', el.dataValues)
                for (let j = 0; j < adGroups.length; j++) {
                  // console.log('adGroups j', adGroups[j])
                  if (adGroups[j].match(el.dataValues.username)) {
                    // console.log('found it', el.dataValues)
                    userID = el.id
                    foundGroup = el.dataValues.username;
                    foundRoles = el.dataValues;
                    break // TODO allow for membership in multiple groups, like I do multiple roles, below
                  }
                }
              });
    
              foundRoles.roles.forEach(role => {
                // console.log('roles_perms things', role.roles_perms);
                role.roles_perms.forEach(roleP => {
                  if (foundPerms.indexOf(roleP.dataValues.permission_name) === -1) {
                    foundPerms.push(roleP.dataValues.permission_name);
                    userAbilities.push(allAbilities[allAbilities.findIndex(x => x.name === roleP.dataValues.permission_name)]);
                  }
                })
              });
              req.session.rules = userAbilities;
              let location = {
                office: office,
                company: company,
                department: department
              }
              req.session.location = location
    
              return res.json({id: 0, rules: userAbilities, location: location, "Message": "Login Successful via group membership"});
            }).catch(err => {
              console.error('the error ' + err);
              return res.json({"Message": "Login failed. You neither had a user account in SutterNow, nor did you belong to a valid AD Group."})
            });
          });
    
          return null;
          // res.json({"Message":"Login successful"});
      },
        (err, req, res, next) => {
          /* failWithErrors: true, above, makes this section get called to handle the error.
             We don't handle the logging nor the json return here; instead, we setup the error object and pass it on
             to the error handler which does those things. */
          console.log('Authentication failed; passing error on to error handler...');
          err.route = 'Authentication';
          err.statusCode = 401;
          err.status = 401;
          err.shouldRedirect = req.headers['user-agent'].indexOf('Postman') > -1;
          if (typeof req.flash === 'function' && typeof req.flash('error') !== 'undefined' && req.flash('error').length !== 0) {
            err.message = req.flash('error').slice(-1)[0];
            console.log('Flash error ' + req.flash('error').slice(-1)[0]);
            res.statusMessage = req.flash('error').slice(-1)[0];
          }
          if (app.get('env') === 'production') {
            console.log('stack redacted');
            err.stack = '';// We want to obscure any data the user shouldn't see.
          }
          next(err);
          // return res.json({"Message": "Login failed. " + err.message});
          // return null;
        }
      );