UNPKG

scrivito

Version:

Scrivito is a professional, yet easy to use SaaS Enterprise Content Management Service, built for digital agencies and medium to large businesses. It is completely maintenance-free, cost-effective, and has unprecedented performance and security.

82 lines (68 loc) 2.52 kB
import { AuthorizationProvider, ClientError, ClientErrorRequestDetails, } from 'scrivito_sdk/client'; import { ExponentialBackoff } from 'scrivito_sdk/client/exponential_backoff'; import { isHighSecurityAction } from 'scrivito_sdk/client/is_high_security_action'; import { ScrivitoError } from 'scrivito_sdk/common'; export class TokenAuthorizationError extends ScrivitoError { constructor( readonly message: string, readonly code: string | undefined, readonly httpStatus: number, readonly requestDetails: ClientErrorRequestDetails = {} ) { super(message); } } export class TokenAuthorizationProvider implements AuthorizationProvider { private fetchTokenPromise?: Promise<string | null>; constructor(private fetchToken: () => Promise<string | null>) {} async authorize( request: (auth?: string) => Promise<Response> ): Promise<Response> { const backoff = new ExponentialBackoff(); let fetchedTokenBefore = false; // note: using a loop instead of recursion avoids stack overflow // eslint-disable-next-line no-constant-condition while (true) { if (!this.fetchTokenPromise) { this.fetchTokenPromise = (async () => { if (fetchedTokenBefore) await backoff.nextDelay(); fetchedTokenBefore = true; try { return await this.fetchToken(); } catch (error) { if (error instanceof ClientError && error.httpStatus === 404) { throw new TokenAuthorizationError( error.message, error.code, error.httpStatus, error.requestDetails ); } throw error; } })(); } const tokenPromise = this.fetchTokenPromise; const token = await tokenPromise; const response = token === null ? await request() : await request(`Bearer ${token}`); // IAM always responds with a 401 status code if a new token is needed. // Any other status code indicates that something went wrong. if (response.status !== 401) return response; if (await isHighSecurityAction(response)) return response; // is token renewal already in progress? (concurrency) if (tokenPromise === this.fetchTokenPromise) { // if not: trigger renewal this.fetchTokenPromise = undefined; } } } /** for test purposes */ injectToken(token: string): void { this.fetchTokenPromise = Promise.resolve(token); } }