Source: app/util/setupInjects.js

/* global WP_DEFINE_IS_NODE */
import { captureXhrError } from './raven/raven-client';
import authenticate from './auth/authenticate';
import serviceConfig from '../config/service.configdefinitions';
import Gateway from '../net/gateway/Gateway';
import RESTOutputHandler from '../net/gateway/output/RESTOutputHandler';
import RESTInputHandler from '../net/gateway/input/RESTInputHandler';
import ClientAuthenticationManager from '../../client/util/auth/ClientAuthenticationManager';
import { setAuthTokens } from '../actions/authenticationActions';
import { setValue } from './injector';
import {
  AUTHENTICATION_MANAGER,
  GATEWAY_ACCOUNT,
  GATEWAY_ACCOUNT_MIGRATION,
  GATEWAY_ACCOUNT_AUTH,
  GATEWAY_ACCOUNT_IDS,
  GATEWAY_COMMUNITY_AUTH,
  GATEWAY_COMMUNITY_V2_AUTH,
  GATEWAY_COMMUNITY_V3_AUTH,
  GATEWAY_CONTENT,
  GATEWAY_CONTENT_AUTH,
  GATEWAY_SELF,
  GATEWAY_SHOP,
  GATEWAY_LIVE_AUTH,
  GATEWAY_SHOP_AUTH,
  GATEWAY_DEAL,
  GATEWAY_DEAL_AUTH,
  GATEWAY_FOOD,
  GATEWAY_FOOD_AUTH,
  GATEWAY_PAYMENT_AUTH,
  GATEWAY_MESSAGE_AUTH,
  GATEWAY_ACTIVITY,
  GATEWAY_ACTIVITY_AUTH,
  GATEWAY_GROUP_ACCOUNT,
  GATEWAY_GROUP_ACCOUNT_AUTH,
  GATEWAY_GROUP_SEARCH,
  GATEWAY_COMMUNITY,
  GATEWAY_MIGRATION_AUTH,
  GATEWAY_PUBLICITY_AUTH,
  GATEWAY_ADVERTISEMENT,
  GATEWAY_ACCOUNT_WITHOUT_PATH,
} from '../data/Injectables';
import { getClientCredentialsToken } from '../../server/util/clientCredentialsManagement';
import WebHosts from '../data/enum/WebHost';

const DEFAULT_CONTENT_MAX_AGE = 60 * 60; // 1 hour in seconds

/**
 * Disable SSR cache for public
 * @returns {boolean}
 */
const setUseCache = () => {
  let useCache = true;
  if (WP_DEFINE_IS_NODE && serviceConfig.webHost === WebHosts.PUBLIC) {
    useCache = false;
  }

  return useCache;
};

const addAuthHeaders = options => {
  if (!options.getState) {
    throw new Error(
      `When using the authentication hook on the Gateway, you should pass the setState to the options when doing the Gateway call. Check the action that executes this API call: ${options.url}`,
    );
  }

  return authenticate().then(accessToken => {
    const originalHeaders = options.headers || {};
    // eslint-disable-next-line no-param-reassign
    options.headers = {
      ...originalHeaders,
      Authorization: `Bearer ${accessToken}`,
    };
  });
};

const addClientCredentialsHeaders = (options, config) => {
  // options parameter = options for a specific request as passed to the gateway
  // these come from for example apiRequest.js
  // eslint-disable-next-line no-underscore-dangle
  const userPermissionState = options.getState().authentication?.userPermissionState;

  if (WP_DEFINE_IS_NODE && userPermissionState) {
    // eslint-disable-next-line camelcase
    const { client_id, client_secret } = config.oidc.clientCredentials[serviceConfig.webHost];

    const configParams = {
      client_id,
      client_secret,
      subscriptionType: userPermissionState.subscriptionType,
      configuration_endpoint: `${config.oidc.authority}/.well-known/openid-configuration`,
    };

    return getClientCredentialsToken(configParams).then(accessToken => {
      const originalHeaders = options.headers || {};
      if (accessToken) {
        // eslint-disable-next-line no-param-reassign
        options.headers = {
          ...originalHeaders,
          Authorization: `Bearer ${accessToken}`,
        };
      }
    });
  }

  // NOT NODE (browser)
  return addAuthHeaders(options);
};

/**
 * Sets up the injects for use in the project.
 * This is done in a specific time in the startup flow where the required information is available,
 * but before any of the values are used.
 *
 * @function setupInjects
 * @param config {any} Config object with API information
 * @param dispatch {function} The redux store dispatch function
 * @param clientCredentialsManagerInstance {any} The instance of the ClientCredentialsManager
 */
const setupInjects = ({ config, dispatch }) => {
  const baseGatewayConfig = {
    mode: 'cors', // cors, no-cors, or same-origin
    outputHandler: new RESTOutputHandler(),
    inputHandler: new RESTInputHandler(),
    onError(error) {
      captureXhrError(error);
    },
  };

  const gateways = [
    { name: GATEWAY_ACCOUNT, api: config.api.account, auth: false },
    { name: GATEWAY_ACCOUNT_WITHOUT_PATH, api: config.api.account_without_path, auth: false },
    { name: GATEWAY_ACCOUNT_MIGRATION, api: config.api.account_migration, auth: false },
    {
      name: GATEWAY_ACCOUNT_IDS,
      api: config.api.account,
      auth: false,
      options: {
        url: config.api.account.host,
        credentials: 'include',
      },
    },
    {
      name: GATEWAY_CONTENT,
      api: config.api.content,
      auth: false,
      options: {
        useCache: setUseCache(),
        defaultMaxAge: DEFAULT_CONTENT_MAX_AGE,
      },
    },
    { name: GATEWAY_DEAL, api: config.api.deal, auth: false },
    { name: GATEWAY_FOOD, api: config.api.food, auth: false },
    { name: GATEWAY_ACTIVITY, api: config.api.activity, auth: false },
    { name: GATEWAY_SHOP, api: config.api.shop, auth: false },
    { name: GATEWAY_GROUP_ACCOUNT, api: config.api.groupAccount, auth: false },
    {
      name: GATEWAY_GROUP_SEARCH,
      api: config.api.groupSearch,
      auth: false,
      options: {
        useCache: true,
        defaultMaxAge: DEFAULT_CONTENT_MAX_AGE,
      },
    },
    { name: GATEWAY_COMMUNITY, api: config.api.community, auth: false },
    { name: GATEWAY_ADVERTISEMENT, api: config.api.ads, auth: false },
  ];

  const authGateways = [
    { name: GATEWAY_ACCOUNT_AUTH, api: config.api.account, auth: true },
    {
      name: GATEWAY_CONTENT_AUTH,
      api: config.api.content,
      clientCredentials: true,
      options: {
        useCache: setUseCache(),
        defaultMaxAge: DEFAULT_CONTENT_MAX_AGE,
      },
    },
    { name: GATEWAY_COMMUNITY_AUTH, api: config.api.community, auth: true },
    { name: GATEWAY_COMMUNITY_V2_AUTH, api: config.api.community_v2, auth: true },
    { name: GATEWAY_COMMUNITY_V3_AUTH, api: config.api.community_v3, auth: true },
    { name: GATEWAY_LIVE_AUTH, api: config.api.live, auth: true },
    { name: GATEWAY_SHOP_AUTH, api: config.api.shop, auth: true },
    { name: GATEWAY_DEAL_AUTH, api: config.api.deal, auth: true },
    { name: GATEWAY_FOOD_AUTH, api: config.api.food, auth: true },
    { name: GATEWAY_PAYMENT_AUTH, api: config.api.payment, auth: true },
    { name: GATEWAY_MESSAGE_AUTH, api: config.api.message, auth: true },
    { name: GATEWAY_GROUP_ACCOUNT_AUTH, api: config.api.groupAccount, auth: true },
    { name: GATEWAY_ACTIVITY_AUTH, api: config.api.activity, auth: true },
    { name: GATEWAY_MIGRATION_AUTH, api: config.api.migration, auth: true },
    { name: GATEWAY_PUBLICITY_AUTH, api: config.api.publicity, auth: true },
  ];

  if (!WP_DEFINE_IS_NODE) {
    const gatewaySelf = new Gateway({
      url: `${window.location.protocol}//${window.location.host}`,
      outputHandler: new RESTOutputHandler(),
      inputHandler: new RESTInputHandler(),
    });
    setValue(GATEWAY_SELF, gatewaySelf);
  }

  // auth gateways make use of the authenticationManager, so can not be used in normal cases
  if (!WP_DEFINE_IS_NODE && serviceConfig.useClientAuthentication) {
    const authenticationManager = new ClientAuthenticationManager(config.oidc);
    authenticationManager.on('authtoken', ({ idToken, accessToken }) =>
      dispatch(setAuthTokens(idToken, accessToken)),
    );
    setValue(AUTHENTICATION_MANAGER, authenticationManager);

    gateways.push(...authGateways);
  }

  // auth gateways make use of the authenticationManager, so can not be used in normal cases
  if (WP_DEFINE_IS_NODE && serviceConfig.useServerAuthentication) {
    gateways.push(...authGateways);
  }

  const authOptions = {
    beforeRequest(options) {
      return addAuthHeaders(options);
    },
  };

  const clientCredentialsOptions = {
    beforeRequest(options) {
      return addClientCredentialsHeaders(options, config);
    },
  };

  gateways.forEach(gateway => {
    const gatewayInstance = new Gateway({
      ...baseGatewayConfig,
      ...(gateway.auth ? authOptions : {}),
      ...(gateway.clientCredentials ? clientCredentialsOptions : {}),
      url: `${gateway.api.host}${gateway.api.path}`,
      ...gateway.options,
    });

    if (gateway.auth) {
      // this flag is set to facilitate the forceFailOnServerAuthGateway option
      gatewayInstance.authEnabled = true;
    }
    setValue(gateway.name, gatewayInstance);
  });
};

export default setupInjects;