From 76b28517ecd932d60b9168287da3666772865a01 Mon Sep 17 00:00:00 2001 From: Tushar Pandey Date: Fri, 10 Jan 2025 15:05:58 +0530 Subject: [PATCH] accept a function as token --- src/management/management-client-options.ts | 2 +- src/management/token-provider-middleware.ts | 6 +- src/utils.ts | 22 ++++++++ test/lib/utils.test.ts | 23 ++++++++ .../token-provider-middleware.test.ts | 55 +++++++++++++++++++ 5 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 test/lib/utils.test.ts diff --git a/src/management/management-client-options.ts b/src/management/management-client-options.ts index 2ef993a00..496bf08aa 100644 --- a/src/management/management-client-options.ts +++ b/src/management/management-client-options.ts @@ -6,7 +6,7 @@ export interface ManagementClientOptions extends ClientOptions { } export interface ManagementClientOptionsWithToken extends ManagementClientOptions { - token: string; + token: string | (() => Promise) | (() => string); } export interface ManagementClientOptionsWithClientSecret extends ManagementClientOptions { diff --git a/src/management/token-provider-middleware.ts b/src/management/token-provider-middleware.ts index 9cdfed8ea..4ffb6c294 100644 --- a/src/management/token-provider-middleware.ts +++ b/src/management/token-provider-middleware.ts @@ -7,14 +7,16 @@ import { } from './management-client-options.js'; import { TokenProvider } from './token-provider.js'; +import { resolveValueToPromise } from '../utils.js'; + export class TokenProviderMiddleware implements Middleware { - private tokenProvider: { getAccessToken: () => Promise }; + private readonly tokenProvider: { getAccessToken: () => Promise }; constructor( options: ManagementClientOptionsWithToken | ManagementClientOptionsWithClientCredentials ) { if ('token' in options) { this.tokenProvider = { - getAccessToken: () => Promise.resolve(options.token), + getAccessToken: () => resolveValueToPromise(options.token), }; } else { this.tokenProvider = new TokenProvider({ diff --git a/src/utils.ts b/src/utils.ts index 6e668746c..24e250daa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -15,3 +15,25 @@ export const generateClientInfo = () => ({ * @private */ export const mtlsPrefix = 'mtls'; + +type SyncGetter = () => T; +type AsyncGetter = () => Promise; +/** + * Resolves a value that can be a static value, a synchronous function, or an asynchronous function. + * + * @template T - The type of the value to be resolved. + * @param {T | SyncGetter | AsyncGetter} value - The value to be resolved. It can be: + * - A static value of type T. + * - A synchronous function that returns a value of type T. + * - An asynchronous function that returns a Promise of type T. + * @returns {Promise} A promise that resolves to the value of type T. + */ +export const resolveValueToPromise = async ( + value: T | SyncGetter | AsyncGetter +): Promise => { + if (typeof value === 'function') { + const result = (value as SyncGetter | AsyncGetter)(); // Call the function + return result instanceof Promise ? result : Promise.resolve(result); // Handle sync/async + } + return Promise.resolve(value); // Static value +}; diff --git a/test/lib/utils.test.ts b/test/lib/utils.test.ts new file mode 100644 index 000000000..3bd6005d7 --- /dev/null +++ b/test/lib/utils.test.ts @@ -0,0 +1,23 @@ +import { resolveValueToPromise } from '../../src/utils.js'; + +describe('resolveValueToPromise', () => { + it('should resolve a static string value', async () => { + const value = 'staticValue'; + const result = await resolveValueToPromise(value); + expect(result).toBe(value); + }); + + it('should resolve a synchronous function returning a string', async () => { + const value = 'syncValue'; + const syncFunction = () => value; + const result = await resolveValueToPromise(syncFunction); + expect(result).toBe(value); + }); + + it('should resolve an asynchronous function returning a string', async () => { + const value = 'asyncValue'; + const asyncFunction = async () => value; + const result = await resolveValueToPromise(asyncFunction); + expect(result).toBe(value); + }); +}); diff --git a/test/management/token-provider-middleware.test.ts b/test/management/token-provider-middleware.test.ts index 9a2eb3b9d..00877c699 100644 --- a/test/management/token-provider-middleware.test.ts +++ b/test/management/token-provider-middleware.test.ts @@ -99,4 +99,59 @@ describe('TokenProviderMiddleware', () => { ).resolves.toMatchObject({}); expect(customFetch).toHaveBeenCalled(); }); + + it('should use provided access token as a string', async () => { + await expect(tokenClient.testRequest({ path: '/foo', method: 'GET' })).resolves.toMatchObject( + {} + ); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + authorization: 'Bearer token', + }) + ); + }); + + it('should use provided access token as a sync function', async () => { + const syncTokenClient = new TestClient({ + ...opts, + middleware: [ + new TokenProviderMiddleware({ + ...opts, + domain, + token: () => 'sync-token', + }), + ], + }); + + await expect( + syncTokenClient.testRequest({ path: '/foo', method: 'GET' }) + ).resolves.toMatchObject({}); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + authorization: 'Bearer sync-token', + }) + ); + }); + + it('should use provided access token as an async function', async () => { + const asyncTokenClient = new TestClient({ + ...opts, + middleware: [ + new TokenProviderMiddleware({ + ...opts, + domain, + token: async () => 'async-token', + }), + ], + }); + + await expect( + asyncTokenClient.testRequest({ path: '/foo', method: 'GET' }) + ).resolves.toMatchObject({}); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + authorization: 'Bearer async-token', + }) + ); + }); });