Source: server/util/clientCredentialsManagement.js

/* eslint-disable import/prefer-default-export */
import fetch from 'node-fetch';
import stringify from 'qs/lib/stringify';
import debugLib from 'debug';
import jwtDecode from 'jwt-decode';
import { OIDC_REQUEST_TIMEOUT } from './AuthenticationHelper/constants';
import serviceConfig from '../../app/config/service.configdefinitions';
import OidcDiscovery from './AuthenticationHelper/OidcDiscovery';

const debug = debugLib('SlimmingWorld:clientCredentialsManagement');
const EXPIRATION_TIME_LEEWAY = 10000;

function getOidcDiscovery(config) {
  return new OidcDiscovery(
    config,
    serviceConfig.useServerAuthentication || serviceConfig.useOidcDiscovery,
  );
}

/**
 * To memoize the token for caching purposes
 */
class TokenCache {
  constructor() {
    this.token = null;
    this.expiration = null;
    this.credentials = null;
    this.tokenRequest = null;
  }

  get currentToken() {
    if (this.token === null || this.isExpired) {
      return null;
    }

    return this.token;
  }

  get isExpired() {
    return this.expiration * 1000 - Date.now() < EXPIRATION_TIME_LEEWAY;
  }

  get currentCredentials() {
    return this.credentials;
  }
}

const tokenCacheMap = {};

function getTokenCacheFor(subscriptionType) {
  if (!tokenCacheMap[subscriptionType]) {
    tokenCacheMap[subscriptionType] = new TokenCache();
  }
  return tokenCacheMap[subscriptionType];
}

async function requestClientCredentialsToken({
  /* eslint-disable */
  grant_type,
  client_id,
  client_secret,
  configuration_endpoint,
  /* eslint-enable */
  subscriptionType,
}) {
  const tokenCache = getTokenCacheFor(subscriptionType);

  const oidcDiscovery = getOidcDiscovery({
    client_id,
    client_secret,
    configuration_endpoint,
  });

  await oidcDiscovery.discoverComplete;
  const res = await fetch(oidcDiscovery.providerConfig.token_endpoint, {
    method: 'POST',
    body: stringify({
      grant_type: 'client_credentials',
      client_id,
      client_secret,
      subscriptionType,
    }),
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    timeout: OIDC_REQUEST_TIMEOUT,
  });

  const { access_token: accessToken } = await res.json();

  try {
    const expireAt = jwtDecode(accessToken).exp;
    tokenCache.token = accessToken;
    tokenCache.expiration = expireAt;
    /* eslint-disable-next-line camelcase */
    tokenCache.credentials = `${client_id}_${client_secret}`;

    return accessToken;
  } catch (e) {
    tokenCache.tokenRequest = null;
    throw new Error('requestClientCredentialsToken accessToken undefined');
  }
}

// eslint-disable-next-line import/prefer-default-export
export async function getClientCredentialsToken({
  /* eslint-disable */
  grant_type,
  client_id,
  client_secret,
  configuration_endpoint,
  /* eslint-enable */
  subscriptionType,
}) {
  const tokenCache = getTokenCacheFor(subscriptionType);

  if (tokenCache.tokenRequest) {
    debug('Client credential tokenRequest exist');
    return tokenCache.tokenRequest;
  }

  if (
    tokenCache.currentToken &&
    /* eslint-disable-next-line camelcase */
    tokenCache?.currentCredentials === `${client_id}_${client_secret}`
  ) {
    debug('Client credential token exist');
    return tokenCache.currentToken;
  }

  debug('Request client credential token');
  tokenCache.tokenRequest = requestClientCredentialsToken({
    /* eslint-disable */
    grant_type,
    client_id,
    client_secret,
    configuration_endpoint,
    /* eslint-enable */
    subscriptionType,
  });
  await tokenCache.tokenRequest;
  tokenCache.tokenRequest = null;
  return tokenCache.currentToken;
}