Skip to content

Commit

Permalink
fix(clerk-js): Cache logic around org ID pass to getToken() (#4013)
Browse files Browse the repository at this point in the history
  • Loading branch information
BRKalow authored Aug 23, 2024
1 parent 96234ce commit 748227c
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .changeset/early-ads-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
20 changes: 11 additions & 9 deletions packages/clerk-js/src/core/resources/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ export class Session extends BaseResource implements SessionResource {
// e.g. session id is 'sess_abc12345' and jwt template name is 'haris'
// The session token ID will be 'sess_abc12345' and the jwt template token ID will be 'sess_abc12345-haris'
#getCacheId(template?: string, organizationId?: string) {
return [this.id, template, organizationId, this.updatedAt.getTime()].filter(Boolean).join('-');
const resolvedOrganizationId =
typeof organizationId === 'undefined' ? this.lastActiveOrganizationId : organizationId;
return [this.id, template, resolvedOrganizationId, this.updatedAt.getTime()].filter(Boolean).join('-');
}

protected fromJSON(data: SessionJSON | null): this {
Expand Down Expand Up @@ -151,12 +153,12 @@ export class Session extends BaseResource implements SessionResource {
return null;
}

const {
leewayInSeconds,
template,
skipCache = false,
organizationId = Session.clerk.organization?.id,
} = options || {};
const { leewayInSeconds, template, skipCache = false } = options || {};

// If no organization ID is provided, default to the selected organization in memory
// Note: this explicitly allows passing `null` or `""`, which should select the personal workspace.
const organizationId =
typeof options?.organizationId === 'undefined' ? Session.clerk.organization?.id : options?.organizationId;

if (!template && Number(leewayInSeconds) >= 60) {
throw new Error('Leeway can not exceed the token lifespan (60 seconds)');
Expand All @@ -166,7 +168,7 @@ export class Session extends BaseResource implements SessionResource {
const cachedEntry = skipCache ? undefined : SessionTokenCache.get({ tokenId }, leewayInSeconds);

// Dispatch tokenUpdate only for __session tokens with the session's active organization ID, and not JWT templates
const shouldDispatchTokenUpdate = !template && options?.organizationId === Session.clerk.organization?.id;
const shouldDispatchTokenUpdate = !template && organizationId === Session.clerk.organization?.id;

if (cachedEntry) {
const cachedToken = await cachedEntry.tokenResolver;
Expand All @@ -178,7 +180,7 @@ export class Session extends BaseResource implements SessionResource {
}
const path = template ? `${this.path()}/tokens/${template}` : `${this.path()}/tokens`;
// TODO: update template endpoint to accept organizationId
const params = template ? {} : { ...(organizationId && { organizationId }) };
const params = template ? {} : { organizationId };
const tokenResolver = Token.create(path, params);
SessionTokenCache.set({ tokenId, tokenResolver });
return tokenResolver.then(token => {
Expand Down
64 changes: 63 additions & 1 deletion packages/clerk-js/src/core/resources/__tests__/Session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import type { OrganizationJSON, SessionJSON } from '@clerk/types';
import { eventBus } from '../../events';
import { createFapiClient } from '../../fapiClient';
import { clerkMock, createUser, mockDevClerkInstance, mockJwt, mockNetworkFailedFetch } from '../../test/fixtures';
import { SessionTokenCache } from '../../tokenCache';
import { BaseResource, Organization, Session } from '../internal';

describe('Session', () => {
afterEach(() => {
SessionTokenCache.clear();
});

describe('creating new session', () => {
let dispatchSpy;

Expand All @@ -19,6 +24,7 @@ describe('Session', () => {
BaseResource.clerk = null as any;
// @ts-ignore
global.fetch?.mockClear();
SessionTokenCache.clear();
});

it('dispatches token:update event on initialization with lastActiveToken', () => {
Expand Down Expand Up @@ -53,7 +59,56 @@ describe('Session', () => {
BaseResource.clerk = null as any;
});

it('dispatches token:update event on getToken', async () => {
it('dispatches token:update event on getToken without active organization', async () => {
const session = new Session({
status: 'active',
id: 'session_1',
object: 'session',
user: createUser({}),
last_active_organization_id: null,
actor: null,
created_at: new Date().getTime(),
updated_at: new Date().getTime(),
} as SessionJSON);

await session.getToken();

expect(dispatchSpy).toHaveBeenCalledTimes(1);
expect(dispatchSpy.mock.calls[0]).toMatchSnapshot();
});

it('hydrates token cache from lastActiveToken', async () => {
BaseResource.clerk = clerkMock({
organization: new Organization({ id: 'activeOrganization' } as OrganizationJSON),
}) as any;

const session = new Session({
status: 'active',
id: 'session_1',
object: 'session',
user: createUser({}),
last_active_organization_id: 'activeOrganization',
last_active_token: { object: 'token', jwt: mockJwt },
actor: null,
created_at: new Date().getTime(),
updated_at: new Date().getTime(),
} as SessionJSON);

const token = await session.getToken();

await session.getToken({ organizationId: 'activeOrganization' });

expect(BaseResource.clerk.getFapiClient().request).not.toHaveBeenCalled();

expect(token).toEqual(mockJwt);
expect(dispatchSpy).toHaveBeenCalledTimes(3);
});

it('dispatches token:update event on getToken with active organization', async () => {
BaseResource.clerk = clerkMock({
organization: new Organization({ id: 'activeOrganization' } as OrganizationJSON),
}) as any;

const session = new Session({
status: 'active',
id: 'session_1',
Expand All @@ -72,6 +127,10 @@ describe('Session', () => {
});

it('does not dispatch token:update if template is provided', async () => {
BaseResource.clerk = clerkMock({
organization: new Organization({ id: 'activeOrganization' } as OrganizationJSON),
}) as any;

const session = new Session({
status: 'active',
id: 'session_1',
Expand All @@ -92,6 +151,7 @@ describe('Session', () => {
BaseResource.clerk = clerkMock({
organization: new Organization({ id: 'activeOrganization' } as OrganizationJSON),
}) as any;

const session = new Session({
status: 'active',
id: 'session_1',
Expand All @@ -112,6 +172,7 @@ describe('Session', () => {
BaseResource.clerk = clerkMock({
organization: new Organization({ id: 'anotherOrganization' } as OrganizationJSON),
}) as any;

const session = new Session({
status: 'active',
id: 'session_1',
Expand Down Expand Up @@ -163,6 +224,7 @@ describe('Session', () => {

const token = await session.getToken();

expect(global.fetch).toHaveBeenCalled();
expect(dispatchSpy).toHaveBeenCalledTimes(1);
expect(token).toEqual(null);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,41 @@ exports[`Session creating new session dispatches token:update event on initializ
]
`;

exports[`Session getToken() dispatches token:update event on getToken 1`] = `
exports[`Session getToken() dispatches token:update event on getToken with active organization 1`] = `
[
"token:update",
{
"token": Token {
"getRawString": [Function],
"jwt": {
"claims": {
"__raw": "eyJhbGciOiJSUzI1NiIsImtpZCI6Imluc18yR0lvUWhiVXB5MGhYN0IyY1ZrdVRNaW5Yb0QiLCJ0eXAiOiJKV1QifQ.eyJhenAiOiJodHRwczovL2FjY291bnRzLmluc3BpcmVkLnB1bWEtNzQubGNsLmRldiIsImV4cCI6MTY2NjY0ODMxMCwiaWF0IjoxNjY2NjQ4MjUwLCJpc3MiOiJodHRwczovL2NsZXJrLmluc3BpcmVkLnB1bWEtNzQubGNsLmRldiIsIm5iZiI6MTY2NjY0ODI0MCwic2lkIjoic2Vzc18yR2JEQjRlbk5kQ2E1dlMxenBDM1h6Zzl0SzkiLCJzdWIiOiJ1c2VyXzJHSXBYT0VwVnlKdzUxcmtabjlLbW5jNlN4ciJ9.n1Usc-DLDftqA0Xb-_2w8IGs4yjCmwc5RngwbSRvwevuZOIuRoeHmE2sgCdEvjfJEa7ewL6EVGVcM557TWPW--g_J1XQPwBy8tXfz7-S73CEuyRFiR97L2AHRdvRtvGtwR-o6l8aHaFxtlmfWbQXfg4kFJz2UGe9afmh3U9-f_4JOZ5fa3mI98UMy1-bo20vjXeWQ9aGrqaxHQxjnzzC-1Kpi5LdPvhQ16H0dPB8MHRTSM5TAuLKTpPV7wqixmbtcc2-0k6b9FKYZNqRVTaIyV-lifZloBvdzlfOF8nW1VVH_fx-iW5Q3hovHFcJIULHEC1kcAYTubbxzpgeVQepGg",
"azp": "https://accounts.inspired.puma-74.lcl.dev",
"exp": 1666648310,
"iat": 1666648250,
"iss": "https://clerk.inspired.puma-74.lcl.dev",
"nbf": 1666648240,
"sid": "sess_2GbDB4enNdCa5vS1zpC3Xzg9tK9",
"sub": "user_2GIpXOEpVyJw51rkZn9Kmnc6Sxr",
},
"encoded": {
"header": "eyJhbGciOiJSUzI1NiIsImtpZCI6Imluc18yR0lvUWhiVXB5MGhYN0IyY1ZrdVRNaW5Yb0QiLCJ0eXAiOiJKV1QifQ",
"payload": "eyJhenAiOiJodHRwczovL2FjY291bnRzLmluc3BpcmVkLnB1bWEtNzQubGNsLmRldiIsImV4cCI6MTY2NjY0ODMxMCwiaWF0IjoxNjY2NjQ4MjUwLCJpc3MiOiJodHRwczovL2NsZXJrLmluc3BpcmVkLnB1bWEtNzQubGNsLmRldiIsIm5iZiI6MTY2NjY0ODI0MCwic2lkIjoic2Vzc18yR2JEQjRlbk5kQ2E1dlMxenBDM1h6Zzl0SzkiLCJzdWIiOiJ1c2VyXzJHSXBYT0VwVnlKdzUxcmtabjlLbW5jNlN4ciJ9",
"signature": "n1Usc-DLDftqA0Xb-_2w8IGs4yjCmwc5RngwbSRvwevuZOIuRoeHmE2sgCdEvjfJEa7ewL6EVGVcM557TWPW--g_J1XQPwBy8tXfz7-S73CEuyRFiR97L2AHRdvRtvGtwR-o6l8aHaFxtlmfWbQXfg4kFJz2UGe9afmh3U9-f_4JOZ5fa3mI98UMy1-bo20vjXeWQ9aGrqaxHQxjnzzC-1Kpi5LdPvhQ16H0dPB8MHRTSM5TAuLKTpPV7wqixmbtcc2-0k6b9FKYZNqRVTaIyV-lifZloBvdzlfOF8nW1VVH_fx-iW5Q3hovHFcJIULHEC1kcAYTubbxzpgeVQepGg",
},
"header": {
"alg": "RS256",
"kid": "ins_2GIoQhbUpy0hX7B2cVkuTMinXoD",
"typ": "JWT",
},
},
"pathRoot": "/client/sessions/session_1/tokens",
},
},
]
`;

exports[`Session getToken() dispatches token:update event on getToken without active organization 1`] = `
[
"token:update",
{
Expand Down

0 comments on commit 748227c

Please sign in to comment.