UNPKG

@zeushq/nextjs-zidentity

Version:

Next.js SDK for signin in with Zeus Identity

138 lines (120 loc) 4.6 kB
import { Issuer, custom, HttpOptions, Client, EndSessionParameters } from 'openid-client'; import url, { UrlObject } from 'url'; import urlJoin from 'url-join'; import createDebug from './utils/debug'; import { Config } from './config'; const debug = createDebug('client'); export interface ClientFactory { (): Promise<Client>; } export type Telemetry = { name: string; version: string; }; function sortSpaceDelimitedString(str: string): string { return str.split(' ').sort().join(' '); } // Issuer.discover throws an `AggregateError` in some cases, this error includes the stack trace in the // message which causes the stack to be exposed when reporting the error in production. Am using the non standard // `_errors` property to identify the polyfilled `AggregateError` // See https://github.com/sindresorhus/aggregate-error/issues/4#issuecomment-488356468 function normalizeAggregateError(e: Error | (Error & { _errors: Error[] })): Error { if ('_errors' in e) { return e._errors[0]; } return e; } export default function get(config: Config, { name, version }: Telemetry): ClientFactory { let client: Client | null = null; return async (): Promise<Client> => { if (client) { return client; } const defaultHttpOptions = (options: HttpOptions): HttpOptions => ({ ...options, headers: { ...options.headers, 'User-Agent': `${name}/${version}`, ...(config.enableTelemetry ? { 'ZIdentity-Client': Buffer.from( JSON.stringify({ name, version, env: { node: process.version } }) ).toString('base64') } : undefined) }, timeout: config.httpTimeout }); const applyHttpOptionsCustom = (entity: Issuer<Client> | typeof Issuer | Client): void => { // eslint-disable-next-line no-param-reassign entity[custom.http_options] = defaultHttpOptions; }; applyHttpOptionsCustom(Issuer); let issuer: Issuer<Client>; try { issuer = await Issuer.discover(config.issuerBaseURL); } catch (e: any) { throw normalizeAggregateError(e); } applyHttpOptionsCustom(issuer); const issuerTokenAlgs = Array.isArray(issuer.id_token_signing_alg_values_supported) ? issuer.id_token_signing_alg_values_supported : []; if (!issuerTokenAlgs.includes(config.idTokenSigningAlg)) { debug( 'ID token algorithm %o is not supported by the issuer. Supported ID token algorithms are: %o.', config.idTokenSigningAlg, issuerTokenAlgs ); } const configRespType = sortSpaceDelimitedString(config.authorizationParams.response_type); const issuerRespTypes = Array.isArray(issuer.response_types_supported) ? issuer.response_types_supported : []; issuerRespTypes.map(sortSpaceDelimitedString); if (!issuerRespTypes.includes(configRespType)) { debug( 'Response type %o is not supported by the issuer. Supported response types are: %o.', configRespType, issuerRespTypes ); } const configRespMode = config.authorizationParams.response_mode; const issuerRespModes = Array.isArray(issuer.response_modes_supported) ? issuer.response_modes_supported : []; if (configRespMode && !issuerRespModes.includes(configRespMode)) { debug( 'Response mode %o is not supported by the issuer. Supported response modes are %o.', configRespMode, issuerRespModes ); } client = new issuer.Client({ client_id: config.clientID, client_secret: config.clientSecret, id_token_signed_response_alg: config.idTokenSigningAlg }); applyHttpOptionsCustom(client); client[custom.clock_tolerance] = config.clockTolerance; if (config.idpLogout && !issuer.end_session_endpoint) { if (config.zIdentityLogout || (url.parse(issuer.metadata.issuer).hostname as string).match('\\.zeusdev\\.io$')) { Object.defineProperty(client, 'endSessionUrl', { value(params: EndSessionParameters) { const parsedUrl = url.parse(urlJoin(issuer.metadata.issuer, '/oauth/logout')); (parsedUrl as UrlObject).query = { returnTo: params.post_logout_redirect_uri, client_id: config.clientID }; return url.format(parsedUrl); } }); } else { debug('the issuer does not support RP-Initiated Logout'); } } return client; }; }