From eada01727571fe0aae632696baa97bc4ab6be521 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 21 Apr 2024 14:55:26 -0300 Subject: fix challenger api --- packages/taler-util/src/http-client/challenger.ts | 67 +++++++++++++++-------- packages/taler-util/src/http-client/types.ts | 8 +-- packages/taler-util/src/http-common.ts | 41 ++++++++++++++ packages/taler-util/src/operation.ts | 5 +- 4 files changed, 92 insertions(+), 29 deletions(-) (limited to 'packages') diff --git a/packages/taler-util/src/http-client/challenger.ts b/packages/taler-util/src/http-client/challenger.ts index fa4214aa6..8d23ed273 100644 --- a/packages/taler-util/src/http-client/challenger.ts +++ b/packages/taler-util/src/http-client/challenger.ts @@ -4,10 +4,13 @@ import { createPlatformHttpLib } from "../http.js"; import { LibtoolVersion } from "../libtool-version.js"; import { FailCasesByMethod, + RedirectResult, ResultByMethod, + opFixedSuccess, + opKnownAlternativeFailure, opKnownHttpFailure, opSuccessFromHttp, - opUnknownFailure + opUnknownFailure, } from "../operation.js"; import { AccessToken, @@ -16,7 +19,8 @@ import { codecForChallengeStatus, codecForChallengerAuthResponse, codecForChallengerInfoResponse, - codecForChallengerTermsOfServiceResponse + codecForChallengerTermsOfServiceResponse, + codecForInvalidPinResponse, } from "./types.js"; import { makeBearerTokenAuthHeader } from "./utils.js"; @@ -91,7 +95,12 @@ export class ChallengerHttpClient { * https://docs.taler.net/core/api-challenger.html#post--authorize-$NONCE * */ - async login(nonce: string, clientId: string, redirectUri: string, state: string | undefined) { + async login( + nonce: string, + clientId: string, + redirectUri: string, + state: string | undefined, + ) { const url = new URL(`authorize/${nonce}`, this.baseUrl); url.searchParams.set("response_type", "code"); url.searchParams.set("client_id", clientId); @@ -127,17 +136,23 @@ export class ChallengerHttpClient { */ async challenge(nonce: string, body: Record<"email", string>) { const url = new URL(`challenge/${nonce}`, this.baseUrl); - + const resp = await this.httpLib.fetch(url.href, { method: "POST", body: new URLSearchParams(Object.entries(body)).toString(), headers: { - "Content-Type": "application/x-www-form-urlencoded" - } + "Content-Type": "application/x-www-form-urlencoded", + }, + redirect: "manual", }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForChallengeCreateResponse()); + case HttpStatusCode.Found: + const redirect = resp.headers.get("Location")!; + return opFixedSuccess({ + redirectURL: new URL(redirect), + }); case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: @@ -165,23 +180,25 @@ export class ChallengerHttpClient { method: "POST", body: new URLSearchParams(Object.entries(body)).toString(), headers: { - "Content-Type": "application/x-www-form-urlencoded" + "Content-Type": "application/x-www-form-urlencoded", }, redirect: "manual", }); switch (resp.status) { case HttpStatusCode.Found: - const redirect = resp.headers.get("Location")! - const uri = new URL(redirect) - const code = uri.searchParams.get("code")! - return { - type: "ok" as const, - body: { code } - } - // return opSuccessFromHttp(resp, codecForChallengeCreateResponse()); + const redirect = resp.headers.get("Location")!; + return opFixedSuccess({ + redirectURL: new URL(redirect), + }); case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, resp); - case HttpStatusCode.NotFound: + case HttpStatusCode.Forbidden: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForInvalidPinResponse(), + ); + case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotAcceptable: return opKnownHttpFailure(resp.status, resp); @@ -210,15 +227,17 @@ export class ChallengerHttpClient { const resp = await this.httpLib.fetch(url.href, { method: "POST", headers: { - "Content-Type": "application/x-www-form-urlencoded" + "Content-Type": "application/x-www-form-urlencoded", }, - body: new URLSearchParams(Object.entries({ - client_id, - redirect_uri, - client_secret, - code, - grant_type: "authorization_code", - })).toString(), + body: new URLSearchParams( + Object.entries({ + client_id, + redirect_uri, + client_secret, + code, + grant_type: "authorization_code", + }), + ).toString(), }); switch (resp.status) { case HttpStatusCode.Ok: diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index e12c2ed6b..13ef9a3e6 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -1533,7 +1533,7 @@ export const codecForChallengeCreateResponse = export const codecForInvalidPinResponse = (): Codec => buildCodecForObject() - .property("ec", codecForNumber()) + .property("ec", codecOptional(codecForNumber())) .property("hint", codecForAny()) .property("addresses_left", codecForNumber()) .property("pin_transmissions_left", codecForNumber()) @@ -5319,17 +5319,17 @@ export namespace ChallengerApi { // timestamp explaining when we would re-transmit the challenge the next // time (at the earliest) if requested by the user - next_tx_time: String; + next_tx_time: string; } export interface InvalidPinResponse { // numeric Taler error code, should be shown to indicate the error // compactly for reporting to developers - ec: Integer; + ec?: number; // human-readable Taler error code, should be shown for the user to // understand the error - hint: String; + hint: string; // how many times is the user still allowed to change the address; // if 0, the user should not be shown a link to jump to the diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts index cc75debd5..d8cd36287 100644 --- a/packages/taler-util/src/http-common.ts +++ b/packages/taler-util/src/http-common.ts @@ -268,6 +268,47 @@ export async function readSuccessResponseJsonOrErrorCode( }; } +export async function readResponseJsonOrErrorCode( + httpResponse: HttpResponse, + codec: Codec, +): Promise<{ isError: boolean; response: T }> { + let respJson; + try { + respJson = await httpResponse.json(); + } catch (e: any) { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, + { + requestUrl: httpResponse.requestUrl, + requestMethod: httpResponse.requestMethod, + httpStatusCode: httpResponse.status, + validationError: e.toString(), + }, + "Couldn't parse JSON format from response", + ); + } + let parsedResponse: T; + try { + parsedResponse = codec.decode(respJson); + } catch (e: any) { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, + { + requestUrl: httpResponse.requestUrl, + requestMethod: httpResponse.requestMethod, + httpStatusCode: httpResponse.status, + validationError: e.toString(), + }, + "Response invalid", + ); + } + return { + isError: !(httpResponse.status >= 200 && httpResponse.status < 300), + response: parsedResponse, + }; +} + + type HttpErrorDetails = { requestUrl: string; requestMethod: string; diff --git a/packages/taler-util/src/operation.ts b/packages/taler-util/src/operation.ts index 771f5860b..e2ab9d4e4 100644 --- a/packages/taler-util/src/operation.ts +++ b/packages/taler-util/src/operation.ts @@ -19,6 +19,7 @@ */ import { HttpResponse, + readResponseJsonOrErrorCode, readSuccessResponseJsonOrThrow, readTalerErrorResponse, } from "./http-common.js"; @@ -126,7 +127,7 @@ export async function opKnownAlternativeFailure( s: T, codec: Codec, ): Promise> { - const body = await readSuccessResponseJsonOrThrow(resp, codec); + const body = (await readResponseJsonOrErrorCode(resp, codec)).response; return { type: "fail", case: s, body }; } @@ -193,3 +194,5 @@ export type FailCasesByMethod = Exclude< ResultByMethod, OperationOk >; + +export type RedirectResult = { redirectURL: URL } -- cgit v1.2.3