Skip to content

Commit

Permalink
cte unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tusharpandey13 committed Feb 22, 2025
1 parent f9cf6f9 commit 2d89e85
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 6 deletions.
163 changes: 163 additions & 0 deletions __tests__/Auth0Client/exchangeToken.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { verify } from '../../src/jwt';
import { MessageChannel } from 'worker_threads';
import * as utils from '../../src/utils';
import * as scope from '../../src/scope';

// @ts-ignore

import {
assertPostFn,
fetchResponse,
setupFn,
setupMessageEventLister
} from './helpers';

import {
TEST_ACCESS_TOKEN,
TEST_CLIENT_ID,
TEST_CODE,
TEST_CODE_CHALLENGE,
TEST_CODE_VERIFIER,
TEST_ID_TOKEN,
TEST_REDIRECT_URI,
TEST_REFRESH_TOKEN,
TEST_STATE
} from '../constants';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused imports TEST_CLIENT_ID, TEST_CODE, TEST_CODE_VERIFIER, TEST_REDIRECT_URI.

import { Auth0ClientOptions } from '../../src';
import { DEFAULT_AUTH0_CLIENT } from '../../src/constants';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused import DEFAULT_AUTH0_CLIENT.
import { expect } from '@jest/globals';
import { CustomTokenExchangeOptions } from '../../src/TokenExchange';

jest.mock('es-cookie');
jest.mock('../../src/jwt');
jest.mock('../../src/worker/token.worker');

const mockWindow = <any>global;
const mockFetch = <jest.Mock>mockWindow.fetch;
const mockVerify = <jest.Mock>verify;
const assertPost = assertPostFn(mockFetch);

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable assertPost.

jest
.spyOn(utils, 'bufferToBase64UrlEncoded')
.mockReturnValue(TEST_CODE_CHALLENGE);

jest.spyOn(utils, 'runPopup');

const setup = setupFn(mockVerify);

describe('Auth0Client', () => {
const oldWindowLocation = window.location;

beforeEach(() => {
// https://www.benmvp.com/blog/mocking-window-location-methods-jest-jsdom/
delete window.location;
window.location = Object.defineProperties(
{},
{
...Object.getOwnPropertyDescriptors(oldWindowLocation),
assign: {
configurable: true,
value: jest.fn()
}
}
) as Location;
// --

mockWindow.open = jest.fn();
mockWindow.addEventListener = jest.fn();
mockWindow.crypto = {
subtle: {
digest: () => 'foo'
},
getRandomValues() {
return '123';
}
};
mockWindow.MessageChannel = MessageChannel;
mockWindow.Worker = {};
jest.spyOn(scope, 'getUniqueScopes');
sessionStorage.clear();
});

afterEach(() => {
mockFetch.mockReset();
jest.clearAllMocks();
window.location = oldWindowLocation;
});

describe('getTokenWithPopup()', () => {
const localSetup = async (clientOptions?: Partial<Auth0ClientOptions>) => {
const auth0 = setup(clientOptions);

setupMessageEventLister(mockWindow, { state: TEST_STATE });

mockFetch.mockResolvedValueOnce(
fetchResponse(true, {
id_token: TEST_ID_TOKEN,
refresh_token: TEST_REFRESH_TOKEN,
access_token: TEST_ACCESS_TOKEN,
expires_in: 86400
})
);

auth0['_requestToken'] = async function (requestOptions: any) {
return {
decodedToken: {
encoded: {
header: 'fake_header',
payload: 'fake_payload',
signature: 'fake_signature'
},
header: {},
claims: { __raw: 'fake_raw' },
user: {}
},
id_token: 'fake_id_token',
access_token: 'fake_access_token',
expires_in: 3600,
scope: requestOptions.scope
};
};

return auth0;
};

it('calls `loginWithPopup` with the correct default options', async () => {
const auth0 = await localSetup();
const cteOptions: CustomTokenExchangeOptions = {
subject_token: 'external_token_value',
subject_token_type: 'urn:acme:legacy-system-token', // valid token type (not reserved)
scope: 'openid profile email',
audience: 'https://api.test.com'
};
const result = await auth0.exchangeToken(cteOptions);
console.log(result);
expect(result.id_token).toEqual('fake_id_token');
expect(result.access_token).toEqual('fake_access_token');
expect(result.expires_in).toEqual(3600);
expect(typeof result.scope).toBe('string');
});

it('should throw an error for invalid subject_token_type from reserved namespaces', async () => {
// List of reserved token types that must be rejected.
const invalidTokenTypes = [
'urn:ietf:params:oauth:foo',
'https://auth0.com/token',
'urn:auth0:token'
];

const auth0 = await localSetup();

// Each invalid token type should cause exchangeToken to reject with an Error.
for (const tokenType of invalidTokenTypes) {
const cteOptions: CustomTokenExchangeOptions = {
subject_token: 'external_token_value',
subject_token_type: tokenType,
audience: 'https://api.test.com'
};
await expect(auth0.exchangeToken(cteOptions)).rejects.toThrow(Error);
}
});
});
});
6 changes: 0 additions & 6 deletions src/Auth0Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1145,19 +1145,13 @@ export class Auth0Client {
/*
Custom Token Exchange
* **Implementation Notes:**
*
* - Ensure that the `subject_token` provided has been securely obtained and is valid according
* to your external identity provider's policies before invoking this function.
* - The function leverages internal helper methods:
* - `validateTokenType` confirms that the `subject_token_type` is supported.
* - `getUniqueScopes` merges and de-duplicates scopes between the provided options and
* the instance's default scopes.
* - `_requestToken` performs the actual HTTP request to the token endpoint.
* - For front-end SDKs (like spa-js), note that tokens are automatically cached in both cookies
* and memory to optimize subsequent authentication flows.
* - When integrating Custom Token Exchange into your application, always ensure that caching,
* token renewal, and token validation logic follow security best practices.
*
*/

/**
Expand Down

0 comments on commit 2d89e85

Please sign in to comment.