<commit_before>import debugLib from 'debug';
import config from 'config';
import jwt from 'jsonwebtoken';
import passport from 'passport';
import request from 'request-promise';
import { get, omitBy, isNil } from 'lodash';
import { URLSearchParams } from 'url';

import models from '../models';
import errors from '../lib/errors';
import logger from '../lib/logger';
import paymentProviders from '../paymentProviders';
import * as connectedAccounts from '../controllers/connectedAccounts';

const { User } = models;

const { BadRequest, CustomError, Unauthorized } = errors;

const { jwtSecret } = config.keys.opencollective;

const debug = debugLib('auth');

/**
 * Middleware related to authentication.
 *
 * Identification is provided through two vectors:
 * - api_key URL parameter which uniquely identifies an application
 * - JSON web token JWT payload which contains 3 items:
 *   - sub: user ID
 *   - scope: user scope (e.g. 'subscriptions')
 *
 * Thus:
 * - a user is identified with a JWT
 */

/**
 * Express-jwt will either force all routes to have auth and throw
 * errors for public routes. Or authorize all the routes and not throw
 * expirations errors. This is a cleaned up version of that code that only
 * decodes the token (expected behaviour).
 */
export const parseJwtNoExpiryCheck = (req, res, next) => {
  let token = req.params.access_token || req.query.access_token || req.body.access_token;
  if (!token) {
    const header = req.headers && req.headers.authorization;
    if (!header) {
      return next();
    }

    const parts = header.split(' ');
    const scheme = parts[0];
    token = parts[1];
    if (!/^Bearer$/i.test(scheme) || !token) {
      return next(new BadRequest('Format is Authorization: Bearer [token]'));
    }
  }

  jwt.verify(token, jwtSecret, (err, decoded) => {
    // JWT library either returns an error or the decoded version
    if (err && err.name === 'TokenExpiredError') {
      req.jwtExpired = true;
      req.jwtPayload = jwt.decode(token, jwtSecret); // we need to decode again
    } else if (err) {
      return next(new BadRequest(err.message));
    } else {
      req.jwtPayload = decoded;
    }

    return next();
  });
};

export const checkJwtExpiry = (req, res, next) => {
  if (req.jwtExpired) {
    return next(new CustomError(401, 'jwt_expired', 'jwt expired'));
  }

  return next();
};

/**
 * Authenticate the user using the JWT token and populates:
 *  - req.remoteUser
 *  - req.remoteUser.memberships[CollectiveId] = [roles]
 */
export const _authenticateUserByJwt = async (req, res, next) => {
  if (!req.jwtPayload) {
    return next();
  }

  const userId = Number(req.jwtPayload.sub);
  const user = await User.findByPk(userId);
  if (!user) {
    throw errors.Unauthorized(`User id ${userId} not found`);
  }

  /**
   * Functionality for one-time login links. We check that the lastLoginAt
   * in the JWT matches the lastLoginAt in the db. If so, we allow the user
   * to log in, and update the lastLoginAt.
   */
  if (req.jwtPayload.scope === 'login') {
    if (user.lastLoginAt) {
      if (!req.jwtPayload.lastLoginAt) {<commit_msg>refactor(authentication): don't throw error in _authenticateUserByJwt<commit_after>import debugLib from 'debug';
import config from 'config';
import jwt from 'jsonwebtoken';
import passport from 'passport';
import request from 'request-promise';
import { get, omitBy, isNil } from 'lodash';
import { URLSearchParams } from 'url';

import models from '../models';
import errors from '../lib/errors';
import logger from '../lib/logger';
import paymentProviders from '../paymentProviders';
import * as connectedAccounts from '../controllers/connectedAccounts';

const { User } = models;

const { BadRequest, CustomError, Unauthorized } = errors;

const { jwtSecret } = config.keys.opencollective;

const debug = debugLib('auth');

/**
 * Middleware related to authentication.
 *
 * Identification is provided through two vectors:
 * - api_key URL parameter which uniquely identifies an application
 * - JSON web token JWT payload which contains 3 items:
 *   - sub: user ID
 *   - scope: user scope (e.g. 'subscriptions')
 *
 * Thus:
 * - a user is identified with a JWT
 */

/**
 * Express-jwt will either force all routes to have auth and throw
 * errors for public routes. Or authorize all the routes and not throw
 * expirations errors. This is a cleaned up version of that code that only
 * decodes the token (expected behaviour).
 */
export const parseJwtNoExpiryCheck = (req, res, next) => {
  let token = req.params.access_token || req.query.access_token || req.body.access_token;
  if (!token) {
    const header = req.headers && req.headers.authorization;
    if (!header) {
      return next();
    }

    const parts = header.split(' ');
    const scheme = parts[0];
    token = parts[1];
    if (!/^Bearer$/i.test(scheme) || !token) {
      return next(new BadRequest('Format is Authorization: Bearer [token]'));
    }
  }

  jwt.verify(token, jwtSecret, (err, decoded) => {
    // JWT library either returns an error or the decoded version
    if (err && err.name === 'TokenExpiredError') {
      req.jwtExpired = true;
      req.jwtPayload = jwt.decode(token, jwtSecret); // we need to decode again
    } else if (err) {
      return next(new BadRequest(err.message));
    } else {
      req.jwtPayload = decoded;
    }

    return next();
  });
};

export const checkJwtExpiry = (req, res, next) => {
  if (req.jwtExpired) {
    return next(new CustomError(401, 'jwt_expired', 'jwt expired'));
  }

  return next();
};

/**
 * Authenticate the user using the JWT token and populates:
 *  - req.remoteUser
 *  - req.remoteUser.memberships[CollectiveId] = [roles]
 */
export const _authenticateUserByJwt = async (req, res, next) => {
  if (!req.jwtPayload) {
    next();
    return;
  }

  const userId = Number(req.jwtPayload.sub);
  const user = await User.findByPk(userId);
  if (!user) {
    logger.warn(`User id ${userId} not found`);
    next();
    return;
  }

  /**
   * Functionality for one-time login links. We check that the lastLoginAt
   * in the JWT matches the lastLoginAt in the db. If so, we allow the user
   * to log in, and update the lastLoginAt.
   */
  if (req.jwtPayload.scope === 'login') {
    if (user.lastLoginAt) {
      if (!req.jwtPayload.lastLoginAt) {
{'fraction_non_alphanumeric': 0.1269966787917128, 'fraction_numerical': 0.0018978333069745374, 'mean_word_length': 3.4008350730688934, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4721228', 'n_tokens_mistral': 1989, 'n_tokens_neox': 1942, 'n_words': 646}