Skip to content

Commit

Permalink
Ensure to pass-trough options to TokenProvider (#938)
Browse files Browse the repository at this point in the history
  • Loading branch information
frederikprijck authored Sep 15, 2023
1 parent 3c4ac31 commit c219835
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 36 deletions.
1 change: 1 addition & 0 deletions src/management/management-client-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ManagementClientOptionsWithClientSecret extends ManagementClien
export interface ManagementClientOptionsWithClientAssertion extends ManagementClientOptions {
clientId: string;
clientAssertionSigningKey: string;
clientAssertionSigningAlg?: string;
}

export type ManagementClientOptionsWithClientCredentials =
Expand Down
3 changes: 1 addition & 2 deletions src/management/token-provider-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ export class TokenProviderMiddleware implements Middleware {
};
} else {
this.tokenProvider = new TokenProvider({
clientId: options.clientId,
domain: options.domain,
...options,
audience: options.audience ?? `https://${options.domain}/api/v2/`,
...{ clientSecret: (options as ManagementClientOptionsWithClientSecret).clientSecret },
...{
Expand Down
32 changes: 13 additions & 19 deletions src/management/token-provider.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@
import { AuthenticationClient } from '../auth/index.js';
import { TokenSet } from '../auth/oauth.js';
import { JSONApiResponse } from '../lib/models.js';
import {
ManagementClientOptionsWithClientAssertion,
ManagementClientOptionsWithClientCredentials,
ManagementClientOptionsWithClientSecret,
} from './management-client-options.js';

const LEEWAY = 10 * 1000;

interface BaseOptions {
domain: string;
audience: string;
clientId: string;
enableCache?: boolean;
}

export class TokenProvider {
private authenticationClient: AuthenticationClient;
private expiresAt = 0;
private accessToken = '';
private pending: Promise<JSONApiResponse<TokenSet>> | undefined;

constructor(options: BaseOptions & { clientSecret: string });
constructor(options: BaseOptions & { clientAssertionSigningKey: string });
constructor(private options: any) {
this.authenticationClient = new AuthenticationClient({
clientId: options.clientId,
domain: options.domain,
clientSecret: options.clientSecret,
clientAssertionSigningKey: options.clientAssertionSigningKey,
});
constructor(options: ManagementClientOptionsWithClientSecret & { audience: string });
constructor(options: ManagementClientOptionsWithClientAssertion & { audience: string });
constructor(
private options: ManagementClientOptionsWithClientCredentials & { audience: string }
) {
this.authenticationClient = new AuthenticationClient(options);
}

public async getAccessToken() {
const disableCache = this.options.enableCache === false;
if (disableCache || !this.accessToken || Date.now() > this.expiresAt - LEEWAY) {
if (!this.accessToken || Date.now() > this.expiresAt - LEEWAY) {
this.pending =
(!disableCache && this.pending) ||
this.pending ||
this.authenticationClient.oauth.clientCredentialsGrant({
audience: this.options.audience,
});
Expand Down
14 changes: 13 additions & 1 deletion test/management/token-provider-middleware.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import nock from 'nock';
import { jest } from '@jest/globals';
import { RequestOpts, InitOverrideFunction } from '../../src/lib/index.js';
import { RequestOpts, InitOverrideFunction, FetchAPI } from '../../src/lib/index.js';
import { BaseAPI } from '../../src/lib/runtime.js';
import { TokenProviderMiddleware } from '../../src/management/token-provider-middleware.js';

const domain = 'test-domain.auth0.com';

const customFetch = jest
.fn<FetchAPI>()
.mockImplementation((url: URL | RequestInfo, init?: RequestInit) => fetch(url, init));

const opts = {
baseUrl: `https://${domain}`,
clientId: 'test-client-id',
Expand All @@ -14,6 +18,7 @@ const opts = {
parseError: async (response: Response) => {
return new Error(`${response.status}`);
},
fetch: customFetch,
};

export class TestClient extends BaseAPI {
Expand Down Expand Up @@ -87,4 +92,11 @@ describe('TokenProviderMiddleware', () => {
})
);
});

it('should use custom fetch', async () => {
await expect(
clientSecretClient.testRequest({ path: '/foo', method: 'GET' })
).resolves.toMatchObject({});
expect(customFetch).toHaveBeenCalled();
});
});
24 changes: 11 additions & 13 deletions test/management/token-provider.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import nock from 'nock';
import { jest } from '@jest/globals';
import { TokenProvider } from '../../src/management/token-provider.js';
import { FetchAPI } from '../../src/lib/models.js';

const opts = {
domain: 'test-domain.auth0.com',
Expand Down Expand Up @@ -47,13 +48,6 @@ describe('TokenProvider', () => {
expect(spy).toHaveBeenCalledTimes(1);
});

it('should get a new access token if caching disabled', async () => {
const tp = new TokenProvider({ ...opts, enableCache: false });
expect(await tp.getAccessToken()).toBe('my-access-token');
expect(await tp.getAccessToken()).toBe('my-access-token');
expect(spy).toHaveBeenCalledTimes(2);
});

it('should get a new access token if token expires', async () => {
jest.useFakeTimers({ doNotFake: ['nextTick'] });
const tp = new TokenProvider(opts);
Expand Down Expand Up @@ -93,12 +87,16 @@ describe('TokenProvider', () => {
expect(spy).toHaveBeenCalledTimes(1);
});

it('should not cache concurrent requests when caching disabled', async () => {
const tp = new TokenProvider({ ...opts, enableCache: false });
expect(
await Promise.all([tp.getAccessToken(), tp.getAccessToken(), tp.getAccessToken()])
).toEqual(['my-access-token', 'my-access-token', 'my-access-token']);
it('should use a custom fetch', async () => {
const customFetch = jest
.fn<FetchAPI>()
.mockImplementation((url: URL | RequestInfo, init?: RequestInit) => fetch(url, init));

expect(spy).toHaveBeenCalledTimes(3);
const tp = new TokenProvider({
...opts,
fetch: customFetch as any,
});
expect(await tp.getAccessToken()).toBe('my-access-token');
expect(customFetch).toHaveBeenCalled();
});
});
2 changes: 1 addition & 1 deletion v4_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ Some method names have been changed to better align with the documentation.
The following configuration options have changed:

- `scope` - The Management Client uses the Client Credentials grant which gets all the scopes granted to the application. So this is redundant and has been removed.
- `tokenProvider.enableCache` - Use the `enableCache` option of the Management Client.
- `tokenProvider.enableCache` - Each instance of the Management Client has its own cache, if you want a new cache you want to instantiate a new Management Client.
- `tokenProvider.cacheTTLInSeconds` - Each instance of the Management Client only stores a single access token, so this functionality has been removed.
- `proxy` - You should now provide an [HttpsAgent](https://nodejs.org/api/https.html#class-httpsagent) as the `agent` config.
- `includeResponseHeaders` - This has been removed, all return types include the response headers by default.
Expand Down

0 comments on commit c219835

Please sign in to comment.