diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts index 542e7eafb..5896dc655 100644 --- a/packages/anastasis-core/src/index.ts +++ b/packages/anastasis-core/src/index.ts @@ -196,6 +196,7 @@ function getCountries( export async function getBackupStartState(): Promise { return { + reducer_type: "backup", backup_state: BackupStates.ContinentSelecting, continents: getContinents({ requireProvider: true, @@ -205,6 +206,7 @@ export async function getBackupStartState(): Promise { export async function getRecoveryStartState(): Promise { 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 ?? "", 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 { 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 { - 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; } diff --git a/packages/anastasis-core/src/reducer-types.ts b/packages/anastasis-core/src/reducer-types.ts index 7e8d4da25..03b40e4bd 100644 --- a/packages/anastasis-core/src/reducer-types.ts +++ b/packages/anastasis-core/src/reducer-types.ts @@ -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; }[]; } diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx index c14365d24..501415c40 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx @@ -19,7 +19,7 @@ export function AttributeEntryScreen(): VNode { const [attrs, setAttrs] = useState>( currentIdentityAttributes, ); - const isBackup = state && state.backup_state; + const isBackup = state?.reducer_type === "backup"; const [askUserIfSure, setAskUserIfSure] = useState(false); if (!reducer) { diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index 17987796f..f0cf9b88c 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -30,10 +30,7 @@ export function AuthenticationEditorScreen(): VNode { if (!reducer) { return
no reducer in context
; } - if ( - !reducer.currentReducerState || - reducer.currentReducerState.backup_state === undefined - ) { + if (reducer.currentReducerState?.reducer_type !== "backup") { return
invalid state
; } const configuredAuthMethods: AuthMethod[] = @@ -62,7 +59,7 @@ export function AuthenticationEditorScreen(): VNode { const authAvailableSet = new Set(); 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); } diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx index d6272d843..cbdfcdce7 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx @@ -9,10 +9,7 @@ export function BackupFinishedScreen(): VNode { if (!reducer) { return
no reducer in context
; } - if ( - !reducer.currentReducerState || - reducer.currentReducerState.backup_state === undefined - ) { + if (reducer.currentReducerState?.reducer_type !== "backup") { return
invalid state
; } const details = reducer.currentReducerState.success_details; diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index f24478d19..11001194a 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -55,10 +55,7 @@ export function ChallengeOverviewScreen(): VNode { if (!reducer) { return
no reducer in context
; } - if ( - !reducer.currentReducerState || - reducer.currentReducerState.recovery_state === undefined - ) { + if (reducer.currentReducerState?.reducer_type !== "recovery") { return
invalid state
; } diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx index ffcc8fafc..2e14f44cf 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx @@ -7,10 +7,7 @@ export function ChallengePayingScreen(): VNode { if (!reducer) { return
no reducer in context
; } - if ( - !reducer.currentReducerState || - reducer.currentReducerState.recovery_state === undefined - ) { + if (reducer.currentReducerState?.reducer_type !== "recovery") { return
invalid state
; } const payments = [""]; //reducer.currentReducerState.payments ?? diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx index 4e0dc2906..a57f7b08b 100644 --- a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx @@ -38,10 +38,7 @@ export function EditPoliciesScreen({ if (!reducer) { return
no reducer in context
; } - if ( - !reducer.currentReducerState || - reducer.currentReducerState.backup_state === undefined - ) { + if (reducer.currentReducerState?.reducer_type !== "backup") { return
invalid state
; } diff --git a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx index c3568b32d..2b2096dab 100644 --- a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx @@ -7,10 +7,7 @@ export function PoliciesPayingScreen(): VNode { if (!reducer) { return
no reducer in context
; } - if ( - !reducer.currentReducerState || - reducer.currentReducerState.backup_state === undefined - ) { + if (reducer.currentReducerState?.reducer_type !== "backup") { return
invalid state
; } const payments = reducer.currentReducerState.policy_payment_requests ?? []; diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx index d83482559..326a9a59d 100644 --- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx @@ -18,10 +18,7 @@ export function RecoveryFinishedScreen(): VNode { if (!reducer) { return
no reducer in context
; } - if ( - !reducer.currentReducerState || - reducer.currentReducerState.recovery_state === undefined - ) { + if (reducer.currentReducerState?.reducer_type !== "recovery") { return
invalid state
; } const secretName = reducer.currentReducerState.recovery_document?.secret_name; diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx index 4253f4617..5d75d5aaa 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx @@ -15,10 +15,7 @@ export function ReviewPoliciesScreen(): VNode { if (!reducer) { return
no reducer in context
; } - if ( - !reducer.currentReducerState || - reducer.currentReducerState.backup_state === undefined - ) { + if (reducer.currentReducerState?.reducer_type !== "backup") { return
invalid state
; } diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx index d9bf084ab..0931f4069 100644 --- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx @@ -27,10 +27,7 @@ export function SecretEditorScreen(): VNode { if (!reducer) { return
no reducer in context
; } - if ( - !reducer.currentReducerState || - reducer.currentReducerState.backup_state === undefined - ) { + if (reducer.currentReducerState?.reducer_type !== "backup") { return
invalid state
; } diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx index 84f0303fe..45d8e46f3 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx @@ -31,7 +31,7 @@ export function SecretSelectionScreen(): VNode { if ( !reducer.currentReducerState || - reducer.currentReducerState.recovery_state === undefined + reducer.currentReducerState.reducer_type !== "recovery" ) { return
invalid state
; } @@ -73,14 +73,17 @@ export function SecretSelectionScreen(): VNode { } return ( - +

Found versions:

{policies.map((x) => (
{x.policy_hash} / {x.secret_name}