summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-01-10 14:25:39 -0300
committerSebastian <sebasjm@gmail.com>2024-01-10 14:25:39 -0300
commit2c7db170a45fcb82deae3892d610b6b2805ee46c (patch)
tree09a4218a05e18784e3dfb2dfe211836185769924
parentb609f48ae664618b6f4e75e42221f1240c8f61f0 (diff)
downloadwallet-core-2c7db170a45fcb82deae3892d610b6b2805ee46c.tar.gz
wallet-core-2c7db170a45fcb82deae3892d610b6b2805ee46c.tar.bz2
wallet-core-2c7db170a45fcb82deae3892d610b6b2805ee46c.zip
bank: handles 2fa response
-rw-r--r--packages/demobank-ui/src/pages/OperationState/views.tsx35
-rw-r--r--packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx39
-rw-r--r--packages/demobank-ui/src/pages/RegistrationPage.tsx159
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx7
-rw-r--r--packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx15
-rw-r--r--packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx7
-rw-r--r--packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx18
-rw-r--r--packages/demobank-ui/src/pages/admin/RemoveAccount.tsx7
-rw-r--r--packages/demobank-ui/src/pages/business/CreateCashout.tsx8
-rw-r--r--packages/taler-harness/src/index.ts6
-rw-r--r--packages/taler-util/src/http-client/bank-core.ts17
-rw-r--r--packages/taler-util/src/http-client/types.ts7
-rw-r--r--packages/taler-util/src/operation.ts68
-rw-r--r--packages/taler-util/src/taler-error-codes.ts8
14 files changed, 250 insertions, 151 deletions
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx
index a02bb3bbd..98eb7169f 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -96,45 +96,52 @@ export function NeedConfirmationView({ error, onAbort: doAbort, onConfirm: doCon
async function onConfirm() {
errorHandler(async () => {
if (!doConfirm) return;
- const hasError = await doConfirm()
- if (!hasError) {
+ const resp = await doConfirm()
+ if (!resp) {
if (!settings.showWithdrawalSuccess) {
notifyInfo(i18n.str`Wire transfer completed!`)
}
return
}
- switch (hasError.case) {
+ switch (resp.case) {
case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return notify({
type: "error",
title: i18n.str`The withdrawal has been aborted previously and can't be confirmed`,
- description: hasError.detail.hint as TranslatedString,
- debug: hasError.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
})
case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return notify({
type: "error",
title: i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`,
- description: hasError.detail.hint as TranslatedString,
- debug: hasError.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
})
case HttpStatusCode.BadRequest: return notify({
type: "error",
title: i18n.str`The operation id is invalid.`,
- description: hasError.detail.hint as TranslatedString,
- debug: hasError.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
});
case HttpStatusCode.NotFound: return notify({
type: "error",
title: i18n.str`The operation was not found.`,
- description: hasError.detail.hint as TranslatedString,
- debug: hasError.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
});
case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
type: "error",
title: i18n.str`Your balance is not enough.`,
- description: hasError.detail.hint as TranslatedString,
- debug: hasError.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
});
- default: assertUnreachable(hasError)
+ case HttpStatusCode.Accepted: {
+ resp.body.challenge_id;
+ return notify({
+ type: "info",
+ title: i18n.str`The operation needs a confirmation to complete.`,
+ });
+ }
+ default: assertUnreachable(resp)
}
})
}
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 7a94f5486..2ef93d35c 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -146,50 +146,57 @@ export function PaytoWireTransferForm({
const puri = payto_uri;
await handleError(async () => {
- const res = await api.createTransaction(credentials, {
+ const resp = await api.createTransaction(credentials, {
payto_uri: puri,
amount: sendingAmount,
});
mutate(() => true)
- if (res.type === "fail") {
- switch (res.case) {
+ if (resp.type === "fail") {
+ switch (resp.case) {
case HttpStatusCode.BadRequest: return notify({
type: "error",
title: i18n.str`The request was invalid or the payto://-URI used unacceptable features.`,
- description: res.detail.hint as TranslatedString,
- debug: res.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
})
case HttpStatusCode.Unauthorized: return notify({
type: "error",
title: i18n.str`Not enough permission to complete the operation.`,
- description: res.detail.hint as TranslatedString,
- debug: res.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
})
case TalerErrorCode.BANK_UNKNOWN_CREDITOR: return notify({
type: "error",
title: i18n.str`The destination account "${puri}" was not found.`,
- description: res.detail.hint as TranslatedString,
- debug: res.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
})
case TalerErrorCode.BANK_SAME_ACCOUNT: return notify({
type: "error",
title: i18n.str`The origin and the destination of the transfer can't be the same.`,
- description: res.detail.hint as TranslatedString,
- debug: res.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
})
case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
type: "error",
title: i18n.str`Your balance is not enough.`,
- description: res.detail.hint as TranslatedString,
- debug: res.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
})
case HttpStatusCode.NotFound: return notify({
type: "error",
title: i18n.str`The origin account "${puri}" was not found.`,
- description: res.detail.hint as TranslatedString,
- debug: res.detail,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
})
- default: assertUnreachable(res)
+ case HttpStatusCode.Accepted: {
+ resp.body.challenge_id;
+ return notify({
+ type: "info",
+ title: i18n.str`The operation needs a confirmation to complete.`,
+ });
+ }
+ default: assertUnreachable(resp)
}
}
onSuccess();
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index 89bfbcb35..e7ed8a2b8 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -98,78 +98,101 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
async function doRegistrationAndLogin(name: string, username: string, password: string, onComplete: () => void) {
await handleError(async () => {
- const creationResponse = await api.createAccount("" as AccessToken, { name, username, password });
- if (creationResponse.type === "fail") {
- switch (creationResponse.case) {
- case HttpStatusCode.BadRequest: return notify({
- type: "error",
- title: i18n.str`Server replied with invalid phone or email.`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
- type: "error",
- title: i18n.str`Registration is disabled because the bank ran out of bonus credit.`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- case HttpStatusCode.Unauthorized: return notify({
- type: "error",
- title: i18n.str`No enough permission to create that account.`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return notify({
- type: "error",
- title: i18n.str`That account id is already taken.`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return notify({
- type: "error",
- title: i18n.str`That username is already taken.`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return notify({
- type: "error",
- title: i18n.str`That username can't be used because is reserved.`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return notify({
- type: "error",
- title: i18n.str`Only admin is allow to set debt limit.`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- default: assertUnreachable(creationResponse)
+ createAccount: {
+ const resp = await api.createAccount("" as AccessToken, { name, username, password });
+ if (resp.type === "fail") {
+ switch (resp.case) {
+ case HttpStatusCode.BadRequest: return notify({
+ type: "error",
+ title: i18n.str`Server replied with invalid phone or email.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
+ type: "error",
+ title: i18n.str`Registration is disabled because the bank ran out of bonus credit.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case HttpStatusCode.Unauthorized: return notify({
+ type: "error",
+ title: i18n.str`No enough permission to create that account.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return notify({
+ type: "error",
+ title: i18n.str`That account id is already taken.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return notify({
+ type: "error",
+ title: i18n.str`That username is already taken.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return notify({
+ type: "error",
+ title: i18n.str`That username can't be used because is reserved.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return notify({
+ type: "error",
+ title: i18n.str`Only admin is allow to set debt limit.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case TalerErrorCode.BANK_MISSING_TAN_INFO: return notify({
+ type: "error",
+ title: i18n.str`No information for the selected authentication channel.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: return notify({
+ type: "error",
+ title: i18n.str`Authentication channel is not supported.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL: return notify({
+ type: "error",
+ title: i18n.str`Only admin can create accounts with second factor authentication.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
}
}
- const resp = await api.getAuthenticationAPI(username).createAccessToken(password, {
- scope: "readwrite",
- duration: { d_us: "forever" },
- refreshable: true,
- })
+ login: {
+ const resp = await api.getAuthenticationAPI(username).createAccessToken(password, {
+ scope: "readwrite",
+ duration: { d_us: "forever" },
+ refreshable: true,
+ })
- if (resp.type === "ok") {
- backend.logIn({ username, token: resp.body.access_token });
- } else {
- switch (resp.case) {
- case "wrong-credentials": return notify({
- type: "error",
- title: i18n.str`Wrong credentials for "${username}"`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "not-found": return notify({
- type: "error",
- title: i18n.str`Account not found`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- default: assertUnreachable(resp)
+ if (resp.type === "ok") {
+ backend.logIn({ username, token: resp.body.access_token });
+ } else {
+ switch (resp.case) {
+ case "wrong-credentials": return notify({
+ type: "error",
+ title: i18n.str`Wrong credentials for "${username}"`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Account not found`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
}
+
}
onComplete()
})
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index ed1db854a..6f18e1283 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -153,6 +153,13 @@ export function WithdrawalConfirmationQuestion({
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
+ case HttpStatusCode.Accepted: {
+ resp.body.challenge_id;
+ return notify({
+ type: "info",
+ title: i18n.str`The operation needs a confirmation to complete.`,
+ });
+ }
default: assertUnreachable(resp)
}
}
diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
index 98fb72283..1bf21f62e 100644
--- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
@@ -96,6 +96,21 @@ export function ShowAccountDetails({
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
+ case HttpStatusCode.Accepted: {
+ resp.body.challenge_id;
+ return notify({
+ type: "info",
+ title: i18n.str`Cashout created but confirmation is required.`,
+ });
+ }
+ case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: {
+ return notify({
+ type: "error",
+ title: i18n.str`Authentication channel is not supported.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ }
default: assertUnreachable(resp)
}
}
diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
index 95c425dc7..ed074b9c4 100644
--- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
@@ -74,6 +74,13 @@ export function UpdateAccountPassword({
type: "error",
title: i18n.str`Your current password doesn't match, can't change to a new password.`
})
+ case HttpStatusCode.Accepted: {
+ resp.body.challenge_id;
+ return notify({
+ type: "info",
+ title: i18n.str`Cashout created but confirmation is required.`,
+ });
+ }
default: assertUnreachable(resp)
}
}
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index d6b7d5b1e..1cfbd8234 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -90,6 +90,24 @@ export function CreateNewAccount({
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
+ case TalerErrorCode.BANK_MISSING_TAN_INFO: return notify({
+ type: "error",
+ title: i18n.str`No information for the selected authentication channel.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: return notify({
+ type: "error",
+ title: i18n.str`Authentication channel is not supported.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL: return notify({
+ type: "error",
+ title: i18n.str`Only admin can create accounts with second factor authentication.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
default: assertUnreachable(resp)
}
}
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index d47f48dd9..330ebf3a9 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -89,6 +89,13 @@ export function RemoveAccount({
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
+ case HttpStatusCode.Accepted: {
+ resp.body.challenge_id;
+ return notify({
+ type: "info",
+ title: i18n.str`The operation needs a confirmation to complete.`,
+ });
+ }
default: {
assertUnreachable(resp)
}
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index c3921cbd0..9ee5cbeaf 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -16,6 +16,7 @@
import {
Amounts,
HttpStatusCode,
+ TalerCorebankApi,
TalerError,
TalerErrorCode,
TranslatedString,
@@ -197,6 +198,13 @@ export function CreateCashout({
notifyInfo(i18n.str`Cashout created`)
} else {
switch (resp.case) {
+ case HttpStatusCode.Accepted: {
+ resp.body.challenge_id;
+ return notify({
+ type: "info",
+ title: i18n.str`Cashout created but confirmation is required.`,
+ });
+ }
case HttpStatusCode.NotFound: return notify({
type: "error",
title: i18n.str`Account not found`,
diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts
index 7234f84d0..84d2d60f0 100644
--- a/packages/taler-harness/src/index.ts
+++ b/packages/taler-harness/src/index.ts
@@ -27,17 +27,13 @@ import {
MerchantApiClient,
MerchantInstanceConfig,
RegisterAccountRequest,
- TalerCoreBankHttpClient,
TalerCorebankApiClient,
- TalerError,
- TestForApi,
addPaytoQueryParams,
decodeCrock,
generateIban,
j2s,
rsaBlind,
- setGlobalLogLevelFromString,
- setPrintHttpRequestAsCurl,
+ setGlobalLogLevelFromString
} from "@gnu-taler/taler-util";
import { clk } from "@gnu-taler/taler-util/clk";
import {
diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts
index dd0948250..50cedefa9 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -21,6 +21,7 @@ import {
codecForChallenge,
codecForTalerErrorDetail,
codecForTanTransmission,
+ opKnownAlternativeFailure,
opKnownHttpFailure,
opKnownTalerFailure
} from "@gnu-taler/taler-util";
@@ -108,6 +109,9 @@ export class TalerCoreBankHttpClient {
case TalerErrorCode.BANK_UNALLOWED_DEBIT: return opKnownTalerFailure(details.code, resp);
case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return opKnownTalerFailure(details.code, resp);
case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return opKnownTalerFailure(details.code, resp);
+ case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL: return opKnownTalerFailure(details.code, resp);
+ case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: return opKnownTalerFailure(details.code, resp);
+ case TalerErrorCode.BANK_MISSING_TAN_INFO: return opKnownTalerFailure(details.code, resp);
default: return opUnknownFailure(resp, body)
}
}
@@ -127,7 +131,7 @@ export class TalerCoreBankHttpClient {
},
});
switch (resp.status) {
- case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
+ case HttpStatusCode.Accepted: return opKnownAlternativeFailure(resp, resp.status, codecForChallenge())
case HttpStatusCode.NoContent: return opEmptySuccess()
case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp);
@@ -158,7 +162,7 @@ export class TalerCoreBankHttpClient {
},
});
switch (resp.status) {
- case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
+ case HttpStatusCode.Accepted: return opKnownAlternativeFailure(resp, resp.status, codecForChallenge())
case HttpStatusCode.NoContent: return opEmptySuccess()
case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp);
@@ -170,6 +174,7 @@ export class TalerCoreBankHttpClient {
case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return opKnownTalerFailure(details.code, resp);
case TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT: return opKnownTalerFailure(details.code, resp);
case TalerErrorCode.BANK_NON_ADMIN_PATCH_CONTACT: return opKnownTalerFailure(details.code, resp);
+ case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: return opKnownTalerFailure(details.code, resp);
default: return opUnknownFailure(resp, body)
}
}
@@ -191,7 +196,7 @@ export class TalerCoreBankHttpClient {
},
});
switch (resp.status) {
- case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
+ case HttpStatusCode.Accepted: return opKnownAlternativeFailure(resp, resp.status, codecForChallenge())
case HttpStatusCode.NoContent: return opEmptySuccess()
case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp);
@@ -333,7 +338,7 @@ export class TalerCoreBankHttpClient {
body,
});
switch (resp.status) {
- case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
+ case HttpStatusCode.Accepted: return opKnownAlternativeFailure(resp, resp.status, codecForChallenge())
case HttpStatusCode.Ok: return opSuccess(resp, codecForCreateTransactionResponse())
case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp);
@@ -413,7 +418,7 @@ export class TalerCoreBankHttpClient {
},
});
switch (resp.status) {
- case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
+ case HttpStatusCode.Accepted: return opKnownAlternativeFailure(resp, resp.status, codecForChallenge())
case HttpStatusCode.NoContent: return opEmptySuccess()
//FIXME: missing in docs
case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, resp)
@@ -475,7 +480,7 @@ export class TalerCoreBankHttpClient {
body,
});
switch (resp.status) {
- case HttpStatusCode.Accepted: return opSuccess(resp, codecForChallenge())
+ case HttpStatusCode.Accepted: return opKnownAlternativeFailure(resp, resp.status, codecForChallenge())
case HttpStatusCode.Ok: return opSuccess(resp, codecForCashoutPending())
case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp)
case HttpStatusCode.Conflict: {
diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts
index f43a0a3a1..740d4204e 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -1469,8 +1469,13 @@ export namespace TalerCorebankApi {
payto_uri?: PaytoString;
// If present, set the max debit allowed for this user
- // Only admin can change this property.
+ // Only admin can set this property.
debit_threshold?: AmountString;
+
+ // If present, enables 2FA and set the TAN channel used for challenges
+ // Only admin can set this property, other user can reconfig their account
+ // after creation.
+ tan_channel?: TanChannel;
}
export interface ChallengeContactData {
diff --git a/packages/taler-util/src/operation.ts b/packages/taler-util/src/operation.ts
index 8b264d905..fd31fce39 100644
--- a/packages/taler-util/src/operation.ts
+++ b/packages/taler-util/src/operation.ts
@@ -3,23 +3,40 @@ import { Codec, HttpStatusCode, TalerError, TalerErrorCode, TalerErrorDetail } f
export type OperationResult<Body, ErrorEnum> =
| OperationOk<Body>
+ | OperationAlternative<ErrorEnum, Body>
| OperationFail<ErrorEnum>;
-export interface OperationOk<T> {
- type: "ok",
- body: T;
-}
export function isOperationOk<T, E>(c: OperationResult<T, E>): c is OperationOk<T> {
return c.type === "ok"
}
export function isOperationFail<T, E>(c: OperationResult<T, E>): c is OperationFail<E> {
return c.type === "fail"
}
+
+/**
+ * succesful operation
+ */
+export interface OperationOk<T> {
+ type: "ok",
+ body: T;
+}
+
+/**
+ * unsuccesful operation, see details
+ */
export interface OperationFail<T> {
type: "fail",
case: T,
detail: TalerErrorDetail,
}
+/**
+ * unsuccesful operation, see body
+ */
+export interface OperationAlternative<T, B> {
+ type: "fail",
+ case: T,
+ body: B,
+}
export async function opSuccess<T>(resp: HttpResponse, codec: Codec<T>): Promise<OperationOk<T>> {
const body = await readSuccessResponseJsonOrThrow(resp, codec)
@@ -31,6 +48,11 @@ export function opFixedSuccess<T>(body: T): OperationOk<T> {
export function opEmptySuccess(): OperationOk<void> {
return { type: "ok" as const, body: void 0 }
}
+
+export async function opKnownAlternativeFailure<T extends HttpStatusCode, B>(resp: HttpResponse, s: T, codec: Codec<B>): Promise<OperationAlternative<T, B>> {
+ const body = await readSuccessResponseJsonOrThrow(resp, codec)
+ return { type: "fail", case: s, body }
+}
export async function opKnownHttpFailure<T extends HttpStatusCode>(s: T, resp: HttpResponse): Promise<OperationFail<T>> {
const detail = await readTalerErrorResponse(resp)
return { type: "fail", case: s, detail }
@@ -43,6 +65,7 @@ export async function opKnownFailure<T extends string>(s: T, resp: HttpResponse)
const detail = await readTalerErrorResponse(resp)
return { type: "fail", case: s, detail }
}
+
export function opUnknownFailure(resp: HttpResponse, text: string): never {
throw TalerError.fromDetail(
TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
@@ -55,23 +78,6 @@ export function opUnknownFailure(resp: HttpResponse, text: string): never {
`Unexpected HTTP status ${resp.status} in response`,
);
}
-export async function succeedOrThrow<R, E>(cb: () => Promise<OperationResult<R, E>>): Promise<R> {
- const resp = await cb()
- if (resp.type === "ok") return resp.body
- throw TalerError.fromUncheckedDetail({ ...resp.detail, case: resp.case })
-}
-export async function failOrThrow<E>(s: E, cb: () => Promise<OperationResult<unknown, E>>): Promise<TalerErrorDetail> {
- const resp = await cb()
- if (resp.type === "ok") {
- throw TalerError.fromException(new Error(`request succeed but failure "${s}" was expected`))
- }
- if (resp.case === s) {
- return resp.detail
- }
- throw TalerError.fromException(new Error(`request failed with "${resp.case}" but case "${s}" was expected`))
-}
-
-
export type ResultByMethod<TT extends object, p extends keyof TT> = TT[p] extends (...args: any[]) => infer Ret ?
Ret extends Promise<infer Result> ?
@@ -81,23 +87,3 @@ export type ResultByMethod<TT extends object, p extends keyof TT> = TT[p] extend
export type FailCasesByMethod<TT extends object, p extends keyof TT> = Exclude<ResultByMethod<TT, p>, OperationOk<any>>
-type MethodsOfOperations<T extends object> = keyof {
- [P in keyof T as
- //when the property is a function
- T[P] extends (...args: any[]) => infer Ret ?
- // that returns a promise
- Ret extends Promise<infer Result> ?
- // of an operation
- Result extends OperationResult<any, any> ?
- P : never
- : never
- : never]: any
-}
-
-type AllKnownCases<t extends object, d extends keyof t> = "success" | FailCasesByMethod<t, d>["case"]
-
-export type TestForApi<ApiType extends object> = {
- [OpType in MethodsOfOperations<ApiType> as `test_${OpType & string}`]: {
- [c in AllKnownCases<ApiType, OpType>]: undefined | (() => Promise<void>);
- };
-};
diff --git a/packages/taler-util/src/taler-error-codes.ts b/packages/taler-util/src/taler-error-codes.ts
index 2361b6d73..9dd965d1b 100644
--- a/packages/taler-util/src/taler-error-codes.ts
+++ b/packages/taler-util/src/taler-error-codes.ts
@@ -3513,6 +3513,14 @@ export enum TalerErrorCode {
/**
+ * A non-admin user has tried to create an account with 2fa.
+ * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ BANK_NON_ADMIN_SET_TAN_CHANNEL = 5145,
+
+
+ /**
* The sync service failed find the account in its database.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
* (A value of 0 indicates that the error is generated client-side).