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 + */ + +/** + * 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; } /** @@ -140,22 +141,12 @@ 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; +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 => + buildCodecForObject() + .property("method", codecForConstString("FILE_WRITTEN")) + .property("filename", codecForString()) + .build("FileChallengeInstructionMessage"); + +export const codecForPinChallengeInstructionMessage = + (): Codec => + buildCodecForObject() + .property("method", codecForConstString("TAN_SENT")) + .property("tan_address_hint", codecForString()) + .build("PinChallengeInstructionMessage"); + +export const codecForIbanChallengeInstructionMessage = + (): Codec => + buildCodecForObject() + .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 => + buildCodecForUnion() + .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; diff --git a/packages/anastasis-webui/package.json b/packages/anastasis-webui/package.json index 2327b5e12..a855ffa94 100644 --- a/packages/anastasis-webui/package.json +++ b/packages/anastasis-webui/package.json @@ -15,7 +15,6 @@ "pretty": "prettier --write src", "storybook": "start-storybook -p 6006" }, - "type": "module", "eslintConfig": { "parser": "@typescript-eslint/parser", "extends": [ @@ -62,4 +61,4 @@ "sirv-cli": "^1.0.14", "typescript": "^4.5.4" } -} \ No newline at end of file +} diff --git a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts index 7274c3d03..434e5fb09 100644 --- a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts +++ b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts @@ -1,3 +1,22 @@ +/* + 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 + */ + +/** + * Imports. + */ import { TalerErrorCode } from "@gnu-taler/taler-util"; import { AggregatedPolicyMetaInfo, @@ -7,7 +26,6 @@ import { getBackupStartState, getRecoveryStartState, mergeDiscoveryAggregate, - PolicyMetaInfo, RecoveryStates, reduceAction, ReducerState, diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx index e26ba706f..49cddc8b7 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx @@ -44,14 +44,6 @@ export const NewProviderWithoutProviderList = createExample(TestedComponent, { authentication_providers: {}, } as ReducerState); -export const NewVideoProvider = createExample( - TestedComponent, - { - ...reducerStatesExample.authEditing, - } as ReducerState, - { providerType: "video" }, -); - export const NewSmsProvider = createExample( TestedComponent, { diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx index 3bd6a0c17..3d765aa86 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -249,19 +249,15 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample( }, challenge_feedback: { "uuid-1": { state: ChallengeFeedbackStatus.Solved.toString() }, - "uuid-2": { - state: ChallengeFeedbackStatus.Message.toString(), - message: "Challenge should be solved", - }, "uuid-3": { state: ChallengeFeedbackStatus.AuthIban.toString(), challenge_amount: "EUR:1", - credit_iban: "DE12345789000", - business_name: "Data Loss Incorporated", + target_iban: "DE12345789000", + target_business_name: "Data Loss Incorporated", wire_transfer_subject: "Anastasis 987654321", }, "uuid-4": { - state: ChallengeFeedbackStatus.Payment.toString(), + state: ChallengeFeedbackStatus.TalerPayment.toString(), taler_pay_uri: "taler://pay/...", provider: "https://localhost:8080/", payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", @@ -270,11 +266,6 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample( state: ChallengeFeedbackStatus.RateLimitExceeded.toString(), // "error_code": 8121 }, - "uuid-6": { - state: ChallengeFeedbackStatus.Redirect.toString(), - redirect_url: "https://videoconf.example.com/", - http_status: 303, - }, "uuid-7": { state: ChallengeFeedbackStatus.ServerFailure.toString(), http_status: 500, diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index c4047f0b3..6660e63de 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -14,11 +14,8 @@ function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) { return null; } switch (feedback.state) { - case ChallengeFeedbackStatus.Message: - return
{feedback.message}
; case ChallengeFeedbackStatus.Solved: return
; - case ChallengeFeedbackStatus.Pending: case ChallengeFeedbackStatus.AuthIban: return null; case ChallengeFeedbackStatus.ServerFailure: @@ -43,7 +40,6 @@ function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) { provider for further information.
); - case ChallengeFeedbackStatus.Redirect: default: return
; } @@ -178,7 +174,7 @@ export function ChallengeOverviewScreen(): VNode { case ChallengeFeedbackStatus.RateLimitExceeded: return
; case ChallengeFeedbackStatus.AuthIban: - case ChallengeFeedbackStatus.Payment: + case ChallengeFeedbackStatus.TalerPayment: return (
); - case ChallengeFeedbackStatus.Redirect: - return ( -
- - Go to {feedback.redirect_url} - -
- ); case ChallengeFeedbackStatus.Solved: return (
diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx index 1070cf8a9..3691d1416 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx @@ -16,19 +16,7 @@ export function SolveOverviewFeedbackDisplay(props: { return
; } switch (feedback.state) { - case ChallengeFeedbackStatus.Message: - return ( - - ); - case ChallengeFeedbackStatus.Payment: + case ChallengeFeedbackStatus.TalerPayment: return ( @@ -80,22 +68,6 @@ export function SolveOverviewFeedbackDisplay(props: { ]} /> ); - case ChallengeFeedbackStatus.Redirect: - return ( - - Please visit this link: {feedback.redirect_url} - - ), - }, - ]} - /> - ); case ChallengeFeedbackStatus.Unsupported: return ( ); default: + console.warn( + `unknown challenge feedback status ${JSON.stringify(feedback)}`, + ); return
; } } diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx index 1e7053df5..d82111979 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx @@ -80,7 +80,7 @@ export const PaymentFeedback = createExample( selected_challenge_uuid: "uuid-1", challenge_feedback: { "uuid-1": { - state: ChallengeFeedbackStatus.Payment, + state: ChallengeFeedbackStatus.TalerPayment, taler_pay_uri: "taler://pay/...", provider: "https://localhost:8080/", payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx index 4f7f21324..935b45a77 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx @@ -9,6 +9,7 @@ import { TextInput } from "../../../components/fields/TextInput"; import { useAnastasisContext } from "../../../context/anastasis"; import { AnastasisClientFrame } from "../index"; import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { shouldHideConfirm } from "./helpers"; import { AuthMethodSolveProps } from "./index"; export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { @@ -103,12 +104,6 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - const shouldHideConfirm = - feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || - feedback?.state === ChallengeFeedbackStatus.Redirect || - feedback?.state === ChallengeFeedbackStatus.Unsupported || - feedback?.state === ChallengeFeedbackStatus.TruthUnknown; - return ( @@ -160,7 +155,7 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { - {!shouldHideConfirm && ( + {!shouldHideConfirm(feedback) && ( Confirm diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx index b58952feb..39788b538 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx @@ -5,10 +5,10 @@ import { import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AsyncButton } from "../../../components/AsyncButton"; -import { TextInput } from "../../../components/fields/TextInput"; import { useAnastasisContext } from "../../../context/anastasis"; import { AnastasisClientFrame } from "../index"; import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { shouldHideConfirm } from "./helpers"; import { AuthMethodSolveProps } from "./index"; export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { @@ -79,12 +79,6 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - const shouldHideConfirm = - feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || - feedback?.state === ChallengeFeedbackStatus.Redirect || - feedback?.state === ChallengeFeedbackStatus.Unsupported || - feedback?.state === ChallengeFeedbackStatus.TruthUnknown; - return ( @@ -101,7 +95,7 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { - {!shouldHideConfirm && ( + {!shouldHideConfirm(feedback) && ( Confirm diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx index fcff0b498..382ffa00a 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx @@ -9,6 +9,7 @@ import { TextInput } from "../../../components/fields/TextInput"; import { useAnastasisContext } from "../../../context/anastasis"; import { AnastasisClientFrame } from "../index"; import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { shouldHideConfirm } from "./helpers"; import { AuthMethodSolveProps } from "./index"; export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { @@ -102,12 +103,6 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - const shouldHideConfirm = - feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || - feedback?.state === ChallengeFeedbackStatus.Redirect || - feedback?.state === ChallengeFeedbackStatus.Unsupported || - feedback?.state === ChallengeFeedbackStatus.TruthUnknown; - return ( @@ -130,7 +125,7 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { - {!shouldHideConfirm && ( + {!shouldHideConfirm(feedback) && ( Confirm diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx index c24ab0503..51d0a9993 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx @@ -20,6 +20,7 @@ */ import { + ChallengeFeedbackBankTransferRequired, ChallengeFeedbackStatus, ReducerState, } from "@gnu-taler/anastasis-core"; @@ -62,28 +63,6 @@ export const WithoutFeedback = createExample( }, ); -export const MessageFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [ - { - cost: "USD:1", - instructions: "does P equals NP?", - type: "question", - uuid: "ASDASDSAD!1", - }, - ], - policies: [], - }, - selected_challenge_uuid: "ASDASDSAD!1", - challenge_feedback: { - "ASDASDSAD!1": { - state: ChallengeFeedbackStatus.Message, - message: "Challenge should be solved", - }, - }, -} as ReducerState); - export const ServerFailureFeedback = createExample( TestedComponent[type].solve, { @@ -92,7 +71,7 @@ export const ServerFailureFeedback = createExample( challenges: [ { cost: "USD:1", - instructions: "does P equals NP?", + instructions: "does P equal NP?", type: "question", uuid: "ASDASDSAD!1", }, @@ -110,29 +89,6 @@ export const ServerFailureFeedback = createExample( } as ReducerState, ); -export const RedirectFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [ - { - cost: "USD:1", - instructions: "does P equals NP?", - type: "question", - uuid: "ASDASDSAD!1", - }, - ], - policies: [], - }, - selected_challenge_uuid: "ASDASDSAD!1", - challenge_feedback: { - "ASDASDSAD!1": { - state: ChallengeFeedbackStatus.Redirect, - http_status: 302, - redirect_url: "http://video.taler.net", - }, - }, -} as ReducerState); - export const MessageRateLimitExceededFeedback = createExample( TestedComponent[type].solve, { @@ -201,6 +157,15 @@ export const TruthUnknownFeedback = createExample(TestedComponent[type].solve, { }, } as ReducerState); +const ibanFeedback: ChallengeFeedbackBankTransferRequired = { + state: ChallengeFeedbackStatus.AuthIban, + challenge_amount: "EUR:1", + target_iban: "DE12345789000", + target_business_name: "Data Loss Incorporated", + wire_transfer_subject: "Anastasis 987654321", + answer_code: 987654321, +}; + export const AuthIbanFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { @@ -216,23 +181,7 @@ export const AuthIbanFeedback = createExample(TestedComponent[type].solve, { }, selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - "ASDASDSAD!1": { - state: ChallengeFeedbackStatus.AuthIban, - challenge_amount: "EUR:1", - credit_iban: "DE12345789000", - business_name: "Data Loss Incorporated", - wire_transfer_subject: "Anastasis 987654321", - answer_code: 987654321, - // Fields that follow are only for compatibility with C reducer, - // will be removed eventually, - details: { - business_name: "foo", - challenge_amount: "foo", - credit_iban: "foo", - wire_transfer_subject: "foo", - }, - method: "iban", - }, + "ASDASDSAD!1": ibanFeedback, }, } as ReducerState); @@ -252,7 +201,7 @@ export const PaymentFeedback = createExample(TestedComponent[type].solve, { selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { "ASDASDSAD!1": { - state: ChallengeFeedbackStatus.Payment, + state: ChallengeFeedbackStatus.TalerPayment, taler_pay_uri: "taler://pay/...", provider: "https://localhost:8080/", payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx index 058efe009..bc0b67dcb 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx @@ -9,6 +9,7 @@ import { TextInput } from "../../../components/fields/TextInput"; import { useAnastasisContext } from "../../../context/anastasis"; import { AnastasisClientFrame } from "../index"; import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { shouldHideConfirm } from "./helpers"; import { AuthMethodSolveProps } from "./index"; export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { @@ -79,12 +80,6 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - const shouldHideConfirm = - feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || - feedback?.state === ChallengeFeedbackStatus.Redirect || - feedback?.state === ChallengeFeedbackStatus.Unsupported || - feedback?.state === ChallengeFeedbackStatus.TruthUnknown; - return ( @@ -110,7 +105,7 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { - {!shouldHideConfirm && ( + {!shouldHideConfirm(feedback) && ( Confirm diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx index 3b00f6f2a..f3d304c74 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx @@ -9,6 +9,7 @@ import { TextInput } from "../../../components/fields/TextInput"; import { useAnastasisContext } from "../../../context/anastasis"; import { AnastasisClientFrame } from "../index"; import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { shouldHideConfirm } from "./helpers"; import { AuthMethodSolveProps } from "./index"; export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { @@ -103,12 +104,6 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - const shouldHideConfirm = - feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || - feedback?.state === ChallengeFeedbackStatus.Redirect || - feedback?.state === ChallengeFeedbackStatus.Unsupported || - feedback?.state === ChallengeFeedbackStatus.TruthUnknown; - return ( @@ -160,7 +155,7 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { - {!shouldHideConfirm && ( + {!shouldHideConfirm(feedback) && ( Confirm diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx index ee4937441..6b98f8ece 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx @@ -9,6 +9,7 @@ import { TextInput } from "../../../components/fields/TextInput"; import { useAnastasisContext } from "../../../context/anastasis"; import { AnastasisClientFrame } from "../index"; import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { shouldHideConfirm } from "./helpers"; import { AuthMethodSolveProps } from "./index"; export function AuthMethodTotpSolve(props: AuthMethodSolveProps): VNode { @@ -81,12 +82,6 @@ export function AuthMethodTotpSolve(props: AuthMethodSolveProps): VNode { reducer?.back(); } - const shouldHideConfirm = - feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || - feedback?.state === ChallengeFeedbackStatus.Redirect || - feedback?.state === ChallengeFeedbackStatus.Unsupported || - feedback?.state === ChallengeFeedbackStatus.TruthUnknown; - return ( @@ -108,7 +103,7 @@ export function AuthMethodTotpSolve(props: AuthMethodSolveProps): VNode { - {!shouldHideConfirm && ( + {!shouldHideConfirm(feedback) && ( Confirm diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx deleted file mode 100644 index 4aad0a097..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { createExample, reducerStatesExample } from "../../../utils"; -import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; -import logoImage from "../../../assets/logo.jpeg"; - -export default { - title: "Pages/backup/AuthorizationMethod/AuthMethods/Video", - component: TestedComponent, - args: { - order: 5, - }, - argTypes: { - onUpdate: { action: "onUpdate" }, - onBack: { action: "onBack" }, - }, -}; - -const type: KnownAuthMethods = "video"; - -export const Empty = createExample( - TestedComponent[type].setup, - reducerStatesExample.authEditing, - { - configured: [], - }, -); - -export const WithOneExample = createExample( - TestedComponent[type].setup, - reducerStatesExample.authEditing, - { - configured: [ - { - challenge: "qwe", - type, - instructions: logoImage, - remove: () => null, - }, - ], - }, -); - -export const WithMoreExamples = createExample( - TestedComponent[type].setup, - reducerStatesExample.authEditing, - { - configured: [ - { - challenge: "qwe", - type, - instructions: logoImage, - remove: () => null, - }, - { - challenge: "qwe", - type, - instructions: logoImage, - remove: () => null, - }, - ], - }, -); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx deleted file mode 100644 index 04a129c4a..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { ImageInput } from "../../../components/fields/ImageInput"; -import { AuthMethodSetupProps } from "./index"; -import { AnastasisClientFrame } from "../index"; - -export function AuthMethodVideoSetup({ - cancel, - addAuthMethod, - configured, -}: AuthMethodSetupProps): VNode { - const [image, setImage] = useState(""); - const addVideoAuth = (): void => { - addAuthMethod({ - authentication_method: { - type: "video", - instructions: "Join a video call", - challenge: encodeCrock(stringToBytes(image)), - }, - }); - }; - function goNextIfNoErrors(): void { - addVideoAuth(); - } - return ( - -

- For video identification, you need to provide a passport-style - photograph. When recovering your secret, you will be asked to join a - video call. During that call, a human will use the photograph to verify - your identity. -

-
- -
- {configured.length > 0 && ( -
-
Your photographs:
-
- {configured.map((c, i) => { - return ( -
- -
- -
-
- ); - })} -
-
- )} -
-
- - -
-
-
- ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx deleted file mode 100644 index 0e454dd73..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { - ChallengeFeedbackStatus, - ReducerState, -} from "@gnu-taler/anastasis-core"; -import { createExample, reducerStatesExample } from "../../../utils"; -import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; - -export default { - title: "Pages/recovery/SolveChallenge/AuthMethods/video", - component: TestedComponent, - args: { - order: 5, - }, - argTypes: { - onUpdate: { action: "onUpdate" }, - onBack: { action: "onBack" }, - }, -}; - -const type: KnownAuthMethods = "video"; - -export const WithoutFeedback = createExample( - TestedComponent[type].solve, - { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [ - { - cost: "USD:1", - instructions: "does P equals NP?", - type: "question", - uuid: "uuid-1", - }, - ], - policies: [], - }, - selected_challenge_uuid: "uuid-1", - } as ReducerState, - { - id: "uuid-1", - }, -); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx deleted file mode 100644 index e0ebdce76..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { - ChallengeFeedbackStatus, - ChallengeInfo, -} from "@gnu-taler/anastasis-core"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { AsyncButton } from "../../../components/AsyncButton"; -import { TextInput } from "../../../components/fields/TextInput"; -import { useAnastasisContext } from "../../../context/anastasis"; -import { AnastasisClientFrame } from "../index"; -import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; -import { AuthMethodSolveProps } from "./index"; - -export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode { - const [answer, setAnswer] = useState(""); - - const reducer = useAnastasisContext(); - if (!reducer) { - return ( - -
no reducer in context
-
- ); - } - if ( - reducer.currentReducerState?.reducer_type !== "recovery" - ) { - return ( - -
invalid state
-
- ); - } - - if (!reducer.currentReducerState.recovery_information) { - return ( - -
no recovery information found
-
- ); - } - if (!reducer.currentReducerState.selected_challenge_uuid) { - return ( - -
invalid state
-
- -
-
- ); - } - - const chArr = reducer.currentReducerState.recovery_information.challenges; - const challengeFeedback = - reducer.currentReducerState.challenge_feedback ?? {}; - const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; - const challenges: { - [uuid: string]: ChallengeInfo; - } = {}; - for (const ch of chArr) { - challenges[ch.uuid] = ch; - } - const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid]; - - async function onNext(): Promise { - return reducer?.transition("solve_challenge", { answer }); - } - function onCancel(): void { - reducer?.back(); - } - - const shouldHideConfirm = - feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || - feedback?.state === ChallengeFeedbackStatus.Redirect || - feedback?.state === ChallengeFeedbackStatus.Unsupported || - feedback?.state === ChallengeFeedbackStatus.TruthUnknown; - - return ( - - -

You are gonna be called to check your identity

- - -
- - {!shouldHideConfirm && ( - - Confirm - - )} -
-
- ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/helpers.ts b/packages/anastasis-webui/src/pages/home/authMethod/helpers.ts new file mode 100644 index 000000000..2f5e3773e --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/helpers.ts @@ -0,0 +1,12 @@ +import { + ChallengeFeedback, + ChallengeFeedbackStatus, +} from "@gnu-taler/anastasis-core"; + +export function shouldHideConfirm(feedback: ChallengeFeedback): boolean { + return ( + feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || + feedback?.state === ChallengeFeedbackStatus.Unsupported || + feedback?.state === ChallengeFeedbackStatus.TruthUnknown + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx index 64cf07cd6..a1ab9cd28 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx @@ -3,22 +3,18 @@ import { h, VNode } from "preact"; import postalIcon from "../../../assets/icons/auth_method/postal.svg"; import questionIcon from "../../../assets/icons/auth_method/question.svg"; import smsIcon from "../../../assets/icons/auth_method/sms.svg"; -import videoIcon from "../../../assets/icons/auth_method/video.svg"; import { AuthMethodEmailSetup as EmailSetup } from "./AuthMethodEmailSetup"; import { AuthMethodEmailSolve as EmailSolve } from "./AuthMethodEmailSolve"; import { AuthMethodIbanSetup as IbanSetup } from "./AuthMethodIbanSetup"; -import { AuthMethodPostSetup as PostalSetup } from "./AuthMethodPostSetup"; -import { AuthMethodQuestionSetup as QuestionSetup } from "./AuthMethodQuestionSetup"; -import { AuthMethodSmsSetup as SmsSetup } from "./AuthMethodSmsSetup"; -import { AuthMethodTotpSetup as TotpSetup } from "./AuthMethodTotpSetup"; -import { AuthMethodVideoSetup as VideoSetup } from "./AuthMethodVideoSetup"; - import { AuthMethodIbanSolve as IbanSolve } from "./AuthMethodIbanSolve"; +import { AuthMethodPostSetup as PostalSetup } from "./AuthMethodPostSetup"; import { AuthMethodPostSolve as PostalSolve } from "./AuthMethodPostSolve"; +import { AuthMethodQuestionSetup as QuestionSetup } from "./AuthMethodQuestionSetup"; import { AuthMethodQuestionSolve as QuestionSolve } from "./AuthMethodQuestionSolve"; +import { AuthMethodSmsSetup as SmsSetup } from "./AuthMethodSmsSetup"; import { AuthMethodSmsSolve as SmsSolve } from "./AuthMethodSmsSolve"; +import { AuthMethodTotpSetup as TotpSetup } from "./AuthMethodTotpSetup"; import { AuthMethodTotpSolve as TotpSolve } from "./AuthMethodTotpSolve"; -import { AuthMethodVideoSolve as VideoSolve } from "./AuthMethodVideoSolve"; export type AuthMethodWithRemove = AuthMethod & { remove: () => void }; @@ -40,14 +36,12 @@ interface AuthMethodConfiguration { solve: (props: AuthMethodSolveProps) => VNode; skip?: boolean; } -// export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban"; const ALL_METHODS = [ "sms", "email", "post", "question", - "video", "totp", "iban", ] as const; @@ -97,11 +91,4 @@ export const authMethods: KnowMethodConfig = { setup: TotpSetup, solve: TotpSolve, }, - video: { - icon: , - label: "Video", - setup: VideoSetup, - solve: VideoSolve, - skip: true, - }, }; diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts index c9889160a..09d6856ca 100644 --- a/packages/taler-util/src/payto.ts +++ b/packages/taler-util/src/payto.ts @@ -14,10 +14,14 @@ GNU Taler; see the file COPYING. If not, see */ -import { generateFakeSegwitAddress } from "./index.js"; +import { generateFakeSegwitAddress } from "./bitcoin.js"; import { URLSearchParams } from "./url.js"; -export type PaytoUri = PaytoUriUnknown | PaytoUriIBAN | PaytoUriTalerBank | PaytoUriBitcoin; +export type PaytoUri = + | PaytoUriUnknown + | PaytoUriIBAN + | PaytoUriTalerBank + | PaytoUriBitcoin; interface PaytoUriGeneric { targetType: string; @@ -31,38 +35,41 @@ interface PaytoUriUnknown extends PaytoUriGeneric { interface PaytoUriIBAN extends PaytoUriGeneric { isKnown: true; - targetType: 'iban', + targetType: "iban"; iban: string; } interface PaytoUriTalerBank extends PaytoUriGeneric { isKnown: true; - targetType: 'x-taler-bank', + targetType: "x-taler-bank"; host: string; account: string; } interface PaytoUriBitcoin extends PaytoUriGeneric { isKnown: true; - targetType: 'bitcoin', - generateSegwitAddress: (r: string) => { addr1: string, addr2: string }; - addr1?: string, addr2?: string, + targetType: "bitcoin"; + generateSegwitAddress: (r: string) => { addr1: string; addr2: string }; + addr1?: string; + addr2?: string; } const paytoPfx = "payto://"; - - function buildSegwitGenerator(result: PaytoUriBitcoin, targetPath: string) { //generate segwit address just once, save addr in payto object //and use it as cache - return function generateSegwitAddress(reserve: string): { addr1: string, addr2: string } { - if (result.addr1 && result.addr2) return { addr1: result.addr1, addr2: result.addr2 }; - const { addr1, addr2 } = generateFakeSegwitAddress(reserve, targetPath) - result.addr1 = addr1 - result.addr2 = addr2 - return { addr1, addr2 } - } + return function generateSegwitAddress(reserve: string): { + addr1: string; + addr2: string; + } { + if (result.addr1 && result.addr2) + return { addr1: result.addr1, addr2: result.addr2 }; + const { addr1, addr2 } = generateFakeSegwitAddress(reserve, targetPath); + result.addr1 = addr1; + result.addr2 = addr2; + return { addr1, addr2 }; + }; } /** * Add query parameters to a payto URI @@ -81,27 +88,27 @@ export function addPaytoQueryParams( /** * Serialize a PaytoURI into a valid payto:// string - * - * @param p - * @returns + * + * @param p + * @returns */ export function stringifyPaytoUri(p: PaytoUri): string { - const url = `${paytoPfx}${p.targetType}//${p.targetPath}` + const url = `${paytoPfx}${p.targetType}//${p.targetPath}`; if (p.params) { const search = Object.entries(p.params) .map(([key, value]) => `${key}=${value}`) .join("&"); - return `${url}?${search}` + return `${url}?${search}`; } - return url + return url; } /** * Parse a valid payto:// uri into a PaytoUri object * RFC 8905 - * - * @param s - * @returns + * + * @param s + * @returns */ export function parsePaytoUri(s: string): PaytoUri | undefined { if (!s.startsWith(paytoPfx)) { @@ -127,47 +134,44 @@ export function parsePaytoUri(s: string): PaytoUri | undefined { params[v] = k; }); - if (targetType === 'x-taler-bank') { - const parts = targetPath.split('/') - const host = parts[0] - const account = parts[1] + if (targetType === "x-taler-bank") { + const parts = targetPath.split("/"); + const host = parts[0]; + const account = parts[1]; return { targetPath, targetType, params, isKnown: true, - host, account, + host, + account, }; - } - if (targetType === 'iban') { + if (targetType === "iban") { return { isKnown: true, targetPath, targetType, params, - iban: targetPath + iban: targetPath, }; - } - if (targetType === 'bitcoin') { - + if (targetType === "bitcoin") { const result: PaytoUriBitcoin = { isKnown: true, targetPath, targetType, params, - generateSegwitAddress: (): any => null - } + generateSegwitAddress: (): any => null, + }; - result.generateSegwitAddress = buildSegwitGenerator(result, targetPath) + result.generateSegwitAddress = buildSegwitGenerator(result, targetPath); return result; - } return { targetPath, targetType, params, - isKnown: false + isKnown: false, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 89a87ff00..8f0d6745d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,7 @@ importers: '@types/enzyme': ^3.10.11 '@typescript-eslint/eslint-plugin': ^5.3.0 '@typescript-eslint/parser': ^5.3.0 + babel-plugin-add-import-extension: ^1.6.0 base64-inline-loader: 1.1.1 bulma: ^0.9.3 bulma-checkbox: ^1.1.1 @@ -106,6 +107,7 @@ importers: '@types/enzyme': 3.10.11 '@typescript-eslint/eslint-plugin': 5.11.0_de5a1ddccd75ca1e499b8b8491d3dcba '@typescript-eslint/parser': 5.11.0_eslint@8.8.0+typescript@4.5.5 + babel-plugin-add-import-extension: 1.6.0_@babel+core@7.13.16 bulma: 0.9.3 bulma-checkbox: 1.2.1 bulma-radio: 1.2.0 @@ -7137,6 +7139,15 @@ packages: schema-utils: 2.7.1 dev: true + /babel-plugin-add-import-extension/1.6.0_@babel+core@7.13.16: + resolution: {integrity: sha512-JVSQPMzNzN/S4wPRoKQ7+u8PlkV//BPUMnfWVbr63zcE+6yHdU2Mblz10Vf7qe+6Rmu4svF5jG7JxdcPi9VvKg==} + peerDependencies: + '@babel/core': '>=7.0.0' + dependencies: + '@babel/core': 7.13.16 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /babel-plugin-apply-mdx-type-prop/1.6.22_@babel+core@7.12.9: resolution: {integrity: sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==} peerDependencies: