summaryrefslogtreecommitdiff
path: root/packages/taler-util/src/http-client/challenger.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-util/src/http-client/challenger.ts')
-rw-r--r--packages/taler-util/src/http-client/challenger.ts291
1 files changed, 291 insertions, 0 deletions
diff --git a/packages/taler-util/src/http-client/challenger.ts b/packages/taler-util/src/http-client/challenger.ts
new file mode 100644
index 000000000..aa530570d
--- /dev/null
+++ b/packages/taler-util/src/http-client/challenger.ts
@@ -0,0 +1,291 @@
+import { HttpRequestLibrary, readTalerErrorResponse } from "../http-common.js";
+import { HttpStatusCode } from "../http-status-codes.js";
+import { createPlatformHttpLib } from "../http.js";
+import { TalerCoreBankCacheEviction } from "../index.node.js";
+import { LibtoolVersion } from "../libtool-version.js";
+import {
+ FailCasesByMethod,
+ RedirectResult,
+ ResultByMethod,
+ opFixedSuccess,
+ opKnownAlternativeFailure,
+ opKnownHttpFailure,
+ opSuccessFromHttp,
+ opUnknownFailure,
+} from "../operation.js";
+import {
+ AccessToken,
+ codecForChallengeCreateResponse,
+ codecForChallengeSetupResponse,
+ codecForChallengeStatus,
+ codecForChallengerAuthResponse,
+ codecForChallengerInfoResponse,
+ codecForChallengerTermsOfServiceResponse,
+ codecForInvalidPinResponse,
+} from "./types.js";
+import { CacheEvictor, makeBearerTokenAuthHeader, nullEvictor } from "./utils.js";
+
+export type ChallengerResultByMethod<prop extends keyof ChallengerHttpClient> =
+ ResultByMethod<ChallengerHttpClient, prop>;
+export type ChallengerErrorsByMethod<prop extends keyof ChallengerHttpClient> =
+ FailCasesByMethod<ChallengerHttpClient, prop>;
+
+export enum ChallengerCacheEviction {
+ CREATE_CHALLENGE,
+}
+
+/**
+ */
+export class ChallengerHttpClient {
+ httpLib: HttpRequestLibrary;
+ cacheEvictor: CacheEvictor<ChallengerCacheEviction>;
+ public readonly PROTOCOL_VERSION = "1:0:0";
+
+ constructor(
+ readonly baseUrl: string,
+ httpClient?: HttpRequestLibrary,
+ cacheEvictor?: CacheEvictor<ChallengerCacheEviction>,
+ ) {
+ this.httpLib = httpClient ?? createPlatformHttpLib();
+ this.cacheEvictor = cacheEvictor ?? nullEvictor;
+ }
+
+ isCompatible(version: string): boolean {
+ const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version);
+ return compare?.compatible ?? false;
+ }
+ /**
+ * https://docs.taler.net/core/api-challenger.html#get--config
+ *
+ */
+ async getConfig() {
+ const url = new URL(`config`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(
+ resp,
+ codecForChallengerTermsOfServiceResponse(),
+ );
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+ /**
+ * https://docs.taler.net/core/api-challenger.html#post--setup-$CLIENT_ID
+ *
+ */
+ async setup(clientId: string, token: AccessToken) {
+ const url = new URL(`setup/${clientId}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(token),
+ },
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(resp, codecForChallengeSetupResponse());
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ // LOGIN
+
+ /**
+ * https://docs.taler.net/core/api-challenger.html#post--authorize-$NONCE
+ *
+ */
+ 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);
+ url.searchParams.set("redirect_uri", redirectUri);
+ if (state) {
+ url.searchParams.set("state", state);
+ }
+ // url.searchParams.set("scope", "code");
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(resp, codecForChallengeStatus());
+ case HttpStatusCode.BadRequest:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotAcceptable:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.InternalServerError:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ // CHALLENGE
+
+ /**
+ * https://docs.taler.net/core/api-challenger.html#post--challenge-$NONCE
+ *
+ */
+ 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",
+ },
+ redirect: "manual",
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok: {
+ await this.cacheEvictor.notifySuccess(
+ ChallengerCacheEviction.CREATE_CHALLENGE,
+ );
+ return opSuccessFromHttp(resp, codecForChallengeCreateResponse());
+ }
+ case HttpStatusCode.Found:
+ const redirect = resp.headers.get("Location")!;
+ return opFixedSuccess<RedirectResult>({
+ redirectURL: new URL(redirect),
+ });
+ case HttpStatusCode.BadRequest:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotAcceptable:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.TooManyRequests:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.InternalServerError:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ // SOLVE
+
+ /**
+ * https://docs.taler.net/core/api-challenger.html#post--solve-$NONCE
+ *
+ */
+ async solve(nonce: string, body: Record<string, string>) {
+ const url = new URL(`solve/${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",
+ },
+ redirect: "manual",
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Found:
+ const redirect = resp.headers.get("Location")!;
+ return opFixedSuccess<RedirectResult>({
+ redirectURL: new URL(redirect),
+ });
+ case HttpStatusCode.BadRequest:
+ return opKnownHttpFailure(resp.status, resp);
+ 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);
+ case HttpStatusCode.TooManyRequests:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.InternalServerError:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ // AUTH
+
+ /**
+ * https://docs.taler.net/core/api-challenger.html#post--token
+ *
+ */
+ async token(
+ client_id: string,
+ redirect_uri: string,
+ client_secret: AccessToken,
+ code: string,
+ ) {
+ const url = new URL(`token`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: new URLSearchParams(
+ Object.entries({
+ client_id,
+ redirect_uri,
+ client_secret,
+ code,
+ grant_type: "authorization_code",
+ }),
+ ).toString(),
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(resp, codecForChallengerAuthResponse());
+ case HttpStatusCode.Forbidden:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ // INFO
+
+ /**
+ * https://docs.taler.net/core/api-challenger.html#get--info
+ *
+ */
+ async info(token: AccessToken) {
+ const url = new URL(`info`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(token),
+ },
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(resp, codecForChallengerInfoResponse());
+ case HttpStatusCode.Forbidden:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+}