anastasis-core: async provider synchronization
This commit is contained in:
parent
098d1eb7eb
commit
d1b4cc994b
@ -94,6 +94,7 @@ import {
|
|||||||
PolicyMetaInfo,
|
PolicyMetaInfo,
|
||||||
ChallengeInfo,
|
ChallengeInfo,
|
||||||
AggregatedPolicyMetaInfo,
|
AggregatedPolicyMetaInfo,
|
||||||
|
AuthenticationProviderStatusMap,
|
||||||
} from "./reducer-types.js";
|
} from "./reducer-types.js";
|
||||||
import fetchPonyfill from "fetch-ponyfill";
|
import fetchPonyfill from "fetch-ponyfill";
|
||||||
import {
|
import {
|
||||||
@ -329,15 +330,9 @@ async function backupEnterUserAttributes(
|
|||||||
args: ActionArgsEnterUserAttributes,
|
args: ActionArgsEnterUserAttributes,
|
||||||
): Promise<ReducerStateBackup> {
|
): Promise<ReducerStateBackup> {
|
||||||
const attributes = args.identity_attributes;
|
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 = {
|
const newState = {
|
||||||
...state,
|
...state,
|
||||||
backup_state: BackupStates.AuthenticationsEditing,
|
backup_state: BackupStates.AuthenticationsEditing,
|
||||||
authentication_providers: newProviders,
|
|
||||||
identity_attributes: attributes,
|
identity_attributes: attributes,
|
||||||
};
|
};
|
||||||
return newState;
|
return newState;
|
||||||
@ -733,15 +728,23 @@ async function uploadSecret(
|
|||||||
async function downloadPolicy(
|
async function downloadPolicy(
|
||||||
state: ReducerStateRecovery,
|
state: ReducerStateRecovery,
|
||||||
): Promise<ReducerStateRecovery | ReducerStateError> {
|
): Promise<ReducerStateRecovery | ReducerStateError> {
|
||||||
|
logger.info("downloading policy");
|
||||||
let foundRecoveryInfo: RecoveryInternalData | undefined = undefined;
|
let foundRecoveryInfo: RecoveryInternalData | undefined = undefined;
|
||||||
let recoveryDoc: RecoveryDocument | undefined = undefined;
|
let recoveryDoc: RecoveryDocument | undefined = undefined;
|
||||||
const userAttributes = state.identity_attributes!;
|
const userAttributes = state.identity_attributes!;
|
||||||
if (!state.selected_version) {
|
if (!state.selected_version) {
|
||||||
throw Error("invalid state");
|
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) {
|
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") {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
const userId = await userIdentifierDerive(userAttributes, pi.provider_salt);
|
const userId = await userIdentifierDerive(userAttributes, pi.provider_salt);
|
||||||
@ -750,6 +753,9 @@ async function downloadPolicy(
|
|||||||
reqUrl.searchParams.set("version", `${prov.version}`);
|
reqUrl.searchParams.set("version", `${prov.version}`);
|
||||||
const resp = await fetch(reqUrl.href);
|
const resp = await fetch(reqUrl.href);
|
||||||
if (resp.status !== 200) {
|
if (resp.status !== 200) {
|
||||||
|
logger.info(
|
||||||
|
`Could not download policy from provider ${prov.url}, status ${resp.status}`,
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const body = await resp.arrayBuffer();
|
const body = await resp.arrayBuffer();
|
||||||
@ -1058,16 +1064,10 @@ async function recoveryEnterUserAttributes(
|
|||||||
args: ActionArgsEnterUserAttributes,
|
args: ActionArgsEnterUserAttributes,
|
||||||
): Promise<ReducerStateRecovery | ReducerStateError> {
|
): Promise<ReducerStateRecovery | ReducerStateError> {
|
||||||
// FIXME: validate attributes
|
// 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 = {
|
const st: ReducerStateRecovery = {
|
||||||
...state,
|
...state,
|
||||||
recovery_state: RecoveryStates.SecretSelecting,
|
recovery_state: RecoveryStates.SecretSelecting,
|
||||||
identity_attributes: args.identity_attributes,
|
identity_attributes: args.identity_attributes,
|
||||||
authentication_providers: newProviders,
|
|
||||||
};
|
};
|
||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
@ -1514,7 +1514,7 @@ async function nextFromChallengeSelecting(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncProviders(
|
async function syncAllProvidersTransition(
|
||||||
state: ReducerStateRecovery,
|
state: ReducerStateRecovery,
|
||||||
args: void,
|
args: void,
|
||||||
): Promise<ReducerStateRecovery | ReducerStateError> {
|
): Promise<ReducerStateRecovery | ReducerStateError> {
|
||||||
@ -1722,7 +1722,7 @@ const recoveryTransitions: Record<
|
|||||||
),
|
),
|
||||||
...transition("poll", codecForAny(), pollChallenges),
|
...transition("poll", codecForAny(), pollChallenges),
|
||||||
...transition("next", codecForAny(), nextFromChallengeSelecting),
|
...transition("next", codecForAny(), nextFromChallengeSelecting),
|
||||||
...transition("sync_providers", codecForAny(), syncProviders),
|
...transition("sync_providers", codecForAny(), syncAllProvidersTransition),
|
||||||
},
|
},
|
||||||
[RecoveryStates.ChallengeSolving]: {
|
[RecoveryStates.ChallengeSolving]: {
|
||||||
...transitionRecoveryJump("back", RecoveryStates.ChallengeSelecting),
|
...transitionRecoveryJump("back", RecoveryStates.ChallengeSelecting),
|
||||||
@ -1746,6 +1746,7 @@ export async function discoverPolicies(
|
|||||||
|
|
||||||
const providerUrls = Object.keys(state.authentication_providers || {});
|
const providerUrls = Object.keys(state.authentication_providers || {});
|
||||||
// FIXME: Do we need to re-contact providers here / check if they're disabled?
|
// 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) {
|
for (const providerUrl of providerUrls) {
|
||||||
const providerInfo = await getProviderInfo(providerUrl);
|
const providerInfo = await getProviderInfo(providerUrl);
|
||||||
@ -1839,3 +1840,43 @@ export async function reduceAction(
|
|||||||
throw e;
|
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 {
|
export interface ReducerStateRecovery {
|
||||||
reducer_type: "recovery";
|
reducer_type: "recovery";
|
||||||
|
|
||||||
@ -231,7 +235,7 @@ export interface ReducerStateRecovery {
|
|||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
authentication_providers?: { [url: string]: AuthenticationProviderStatus };
|
authentication_providers?: AuthenticationProviderStatusMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -342,7 +346,7 @@ export interface ReducerStateBackupUserAttributesCollecting
|
|||||||
selected_country: string;
|
selected_country: string;
|
||||||
currencies: string[];
|
currencies: string[];
|
||||||
required_attributes: UserAttributeSpec[];
|
required_attributes: UserAttributeSpec[];
|
||||||
authentication_providers: { [url: string]: AuthenticationProviderStatus };
|
authentication_providers: AuthenticationProviderStatusMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionArgsEnterUserAttributes {
|
export interface ActionArgsEnterUserAttributes {
|
||||||
|
@ -21,6 +21,7 @@ import { TalerErrorCode } from "@gnu-taler/taler-util";
|
|||||||
import {
|
import {
|
||||||
AggregatedPolicyMetaInfo,
|
AggregatedPolicyMetaInfo,
|
||||||
BackupStates,
|
BackupStates,
|
||||||
|
completeProviderStatus,
|
||||||
discoverPolicies,
|
discoverPolicies,
|
||||||
DiscoveryCursor,
|
DiscoveryCursor,
|
||||||
getBackupStartState,
|
getBackupStartState,
|
||||||
@ -206,6 +207,44 @@ export function useAnastasisReducer(): AnastasisReducerApi {
|
|||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
setAnastasisStateInternal(newState);
|
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> {
|
async function doTransition(action: string, args: any): Promise<void> {
|
||||||
|
@ -37,7 +37,6 @@ export function ContinentSelectionScreen(): VNode {
|
|||||||
if (!theCountry) return;
|
if (!theCountry) return;
|
||||||
reducer.transition("select_country", {
|
reducer.transition("select_country", {
|
||||||
country_code: countryCode,
|
country_code: countryCode,
|
||||||
currencies: [theCountry.currency],
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,7 +61,6 @@ import {
|
|||||||
getRetryDuration,
|
getRetryDuration,
|
||||||
resetRetryInfo,
|
resetRetryInfo,
|
||||||
RetryInfo,
|
RetryInfo,
|
||||||
updateRetryInfoTimeout,
|
|
||||||
} from "../util/retries.js";
|
} from "../util/retries.js";
|
||||||
import {
|
import {
|
||||||
getExchangeDetails,
|
getExchangeDetails,
|
||||||
|
Loading…
Reference in New Issue
Block a user