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.

120 lines (101 loc) 3.23 kB
import { AuthorizationProvider, RequestFailedError } from 'scrivito_sdk/client'; import { parseErrorResponse } from 'scrivito_sdk/client/parse_error_response'; import { Verification, VerificationForChallenge, Verificator, fetch as fetchVerificatorFunction, } from 'scrivito_sdk/client/verificator_functions'; import { onReset, promiseAndFinally, registerAsyncTask, } from 'scrivito_sdk/common'; export const ERROR_CODE_CLIENT_VERIFICATION_REQUIRED = 'client_verification_required'; export interface Challenge { verificator: Verificator; data: unknown; } interface CurrentComputation { challenge: Challenge; promise: Promise<Verification>; } let computation: CurrentComputation | undefined; let verification: Verification | undefined; export const PublicAuthentication: AuthorizationProvider = { async authorize( request: (authorization: string | undefined) => Promise<Response> ): Promise<Response> { const response = await request(currentAuthorization()); if (response.status === 401) { // response.text is a macrotask in firefox. // it needs to be registered explicitly, to work with flushPromises. const responseText = await registerAsyncTask(() => response.clone().text() ); const { details, code } = parseErrorResponse(responseText); if (code === ERROR_CODE_CLIENT_VERIFICATION_REQUIRED) { if (!isChallenge(details)) { throw new RequestFailedError('verification without challenge'); } verification = await computeVerification(details); return this.authorize(request); } } return response; }, // integration test support currentState(): string | null { const authorization = currentAuthorization(); if (authorization) { return `Authorization: ${authorization}`; } if (computation) { const challenge = computation.challenge; return `Pending computation: ${challenge.verificator.id} with ${String( challenge.data )}`; } return null; }, }; async function computeVerification( challenge: Challenge ): Promise<Verification> { if (!computation) { // note that further request's challenges are ignored (intentionally) const { verificator, data } = challenge; const promise = (async () => { const compute: VerificationForChallenge = await fetchVerificatorFunction( verificator.id, verificator.url ); return new Promise<Verification>((resolve) => { compute(data, (result: Verification) => resolve(result)); }); })(); computation = { challenge: { verificator, data }, promise: promiseAndFinally<Verification>(promise, () => { computation = undefined; }), }; } return computation.promise; } function isChallenge(maybeChallenge: object): maybeChallenge is Challenge { return !!(maybeChallenge as Challenge).verificator; } function currentAuthorization(): string | undefined { if (!verification) return; if (verification.expiresAfter < new Date()) { verification = undefined; return; } return verification.authorization; } onReset(() => { computation = undefined; verification = undefined; });