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;