import { Issuer } from 'openid-client';
import url from 'url';
import { createAdminUser } from '../utils/admin.js';
import __dirname from '../../dirname-node.js';
import logger from '../../../shared-electron-server/logging.js';
import { initOpaCache } from '../opa/migrations.js';
import { ensureNoBackslash, sortSpaceDelimitedString, retry } from '../utils/utils.js';
export let client;
/**
* creates openid client with library: https://github.com/panva/node-openid-client/blob/main/docs/README.md#client
*
* @param {Object} config - the iam configuration
* @returns {Object} - OpenID Client
*/
const getClient = async (config) => {
try {
const issuer = await Issuer.discover(config.baseAuthUrl);
if (issuer) {
// check if signing algorithm is supported by idp
const issuerTokenAlgs = Array.isArray(issuer.id_token_signing_alg_values_supported)
? issuer.id_token_signing_alg_values_supported
: [];
if (!issuerTokenAlgs.includes(config.tokenSigningAlgorithm)) {
throw new Error('ID token algorithm is not supported by idp.');
}
// check if response type is supported by idp
const issuerRespTypes = Array.isArray(issuer.response_types_supported)
? issuer.response_types_supported
: [];
if (!issuerRespTypes.map(sortSpaceDelimitedString).includes(config.response_type)) {
throw new Error('Response type is not supported by idp.');
}
// check if response mode is supported by idp
const configRespMode = config.response_mode;
const issuerRespModes = Array.isArray(issuer.response_modes_supported)
? issuer.response_modes_supported
: [];
if (configRespMode && !issuerRespModes.includes(configRespMode)) {
throw new Error('Response mode is not supported by idp.');
}
client = new issuer.Client({
client_id: config.clientID,
client_secret: config.clientSecret,
id_token_signed_response_alg: config.tokenSigningAlgorithm,
token_endpoint_auth_method: config.clientAuthMethod,
});
// custom logout endpoint for auth0 because it doesn't correspond to the standard
if (url.parse(issuer.issuer).hostname.match('\\.auth0\\.com$')) {
client.endSessionUrl = (params) => {
const { id_token_hint, post_logout_redirect_uri, ...extraParams } = params;
const parsedUrl = url.parse(`${ensureNoBackslash(issuer.issuer)}/v2/logout`);
parsedUrl.query = {
...extraParams,
returnTo: post_logout_redirect_uri,
client_id: client.client_id,
};
Object.entries(parsedUrl.query).forEach(([key, value]) => {
if (value === null || value === undefined) {
delete parsedUrl.query[key];
}
});
return url.format(parsedUrl);
};
}
// create admin user at idp if enabled in config
if (config.createIdpAdmin) {
await runClientCredentialsFlow(config);
await createAdminUser();
}
// initialize opa cache with data from server
await retry(() => initOpaCache(), undefined, 3);
return client;
}
} catch (e) {
logger.error(e.toString());
throw new Error(e.toString());
}
};
/**
* runs client credentials flow to re-authenticate service account
*
* @param {Object} config - the iam configuration
*/
export const runClientCredentialsFlow = async (config) => {
try {
const tokenSet = await client.grant({
grant_type: 'client_credentials',
scope: config.clientCredentialScope,
});
if (tokenSet) {
client.tokenSet = tokenSet;
}
} catch (e) {
logger.error(e.toString());
throw new Error(e.toString());
}
};
export default getClient;