Source: server/util/AuthenticationHelper/OidcDiscovery.js

import debugLib from 'debug';
import { OIDC_REQUEST_TIMEOUT } from './constants';

const debug = debugLib('SlimmingWorld:AuthenticationHelper');

class OidcDiscovery {
  enabled = true;
  publicKey = null;
  providerConfig = {};

  /**
   *
   * @param oidcConfig
   * @param {boolean} [enabled] When disabled it will return a resolved promise, but doesn't have
   * any data collected. Can be omitted to only provide a publicKey.
   */
  constructor(oidcConfig, enabled = true) {
    this.config = oidcConfig;

    // passed public key, so disabled by default
    if (typeof enabled === 'string') {
      this.enabled = false;
      this.publicKey = enabled;
    } else {
      this.enabled = enabled;
    }

    this.runDiscovery();
  }

  runDiscovery() {
    this.discoverComplete = this.discover().catch(error => {
      if (error.timeout) {
        debug('OIDC endpoint discovery timed out. Retrying...');
        this.runDiscovery();
      } else {
        debug(`OIDC endpoint discovery failed out. Retrying in ${OIDC_REQUEST_TIMEOUT}ms...`);
        setTimeout(() => this.runDiscovery(), OIDC_REQUEST_TIMEOUT);
      }
      throw error;
    });
  }

  discover() {
    if (this.publicKey || !this.enabled) {
      if (this.publicKey) {
        debug(
          `Skipping discovery because publicKey is provided in constructor: \n  ${this.publicKey.replace(
            /[\n]/g,
            '',
          )}`,
        );
      } else if (!this.enabled) {
        debug('Skipping discovery because it was disabled in constructor!');
      }
      return Promise.resolve();
    }

    return getJSON(`${this.config.configuration_endpoint}`)
      .then(response => {
        Object.assign(this.providerConfig, response);
        return getJSON(response.jwks_uri);
      })
      .then(response => {
        Object.assign(this.providerConfig, response);
        return this.generatePEMPublicKey();
      });
  }

  generatePEMPublicKey() {
    if (
      !isNonEmptyArray(this.providerConfig.keys) ||
      !isNonEmptyArray(this.providerConfig.keys[0].x5c)
    ) {
      throw new Error('Could not find keys in configuration endpoint response');
    }

    const x5c = this.providerConfig.keys[0].x5c[0];
    this.publicKey = certToPEM(x5c);

    debug(
      `Using public key from OID configuration endpoint: \n  ${this.publicKey.replace(
        /[\n]/g,
        '',
      )}`,
    );

    return true;
  }
}

export const certToPEM = cert => {
  let pem = cert.match(/.{1,64}/g).join('\n');
  pem = `-----BEGIN CERTIFICATE-----\n${pem}`;
  pem = `${pem}\n-----END CERTIFICATE-----\n`;
  return pem;
};

const isNonEmptyArray = val => !!val && !!val.length;

const getJSON = url =>
  fetch(url, {
    timeout: OIDC_REQUEST_TIMEOUT,
    headers: {
      Accept: 'application/json',
    },
  }).then(res => res.json());

export default OidcDiscovery;