gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 02/02: anastasis: refactor feedback types


From: gnunet
Subject: [taler-wallet-core] 02/02: anastasis: refactor feedback types
Date: Wed, 03 Nov 2021 13:35:06 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

commit 04356cd23fef76d2020338d2b2b394095fdc2b14
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Nov 3 13:34:57 2021 +0100

    anastasis: refactor feedback types
---
 .../anastasis-core/src/challenge-feedback-types.ts | 149 ++++++++++++++++++++
 packages/anastasis-core/src/index.ts               | 150 ++++++++++++---------
 packages/anastasis-core/src/reducer-types.ts       |  35 +++--
 3 files changed, 249 insertions(+), 85 deletions(-)

diff --git a/packages/anastasis-core/src/challenge-feedback-types.ts 
b/packages/anastasis-core/src/challenge-feedback-types.ts
new file mode 100644
index 00000000..d6a2e3e8
--- /dev/null
+++ b/packages/anastasis-core/src/challenge-feedback-types.ts
@@ -0,0 +1,149 @@
+import { AmountString, HttpStatusCode } from "@gnu-taler/taler-util";
+
+export enum ChallengeFeedbackStatus {
+  Solved = "solved",
+  ServerFailure = "server-failure",
+  TruthUnknown = "truth-unknown",
+  Redirect = "redirect",
+  Payment = "payment",
+  Pending = "pending",
+  Message = "message",
+  Unsupported = "unsupported",
+  RateLimitExceeded = "rate-limit-exceeded",
+  AuthIban = "auth-iban",
+}
+
+export type ChallengeFeedback =
+  | ChallengeFeedbackSolved
+  | ChallengeFeedbackPending
+  | ChallengeFeedbackPayment
+  | ChallengeFeedbackServerFailure
+  | ChallengeFeedbackRateLimitExceeded
+  | ChallengeFeedbackTruthUnknown
+  | ChallengeFeedbackRedirect
+  | ChallengeFeedbackMessage
+  | ChallengeFeedbackUnsupported
+  | ChallengeFeedbackAuthIban;
+
+/**
+ * Challenge has been solved and the key share has
+ * been retrieved.
+ */
+export interface ChallengeFeedbackSolved {
+  state: ChallengeFeedbackStatus.Solved;
+}
+
+/**
+ * The challenge given by the server is unsupported
+ * by the current anastasis client.
+ */
+export interface ChallengeFeedbackUnsupported {
+  state: ChallengeFeedbackStatus.Unsupported;
+  http_status: HttpStatusCode;
+  /**
+   * Human-readable identifier of the unsupported method.
+   */
+  unsupported_method: string;
+}
+
+/**
+ * The user tried to answer too often with a wrong answer.
+ */
+export interface ChallengeFeedbackRateLimitExceeded {
+  state: ChallengeFeedbackStatus.RateLimitExceeded;
+}
+
+/**
+ * Instructions for performing authentication via an
+ * IBAN bank transfer.
+ */
+export interface ChallengeFeedbackAuthIban {
+  state: ChallengeFeedbackStatus.AuthIban;
+
+  /**
+   * Amount that should be transfered for a successful authentication.
+   */
+  challenge_amount: AmountString;
+
+  /**
+   * Account that should be credited.
+   */
+  credit_iban: string;
+
+  /**
+   * Creditor name.
+   */
+  business_name: string;
+
+  /**
+   * Unstructured remittance information that should
+   * be contained in the bank transfer.
+   */
+  wire_transfer_subject: string;
+}
+
+/**
+ * Challenge still needs to be solved.
+ */
+export interface ChallengeFeedbackPending {
+  state: ChallengeFeedbackStatus.Pending;
+}
+
+/**
+ * Human-readable response from the provider
+ * after the user failed to solve the challenge
+ * correctly.
+ */
+export interface ChallengeFeedbackMessage {
+  state: ChallengeFeedbackStatus.Message;
+  message: string;
+}
+
+/**
+ * The server experienced a temporary failure.
+ */
+export interface ChallengeFeedbackServerFailure {
+  state: ChallengeFeedbackStatus.ServerFailure;
+  http_status: HttpStatusCode | 0;
+
+  /**
+   * Taler-style error response, if available.
+   */
+  error_response?: any;
+}
+
+/**
+ * The truth is unknown to the provider.  There
+ * is no reason to continue trying to solve any
+ * challenges in the policy.
+ */
+export interface ChallengeFeedbackTruthUnknown {
+  state: ChallengeFeedbackStatus.TruthUnknown;
+}
+
+/**
+ * The user should be asked to go to a URL
+ * to complete the authentication there.
+ */
+export interface ChallengeFeedbackRedirect {
+  state: ChallengeFeedbackStatus.Redirect;
+  http_status: number;
+  redirect_url: string;
+}
+
+/**
+ * A payment is required before the user can
+ * even attempt to solve the challenge.
+ */
+export interface ChallengeFeedbackPayment {
+  state: ChallengeFeedbackStatus.Payment;
+
+  taler_pay_uri: string;
+
+  provider: string;
+
+  /**
+   * FIXME: Why is this required?!
+   */
+  payment_secret: string;
+}
diff --git a/packages/anastasis-core/src/index.ts 
b/packages/anastasis-core/src/index.ts
index db99db61..859dd083 100644
--- a/packages/anastasis-core/src/index.ts
+++ b/packages/anastasis-core/src/index.ts
@@ -11,10 +11,9 @@ import {
   Duration,
   eddsaSign,
   encodeCrock,
-  getDurationRemaining,
   getRandomBytes,
-  getTimestampNow,
   hash,
+  HttpStatusCode,
   j2s,
   Logger,
   stringToBytes,
@@ -91,6 +90,7 @@ import {
 import { unzlibSync, zlibSync } from "fflate";
 import { EscrowMethod, RecoveryDocument } from "./recovery-document-types.js";
 import { ProviderInfo, suggestPolicies } from "./policy-suggestion.js";
+import { ChallengeFeedback, ChallengeFeedbackStatus } from 
"./challenge-feedback-types.js";
 
 const { fetch } = fetchPonyfill({});
 
@@ -291,7 +291,6 @@ async function backupEnterUserAttributes(
   return newState;
 }
 
-
 /**
  * Truth data as stored in the reducer.
  */
@@ -551,6 +550,7 @@ async function uploadSecret(
 
   return {
     ...state,
+    core_secret: undefined,
     backup_state: BackupStates.BackupFinished,
     success_details: successDetails,
   };
@@ -684,25 +684,24 @@ async function tryRecoverSecret(
   return { ...state };
 }
 
-async function solveChallenge(
+/**
+ * Request a truth, optionally with a challenge solution
+ * provided by the user.
+ */
+async function requestTruth(
   state: ReducerStateRecovery,
-  ta: ActionArgsSolveChallengeRequest,
+  truth: EscrowMethod,
+  solveRequest?: ActionArgsSolveChallengeRequest,
 ): Promise<ReducerStateRecovery | ReducerStateError> {
-  const recDoc: RecoveryDocument = state.verbatim_recovery_document!;
-  const truth = recDoc.escrow_methods.find(
-    (x) => x.uuid === state.selected_challenge_uuid,
-  );
-  if (!truth) {
-    throw "truth for challenge not found";
-  }
-
   const url = new URL(`/truth/${truth.uuid}`, truth.url);
 
-  // FIXME: This isn't correct for non-question truth responses.
-  url.searchParams.set(
-    "response",
-    await secureAnswerHash(ta.answer, truth.uuid, truth.truth_salt),
-  );
+  if (solveRequest) {
+    // FIXME: This isn't correct for non-question truth responses.
+    url.searchParams.set(
+      "response",
+      await secureAnswerHash(solveRequest.answer, truth.uuid, 
truth.truth_salt),
+    );
+  }
 
   const resp = await fetch(url.href, {
     headers: {
@@ -710,48 +709,79 @@ async function solveChallenge(
     },
   });
 
-  if (resp.status !== 200) {
-    return {
-      code: TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_FAILED,
-      hint: "got non-200 response",
-      http_status: resp.status,
-    } as ReducerStateError;
-  }
+  if (resp.status === HttpStatusCode.Ok) {
+    const answerSalt =
+      solveRequest && truth.escrow_type === "question"
+        ? solveRequest.answer
+        : undefined;
 
-  const answerSalt = truth.escrow_type === "question" ? ta.answer : undefined;
+    const userId = await userIdentifierDerive(
+      state.identity_attributes,
+      truth.provider_salt,
+    );
 
-  const userId = await userIdentifierDerive(
-    state.identity_attributes,
-    truth.provider_salt,
-  );
+    const respBody = new Uint8Array(await resp.arrayBuffer());
+    const keyShare = await decryptKeyShare(
+      encodeCrock(respBody),
+      userId,
+      answerSalt,
+    );
 
-  const respBody = new Uint8Array(await resp.arrayBuffer());
-  const keyShare = await decryptKeyShare(
-    encodeCrock(respBody),
-    userId,
-    answerSalt,
-  );
+    const recoveredKeyShares = {
+      ...(state.recovered_key_shares ?? {}),
+      [truth.uuid]: keyShare,
+    };
 
-  const recoveredKeyShares = {
-    ...(state.recovered_key_shares ?? {}),
-    [truth.uuid]: keyShare,
-  };
+    const challengeFeedback: { [x: string]: ChallengeFeedback } = {
+      ...state.challenge_feedback,
+      [truth.uuid]: {
+        state: ChallengeFeedbackStatus.Solved,
+      },
+    };
 
-  const challengeFeedback = {
-    ...state.challenge_feedback,
-    [truth.uuid]: {
-      state: "solved",
-    },
-  };
+    const newState: ReducerStateRecovery = {
+      ...state,
+      recovery_state: RecoveryStates.ChallengeSelecting,
+      challenge_feedback: challengeFeedback,
+      recovered_key_shares: recoveredKeyShares,
+    };
 
-  const newState: ReducerStateRecovery = {
-    ...state,
-    recovery_state: RecoveryStates.ChallengeSelecting,
-    challenge_feedback: challengeFeedback,
-    recovered_key_shares: recoveredKeyShares,
-  };
+    return tryRecoverSecret(newState);
+  }
+
+  if (resp.status === HttpStatusCode.Forbidden) {
+    return {
+      ...state,
+      recovery_state: RecoveryStates.ChallengeSolving,
+      challenge_feedback: {
+        ...state.challenge_feedback,
+        [truth.uuid]: {
+          state: ChallengeFeedbackStatus.Message,
+          message: "Challenge should be solved",
+        },
+      },
+    };
+  }
 
-  return tryRecoverSecret(newState);
+  return {
+    code: TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_FAILED,
+    hint: "got unexpected /truth/ response status",
+    http_status: resp.status,
+  } as ReducerStateError;
+}
+
+async function solveChallenge(
+  state: ReducerStateRecovery,
+  ta: ActionArgsSolveChallengeRequest,
+): Promise<ReducerStateRecovery | ReducerStateError> {
+  const recDoc: RecoveryDocument = state.verbatim_recovery_document!;
+  const truth = recDoc.escrow_methods.find(
+    (x) => x.uuid === state.selected_challenge_uuid,
+  );
+  if (!truth) {
+    throw Error("truth for challenge not found");
+  }
+  return requestTruth(state, truth, ta);
 }
 
 async function recoveryEnterUserAttributes(
@@ -776,19 +806,7 @@ async function selectChallenge(
     throw "truth for challenge not found";
   }
 
-  const url = new URL(`/truth/${truth.uuid}`, truth.url);
-
-  const resp = await fetch(url.href, {
-    headers: {
-      "Anastasis-Truth-Decryption-Key": truth.truth_key,
-    },
-  });
-
-  return {
-    ...state,
-    recovery_state: RecoveryStates.ChallengeSolving,
-    selected_challenge_uuid: ta.uuid,
-  };
+  return requestTruth({ ...state, selected_challenge_uuid: ta.uuid }, truth);
 }
 
 async function backupSelectContinent(
diff --git a/packages/anastasis-core/src/reducer-types.ts 
b/packages/anastasis-core/src/reducer-types.ts
index 94826870..69feb6b6 100644
--- a/packages/anastasis-core/src/reducer-types.ts
+++ b/packages/anastasis-core/src/reducer-types.ts
@@ -8,6 +8,7 @@ import {
   codecForTimestamp,
   Timestamp,
 } from "@gnu-taler/taler-util";
+import { ChallengeFeedback } from "./challenge-feedback-types.js";
 import { KeyShare } from "./crypto.js";
 import { RecoveryDocument } from "./recovery-document-types.js";
 
@@ -185,10 +186,6 @@ export interface ReducerStateRecovery {
   authentication_providers?: { [url: string]: AuthenticationProviderStatus };
 }
 
-export interface ChallengeFeedback {
-  state: string;
-}
-
 export interface ReducerStateError {
   backup_state?: undefined;
   recovery_state?: undefined;
@@ -311,21 +308,10 @@ export interface ActionArgSelectCountry {
   currencies: string[];
 }
 
-export const codecForActionArgSelectCountry = () =>
-  buildCodecForObject<ActionArgSelectCountry>()
-    .property("country_code", codecForString())
-    .property("currencies", codecForList(codecForString()))
-    .build("ActionArgSelectCountry");
-
 export interface ActionArgsSelectChallenge {
   uuid: string;
 }
 
-export const codecForActionArgSelectChallenge = () =>
-  buildCodecForObject<ActionArgsSelectChallenge>()
-    .property("uuid", codecForString())
-    .build("ActionArgSelectChallenge");
-
 export type ActionArgsSolveChallengeRequest = SolveChallengeAnswerRequest;
 
 export interface SolveChallengeAnswerRequest {
@@ -341,6 +327,10 @@ export interface ActionArgsAddPolicy {
   policy: PolicyMember[];
 }
 
+export interface ActionArgsUpdateExpiration {
+  expiration: Timestamp;
+}
+
 export const codecForPolicyMember = () =>
   buildCodecForObject<PolicyMember>()
     .property("authentication_method", codecForNumber())
@@ -352,11 +342,18 @@ export const codecForActionArgsAddPolicy = () =>
     .property("policy", codecForList(codecForPolicyMember()))
     .build("ActionArgsAddPolicy");
 
-export interface ActionArgsUpdateExpiration {
-  expiration: Timestamp;
-}
-
 export const codecForActionArgsUpdateExpiration = () =>
   buildCodecForObject<ActionArgsUpdateExpiration>()
     .property("expiration", codecForTimestamp)
     .build("ActionArgsUpdateExpiration");
+
+export const codecForActionArgSelectChallenge = () =>
+  buildCodecForObject<ActionArgsSelectChallenge>()
+    .property("uuid", codecForString())
+    .build("ActionArgSelectChallenge");
+
+export const codecForActionArgSelectCountry = () =>
+  buildCodecForObject<ActionArgSelectCountry>()
+    .property("country_code", codecForString())
+    .property("currencies", codecForList(codecForString()))
+    .build("ActionArgSelectCountry");

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]