aboutsummaryrefslogtreecommitdiff
path: root/packages/anastasis-core/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/anastasis-core/src')
-rw-r--r--packages/anastasis-core/src/challenge-feedback-types.ts109
-rw-r--r--packages/anastasis-core/src/index.ts139
-rw-r--r--packages/anastasis-core/src/provider-types.ts90
-rw-r--r--packages/anastasis-core/src/reducer-types.ts2
4 files changed, 245 insertions, 95 deletions
diff --git a/packages/anastasis-core/src/challenge-feedback-types.ts b/packages/anastasis-core/src/challenge-feedback-types.ts
index 0770d9296..30f42288f 100644
--- a/packages/anastasis-core/src/challenge-feedback-types.ts
+++ b/packages/anastasis-core/src/challenge-feedback-types.ts
@@ -1,29 +1,48 @@
+/*
+ This file is part of GNU Anastasis
+ (C) 2021-2022 Anastasis SARL
+
+ GNU Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
import { AmountString, HttpStatusCode } from "@gnu-taler/taler-util";
export enum ChallengeFeedbackStatus {
Solved = "solved",
+ CodeInFile = "code-in-file",
+ CodeSent = "code-sent",
ServerFailure = "server-failure",
TruthUnknown = "truth-unknown",
- Redirect = "redirect",
- Payment = "payment",
- Pending = "pending",
- Message = "message",
+ TalerPayment = "taler-payment",
Unsupported = "unsupported",
RateLimitExceeded = "rate-limit-exceeded",
AuthIban = "auth-iban",
+ IncorrectAnswer = "incorrect-answer",
}
export type ChallengeFeedback =
| ChallengeFeedbackSolved
- | ChallengeFeedbackPending
- | ChallengeFeedbackPayment
+ | ChallengeFeedbackCodeInFile
+ | ChallengeFeedbackCodeSent
+ | ChallengeFeedbackIncorrectAnswer
+ | ChallengeFeedbackTalerPaymentRequired
| ChallengeFeedbackServerFailure
| ChallengeFeedbackRateLimitExceeded
| ChallengeFeedbackTruthUnknown
- | ChallengeFeedbackRedirect
- | ChallengeFeedbackMessage
| ChallengeFeedbackUnsupported
- | ChallengeFeedbackAuthIban;
+ | ChallengeFeedbackBankTransferRequired;
/**
* Challenge has been solved and the key share has
@@ -33,13 +52,29 @@ export interface ChallengeFeedbackSolved {
state: ChallengeFeedbackStatus.Solved;
}
+export interface ChallengeFeedbackIncorrectAnswer {
+ state: ChallengeFeedbackStatus.IncorrectAnswer;
+}
+
+export interface ChallengeFeedbackCodeInFile {
+ state: ChallengeFeedbackStatus.CodeInFile;
+ filename: string;
+ display_hint: string;
+}
+
+export interface ChallengeFeedbackCodeSent {
+ state: ChallengeFeedbackStatus.CodeSent;
+ display_hint: string;
+ address_hint: string;
+}
+
/**
* 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.
*/
@@ -57,7 +92,7 @@ export interface ChallengeFeedbackRateLimitExceeded {
* Instructions for performing authentication via an
* IBAN bank transfer.
*/
-export interface ChallengeFeedbackAuthIban {
+export interface ChallengeFeedbackBankTransferRequired {
state: ChallengeFeedbackStatus.AuthIban;
/**
@@ -68,12 +103,12 @@ export interface ChallengeFeedbackAuthIban {
/**
* Account that should be credited.
*/
- credit_iban: string;
+ target_iban: string;
/**
* Creditor name.
*/
- business_name: string;
+ target_business_name: string;
/**
* Unstructured remittance information that should
@@ -81,41 +116,7 @@ export interface ChallengeFeedbackAuthIban {
*/
wire_transfer_subject: string;
- /**
- * FIXME: This field is only present for compatibility with
- * the C reducer test suite.
- */
- method: "iban";
-
answer_code: number;
-
- /**
- * FIXME: This field is only present for compatibility with
- * the C reducer test suite.
- */
- details: {
- challenge_amount: AmountString;
- credit_iban: string;
- business_name: string;
- 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;
}
/**
@@ -141,21 +142,11 @@ export interface ChallengeFeedbackTruthUnknown {
}
/**
- * 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;
+export interface ChallengeFeedbackTalerPaymentRequired {
+ state: ChallengeFeedbackStatus.TalerPayment;
taler_pay_uri: string;
diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts
index 98aba2ce6..055f3fb62 100644
--- a/packages/anastasis-core/src/index.ts
+++ b/packages/anastasis-core/src/index.ts
@@ -25,6 +25,7 @@ import {
} from "@gnu-taler/taler-util";
import { anastasisData } from "./anastasis-data.js";
import {
+ codecForChallengeInstructionMessage,
EscrowConfigurationResponse,
RecoveryMetaResponse,
TruthUploadRequest,
@@ -363,9 +364,10 @@ async function getTruthValue(
case "email":
case "totp":
case "iban":
+ case "post":
return authMethod.challenge;
default:
- throw Error("unknown auth type");
+ throw Error(`unknown auth type '${authMethod.type}'`);
}
}
@@ -947,17 +949,27 @@ async function requestTruth(
const hresp = await getResponseHash(truth, solveRequest);
- const resp = await fetch(url.href, {
- method: "POST",
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- truth_decryption_key: truth.truth_key,
- h_response: hresp,
- }),
- });
+ let resp: Response;
+
+ try {
+ resp = await fetch(url.href, {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ truth_decryption_key: truth.truth_key,
+ h_response: hresp,
+ }),
+ });
+ } catch (e) {
+ return {
+ reducer_type: "error",
+ code: TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_FAILED,
+ hint: "network error",
+ } as ReducerStateError;
+ }
logger.info(
`got POST /truth/.../solve response from ${truth.url}, http status ${resp.status}`,
@@ -1007,6 +1019,19 @@ async function requestTruth(
return tryRecoverSecret(newState);
}
+ if (resp.status === HttpStatusCode.Forbidden) {
+ const challengeFeedback: { [x: string]: ChallengeFeedback } = {
+ ...state.challenge_feedback,
+ [truth.uuid]: {
+ state: ChallengeFeedbackStatus.IncorrectAnswer,
+ },
+ };
+ return {
+ ...state,
+ challenge_feedback: challengeFeedback,
+ };
+ }
+
return {
reducer_type: "error",
code: TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_FAILED,
@@ -1072,6 +1097,9 @@ async function selectChallenge(
const url = new URL(`/truth/${truth.uuid}/challenge`, truth.url);
+ const newFeedback = { ...state.challenge_feedback };
+ delete newFeedback[truth.uuid];
+
switch (truth.escrow_type) {
case ChallengeType.Question:
case ChallengeType.Totp: {
@@ -1079,51 +1107,93 @@ async function selectChallenge(
...state,
recovery_state: RecoveryStates.ChallengeSolving,
selected_challenge_uuid: truth.uuid,
- challenge_feedback: {
- ...state.challenge_feedback,
- [truth.uuid]: {
- state: ChallengeFeedbackStatus.Pending,
- },
- },
+ challenge_feedback: newFeedback,
};
}
}
- const resp = await fetch(url.href, {
- method: "POST",
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- truth_decryption_key: truth.truth_key,
- }),
- });
+ let resp: Response;
+
+ try {
+ resp = await fetch(url.href, {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ truth_decryption_key: truth.truth_key,
+ }),
+ });
+ } catch (e) {
+ const feedback: ChallengeFeedback = {
+ state: ChallengeFeedbackStatus.ServerFailure,
+ http_status: 0,
+ };
+ return {
+ ...state,
+ recovery_state: RecoveryStates.ChallengeSelecting,
+ selected_challenge_uuid: truth.uuid,
+ challenge_feedback: {
+ ...state.challenge_feedback,
+ [truth.uuid]: feedback,
+ },
+ };
+ }
logger.info(
`got GET /truth/.../challenge response from ${truth.url}, http status ${resp.status}`,
);
if (resp.status === HttpStatusCode.Ok) {
+ const respBodyJson = await resp.json();
+ const instr = codecForChallengeInstructionMessage().decode(respBodyJson);
+ let feedback: ChallengeFeedback;
+ switch (instr.method) {
+ case "FILE_WRITTEN": {
+ feedback = {
+ state: ChallengeFeedbackStatus.CodeInFile,
+ display_hint: "TAN code is in file (for debugging)",
+ filename: instr.filename,
+ };
+ break;
+ }
+ case "IBAN_WIRE": {
+ feedback = {
+ state: ChallengeFeedbackStatus.AuthIban,
+ answer_code: instr.answer_code,
+ target_business_name: instr.business_name,
+ challenge_amount: instr.amount,
+ target_iban: instr.credit_iban,
+ wire_transfer_subject: instr.wire_transfer_subject,
+ };
+ break;
+ }
+ case "TAN_SENT": {
+ feedback = {
+ state: ChallengeFeedbackStatus.CodeSent,
+ address_hint: instr.tan_address_hint,
+ display_hint: "Code sent to address",
+ };
+ }
+ }
return {
...state,
recovery_state: RecoveryStates.ChallengeSolving,
selected_challenge_uuid: truth.uuid,
challenge_feedback: {
...state.challenge_feedback,
- [truth.uuid]: {
- state: ChallengeFeedbackStatus.Pending,
- },
+ [truth.uuid]: feedback,
},
};
}
- // FIXME: look at response, include in challenge_feedback!
+ // FIXME: look at more error codes in response
return {
reducer_type: "error",
code: TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_FAILED,
- hint: "got unexpected /truth/.../challenge response status",
+ hint: `got unexpected /truth/.../challenge response status (${resp.status})`,
http_status: resp.status,
} as ReducerStateError;
}
@@ -1727,8 +1797,9 @@ export async function reduceAction(
}
try {
return await h.handler(state, parsedArgs);
- } catch (e) {
+ } catch (e: any) {
logger.error("action handler failed");
+ logger.error(`${e?.stack ?? e}`);
if (e instanceof ReducerError) {
return {
reducer_type: "error",
diff --git a/packages/anastasis-core/src/provider-types.ts b/packages/anastasis-core/src/provider-types.ts
index 72f2dc6e5..4da62fc04 100644
--- a/packages/anastasis-core/src/provider-types.ts
+++ b/packages/anastasis-core/src/provider-types.ts
@@ -16,6 +16,13 @@
import {
AmountString,
+ buildCodecForObject,
+ buildCodecForUnion,
+ Codec,
+ codecForAmountString,
+ codecForConstString,
+ codecForNumber,
+ codecForString,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util";
@@ -122,3 +129,86 @@ export interface RecoveryMetaDataItem {
// document was uploaded.
upload_time: TalerProtocolTimestamp;
}
+
+export type ChallengeInstructionMessage =
+ | FileChallengeInstructionMessage
+ | IbanChallengeInstructionMessage
+ | PinChallengeInstructionMessage;
+
+export interface IbanChallengeInstructionMessage {
+ // What kind of challenge is this?
+ method: "IBAN_WIRE";
+
+ // How much should be wired?
+ amount: AmountString;
+
+ // What is the target IBAN?
+ credit_iban: string;
+
+ // What is the receiver name?
+ business_name: string;
+
+ // What is the expected wire transfer subject?
+ wire_transfer_subject: string;
+
+ // What is the numeric code (also part of the
+ // wire transfer subject) to be hashed when
+ // solving the challenge?
+ answer_code: number;
+
+ // Hint about the origin account that must be used.
+ debit_account_hint: string;
+}
+
+export interface PinChallengeInstructionMessage {
+ // What kind of challenge is this?
+ method: "TAN_SENT";
+
+ // Where was the PIN code sent? Note that this
+ // address will most likely have been obscured
+ // to improve privacy.
+ tan_address_hint: string;
+}
+
+export interface FileChallengeInstructionMessage {
+ // What kind of challenge is this?
+ method: "FILE_WRITTEN";
+
+ // Name of the file where the PIN code was written.
+ filename: string;
+}
+
+export const codecForFileChallengeInstructionMessage =
+ (): Codec<FileChallengeInstructionMessage> =>
+ buildCodecForObject<FileChallengeInstructionMessage>()
+ .property("method", codecForConstString("FILE_WRITTEN"))
+ .property("filename", codecForString())
+ .build("FileChallengeInstructionMessage");
+
+export const codecForPinChallengeInstructionMessage =
+ (): Codec<PinChallengeInstructionMessage> =>
+ buildCodecForObject<PinChallengeInstructionMessage>()
+ .property("method", codecForConstString("TAN_SENT"))
+ .property("tan_address_hint", codecForString())
+ .build("PinChallengeInstructionMessage");
+
+export const codecForIbanChallengeInstructionMessage =
+ (): Codec<IbanChallengeInstructionMessage> =>
+ buildCodecForObject<IbanChallengeInstructionMessage>()
+ .property("method", codecForConstString("IBAN_WIRE"))
+ .property("amount", codecForAmountString())
+ .property("business_name", codecForString())
+ .property("credit_iban", codecForString())
+ .property("wire_transfer_subject", codecForString())
+ .property("answer_code", codecForNumber())
+ .property("debit_account_hint", codecForString())
+ .build("IbanChallengeInstructionMessage");
+
+export const codecForChallengeInstructionMessage =
+ (): Codec<ChallengeInstructionMessage> =>
+ buildCodecForUnion<ChallengeInstructionMessage>()
+ .discriminateOn("method")
+ .alternative("FILE_WRITTEN", codecForFileChallengeInstructionMessage())
+ .alternative("IBAN_WIRE", codecForIbanChallengeInstructionMessage())
+ .alternative("TAN_SENT", codecForPinChallengeInstructionMessage())
+ .build("ChallengeInstructionMessage");
diff --git a/packages/anastasis-core/src/reducer-types.ts b/packages/anastasis-core/src/reducer-types.ts
index 5b5f40297..b7e3148cb 100644
--- a/packages/anastasis-core/src/reducer-types.ts
+++ b/packages/anastasis-core/src/reducer-types.ts
@@ -220,8 +220,6 @@ export interface ReducerStateRecovery {
/**
* Explicitly selected version by the user.
- * FIXME: In the C reducer this is called "version".
- * FIXME: rename to selected_secret / selected_policy?
*/
selected_version?: AggregatedPolicyMetaInfo;