summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-01-05 12:29:53 -0300
committerSebastian <sebasjm@gmail.com>2024-01-05 12:29:53 -0300
commit1b9db448dc3dd4893c484c63cf459d7d9250e693 (patch)
tree0c6ee58d8518412bbf782f0f43d1f467166bf437
parent5208df82d61091bb5492b215d2acf842e89fc048 (diff)
downloadwallet-core-1b9db448dc3dd4893c484c63cf459d7d9250e693.tar.gz
wallet-core-1b9db448dc3dd4893c484c63cf459d7d9250e693.tar.bz2
wallet-core-1b9db448dc3dd4893c484c63cf459d7d9250e693.zip
towards new 2fa
-rw-r--r--packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx4
-rw-r--r--packages/demobank-ui/src/pages/business/CreateCashout.tsx10
-rw-r--r--packages/taler-util/src/http-client/bank-core.ts66
-rw-r--r--packages/taler-util/src/http-client/types.ts140
4 files changed, 163 insertions, 57 deletions
diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
index 1f2d67c49..4b66c0d8d 100644
--- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
@@ -89,9 +89,9 @@ export function ShowAccountDetails({
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
- case "user-cant-change-contact": return notify({
+ case "missing-contact-data": return notify({
type: "error",
- title: i18n.str`You can't change the contact info, please contact the your account administrator.`,
+ title: i18n.str`You need contact data to enable 2FA.`,
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 8987accd1..92c80ea38 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -212,12 +212,6 @@ export function CreateCashout({
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
});
- case "no-contact-info": return notify({
- type: "error",
- title: i18n.str`Missing contact info before to create the cashout`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
case "no-enough-balance": return notify({
type: "error",
title: i18n.str`The account does not have sufficient funds`,
@@ -230,9 +224,9 @@ export function CreateCashout({
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
});
- case "tan-failed": return notify({
+ case "no-cashout-uri": return notify({
type: "error",
- title: i18n.str`Sending the confirmation code failed.`,
+ title: i18n.str`Missing cashout URI in the profile`,
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
});
diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts
index 51d6d7c96..b7e0292bd 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -18,7 +18,9 @@ import {
HttpStatusCode,
LibtoolVersion,
TalerErrorCode,
- codecForTalerErrorDetail
+ codecForChallenge,
+ codecForTalerErrorDetail,
+ codecForTanTransmission
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
@@ -123,6 +125,7 @@ export class TalerCoreBankHttpClient {
},
});
switch (resp.status) {
+ case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
case HttpStatusCode.NoContent: return opEmptySuccess()
case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp);
@@ -153,6 +156,7 @@ export class TalerCoreBankHttpClient {
},
});
switch (resp.status) {
+ case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
case HttpStatusCode.NoContent: return opEmptySuccess()
case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp);
@@ -163,7 +167,7 @@ export class TalerCoreBankHttpClient {
case TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME: return opKnownFailure("user-cant-change-name", resp);
case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return opKnownFailure("user-cant-change-debt", resp);
case TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT: return opKnownFailure("user-cant-change-cashout", resp);
- case TalerErrorCode.BANK_NON_ADMIN_PATCH_CONTACT: return opKnownFailure("user-cant-change-contact", resp);
+ case TalerErrorCode.BANK_MISSING_TAN_INFO: return opKnownFailure("missing-contact-data", resp);
default: return opUnknownFailure(resp, body)
}
}
@@ -185,6 +189,7 @@ export class TalerCoreBankHttpClient {
},
});
switch (resp.status) {
+ case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
case HttpStatusCode.NoContent: return opEmptySuccess()
case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp);
@@ -326,6 +331,7 @@ export class TalerCoreBankHttpClient {
body,
});
switch (resp.status) {
+ case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
case HttpStatusCode.Ok: return opSuccess(resp, codecForCreateTransactionResponse())
case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", resp);
case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp);
@@ -405,6 +411,7 @@ export class TalerCoreBankHttpClient {
},
});
switch (resp.status) {
+ case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
case HttpStatusCode.NoContent: return opEmptySuccess()
//FIXME: missing in docs
case HttpStatusCode.BadRequest: return opKnownFailure("invalid-id", resp)
@@ -466,6 +473,7 @@ export class TalerCoreBankHttpClient {
body,
});
switch (resp.status) {
+ case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
case HttpStatusCode.Ok: return opSuccess(resp, codecForCashoutPending())
case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", resp)
case HttpStatusCode.Conflict: {
@@ -474,20 +482,19 @@ export class TalerCoreBankHttpClient {
switch (details.code) {
case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED: return opKnownFailure("request-already-used", resp);
case TalerErrorCode.BANK_BAD_CONVERSION: return opKnownFailure("incorrect-exchange-rate", resp);
- case TalerErrorCode.BANK_MISSING_TAN_INFO: return opKnownFailure("no-contact-info", resp);
case TalerErrorCode.BANK_UNALLOWED_DEBIT: return opKnownFailure("no-enough-balance", resp);
+ case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return opKnownFailure("no-cashout-uri", resp);
default: return opUnknownFailure(resp, body)
}
}
case HttpStatusCode.NotImplemented: return opKnownFailure("cashout-not-supported", resp);
- case HttpStatusCode.BadGateway: return opKnownFailure("tan-failed", resp);
default: return opUnknownFailure(resp, await resp.text())
}
}
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-abort
- *
+ * @deprecated since 4
*/
async abortCashoutById(auth: UserAndToken, cid: number) {
const url = new URL(`accounts/${auth.username}/cashouts/${cid}/abort`, this.baseUrl);
@@ -508,7 +515,7 @@ export class TalerCoreBankHttpClient {
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
- *
+ * @deprecated since 4
*/
async confirmCashoutById(auth: UserAndToken, cid: number, body: TalerCorebankApi.CashoutConfirmRequest) {
const url = new URL(`accounts/${auth.username}/cashouts/${cid}/confirm`, this.baseUrl);
@@ -522,7 +529,6 @@ export class TalerCoreBankHttpClient {
switch (resp.status) {
case HttpStatusCode.NoContent: return opEmptySuccess()
case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
- // case HttpStatusCode.Forbidden: return opKnownFailure("wrong-tan-or-credential", resp);
case HttpStatusCode.Conflict: {
const body = await resp.json()
const details = codecForTalerErrorDetail().decode(body)
@@ -638,6 +644,52 @@ export class TalerCoreBankHttpClient {
}
//
+ // 2FA
+ //
+ async sendChallenge(auth: UserAndToken, cid: string) {
+ const url = new URL(`accounts/${auth.username}/challenge/${cid}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return opSuccess(resp, codecForTanTransmission())
+ case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp);
+ case HttpStatusCode.NotFound: return opKnownFailure("invalid-challenge", resp);
+ case HttpStatusCode.BadGateway: {
+ const body = await resp.json()
+ const details = codecForTalerErrorDetail().decode(body)
+ switch (details.code) {
+ case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: return opKnownFailure("tan-failed", resp);
+ default: return opUnknownFailure(resp, body)
+ }
+ }
+ default: return opUnknownFailure(resp, await resp.text())
+ }
+ }
+
+ async confirmChallenge(auth: UserAndToken, cid: string) {
+ const url = new URL(`accounts/${auth.username}/challenge/${cid}/confirm`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return opEmptySuccess()
+ case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp);
+ case HttpStatusCode.NotFound: return opKnownFailure("invalid-challenge", resp);
+ case HttpStatusCode.Conflict: return opKnownFailure("wrong-code", resp);
+ case HttpStatusCode.TooManyRequests: return opKnownFailure("too-many-errors", resp);
+ default: return opUnknownFailure(resp, await resp.text())
+ }
+ }
+
+
+ //
// Others API
//
diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts
index b0d4deca1..f43a0a3a1 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -1,3 +1,4 @@
+import { deprecate } from "util";
import { codecForAmountString } from "../amounts.js";
import {
Codec,
@@ -280,8 +281,8 @@ export const codecForCoreBankConfig = (): Codec<TalerCorebankApi.Config> =>
.property("currency_specification", codecForCurrencySpecificiation())
.property("currency", codecForString())
.property("supported_tan_channels", codecForList(codecForEither(
- codecForConstString(TanChannel.SMS),
- codecForConstString(TanChannel.EMAIL),
+ codecForConstString(TalerCorebankApi.TanChannel.SMS),
+ codecForConstString(TalerCorebankApi.TanChannel.EMAIL),
)))
.build("TalerCorebankApi.Config");
@@ -434,8 +435,8 @@ export const codecForBankAccountCreateWithdrawalResponse =
.build("TalerCorebankApi.BankAccountCreateWithdrawalResponse");
export const codecForCashoutPending =
- (): Codec<TalerCorebankApi.CashoutPending> =>
- buildCodecForObject<TalerCorebankApi.CashoutPending>()
+ (): Codec<TalerCorebankApi.CashoutResponse> =>
+ buildCodecForObject<TalerCorebankApi.CashoutResponse>()
.property("cashout_id", codecForNumber())
.build("TalerCorebankApi.CashoutPending");
@@ -463,11 +464,12 @@ export const codecForCashoutInfo = (): Codec<TalerCorebankApi.CashoutInfo> =>
.property("cashout_id", codecForNumber())
.property(
"status",
- codecForEither(
- codecForConstString("pending"),
- codecForConstString("aborted"),
- codecForConstString("confirmed"),
- ),
+ codecOptional(
+ codecForEither(
+ codecForConstString("pending"),
+ codecForConstString("aborted"),
+ codecForConstString("confirmed"),
+ )),
)
.build("TalerCorebankApi.CashoutInfo");
@@ -484,11 +486,12 @@ export const codecForGlobalCashoutInfo =
.property("username", codecForString())
.property(
"status",
- codecForEither(
- codecForConstString("pending"),
- codecForConstString("aborted"),
- codecForConstString("confirmed"),
- ),
+ codecOptional(
+ codecForEither(
+ codecForConstString("pending"),
+ codecForConstString("aborted"),
+ codecForConstString("confirmed"),
+ )),
)
.build("TalerCorebankApi.GlobalCashoutInfo");
@@ -497,26 +500,25 @@ export const codecForCashoutStatusResponse =
buildCodecForObject<TalerCorebankApi.CashoutStatusResponse>()
.property("amount_credit", codecForAmountString())
.property("amount_debit", codecForAmountString())
- .property("confirmation_time", codecOptional(codecForTimestamp))
.property("creation_time", codecForTimestamp)
- // .property("credit_payto_uri", codecForPaytoString())
+ .property(
+ "tan_channel",
+ codecOptional(codecForEither(
+ codecForConstString(TalerCorebankApi.TanChannel.SMS),
+ codecForConstString(TalerCorebankApi.TanChannel.EMAIL),
+ )),
+ )
+ .property("subject", codecForString())
+ .property("confirmation_time", codecOptional(codecForTimestamp))
.property(
"status",
- codecForEither(
+ codecOptional(codecForEither(
codecForConstString("pending"),
codecForConstString("aborted"),
codecForConstString("confirmed"),
- ),
+ )),
)
- .property(
- "tan_channel",
- codecForEither(
- codecForConstString(TanChannel.SMS),
- codecForConstString(TanChannel.EMAIL),
- ),
- )
- .property("subject", codecForString())
- .property("tan_info", codecForString())
+ .property("tan_info", codecOptional(codecForString()))
.build("TalerCorebankApi.CashoutStatusResponse");
export const codecForConversionRatesResponse =
@@ -735,6 +737,22 @@ export const codecForAmlDecisionDetail =
.property("decider_pub", codecForString())
.build("TalerExchangeApi.AmlDecisionDetail");
+export const codecForChallenge =
+ (): Codec<TalerCorebankApi.Challenge> =>
+ buildCodecForObject<TalerCorebankApi.Challenge>()
+ .property("challenge_id", codecForString())
+ .build("TalerCorebankApi.Challenge");
+
+export const codecForTanTransmission =
+ (): Codec<TalerCorebankApi.TanTransmission> =>
+ buildCodecForObject<TalerCorebankApi.TanTransmission>()
+ .property("tan_channel", codecForEither(
+ codecForConstString(TalerCorebankApi.TanChannel.SMS),
+ codecForConstString(TalerCorebankApi.TanChannel.EMAIL),
+ ))
+ .property("tan_info", codecForString())
+ .build("TalerCorebankApi.TanTransmission");
+
interface KycDetail {
provider_section: string;
attributes?: Object;
@@ -894,10 +912,6 @@ const codecForLibtoolVersion = codecForString;
const codecForCurrencyName = codecForString;
const codecForDecimalNumber = codecForString;
-enum TanChannel {
- SMS = "sms",
- EMAIL = "email",
-}
export type WithdrawalOperationStatus =
| "pending"
| "selected"
@@ -1442,10 +1456,6 @@ export namespace TalerCorebankApi {
is_taler_exchange?: boolean;
// Addresses where to send the TAN for transactions.
- // Currently only used for cashouts.
- // If missing, cashouts will fail.
- // In the future, might be used for other transactions
- // as well.
contact_data?: ChallengeContactData;
// 'payto' address of a fiat bank account.
@@ -1497,6 +1507,10 @@ export namespace TalerCorebankApi {
// If present, change the max debit allowed for this user
// Only admin can change this property.
debit_threshold?: AmountString;
+
+ // If present, enables 2FA and set the TAN channel used for challenges
+ tan_channel?: TanChannel;
+
}
export interface AccountPasswordChange {
@@ -1579,6 +1593,9 @@ export namespace TalerCorebankApi {
// Is this a taler exchange account?
is_taler_exchange: boolean;
+
+ // Is 2FA enabled and what channel is used for challenges?
+ tan_channel?: TanChannel;
}
export interface CashoutRequest {
@@ -1613,15 +1630,20 @@ export namespace TalerCorebankApi {
// this field is missing, it defaults to SMS.
// The default choice prefers to change the communication
// channel respect to the one used to issue this request.
+ /**
+ * @deprecated since 4, use 2fa
+ */
tan_channel?: TanChannel;
}
- export interface CashoutPending {
+ export interface CashoutResponse {
// ID identifying the operation being created
- // and now waiting for the TAN confirmation.
cashout_id: number;
}
+ /**
+ * @deprecated since 4, use 2fa
+ */
export interface CashoutConfirmRequest {
// the TAN that confirms $CASHOUT_ID.
tan: string;
@@ -1634,7 +1656,10 @@ export namespace TalerCorebankApi {
export interface CashoutInfo {
cashout_id: number;
- status: "pending" | "aborted" | "confirmed";
+ /**
+ * @deprecated since 4, use new 2fa
+ */
+ status?: "pending" | "aborted" | "confirmed";
}
export interface GlobalCashouts {
// Every string represents a cash-out operation ID.
@@ -1643,11 +1668,13 @@ export namespace TalerCorebankApi {
export interface GlobalCashoutInfo {
cashout_id: number;
username: string;
- status: "pending" | "aborted" | "confirmed";
+ /**
+ * @deprecated since 4, use new 2fa
+ */
+ status?: "pending" | "aborted" | "confirmed";
}
export interface CashoutStatusResponse {
- status: "pending" | "aborted" | "confirmed";
// Amount debited to the internal
// regional currency bank account.
@@ -1666,16 +1693,30 @@ export namespace TalerCorebankApi {
// Time when the cashout was created.
creation_time: Timestamp;
+ /**
+ * @deprecated since 4, use new 2fa
+ */
+ status?: "pending" | "aborted" | "confirmed";
+
// Time when the cashout was confirmed via its TAN.
// Missing when the operation wasn't confirmed yet.
+ /**
+ * @deprecated since 4, use new 2fa
+ */
confirmation_time?: Timestamp;
// Channel of the last successful transmission of the TAN challenge.
// Missing when all transmissions failed.
+ /**
+ * @deprecated since 4, use new 2fa
+ */
tan_channel?: TanChannel;
// Info of the last successful transmission of the TAN challenge.
// Missing when all transmissions failed.
+ /**
+ * @deprecated since 4, use new 2fa
+ */
tan_info?: string;
}
@@ -1767,6 +1808,25 @@ export namespace TalerCorebankApi {
// exchange to another bank account.
talerOutVolume: AmountString;
}
+ export interface TanTransmission {
+ // Channel of the last successful transmission of the TAN challenge.
+ tan_channel: TanChannel;
+
+ // Info of the last successful transmission of the TAN challenge.
+ tan_info: string;
+ }
+
+ export interface Challenge {
+ // Unique identifier of the challenge to solve to run this protected
+ // operation.
+ challenge_id: string;
+ }
+
+ export enum TanChannel {
+ SMS = "sms",
+ EMAIL = "email"
+ }
+
}
export namespace TalerExchangeApi {