From 9281ee9154cfc78d77d35cb4f299a4315a3bf21b Mon Sep 17 00:00:00 2001
From: Haroen Viaene <hello@haroen.me>
Date: Fri, 6 Aug 2021 12:15:13 +0200
Subject: [PATCH] feat(auth): introduce WithinBody option for AuthMode

fixes #1035

Implementation is done by:
- adding a new auth mode
- adding data as the return type for createAuth
- expose data from transporter
- serialize transporter data
- map api key back to query parameter if GET
---
 .../client-analytics/src/createAnalyticsClient.ts  |  2 ++
 packages/client-common/src/createAuth.ts           | 14 +++++++++++++-
 packages/client-common/src/types/Auth.ts           | 12 ++++++++----
 packages/client-common/src/types/AuthModeType.ts   | 11 ++++++++---
 .../src/types/ClientTransporterOptions.ts          |  4 +---
 .../src/createPersonalizationClient.ts             |  2 ++
 packages/client-search/src/createSearchClient.ts   |  2 ++
 packages/recommend/src/createRecommendClient.ts    |  2 ++
 .../transporter/src/concerns/retryableRequest.ts   |  4 +++-
 packages/transporter/src/createTransporter.ts      |  8 +++++---
 packages/transporter/src/serializer.ts             |  7 +++++--
 packages/transporter/src/types/Transporter.ts      |  5 +++++
 .../transporter/src/types/TransporterOptions.ts    |  5 +++++
 13 files changed, 61 insertions(+), 17 deletions(-)

diff --git a/packages/client-analytics/src/createAnalyticsClient.ts b/packages/client-analytics/src/createAnalyticsClient.ts
index f946c33f5..950553497 100644
--- a/packages/client-analytics/src/createAnalyticsClient.ts
+++ b/packages/client-analytics/src/createAnalyticsClient.ts
@@ -29,6 +29,8 @@ export const createAnalyticsClient: CreateClient<
       ...auth.queryParameters(),
       ...options.queryParameters,
     },
+
+    data: auth.data(),
   });
 
   const appId = options.appId;
diff --git a/packages/client-common/src/createAuth.ts b/packages/client-common/src/createAuth.ts
index 071665aef..2dd144cc4 100644
--- a/packages/client-common/src/createAuth.ts
+++ b/packages/client-common/src/createAuth.ts
@@ -8,11 +8,23 @@ export function createAuth(authMode: AuthModeType, appId: string, apiKey: string
 
   return {
     headers(): Readonly<Record<string, string>> {
-      return authMode === AuthMode.WithinHeaders ? credentials : {};
+      if (authMode === AuthMode.WithinHeaders) {
+        return credentials;
+      } else if (authMode === AuthMode.WithinBody) {
+        return {
+          'x-algolia-application-id': appId,
+        };
+      }
+
+      return {};
     },
 
     queryParameters(): Readonly<Record<string, string>> {
       return authMode === AuthMode.WithinQueryParameters ? credentials : {};
     },
+
+    data(): Readonly<Record<string, string>> {
+      return authMode === AuthMode.WithinBody ? { apiKey } : {};
+    },
   };
 }
diff --git a/packages/client-common/src/types/Auth.ts b/packages/client-common/src/types/Auth.ts
index 05b17cf50..f959c3579 100644
--- a/packages/client-common/src/types/Auth.ts
+++ b/packages/client-common/src/types/Auth.ts
@@ -1,13 +1,17 @@
 export type Auth = {
   /**
-   * Returns the headers related to auth. Should be
-   * merged to the transporter headers.
+   * Returns the headers related to auth. Should be merged into the headers.
    */
   readonly headers: () => Readonly<Record<string, string>>;
 
   /**
-   * Returns the query parameters related to auth. Should be
-   * merged to the query parameters headers.
+   * Returns the query parameters related to auth. Should be merged into the
+   * query parameters.
    */
   readonly queryParameters: () => Readonly<Record<string, string>>;
+
+  /**
+   * Returns the data related to auth. Should be merged into the body.
+   */
+  readonly data: () => Readonly<Record<string, string>>;
 };
diff --git a/packages/client-common/src/types/AuthModeType.ts b/packages/client-common/src/types/AuthModeType.ts
index eb3ab43b9..8dfeff7e8 100644
--- a/packages/client-common/src/types/AuthModeType.ts
+++ b/packages/client-common/src/types/AuthModeType.ts
@@ -1,13 +1,18 @@
 export const AuthMode: Readonly<Record<string, AuthModeType>> = {
   /**
-   * If auth credentials should be in query parameters.
+   * Algolia credentials are sent as query parameters
    */
   WithinQueryParameters: 0,
 
   /**
-   * If auth credentials should be in headers.
+   * Algolia credentials are sent as headers
    */
   WithinHeaders: 1,
+
+  /**
+   * Algolia credentials are sent as part of the body
+   */
+  WithinBody: 2,
 };
 
-export type AuthModeType = 0 | 1;
+export type AuthModeType = 0 | 1 | 2;
diff --git a/packages/client-common/src/types/ClientTransporterOptions.ts b/packages/client-common/src/types/ClientTransporterOptions.ts
index 389c80b9b..caacc4ca1 100644
--- a/packages/client-common/src/types/ClientTransporterOptions.ts
+++ b/packages/client-common/src/types/ClientTransporterOptions.ts
@@ -2,9 +2,7 @@ import { Headers, HostOptions, QueryParameters, TransporterOptions } from '@algo
 
 export type ClientTransporterOptions = Pick<
   TransporterOptions,
-  Exclude<keyof TransporterOptions, 'headers'> &
-    Exclude<keyof TransporterOptions, 'queryParameters'> &
-    Exclude<keyof TransporterOptions, 'hosts'>
+  Exclude<keyof TransporterOptions, 'hosts' | 'headers' | 'queryParameters' | 'data'>
 > & {
   /**
    * The hosts used by the requester.
diff --git a/packages/client-personalization/src/createPersonalizationClient.ts b/packages/client-personalization/src/createPersonalizationClient.ts
index 2fc37f4a3..4d3c92cff 100644
--- a/packages/client-personalization/src/createPersonalizationClient.ts
+++ b/packages/client-personalization/src/createPersonalizationClient.ts
@@ -29,6 +29,8 @@ export const createPersonalizationClient: CreateClient<
       ...auth.queryParameters(),
       ...options.queryParameters,
     },
+
+    data: auth.data(),
   });
 
   return addMethods({ appId: options.appId, transporter }, options.methods);
diff --git a/packages/client-search/src/createSearchClient.ts b/packages/client-search/src/createSearchClient.ts
index aa1b80c49..94b0ac7cc 100644
--- a/packages/client-search/src/createSearchClient.ts
+++ b/packages/client-search/src/createSearchClient.ts
@@ -44,6 +44,8 @@ export const createSearchClient: CreateClient<
       ...auth.queryParameters(),
       ...options.queryParameters,
     },
+
+    data: auth.data(),
   });
 
   const base = {
diff --git a/packages/recommend/src/createRecommendClient.ts b/packages/recommend/src/createRecommendClient.ts
index 309dfdb9f..0f3130976 100644
--- a/packages/recommend/src/createRecommendClient.ts
+++ b/packages/recommend/src/createRecommendClient.ts
@@ -44,6 +44,8 @@ export const createRecommendClient: CreateClient<
       ...auth.queryParameters(),
       ...options.queryParameters,
     },
+
+    data: auth.data(),
   });
 
   const base = {
diff --git a/packages/transporter/src/concerns/retryableRequest.ts b/packages/transporter/src/concerns/retryableRequest.ts
index 0d9f29de2..63e4eb3bc 100644
--- a/packages/transporter/src/concerns/retryableRequest.ts
+++ b/packages/transporter/src/concerns/retryableRequest.ts
@@ -31,7 +31,7 @@ export function retryableRequest<TResponse>(
   /**
    * First we prepare the payload that do not depend from hosts.
    */
-  const data = serializeData(request, requestOptions);
+  const data = serializeData(transporter, request, requestOptions);
   const headers = serializeHeaders(transporter, requestOptions);
   const method = request.method;
 
@@ -40,6 +40,8 @@ export function retryableRequest<TResponse>(
     request.method !== MethodEnum.Get
       ? {}
       : {
+          // if AuthMode.WithinData, we forcibly map apiKey back to a query param
+          'x-algolia-api-key': transporter.data?.apiKey,
           ...request.data,
           ...requestOptions.data,
         };
diff --git a/packages/transporter/src/createTransporter.ts b/packages/transporter/src/createTransporter.ts
index 278cf225e..acba6129c 100644
--- a/packages/transporter/src/createTransporter.ts
+++ b/packages/transporter/src/createTransporter.ts
@@ -21,6 +21,7 @@ export function createTransporter(options: TransporterOptions): Transporter {
     hosts,
     queryParameters,
     headers,
+    data,
   } = options;
 
   const transporter: Transporter = {
@@ -33,6 +34,7 @@ export function createTransporter(options: TransporterOptions): Transporter {
     userAgent,
     headers,
     queryParameters,
+    data,
     hosts: hosts.map(host => createStatelessHost(host)),
     read<TResponse>(
       request: Request,
@@ -40,7 +42,7 @@ export function createTransporter(options: TransporterOptions): Transporter {
     ): Readonly<Promise<TResponse>> {
       /**
        * First, we compute the user request options. Now, keep in mind,
-       * that using request options the user is able to modified the intire
+       * that using request options the user is able to modified the entire
        * payload of the request. Such as headers, query parameters, and others.
        */
       const mappedRequestOptions = createMappedRequestOptions(
@@ -73,7 +75,7 @@ export function createTransporter(options: TransporterOptions): Transporter {
           : request.cacheable;
 
       /**
-       * If is not "cacheable", we immediatly trigger the retryable request, no
+       * If is not "cacheable", we immediately trigger the retryable request, no
        * need to check cache implementations.
        */
       if (cacheable !== true) {
@@ -96,7 +98,7 @@ export function createTransporter(options: TransporterOptions): Transporter {
 
       /**
        * With the computed key, we first ask the responses cache
-       * implemention if this request was been resolved before.
+       * implementation if this request was been resolved before.
        */
       return transporter.responsesCache.get(
         key,
diff --git a/packages/transporter/src/serializer.ts b/packages/transporter/src/serializer.ts
index 79f9a503a..053fc7de4 100644
--- a/packages/transporter/src/serializer.ts
+++ b/packages/transporter/src/serializer.ts
@@ -36,19 +36,22 @@ export function serializeQueryParameters(parameters: Readonly<Record<string, any
 }
 
 export function serializeData(
+  transporter: Transporter,
   request: Request,
   requestOptions: RequestOptions
 ): string | undefined {
   if (
     request.method === MethodEnum.Get ||
-    (request.data === undefined && requestOptions.data === undefined)
+    (transporter.data === undefined &&
+      request.data === undefined &&
+      requestOptions.data === undefined)
   ) {
     return undefined;
   }
 
   const data = Array.isArray(request.data)
     ? request.data
-    : { ...request.data, ...requestOptions.data };
+    : { ...transporter.data, ...request.data, ...requestOptions.data };
 
   return JSON.stringify(data);
 }
diff --git a/packages/transporter/src/types/Transporter.ts b/packages/transporter/src/types/Transporter.ts
index 509933a5d..5576f2171 100644
--- a/packages/transporter/src/types/Transporter.ts
+++ b/packages/transporter/src/types/Transporter.ts
@@ -68,6 +68,11 @@ export type Transporter = {
    */
   readonly queryParameters: QueryParameters;
 
+  /**
+   * data sent on each request
+   */
+  readonly data: Record<string, string> | undefined;
+
   /**
    * The hosts used by the retry strategy.
    *
diff --git a/packages/transporter/src/types/TransporterOptions.ts b/packages/transporter/src/types/TransporterOptions.ts
index bb201411c..c363d7586 100644
--- a/packages/transporter/src/types/TransporterOptions.ts
+++ b/packages/transporter/src/types/TransporterOptions.ts
@@ -64,6 +64,11 @@ export type TransporterOptions = {
    */
   readonly queryParameters: QueryParameters;
 
+  /**
+   * The data set by the requester (credentials)
+   */
+  readonly data: Record<string, string>;
+
   /**
    * The user agent used. Sent on query parameters.
    */