anastasis-core: clean up policy fetching and provider sync
This commit is contained in:
parent
d1b4cc994b
commit
5a4b6c7eb6
@ -721,6 +721,65 @@ async function uploadSecret(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PolicyDownloadResult {
|
||||||
|
recoveryDoc: RecoveryDocument;
|
||||||
|
recoveryData: RecoveryInternalData;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadPolicyFromProvider(
|
||||||
|
state: ReducerStateRecovery,
|
||||||
|
providerUrl: string,
|
||||||
|
version: number,
|
||||||
|
): Promise<PolicyDownloadResult | undefined> {
|
||||||
|
logger.info(`trying to download policy from ${providerUrl}`);
|
||||||
|
const userAttributes = state.identity_attributes!;
|
||||||
|
let pi = state.authentication_providers?.[providerUrl];
|
||||||
|
if (!pi || pi.status !== "ok") {
|
||||||
|
// FIXME: this one blocks!
|
||||||
|
logger.info(`fetching provider info for ${providerUrl}`);
|
||||||
|
pi = await getProviderInfo(providerUrl);
|
||||||
|
}
|
||||||
|
logger.info(`new provider status is ${pi.status}`);
|
||||||
|
if (pi.status !== "ok") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const userId = await userIdentifierDerive(userAttributes, pi.provider_salt);
|
||||||
|
const acctKeypair = accountKeypairDerive(userId);
|
||||||
|
const reqUrl = new URL(`policy/${acctKeypair.pub}`, providerUrl);
|
||||||
|
reqUrl.searchParams.set("version", `${version}`);
|
||||||
|
const resp = await fetch(reqUrl.href);
|
||||||
|
if (resp.status !== 200) {
|
||||||
|
logger.info(
|
||||||
|
`Could not download policy from provider ${providerUrl}, status ${resp.status}`,
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const body = await resp.arrayBuffer();
|
||||||
|
const bodyDecrypted = await decryptRecoveryDocument(
|
||||||
|
userId,
|
||||||
|
encodeCrock(body),
|
||||||
|
);
|
||||||
|
const rd: RecoveryDocument = await uncompressRecoveryDoc(
|
||||||
|
decodeCrock(bodyDecrypted),
|
||||||
|
);
|
||||||
|
// FIXME: Not clear why we do this, since we always have an explicit version by now.
|
||||||
|
let policyVersion = 0;
|
||||||
|
try {
|
||||||
|
policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn("Could not read policy version header");
|
||||||
|
policyVersion = version;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
recoveryDoc: rd,
|
||||||
|
recoveryData: {
|
||||||
|
provider_url: providerUrl,
|
||||||
|
secret_name: rd.secret_name ?? "<unknown>",
|
||||||
|
version: policyVersion,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download policy based on current user attributes and selected
|
* Download policy based on current user attributes and selected
|
||||||
* version in the state.
|
* version in the state.
|
||||||
@ -729,56 +788,19 @@ async function downloadPolicy(
|
|||||||
state: ReducerStateRecovery,
|
state: ReducerStateRecovery,
|
||||||
): Promise<ReducerStateRecovery | ReducerStateError> {
|
): Promise<ReducerStateRecovery | ReducerStateError> {
|
||||||
logger.info("downloading policy");
|
logger.info("downloading policy");
|
||||||
let foundRecoveryInfo: RecoveryInternalData | undefined = undefined;
|
|
||||||
let recoveryDoc: RecoveryDocument | undefined = undefined;
|
|
||||||
const userAttributes = state.identity_attributes!;
|
|
||||||
if (!state.selected_version) {
|
if (!state.selected_version) {
|
||||||
throw Error("invalid state");
|
throw Error("invalid state");
|
||||||
}
|
}
|
||||||
|
let policyDownloadResult: PolicyDownloadResult | undefined = undefined;
|
||||||
// FIXME: Do this concurrently/asynchronously so that one slow provider doens't block us.
|
// 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) {
|
||||||
let pi = state.authentication_providers?.[prov.url];
|
const res = await downloadPolicyFromProvider(state, prov.url, prov.version);
|
||||||
if (!pi || pi.status !== "ok") {
|
if (res) {
|
||||||
// FIXME: this one blocks!
|
policyDownloadResult = res;
|
||||||
logger.info(`fetching provider info for ${prov.url}`);
|
break;
|
||||||
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);
|
|
||||||
const acctKeypair = accountKeypairDerive(userId);
|
|
||||||
const reqUrl = new URL(`policy/${acctKeypair.pub}`, prov.url);
|
|
||||||
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();
|
|
||||||
const bodyDecrypted = await decryptRecoveryDocument(
|
|
||||||
userId,
|
|
||||||
encodeCrock(body),
|
|
||||||
);
|
|
||||||
const rd: RecoveryDocument = await uncompressRecoveryDoc(
|
|
||||||
decodeCrock(bodyDecrypted),
|
|
||||||
);
|
|
||||||
let policyVersion = 0;
|
|
||||||
try {
|
|
||||||
policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
|
|
||||||
} catch (e) {}
|
|
||||||
foundRecoveryInfo = {
|
|
||||||
provider_url: prov.url,
|
|
||||||
secret_name: rd.secret_name ?? "<unknown>",
|
|
||||||
version: policyVersion,
|
|
||||||
};
|
|
||||||
recoveryDoc = rd;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (!foundRecoveryInfo || !recoveryDoc) {
|
if (!policyDownloadResult) {
|
||||||
return {
|
return {
|
||||||
reducer_type: "error",
|
reducer_type: "error",
|
||||||
code: TalerErrorCode.ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED,
|
code: TalerErrorCode.ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED,
|
||||||
@ -787,14 +809,10 @@ async function downloadPolicy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const challenges: ChallengeInfo[] = [];
|
const challenges: ChallengeInfo[] = [];
|
||||||
|
const recoveryDoc = policyDownloadResult.recoveryDoc;
|
||||||
|
|
||||||
for (const x of recoveryDoc.escrow_methods) {
|
for (const x of recoveryDoc.escrow_methods) {
|
||||||
const pi = state.authentication_providers?.[x.url];
|
|
||||||
if (!pi || pi.status !== "ok") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
challenges.push({
|
challenges.push({
|
||||||
cost: pi.methods.find((m) => m.type === x.escrow_type)?.usage_fee!,
|
|
||||||
instructions: x.instructions,
|
instructions: x.instructions,
|
||||||
type: x.escrow_type,
|
type: x.escrow_type,
|
||||||
uuid: x.uuid,
|
uuid: x.uuid,
|
||||||
@ -814,7 +832,7 @@ async function downloadPolicy(
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
recovery_state: RecoveryStates.ChallengeSelecting,
|
recovery_state: RecoveryStates.ChallengeSelecting,
|
||||||
recovery_document: foundRecoveryInfo,
|
recovery_document: policyDownloadResult.recoveryData,
|
||||||
recovery_information: recoveryInfo,
|
recovery_information: recoveryInfo,
|
||||||
verbatim_recovery_document: recoveryDoc,
|
verbatim_recovery_document: recoveryDoc,
|
||||||
};
|
};
|
||||||
@ -1276,7 +1294,6 @@ function transitionRecoveryJump(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME: doest the same that addProviderRecovery, but type are not generic enough
|
|
||||||
async function addProviderBackup(
|
async function addProviderBackup(
|
||||||
state: ReducerStateBackup,
|
state: ReducerStateBackup,
|
||||||
args: ActionArgsAddProvider,
|
args: ActionArgsAddProvider,
|
||||||
@ -1291,7 +1308,6 @@ async function addProviderBackup(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME: doest the same that deleteProviderRecovery, but type are not generic enough
|
|
||||||
async function deleteProviderBackup(
|
async function deleteProviderBackup(
|
||||||
state: ReducerStateBackup,
|
state: ReducerStateBackup,
|
||||||
args: ActionArgsDeleteProvider,
|
args: ActionArgsDeleteProvider,
|
||||||
@ -1419,6 +1435,14 @@ async function nextFromAuthenticationsEditing(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const pol = suggestPolicies(methods, providers);
|
const pol = suggestPolicies(methods, providers);
|
||||||
|
if (pol.policies.length === 0) {
|
||||||
|
return {
|
||||||
|
reducer_type: "error",
|
||||||
|
code: TalerErrorCode.ANASTASIS_REDUCER_ACTION_INVALID,
|
||||||
|
detail:
|
||||||
|
"Unable to suggest any policies. Check if providers are available and reachable.",
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
backup_state: BackupStates.PoliciesReviewing,
|
backup_state: BackupStates.PoliciesReviewing,
|
||||||
@ -1514,10 +1538,11 @@ async function nextFromChallengeSelecting(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncAllProvidersTransition(
|
async function syncOneProviderRecoveryTransition(
|
||||||
state: ReducerStateRecovery,
|
state: ReducerStateRecovery,
|
||||||
args: void,
|
args: void,
|
||||||
): Promise<ReducerStateRecovery | ReducerStateError> {
|
): Promise<ReducerStateRecovery | ReducerStateError> {
|
||||||
|
// FIXME: Should we not add this when we obtain the recovery document?
|
||||||
const escrowMethods = state.verbatim_recovery_document?.escrow_methods ?? [];
|
const escrowMethods = state.verbatim_recovery_document?.escrow_methods ?? [];
|
||||||
if (escrowMethods.length === 0) {
|
if (escrowMethods.length === 0) {
|
||||||
return {
|
return {
|
||||||
@ -1541,6 +1566,56 @@ async function syncAllProvidersTransition(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const [provUrl, pi] of Object.entries(
|
||||||
|
state.authentication_providers ?? {},
|
||||||
|
)) {
|
||||||
|
if (
|
||||||
|
pi.status === "ok" ||
|
||||||
|
pi.status === "disabled" ||
|
||||||
|
pi.status === "error"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const newPi = await getProviderInfo(provUrl);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authentication_providers: {
|
||||||
|
...state.authentication_providers,
|
||||||
|
[provUrl]: newPi,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
reducer_type: "error",
|
||||||
|
code: TalerErrorCode.ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED,
|
||||||
|
hint: "all providers are already synced",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncOneProviderBackupTransition(
|
||||||
|
state: ReducerStateBackup,
|
||||||
|
args: void,
|
||||||
|
): Promise<ReducerStateBackup | ReducerStateError> {
|
||||||
|
for (const [provUrl, pi] of Object.entries(
|
||||||
|
state.authentication_providers ?? {},
|
||||||
|
)) {
|
||||||
|
if (
|
||||||
|
pi.status === "ok" ||
|
||||||
|
pi.status === "disabled" ||
|
||||||
|
pi.status === "error"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const newPi = await getProviderInfo(provUrl);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authentication_providers: {
|
||||||
|
...state.authentication_providers,
|
||||||
|
[provUrl]: newPi,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
reducer_type: "error",
|
reducer_type: "error",
|
||||||
code: TalerErrorCode.ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED,
|
code: TalerErrorCode.ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED,
|
||||||
@ -1630,6 +1705,11 @@ const backupTransitions: Record<
|
|||||||
codecForActionArgsEnterUserAttributes(),
|
codecForActionArgsEnterUserAttributes(),
|
||||||
backupEnterUserAttributes,
|
backupEnterUserAttributes,
|
||||||
),
|
),
|
||||||
|
...transition(
|
||||||
|
"sync_providers",
|
||||||
|
codecForAny(),
|
||||||
|
syncOneProviderBackupTransition,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
[BackupStates.AuthenticationsEditing]: {
|
[BackupStates.AuthenticationsEditing]: {
|
||||||
...transitionBackupJump("back", BackupStates.UserAttributesCollecting),
|
...transitionBackupJump("back", BackupStates.UserAttributesCollecting),
|
||||||
@ -1637,6 +1717,11 @@ const backupTransitions: Record<
|
|||||||
...transition("delete_authentication", codecForAny(), deleteAuthentication),
|
...transition("delete_authentication", codecForAny(), deleteAuthentication),
|
||||||
...transition("add_provider", codecForAny(), addProviderBackup),
|
...transition("add_provider", codecForAny(), addProviderBackup),
|
||||||
...transition("delete_provider", codecForAny(), deleteProviderBackup),
|
...transition("delete_provider", codecForAny(), deleteProviderBackup),
|
||||||
|
...transition(
|
||||||
|
"sync_providers",
|
||||||
|
codecForAny(),
|
||||||
|
syncOneProviderBackupTransition,
|
||||||
|
),
|
||||||
...transition("next", codecForAny(), nextFromAuthenticationsEditing),
|
...transition("next", codecForAny(), nextFromAuthenticationsEditing),
|
||||||
},
|
},
|
||||||
[BackupStates.PoliciesReviewing]: {
|
[BackupStates.PoliciesReviewing]: {
|
||||||
@ -1722,7 +1807,11 @@ const recoveryTransitions: Record<
|
|||||||
),
|
),
|
||||||
...transition("poll", codecForAny(), pollChallenges),
|
...transition("poll", codecForAny(), pollChallenges),
|
||||||
...transition("next", codecForAny(), nextFromChallengeSelecting),
|
...transition("next", codecForAny(), nextFromChallengeSelecting),
|
||||||
...transition("sync_providers", codecForAny(), syncAllProvidersTransition),
|
...transition(
|
||||||
|
"sync_providers",
|
||||||
|
codecForAny(),
|
||||||
|
syncOneProviderRecoveryTransition,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
[RecoveryStates.ChallengeSolving]: {
|
[RecoveryStates.ChallengeSolving]: {
|
||||||
...transitionRecoveryJump("back", RecoveryStates.ChallengeSelecting),
|
...transitionRecoveryJump("back", RecoveryStates.ChallengeSelecting),
|
||||||
|
@ -152,7 +152,6 @@ export interface AuthMethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ChallengeInfo {
|
export interface ChallengeInfo {
|
||||||
cost: string;
|
|
||||||
instructions: string;
|
instructions: string;
|
||||||
type: string;
|
type: string;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user