anastasis-core: async provider synchronization
This commit is contained in:
parent
098d1eb7eb
commit
d1b4cc994b
@ -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<ReducerStateBackup> {
|
||||
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<ReducerStateRecovery | ReducerStateError> {
|
||||
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<ReducerStateRecovery | ReducerStateError> {
|
||||
// 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<ReducerStateRecovery | ReducerStateError> {
|
||||
@ -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<AuthenticationProviderStatusMap> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -21,6 +21,7 @@ import { TalerErrorCode } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
AggregatedPolicyMetaInfo,
|
||||
BackupStates,
|
||||
completeProviderStatus,
|
||||
discoverPolicies,
|
||||
DiscoveryCursor,
|
||||
getBackupStartState,
|
||||
@ -206,6 +207,44 @@ export function useAnastasisReducer(): AnastasisReducerApi {
|
||||
console.log(e);
|
||||
}
|
||||
setAnastasisStateInternal(newState);
|
||||
|
||||
const tryUpdateProviders = () => {
|
||||
const reducerState = newState.reducerState;
|
||||
if (
|
||||
reducerState?.reducer_type !== "backup" &&
|
||||
reducerState?.reducer_type !== "recovery"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const provMap = reducerState.authentication_providers;
|
||||
if (!provMap) {
|
||||
return;
|
||||
}
|
||||
const doUpdate = async () => {
|
||||
const updates = await completeProviderStatus(provMap);
|
||||
if (Object.keys(updates).length === 0) {
|
||||
return;
|
||||
}
|
||||
console.log("got provider updates", updates);
|
||||
const rs2 = reducerState;
|
||||
if (rs2.reducer_type !== "backup" && rs2.reducer_type !== "recovery") {
|
||||
return;
|
||||
}
|
||||
setAnastasisState({
|
||||
...anastasisState,
|
||||
reducerState: {
|
||||
...rs2,
|
||||
authentication_providers: {
|
||||
...rs2.authentication_providers,
|
||||
...updates,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
doUpdate().catch((e) => console.log(e));
|
||||
};
|
||||
|
||||
tryUpdateProviders();
|
||||
};
|
||||
|
||||
async function doTransition(action: string, args: any): Promise<void> {
|
||||
|
@ -37,7 +37,6 @@ export function ContinentSelectionScreen(): VNode {
|
||||
if (!theCountry) return;
|
||||
reducer.transition("select_country", {
|
||||
country_code: countryCode,
|
||||
currencies: [theCountry.currency],
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -61,7 +61,6 @@ import {
|
||||
getRetryDuration,
|
||||
resetRetryInfo,
|
||||
RetryInfo,
|
||||
updateRetryInfoTimeout,
|
||||
} from "../util/retries.js";
|
||||
import {
|
||||
getExchangeDetails,
|
||||
|
Loading…
Reference in New Issue
Block a user