diff --git a/README.md b/README.md index 47a3ccb..10b92ef 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,19 @@ Typesafe access to [Testing Farm's REST API](https://api.dev.testing-farm.io/red ```typescript import TestingFarmAPI from "testing-farm"; +const api = new TestingFarmAPI("https://api.dev.testing-farm.io/v0.1", "api-key"); + +// Passing api key in data - not recommended const api = new TestingFarmAPI("https://api.dev.testing-farm.io/v0.1"); + await api.about(); ``` +> [!WARNING] +> +> Passing the API key in request body is deprecated and not recommended. It is better to pass it in the constructor. +> This way the API key will be passed in the request header as part of `Authorization` header. + ### List a Test Requests documentation of - [`GET /requests`](https://api.dev.testing-farm.io/redoc#operation/get_test_requests_v0_1_requests_get) diff --git a/src/index.ts b/src/index.ts index 9c1bf69..daa6042 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { PublicLink } from './link'; +import { ApiKeyLink, PublicLink, TestingFarmLink } from './link'; import { isError } from './util'; import { About, @@ -46,10 +46,15 @@ export { }; export default class TestingFarmAPI { - private readonly link: PublicLink; - - constructor(instance: string) { - this.link = new PublicLink(new URL(instance)); + private readonly link: TestingFarmLink; + + constructor(instance: string, apiKey?: string) { + // Use PublicLink only for endpoints that don't require authentication + if (!apiKey) { + this.link = new PublicLink(new URL(instance)); + } else { + this.link = new ApiKeyLink(new URL(instance), apiKey); + } } async requests(filter: RequestsFilter): Promise; diff --git a/src/link.ts b/src/link.ts index a353aec..a722b87 100644 --- a/src/link.ts +++ b/src/link.ts @@ -97,3 +97,30 @@ export class PublicLink extends TestingFarmLink { return performRequest(config); } } + +/** + * Handles authentication using an API key. + */ +export class ApiKeyLink extends TestingFarmLink { + public constructor( + instance: URL, + private readonly apiKey: string + ) { + super(instance); + } + + protected async request(config: AxiosRequestConfig): Promise { + return performRequest({ + ...config, + headers: { + ...(config.headers ?? {}), + // https://api.dev.testing-farm.io/redoc#operation/request_a_new_test_v0_1_requests_post + // OAuth2: OAuth2PasswordBearer + // The API key for authentication. + // Flow type: password + // Token URL: token + Authorization: `Bearer ${this.apiKey}`, + }, + }); + } +} diff --git a/src/schema.ts b/src/schema.ts index ab186a7..55bb51c 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -135,7 +135,7 @@ export const requestsFilterSchema = z.object({ export type RequestsFilter = z.infer; export const newRequestSchema = z.object({ - api_key: z.any(), + api_key: z.any().optional(), test: testObjectSchema, environments: z.array(environmentSchema).optional(), notification: notificationSchema.optional().nullable(), @@ -208,7 +208,7 @@ export const requestSchema = z.object({ export type Request = z.infer; export const cancelRequestSchema = z.object({ - api_key: z.any(), + api_key: z.any().optional(), }); export type CancelRequest = z.infer; diff --git a/test/integration/cancel-request.test.ts b/test/integration/cancel-request.test.ts index 380080c..97cb8ff 100644 --- a/test/integration/cancel-request.test.ts +++ b/test/integration/cancel-request.test.ts @@ -14,11 +14,14 @@ describe('Test Testing Farm DELETE /requests/{request_id}', () => { }); test.todo('unsafe response', async () => { - const api = new TestingFarmAPI('https://api.dev.testing-farm.io/v0.1'); + const api = new TestingFarmAPI( + 'https://api.dev.testing-farm.io/v0.1', + 'api_key' + ); const response = await api.cancelRequest( 'f053796b-452e-4da2-b4e1-26eb2f3e721f', - { api_key: 'api_key' }, + {}, false ); expect(response).toBeTypeOf('object');