From d1b4cc994bd287af5c8a3114eab70ee01f92b4ec Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 15 Apr 2022 12:56:16 +0200 Subject: anastasis-core: async provider synchronization --- packages/anastasis-core/src/index.ts | 71 ++++++++++++++++++++++------ packages/anastasis-core/src/reducer-types.ts | 8 +++- 2 files changed, 62 insertions(+), 17 deletions(-) (limited to 'packages/anastasis-core/src') diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts index 5dd560a2c..9db152648 100644 --- a/packages/anastasis-core/src/index.ts +++ b/packages/anastasis-core/src/index.ts @@ -94,6 +94,7 @@ import { PolicyMetaInfo, ChallengeInfo, AggregatedPolicyMetaInfo, + AuthenticationProviderStatusMap, } from "./reducer-types.js"; import fetchPonyfill from "fetch-ponyfill"; import { @@ -329,15 +330,9 @@ async function backupEnterUserAttributes( args: ActionArgsEnterUserAttributes, ): Promise { const attributes = args.identity_attributes; - const providerUrls = Object.keys(state.authentication_providers ?? {}); - const newProviders = state.authentication_providers ?? {}; - for (const url of providerUrls) { - newProviders[url] = await getProviderInfo(url); - } const newState = { ...state, backup_state: BackupStates.AuthenticationsEditing, - authentication_providers: newProviders, identity_attributes: attributes, }; return newState; @@ -733,15 +728,23 @@ async function uploadSecret( async function downloadPolicy( state: ReducerStateRecovery, ): Promise { + logger.info("downloading policy"); let foundRecoveryInfo: RecoveryInternalData | undefined = undefined; let recoveryDoc: RecoveryDocument | undefined = undefined; const userAttributes = state.identity_attributes!; if (!state.selected_version) { throw Error("invalid state"); } + // FIXME: Do this concurrently/asynchronously so that one slow provider doens't block us. for (const prov of state.selected_version.providers) { - const pi = state.authentication_providers?.[prov.url]; + let pi = state.authentication_providers?.[prov.url]; if (!pi || pi.status !== "ok") { + // FIXME: this one blocks! + logger.info(`fetching provider info for ${prov.url}`); + pi = await getProviderInfo(prov.url); + } + logger.info(`new provider status is ${pi.status}`); + if (pi.status !== "ok") { continue; } const userId = await userIdentifierDerive(userAttributes, pi.provider_salt); @@ -750,6 +753,9 @@ async function downloadPolicy( reqUrl.searchParams.set("version", `${prov.version}`); const resp = await fetch(reqUrl.href); if (resp.status !== 200) { + logger.info( + `Could not download policy from provider ${prov.url}, status ${resp.status}`, + ); continue; } const body = await resp.arrayBuffer(); @@ -1058,16 +1064,10 @@ async function recoveryEnterUserAttributes( args: ActionArgsEnterUserAttributes, ): Promise { // FIXME: validate attributes - const providerUrls = Object.keys(state.authentication_providers ?? {}); - const newProviders = state.authentication_providers ?? {}; - for (const url of providerUrls) { - newProviders[url] = await getProviderInfo(url); - } const st: ReducerStateRecovery = { ...state, recovery_state: RecoveryStates.SecretSelecting, identity_attributes: args.identity_attributes, - authentication_providers: newProviders, }; return st; } @@ -1514,7 +1514,7 @@ async function nextFromChallengeSelecting( }; } -async function syncProviders( +async function syncAllProvidersTransition( state: ReducerStateRecovery, args: void, ): Promise { @@ -1722,7 +1722,7 @@ const recoveryTransitions: Record< ), ...transition("poll", codecForAny(), pollChallenges), ...transition("next", codecForAny(), nextFromChallengeSelecting), - ...transition("sync_providers", codecForAny(), syncProviders), + ...transition("sync_providers", codecForAny(), syncAllProvidersTransition), }, [RecoveryStates.ChallengeSolving]: { ...transitionRecoveryJump("back", RecoveryStates.ChallengeSelecting), @@ -1746,6 +1746,7 @@ export async function discoverPolicies( const providerUrls = Object.keys(state.authentication_providers || {}); // FIXME: Do we need to re-contact providers here / check if they're disabled? + // FIXME: Do this concurrently and take the first. Otherwise, one provider might block for a long time. for (const providerUrl of providerUrls) { const providerInfo = await getProviderInfo(providerUrl); @@ -1839,3 +1840,43 @@ export async function reduceAction( throw e; } } + +/** + * Update provider status of providers that we still need to contact. + * + * Returns updates as soon as new information about at least one provider + * is found. + * + * Returns an empty object if provider information is complete. + * + * FIXME: Also pass a cancelation token. + */ +export async function completeProviderStatus( + providerMap: AuthenticationProviderStatusMap, +): Promise { + const updateTasks: Promise<[string, AuthenticationProviderStatus]>[] = []; + for (const [provUrl, pi] of Object.entries(providerMap)) { + switch (pi.status) { + case "ok": + case "error": + case "disabled": + default: + continue; + case "not-contacted": + updateTasks.push( + (async () => { + return [provUrl, await getProviderInfo(provUrl)]; + })(), + ); + } + } + + if (updateTasks.length === 0) { + return {}; + } + + const [firstUrl, firstStatus] = await Promise.race(updateTasks); + return { + [firstUrl]: firstStatus, + }; +} diff --git a/packages/anastasis-core/src/reducer-types.ts b/packages/anastasis-core/src/reducer-types.ts index a0af2f86e..2e03a6ec4 100644 --- a/packages/anastasis-core/src/reducer-types.ts +++ b/packages/anastasis-core/src/reducer-types.ts @@ -186,6 +186,10 @@ export interface RecoveryInformation { }[][]; } +export interface AuthenticationProviderStatusMap { + [url: string]: AuthenticationProviderStatus; +} + export interface ReducerStateRecovery { reducer_type: "recovery"; @@ -231,7 +235,7 @@ export interface ReducerStateRecovery { value: string; }; - authentication_providers?: { [url: string]: AuthenticationProviderStatus }; + authentication_providers?: AuthenticationProviderStatusMap; } /** @@ -342,7 +346,7 @@ export interface ReducerStateBackupUserAttributesCollecting selected_country: string; currencies: string[]; required_attributes: UserAttributeSpec[]; - authentication_providers: { [url: string]: AuthenticationProviderStatus }; + authentication_providers: AuthenticationProviderStatusMap; } export interface ActionArgsEnterUserAttributes { -- cgit v1.2.3