diff options
author | Sebastian <sebasjm@gmail.com> | 2024-01-10 14:25:39 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2024-01-10 14:25:39 -0300 |
commit | 2c7db170a45fcb82deae3892d610b6b2805ee46c (patch) | |
tree | 09a4218a05e18784e3dfb2dfe211836185769924 | |
parent | b609f48ae664618b6f4e75e42221f1240c8f61f0 (diff) |
bank: handles 2fa response
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). |