Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure to pass-trough options to TokenProvider #938

Merged
merged 3 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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