anastasis: tag state properly
This commit is contained in:
parent
f3d8b44743
commit
b28583ba7e
@ -196,6 +196,7 @@ function getCountries(
|
||||
|
||||
export async function getBackupStartState(): Promise<ReducerStateBackup> {
|
||||
return {
|
||||
reducer_type: "backup",
|
||||
backup_state: BackupStates.ContinentSelecting,
|
||||
continents: getContinents({
|
||||
requireProvider: true,
|
||||
@ -205,6 +206,7 @@ export async function getBackupStartState(): Promise<ReducerStateBackup> {
|
||||
|
||||
export async function getRecoveryStartState(): Promise<ReducerStateRecovery> {
|
||||
return {
|
||||
reducer_type: "recovery",
|
||||
recovery_state: RecoveryStates.ContinentSelecting,
|
||||
continents: getContinents({
|
||||
requireProvider: true,
|
||||
@ -571,6 +573,7 @@ async function uploadSecret(
|
||||
const talerPayUri = resp.headers.get("Taler");
|
||||
if (!talerPayUri) {
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_BACKEND_FAILURE,
|
||||
hint: `payment requested, but no taler://pay URI given`,
|
||||
};
|
||||
@ -579,6 +582,7 @@ async function uploadSecret(
|
||||
const parsedUri = parsePayUri(talerPayUri);
|
||||
if (!parsedUri) {
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_BACKEND_FAILURE,
|
||||
hint: `payment requested, but no taler://pay URI given`,
|
||||
};
|
||||
@ -587,6 +591,7 @@ async function uploadSecret(
|
||||
continue;
|
||||
}
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_NETWORK_FAILED,
|
||||
hint: `could not upload truth (HTTP status ${resp.status})`,
|
||||
};
|
||||
@ -674,6 +679,7 @@ async function uploadSecret(
|
||||
const talerPayUri = resp.headers.get("Taler");
|
||||
if (!talerPayUri) {
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_BACKEND_FAILURE,
|
||||
hint: `payment requested, but no taler://pay URI given`,
|
||||
};
|
||||
@ -682,6 +688,7 @@ async function uploadSecret(
|
||||
const parsedUri = parsePayUri(talerPayUri);
|
||||
if (!parsedUri) {
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_BACKEND_FAILURE,
|
||||
hint: `payment requested, but no taler://pay URI given`,
|
||||
};
|
||||
@ -690,6 +697,7 @@ async function uploadSecret(
|
||||
continue;
|
||||
}
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_NETWORK_FAILED,
|
||||
hint: `could not upload policy (http status ${resp.status})`,
|
||||
};
|
||||
@ -734,13 +742,13 @@ async function downloadPolicy(
|
||||
throw Error("invalid state");
|
||||
}
|
||||
for (const prov of state.selected_version.providers) {
|
||||
const pi = state.authentication_providers?.[prov.provider_url];
|
||||
const pi = state.authentication_providers?.[prov.url];
|
||||
if (!pi || pi.status !== "ok") {
|
||||
continue;
|
||||
}
|
||||
const userId = await userIdentifierDerive(userAttributes, pi.salt);
|
||||
const acctKeypair = accountKeypairDerive(userId);
|
||||
const reqUrl = new URL(`policy/${acctKeypair.pub}`, prov.provider_url);
|
||||
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) {
|
||||
@ -759,7 +767,7 @@ async function downloadPolicy(
|
||||
policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
|
||||
} catch (e) {}
|
||||
foundRecoveryInfo = {
|
||||
provider_url: prov.provider_url,
|
||||
provider_url: prov.url,
|
||||
secret_name: rd.secret_name ?? "<unknown>",
|
||||
version: policyVersion,
|
||||
};
|
||||
@ -768,6 +776,7 @@ async function downloadPolicy(
|
||||
}
|
||||
if (!foundRecoveryInfo || !recoveryDoc) {
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED,
|
||||
hint: "No backups found at any provider for your identity information.",
|
||||
};
|
||||
@ -874,7 +883,7 @@ async function pollChallenges(
|
||||
const s2 = await requestTruth(state, truth, {
|
||||
pin: feedback.answer_code,
|
||||
});
|
||||
if (s2.recovery_state) {
|
||||
if (s2.reducer_type === "recovery") {
|
||||
state = s2;
|
||||
}
|
||||
}
|
||||
@ -1001,6 +1010,7 @@ async function requestTruth(
|
||||
}
|
||||
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_FAILED,
|
||||
hint: "got unexpected /truth/ response status",
|
||||
http_status: resp.status,
|
||||
@ -1110,6 +1120,7 @@ async function selectChallenge(
|
||||
// FIXME: look at response, include in challenge_feedback!
|
||||
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_FAILED,
|
||||
hint: "got unexpected /truth/.../challenge response status",
|
||||
http_status: resp.status,
|
||||
@ -1125,6 +1136,7 @@ async function backupSelectContinent(
|
||||
});
|
||||
if (countries.length <= 0) {
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_INPUT_INVALID,
|
||||
hint: "continent not found",
|
||||
};
|
||||
@ -1423,10 +1435,14 @@ async function nextFromChallengeSelecting(
|
||||
args: void,
|
||||
): Promise<ReducerStateRecovery | ReducerStateError> {
|
||||
const s2 = await tryRecoverSecret(state);
|
||||
if (s2.recovery_state === RecoveryStates.RecoveryFinished) {
|
||||
if (
|
||||
s2.reducer_type === "recovery" &&
|
||||
s2.recovery_state === RecoveryStates.RecoveryFinished
|
||||
) {
|
||||
return s2;
|
||||
}
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_ACTION_INVALID,
|
||||
hint: "Not enough challenges solved",
|
||||
};
|
||||
@ -1462,7 +1478,7 @@ export function mergeDiscoveryAggregate(
|
||||
const oldIndex = polHashToIndex[pol.policy_hash];
|
||||
if (oldIndex != null) {
|
||||
aggregatedPolicies[oldIndex].providers.push({
|
||||
provider_url: pol.provider_url,
|
||||
url: pol.provider_url,
|
||||
version: pol.version,
|
||||
});
|
||||
} else {
|
||||
@ -1471,7 +1487,7 @@ export function mergeDiscoveryAggregate(
|
||||
policy_hash: pol.policy_hash,
|
||||
providers: [
|
||||
{
|
||||
provider_url: pol.provider_url,
|
||||
url: pol.provider_url,
|
||||
version: pol.version,
|
||||
},
|
||||
],
|
||||
@ -1592,7 +1608,7 @@ const recoveryTransitions: Record<
|
||||
...transition("add_provider", codecForAny(), addProviderRecovery),
|
||||
...transition("delete_provider", codecForAny(), deleteProviderRecovery),
|
||||
...transition(
|
||||
"change_version",
|
||||
"select_version",
|
||||
codecForActionArgsChangeVersion(),
|
||||
changeVersion,
|
||||
),
|
||||
@ -1621,7 +1637,7 @@ export async function discoverPolicies(
|
||||
state: ReducerState,
|
||||
cursor?: DiscoveryCursor,
|
||||
): Promise<DiscoveryResult> {
|
||||
if (!state.recovery_state) {
|
||||
if (state.reducer_type !== "recovery") {
|
||||
throw Error("can only discover providers in recovery state");
|
||||
}
|
||||
|
||||
@ -1685,12 +1701,14 @@ export async function reduceAction(
|
||||
h = recoveryTransitions[state.recovery_state][action];
|
||||
} else {
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_ACTION_INVALID,
|
||||
hint: `Invalid state (needs backup_state or recovery_state)`,
|
||||
};
|
||||
}
|
||||
if (!h) {
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_ACTION_INVALID,
|
||||
hint: `Unsupported action '${action}' in state '${stateName}'`,
|
||||
};
|
||||
@ -1700,9 +1718,10 @@ export async function reduceAction(
|
||||
parsedArgs = h.argCodec.decode(args);
|
||||
} catch (e: any) {
|
||||
return {
|
||||
reducer_type: "error",
|
||||
code: TalerErrorCode.ANASTASIS_REDUCER_INPUT_INVALID,
|
||||
hint: "argument validation failed",
|
||||
message: e.toString(),
|
||||
detail: e.toString(),
|
||||
};
|
||||
}
|
||||
try {
|
||||
@ -1710,7 +1729,10 @@ export async function reduceAction(
|
||||
} catch (e) {
|
||||
logger.error("action handler failed");
|
||||
if (e instanceof ReducerError) {
|
||||
return e.errorJson;
|
||||
return {
|
||||
reducer_type: "error",
|
||||
...e.errorJson,
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
@ -73,19 +73,30 @@ export interface CoreSecret {
|
||||
}
|
||||
|
||||
export interface ReducerStateBackup {
|
||||
recovery_state?: undefined;
|
||||
reducer_type: "backup";
|
||||
|
||||
backup_state: BackupStates;
|
||||
code?: undefined;
|
||||
|
||||
currencies?: string[];
|
||||
|
||||
continents?: ContinentInfo[];
|
||||
|
||||
countries?: CountryInfo[];
|
||||
|
||||
identity_attributes?: { [n: string]: string };
|
||||
|
||||
authentication_providers?: { [url: string]: AuthenticationProviderStatus };
|
||||
|
||||
authentication_methods?: AuthMethod[];
|
||||
|
||||
required_attributes?: UserAttributeSpec[];
|
||||
|
||||
selected_continent?: string;
|
||||
|
||||
selected_country?: string;
|
||||
|
||||
secret_name?: string;
|
||||
|
||||
policies?: Policy[];
|
||||
|
||||
recovery_data?: {
|
||||
@ -179,18 +190,10 @@ export interface RecoveryInformation {
|
||||
}
|
||||
|
||||
export interface ReducerStateRecovery {
|
||||
reducer_type: "recovery";
|
||||
|
||||
recovery_state: RecoveryStates;
|
||||
|
||||
/**
|
||||
* Unused in the recovery states.
|
||||
*/
|
||||
backup_state?: undefined;
|
||||
|
||||
/**
|
||||
* Unused in the recovery states.
|
||||
*/
|
||||
code?: undefined;
|
||||
|
||||
identity_attributes?: { [n: string]: string };
|
||||
|
||||
continents?: ContinentInfo[];
|
||||
@ -267,11 +270,10 @@ export interface TruthMetaData {
|
||||
}
|
||||
|
||||
export interface ReducerStateError {
|
||||
backup_state?: undefined;
|
||||
recovery_state?: undefined;
|
||||
reducer_type: "error";
|
||||
code: number;
|
||||
hint?: string;
|
||||
message?: string;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
export enum BackupStates {
|
||||
@ -302,12 +304,13 @@ export interface MethodSpec {
|
||||
usage_fee: string;
|
||||
}
|
||||
|
||||
export type AuthenticationProviderStatusEmpty = {
|
||||
export type AuthenticationProviderStatusNotContacted = {
|
||||
status: "not-contacted";
|
||||
};
|
||||
|
||||
export interface AuthenticationProviderStatusOk {
|
||||
status: "ok";
|
||||
|
||||
annual_fee: string;
|
||||
business_name: string;
|
||||
currency: string;
|
||||
@ -320,8 +323,13 @@ export interface AuthenticationProviderStatusOk {
|
||||
// FIXME: add timestamp?
|
||||
}
|
||||
|
||||
export interface AuthenticationProviderStatusDisabled {
|
||||
status: "disabled";
|
||||
}
|
||||
|
||||
export interface AuthenticationProviderStatusError {
|
||||
status: "error";
|
||||
|
||||
http_status?: number;
|
||||
code: number;
|
||||
hint?: string;
|
||||
@ -329,7 +337,8 @@ export interface AuthenticationProviderStatusError {
|
||||
}
|
||||
|
||||
export type AuthenticationProviderStatus =
|
||||
| AuthenticationProviderStatusEmpty
|
||||
| AuthenticationProviderStatusNotContacted
|
||||
| AuthenticationProviderStatusDisabled
|
||||
| AuthenticationProviderStatusError
|
||||
| AuthenticationProviderStatusOk;
|
||||
|
||||
@ -486,7 +495,6 @@ export interface PolicyMetaInfo {
|
||||
secret_name?: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Aggregated / de-duplicated policy meta info.
|
||||
*/
|
||||
@ -495,7 +503,7 @@ export interface AggregatedPolicyMetaInfo {
|
||||
policy_hash: string;
|
||||
attribute_mask: number;
|
||||
providers: {
|
||||
provider_url: string;
|
||||
url: string;
|
||||
version: number;
|
||||
}[];
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export function AttributeEntryScreen(): VNode {
|
||||
const [attrs, setAttrs] = useState<Record<string, string>>(
|
||||
currentIdentityAttributes,
|
||||
);
|
||||
const isBackup = state && state.backup_state;
|
||||
const isBackup = state?.reducer_type === "backup";
|
||||
const [askUserIfSure, setAskUserIfSure] = useState(false);
|
||||
|
||||
if (!reducer) {
|
||||
|
@ -30,10 +30,7 @@ export function AuthenticationEditorScreen(): VNode {
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.backup_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "backup") {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
const configuredAuthMethods: AuthMethod[] =
|
||||
@ -62,7 +59,7 @@ export function AuthenticationEditorScreen(): VNode {
|
||||
const authAvailableSet = new Set<string>();
|
||||
for (const provKey of Object.keys(providers)) {
|
||||
const p = providers[provKey];
|
||||
if ("http_status" in p && !("error_code" in p) && p.methods) {
|
||||
if (p.status === "ok") {
|
||||
for (const meth of p.methods) {
|
||||
authAvailableSet.add(meth.type);
|
||||
}
|
||||
|
@ -9,10 +9,7 @@ export function BackupFinishedScreen(): VNode {
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.backup_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "backup") {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
const details = reducer.currentReducerState.success_details;
|
||||
|
@ -55,10 +55,7 @@ export function ChallengeOverviewScreen(): VNode {
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.recovery_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "recovery") {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,7 @@ export function ChallengePayingScreen(): VNode {
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.recovery_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "recovery") {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
const payments = [""]; //reducer.currentReducerState.payments ??
|
||||
|
@ -38,10 +38,7 @@ export function EditPoliciesScreen({
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.backup_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "backup") {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,7 @@ export function PoliciesPayingScreen(): VNode {
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.backup_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "backup") {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
const payments = reducer.currentReducerState.policy_payment_requests ?? [];
|
||||
|
@ -18,10 +18,7 @@ export function RecoveryFinishedScreen(): VNode {
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.recovery_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "recovery") {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
const secretName = reducer.currentReducerState.recovery_document?.secret_name;
|
||||
|
@ -15,10 +15,7 @@ export function ReviewPoliciesScreen(): VNode {
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.backup_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "backup") {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
|
||||
|
@ -27,10 +27,7 @@ export function SecretEditorScreen(): VNode {
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.backup_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "backup") {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ export function SecretSelectionScreen(): VNode {
|
||||
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.recovery_state === undefined
|
||||
reducer.currentReducerState.reducer_type !== "recovery"
|
||||
) {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
@ -73,14 +73,17 @@ export function SecretSelectionScreen(): VNode {
|
||||
}
|
||||
|
||||
return (
|
||||
<AnastasisClientFrame title="Recovery: Select secret" hideNext="Please select version to recover">
|
||||
<AnastasisClientFrame
|
||||
title="Recovery: Select secret"
|
||||
hideNext="Please select version to recover"
|
||||
>
|
||||
<p>Found versions:</p>
|
||||
{policies.map((x) => (
|
||||
<div>
|
||||
{x.policy_hash} / {x.secret_name}
|
||||
<button
|
||||
onClick={async () => {
|
||||
await reducer.transition("change_version", {
|
||||
await reducer.transition("select_version", {
|
||||
selection: x,
|
||||
});
|
||||
}}
|
||||
@ -119,7 +122,7 @@ export function OldSecretSelectionScreen(): VNode {
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.recovery_state === undefined
|
||||
reducer.currentReducerState.reducer_type !== "recovery"
|
||||
) {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
@ -127,7 +130,7 @@ export function OldSecretSelectionScreen(): VNode {
|
||||
async function doSelectVersion(p: string, n: number): Promise<void> {
|
||||
if (!reducer) return Promise.resolve();
|
||||
return reducer.runTransaction(async (tx) => {
|
||||
await tx.transition("change_version", {
|
||||
await tx.transition("select_version", {
|
||||
version: n,
|
||||
provider_url: p,
|
||||
});
|
||||
|
@ -135,10 +135,7 @@ export function SolveScreen(): VNode {
|
||||
</AnastasisClientFrame>
|
||||
);
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.recovery_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "recovery") {
|
||||
return (
|
||||
<AnastasisClientFrame hideNav title="Recovery problem">
|
||||
<div>invalid state</div>
|
||||
|
@ -7,10 +7,7 @@ export function TruthsPayingScreen(): VNode {
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.backup_state === undefined
|
||||
) {
|
||||
if (reducer.currentReducerState?.reducer_type !== "backup") {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
const payments = reducer.currentReducerState.payments ?? [];
|
||||
|
Loading…
Reference in New Issue
Block a user