diff options
Diffstat (limited to 'packages/taler-util/src/http-client/exchange.ts')
-rw-r--r-- | packages/taler-util/src/http-client/exchange.ts | 221 |
1 files changed, 166 insertions, 55 deletions
diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts index f55be0043..68d68267f 100644 --- a/packages/taler-util/src/http-client/exchange.ts +++ b/packages/taler-util/src/http-client/exchange.ts @@ -1,82 +1,179 @@ -import { HttpRequestLibrary } from "../http-common.js"; +import { HttpRequestLibrary, readTalerErrorResponse } from "../http-common.js"; import { HttpStatusCode } from "../http-status-codes.js"; import { createPlatformHttpLib } from "../http.js"; import { LibtoolVersion } from "../libtool-version.js"; import { hash } from "../nacl-fast.js"; -import { FailCasesByMethod, ResultByMethod, opEmptySuccess, opFixedSuccess, opKnownFailure, opSuccess, opUnknownFailure } from "../operation.js"; -import { TalerSignaturePurpose, amountToBuffer, bufferForUint32, buildSigPS, decodeCrock, eddsaSign, encodeCrock, stringToBytes, timestampRoundedToBuffer } from "../taler-crypto.js"; -import { OfficerAccount, PaginationParams, SigningKey, TalerExchangeApi, codecForAmlDecisionDetails, codecForAmlRecords, codecForExchangeConfig } from "./types.js"; -import { addPaginationParams } from "./utils.js"; +import { + FailCasesByMethod, + ResultByMethod, + opEmptySuccess, + opFixedSuccess, + opKnownHttpFailure, + opSuccessFromHttp, + opUnknownFailure, +} from "../operation.js"; +import { + TalerSignaturePurpose, + amountToBuffer, + bufferForUint32, + buildSigPS, + decodeCrock, + eddsaSign, + encodeCrock, + stringToBytes, + timestampRoundedToBuffer, +} from "../taler-crypto.js"; +import { + OfficerAccount, + PaginationParams, + SigningKey, + TalerExchangeApi, + codecForAmlDecisionDetails, + codecForAmlRecords, + codecForExchangeConfig, + codecForExchangeKeys, +} from "./types.js"; +import { CacheEvictor, addPaginationParams, nullEvictor } from "./utils.js"; + +export type TalerExchangeResultByMethod< + prop extends keyof TalerExchangeHttpClient, +> = ResultByMethod<TalerExchangeHttpClient, prop>; +export type TalerExchangeErrorsByMethod< + prop extends keyof TalerExchangeHttpClient, +> = FailCasesByMethod<TalerExchangeHttpClient, prop>; + +export enum TalerExchangeCacheEviction { + CREATE_DESCISION, +} -export type TalerExchangeResultByMethod<prop extends keyof TalerExchangeHttpClient> = ResultByMethod<TalerExchangeHttpClient, prop> -export type TalerExchangeErrorsByMethod<prop extends keyof TalerExchangeHttpClient> = FailCasesByMethod<TalerExchangeHttpClient, prop> /** */ export class TalerExchangeHttpClient { httpLib: HttpRequestLibrary; - public readonly PROTOCOL_VERSION = "17:0:0"; + public readonly PROTOCOL_VERSION = "18:0:1"; + cacheEvictor: CacheEvictor<TalerExchangeCacheEviction>; constructor( readonly baseUrl: string, httpClient?: HttpRequestLibrary, + cacheEvictor?: CacheEvictor<TalerExchangeCacheEviction>, ) { this.httpLib = httpClient ?? createPlatformHttpLib(); + this.cacheEvictor = cacheEvictor ?? nullEvictor; } isCompatible(version: string): boolean { - const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version) - return compare?.compatible ?? false + const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version); + return compare?.compatible ?? false; } /** - * https://docs.taler.net/core/api-merchant.html#get--config - * + * https://docs.taler.net/core/api-exchange.html#get--seed + * + */ + async getSeed() { + const url = new URL(`seed`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + }); + switch (resp.status) { + case HttpStatusCode.Ok: + const buffer = await resp.bytes(); + const uintar = new Uint8Array(buffer); + + return opFixedSuccess(uintar); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + /** + * https://docs.taler.net/core/api-exchange.html#get--config + * */ async getConfig() { const url = new URL(`config`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { - method: "GET" + method: "GET", + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForExchangeConfig()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + /** + * https://docs.taler.net/core/api-merchant.html#get--config + * + * PARTIALLY IMPLEMENTED!! + */ + async getKeys() { + const url = new URL(`keys`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", }); switch (resp.status) { - case HttpStatusCode.Ok: return opSuccess(resp, codecForExchangeConfig()) - default: return opUnknownFailure(resp, await resp.text()) + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForExchangeKeys()); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } + // TERMS + // // AML operations // /** * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions-$STATE - * + * */ - async getDecisionsByState(auth: OfficerAccount, state: TalerExchangeApi.AmlState, pagination?: PaginationParams) { - const url = new URL(`aml/${auth.id}/decisions/${TalerExchangeApi.AmlState[state]}`, this.baseUrl); - addPaginationParams(url, pagination) + async getDecisionsByState( + auth: OfficerAccount, + state: TalerExchangeApi.AmlState, + pagination?: PaginationParams, + ) { + const url = new URL( + `aml/${auth.id}/decisions/${TalerExchangeApi.AmlState[state]}`, + this.baseUrl, + ); + addPaginationParams(url, pagination); const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { - "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey) - } + "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey), + }, }); switch (resp.status) { - case HttpStatusCode.Ok: return opSuccess(resp, codecForAmlRecords()) - case HttpStatusCode.NoContent: return opFixedSuccess({ records: [] }) + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAmlRecords()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ records: [] }); //this should be unauthorized - case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); - case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); - case HttpStatusCode.NotFound: return opKnownFailure("officer-not-found", resp); - case HttpStatusCode.Conflict: return opKnownFailure("officer-disabled", resp); - default: return opUnknownFailure(resp, await resp.text()) + case HttpStatusCode.Forbidden: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decision-$H_PAYTO - * + * */ async getDecisionDetails(auth: OfficerAccount, account: string) { const url = new URL(`aml/${auth.id}/decision/${account}`, this.baseUrl); @@ -84,48 +181,62 @@ export class TalerExchangeHttpClient { const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { - "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey) - } + "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey), + }, }); switch (resp.status) { - case HttpStatusCode.Ok: return opSuccess(resp, codecForAmlDecisionDetails()) - case HttpStatusCode.NoContent: return opFixedSuccess({ aml_history: [], kyc_attributes: [] }) + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAmlDecisionDetails()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ aml_history: [], kyc_attributes: [] }); //this should be unauthorized - case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); - case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); - case HttpStatusCode.NotFound: return opKnownFailure("officer-not-found", resp); - case HttpStatusCode.Conflict: return opKnownFailure("officer-disabled", resp); - default: return opUnknownFailure(resp, await resp.text()) + case HttpStatusCode.Forbidden: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-exchange.html#post--aml-$OFFICER_PUB-decision - * + * */ - async addDecisionDetails(auth: OfficerAccount, decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">) { + async addDecisionDetails( + auth: OfficerAccount, + decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">, + ) { const url = new URL(`aml/${auth.id}/decision`, this.baseUrl); - const body = buildDecisionSignature(auth.signingKey, decision) + const body = buildDecisionSignature(auth.signingKey, decision); const resp = await this.httpLib.fetch(url.href, { method: "POST", body, }); switch (resp.status) { - case HttpStatusCode.NoContent: return opEmptySuccess() + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); //FIXME: this should be unauthorized - case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); - case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); - //FIXME: this two need to be splitted by error code - case HttpStatusCode.NotFound: return opKnownFailure("officer-or-account-not-found", resp); - case HttpStatusCode.Conflict: return opKnownFailure("officer-disabled-or-recent-decision", resp); - default: return opUnknownFailure(resp, await resp.text()) + case HttpStatusCode.Forbidden: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + //FIXME: this two need to be split by error code + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } - - } function buildQuerySignature(key: SigningKey): string { @@ -140,11 +251,11 @@ function buildDecisionSignature( key: SigningKey, decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">, ): TalerExchangeApi.AmlDecision { - const zero = new Uint8Array(new ArrayBuffer(64)) + const zero = new Uint8Array(new ArrayBuffer(64)); const sigBlob = buildSigPS(TalerSignaturePurpose.TALER_SIGNATURE_AML_DECISION) //TODO: new need the null terminator, also in the exchange - .put(hash(stringToBytes(decision.justification)))//check null + .put(hash(stringToBytes(decision.justification))) //check null .put(timestampRoundedToBuffer(decision.decision_time)) .put(amountToBuffer(decision.new_threshold)) .put(decodeCrock(decision.h_payto)) @@ -155,6 +266,6 @@ function buildDecisionSignature( const officer_sig = encodeCrock(eddsaSign(sigBlob, key)); return { ...decision, - officer_sig - } -}
\ No newline at end of file + officer_sig, + }; +} |