Impersonation is only safe if there are things it cannot do. The hard part is choosing where the line sits: block too much and support becomes useless, block too little and an operator can quietly take over an account.

The line here is drawn at identity and credential changes. Everyday work — properties, units, the actual reason support logged in — stays available:

import AppError from "../../../middlewares/appError.js";

// Reusable guard for routes that shouldn't be touchable while HQLead is
// wearing someone else's identity — credential/identity-security actions
// (MFA enable/disable/etc.) specifically, not everyday actions like
// editing properties, which is the actual point of impersonation.
export function makeBlockDuringImpersonation() {
  return function blockDuringImpersonation(req, res, next) {
    if (req.user?.impersonatedBy) {
      throw new AppError(
        "This action is not available during an impersonated session",
        403
      );
    }
    next();
  };
}

It is deliberately tiny, because it is applied as ordinary middleware on exactly the four MFA-mutating routes rather than being woven into each handler. The JWT carries an impersonatedBy claim; if it is present, this refuses.

The surrounding design matters as much as the code: the impersonation token is a plain 15-minute JWT with no refresh token, so a borrowed identity cannot renew itself. Stopping deletes the Redis key, which kills the session immediately even though the JWT signature stays cryptographically valid for a while longer.