Skip to content
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
42 changes: 38 additions & 4 deletions lib/__tests__/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
import { type JWK, SignJWT, exportJWK, generateKeyPair, importJWK } from 'jose';
import { type SessionManager } from '../sdk/session-managers';

let mockPrivateKey: JWK | undefined;
let mockPublicKey: JWK | undefined;

export const mockJwtAlg = 'RS256';

export const getKeys = async (): Promise<{ privateKey: JWK; publicKey: JWK }> => {
if (mockPrivateKey !== undefined && mockPublicKey !== undefined) {
return { privateKey: mockPrivateKey, publicKey: mockPublicKey };
}
const { publicKey: generatedPublicKey, privateKey: generatedPrivateKey } =
await generateKeyPair(mockJwtAlg);

const generatedPrivateJwk = await exportJWK(generatedPrivateKey);
const generatedPublicJwk = await exportJWK(generatedPublicKey);

mockPrivateKey = generatedPrivateJwk;
mockPublicKey = generatedPublicJwk;

return { privateKey: mockPrivateKey, publicKey: mockPublicKey };
};

export const fetchClient = jest.fn().mockImplementation(
async () =>
await Promise.resolve({
Expand All @@ -9,7 +31,7 @@ export const fetchClient = jest.fn().mockImplementation(
})
);

export const getMockAccessToken = (
export const getMockAccessToken = async (
domain: string = '[email protected]',
isExpired: boolean = false,
isExpClaimMissing: boolean = false
Expand All @@ -35,13 +57,19 @@ export const getMockAccessToken = (
},
};

const { privateKey } = await getKeys();
const key = await importJWK(privateKey, mockJwtAlg);
const jwt = await new SignJWT(tokenPayload)
.setProtectedHeader({ alg: mockJwtAlg })
.sign(key);

return {
token: `.${btoa(JSON.stringify(tokenPayload))}.`,
token: jwt,
payload: tokenPayload,
};
};

export const getMockIdToken = (
export const getMockIdToken = async (
domain: string = '[email protected]',
isExpired: boolean = false
) => {
Expand All @@ -65,8 +93,14 @@ export const getMockIdToken = (
updated_at: iat,
};

const { privateKey } = await getKeys();
const key = await importJWK(privateKey, mockJwtAlg);
const jwt = await new SignJWT(tokenPayload)
.setProtectedHeader({ alg: mockJwtAlg })
.sign(key);

return {
token: `.${btoa(JSON.stringify(tokenPayload))}.`,
token: jwt,
payload: tokenPayload,
};
};
Expand Down
35 changes: 22 additions & 13 deletions lib/__tests__/sdk/oauth2-flows/AuthCodeWithPKCE.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ describe('AuthCodeWitPKCE', () => {
clientId: 'client-id',
};

beforeAll(async () => {
const { publicKey } = await mocks.getKeys();
clientConfig.jwks = { keys: [publicKey] };
});

describe('new AuthCodeWithPKCE', () => {
it('can construct AuthCodeWithPKCE instance', () => {
expect(() => new AuthCodeWithPKCE(clientConfig)).not.toThrowError();
Expand Down Expand Up @@ -119,8 +124,10 @@ describe('AuthCodeWitPKCE', () => {
});

it('saves tokens to memory store after exchanging auth code for tokens', async () => {
const mockAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const mockIdToken = mocks.getMockIdToken(clientConfig.authDomain);
const mockAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain
);
const mockIdToken = await mocks.getMockIdToken(clientConfig.authDomain);
mocks.fetchClient.mockResolvedValue({
json: () => ({
access_token: mockAccessToken.token,
Expand Down Expand Up @@ -159,7 +166,9 @@ describe('AuthCodeWitPKCE', () => {
});

it('return an existing token if an unexpired token is available', async () => {
const mockAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const mockAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain
);
await sessionManager.setSessionItem('access_token', mockAccessToken.token);
const client = new AuthCodeWithPKCE(clientConfig);
const token = await client.getToken(sessionManager);
Expand All @@ -168,7 +177,7 @@ describe('AuthCodeWitPKCE', () => {
});

it('throws an error if no refresh token is found in memory', async () => {
const mockAccessToken = mocks.getMockAccessToken(
const mockAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain,
true
);
Expand All @@ -180,8 +189,8 @@ describe('AuthCodeWitPKCE', () => {
});

it('fetches new tokens if access token is expired and refresh token is available', async () => {
const newAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = mocks.getMockIdToken(clientConfig.authDomain);
const newAccessToken = await mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = await mocks.getMockIdToken(clientConfig.authDomain);
mocks.fetchClient.mockResolvedValue({
json: () => ({
access_token: newAccessToken.token,
Expand All @@ -190,7 +199,7 @@ describe('AuthCodeWitPKCE', () => {
}),
});

const expiredAccessToken = mocks.getMockAccessToken(
const expiredAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain,
true
);
Expand Down Expand Up @@ -219,8 +228,8 @@ describe('AuthCodeWitPKCE', () => {
});

it('overrides SDK version header if options are provided to client constructor', async () => {
const newAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = mocks.getMockIdToken(clientConfig.authDomain);
const newAccessToken = await mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = await mocks.getMockIdToken(clientConfig.authDomain);
mocks.fetchClient.mockResolvedValue({
json: () => ({
access_token: newAccessToken.token,
Expand All @@ -229,7 +238,7 @@ describe('AuthCodeWitPKCE', () => {
}),
});

const expiredAccessToken = mocks.getMockAccessToken(
const expiredAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain,
true
);
Expand Down Expand Up @@ -260,8 +269,8 @@ describe('AuthCodeWitPKCE', () => {
});

it('commits new tokens to memory if new tokens are fetched', async () => {
const newAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = mocks.getMockIdToken(clientConfig.authDomain);
const newAccessToken = await mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = await mocks.getMockIdToken(clientConfig.authDomain);
const newRefreshToken = 'new_refresh_token';

mocks.fetchClient.mockResolvedValue({
Expand All @@ -272,7 +281,7 @@ describe('AuthCodeWitPKCE', () => {
}),
});

const expiredAccessToken = mocks.getMockAccessToken(
const expiredAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain,
true
);
Expand Down
66 changes: 29 additions & 37 deletions lib/__tests__/sdk/oauth2-flows/AuthorizationCode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ describe('AuthorizationCode', () => {
clientId: 'client-id',
};

beforeAll(async () => {
const { publicKey } = await mocks.getKeys();
clientConfig.jwks = { keys: [publicKey] };
});

describe('new AuthorizationCode', () => {
it('can construct AuthorizationCode instance', () => {
expect(
Expand Down Expand Up @@ -164,8 +169,10 @@ describe('AuthorizationCode', () => {
});

it('saves tokens to memory store after exchanging auth code for tokens', async () => {
const mockAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const mockIdToken = mocks.getMockIdToken(clientConfig.authDomain);
const mockAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain
);
const mockIdToken = await mocks.getMockIdToken(clientConfig.authDomain);
mocks.fetchClient.mockResolvedValue({
json: () => ({
access_token: mockAccessToken.token,
Expand Down Expand Up @@ -200,7 +207,9 @@ describe('AuthorizationCode', () => {
});

it('return an existing token if an unexpired token is available', async () => {
const mockAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const mockAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain
);
await sessionManager.setSessionItem('access_token', mockAccessToken.token);
const client = new AuthorizationCode(clientConfig, clientSecret);
const token = await client.getToken(sessionManager);
Expand All @@ -216,7 +225,7 @@ describe('AuthorizationCode', () => {
});

it('throws an error if no refresh token is found in memory', async () => {
const mockAccessToken = mocks.getMockAccessToken(
const mockAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain,
true
);
Expand All @@ -236,7 +245,7 @@ describe('AuthorizationCode', () => {
}),
});

const expiredAccessToken = mocks.getMockAccessToken(
const expiredAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain,
true
);
Expand All @@ -251,8 +260,8 @@ describe('AuthorizationCode', () => {
});

it('fetches new tokens if access token is expired and refresh token is available', async () => {
const newAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = mocks.getMockIdToken(clientConfig.authDomain);
const newAccessToken = await mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = await mocks.getMockIdToken(clientConfig.authDomain);
mocks.fetchClient.mockResolvedValue({
json: () => ({
access_token: newAccessToken.token,
Expand All @@ -261,7 +270,7 @@ describe('AuthorizationCode', () => {
}),
});

const expiredAccessToken = mocks.getMockAccessToken(
const expiredAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain,
true
);
Expand Down Expand Up @@ -291,8 +300,8 @@ describe('AuthorizationCode', () => {
});

it('overrides SDK version header if options are provided to client constructor', async () => {
const newAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = mocks.getMockIdToken(clientConfig.authDomain);
const newAccessToken = await mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = await mocks.getMockIdToken(clientConfig.authDomain);
mocks.fetchClient.mockResolvedValue({
json: () => ({
access_token: newAccessToken.token,
Expand All @@ -301,7 +310,7 @@ describe('AuthorizationCode', () => {
}),
});

const expiredAccessToken = mocks.getMockAccessToken(
const expiredAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain,
true
);
Expand Down Expand Up @@ -335,8 +344,8 @@ describe('AuthorizationCode', () => {
});

it('commits new tokens to memory if new tokens are fetched', async () => {
const newAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = mocks.getMockIdToken(clientConfig.authDomain);
const newAccessToken = await mocks.getMockAccessToken(clientConfig.authDomain);
const newIdToken = await mocks.getMockIdToken(clientConfig.authDomain);
const newRefreshToken = 'new_refresh_token';

mocks.fetchClient.mockResolvedValue({
Expand All @@ -347,7 +356,7 @@ describe('AuthorizationCode', () => {
}),
});

const expiredAccessToken = mocks.getMockAccessToken(
const expiredAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain,
true
);
Expand All @@ -372,7 +381,9 @@ describe('AuthorizationCode', () => {
await sessionManager.setSessionItem('refresh_token', 'mines are here');
await sessionManager.setSessionItem(
'access_token',
mocks.getMockAccessToken(clientConfig.authDomain, true).token
(
await mocks.getMockAccessToken(clientConfig.authDomain, true)
).token
);

const client = new AuthorizationCode(clientConfig, clientSecret);
Expand All @@ -392,7 +403,9 @@ describe('AuthorizationCode', () => {
});

it('fetches user profile using the available access token', async () => {
const mockAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
const mockAccessToken = await mocks.getMockAccessToken(
clientConfig.authDomain
);
await sessionManager.setSessionItem('access_token', mockAccessToken.token);

const headers = new Headers();
Expand All @@ -416,26 +429,5 @@ describe('AuthorizationCode', () => {
{ method: 'GET', headers }
);
});

it('commits fetched user details to memory store', async () => {
const mockAccessToken = mocks.getMockAccessToken(clientConfig.authDomain);
await sessionManager.setSessionItem('access_token', mockAccessToken.token);
const userDetails = {
family_name: 'family_name',
given_name: 'give_name',
email: '[email protected]',
picture: null,
id: 'id',
};

mocks.fetchClient.mockResolvedValue({
json: () => userDetails,
});

const client = new AuthorizationCode(clientConfig, clientSecret);
await client.getUserProfile(sessionManager);
expect(mocks.fetchClient).toHaveBeenCalledTimes(1);
expect(await sessionManager.getSessionItem('user')).toStrictEqual(userDetails);
});
});
});
Loading