From 2c7db170a45fcb82deae3892d610b6b2805ee46c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 10 Jan 2024 14:25:39 -0300 Subject: bank: handles 2fa response --- packages/taler-util/src/http-client/bank-core.ts | 17 +++--- packages/taler-util/src/http-client/types.ts | 7 ++- packages/taler-util/src/operation.ts | 68 ++++++++++-------------- packages/taler-util/src/taler-error-codes.ts | 8 +++ 4 files changed, 52 insertions(+), 48 deletions(-) (limited to 'packages/taler-util/src') 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 = | OperationOk + | OperationAlternative | OperationFail; -export interface OperationOk { - type: "ok", - body: T; -} export function isOperationOk(c: OperationResult): c is OperationOk { return c.type === "ok" } export function isOperationFail(c: OperationResult): c is OperationFail { return c.type === "fail" } + +/** + * succesful operation + */ +export interface OperationOk { + type: "ok", + body: T; +} + +/** + * unsuccesful operation, see details + */ export interface OperationFail { type: "fail", case: T, detail: TalerErrorDetail, } +/** + * unsuccesful operation, see body + */ +export interface OperationAlternative { + type: "fail", + case: T, + body: B, +} export async function opSuccess(resp: HttpResponse, codec: Codec): Promise> { const body = await readSuccessResponseJsonOrThrow(resp, codec) @@ -31,6 +48,11 @@ export function opFixedSuccess(body: T): OperationOk { export function opEmptySuccess(): OperationOk { return { type: "ok" as const, body: void 0 } } + +export async function opKnownAlternativeFailure(resp: HttpResponse, s: T, codec: Codec): Promise> { + const body = await readSuccessResponseJsonOrThrow(resp, codec) + return { type: "fail", case: s, body } +} export async function opKnownHttpFailure(s: T, resp: HttpResponse): Promise> { const detail = await readTalerErrorResponse(resp) return { type: "fail", case: s, detail } @@ -43,6 +65,7 @@ export async function opKnownFailure(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(cb: () => Promise>): Promise { - const resp = await cb() - if (resp.type === "ok") return resp.body - throw TalerError.fromUncheckedDetail({ ...resp.detail, case: resp.case }) -} -export async function failOrThrow(s: E, cb: () => Promise>): Promise { - 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[p] extends (...args: any[]) => infer Ret ? Ret extends Promise ? @@ -81,23 +87,3 @@ export type ResultByMethod = TT[p] extend export type FailCasesByMethod = Exclude, OperationOk> -type MethodsOfOperations = 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 ? - // of an operation - Result extends OperationResult ? - P : never - : never - : never]: any -} - -type AllKnownCases = "success" | FailCasesByMethod["case"] - -export type TestForApi = { - [OpType in MethodsOfOperations as `test_${OpType & string}`]: { - [c in AllKnownCases]: undefined | (() => Promise); - }; -}; 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 @@ -3512,6 +3512,14 @@ export enum TalerErrorCode { BANK_TAN_CHALLENGE_EXPIRED = 5144, + /** + * 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). -- cgit v1.2.3