diff --git a/.changeset/bright-falcons-move.md b/.changeset/bright-falcons-move.md new file mode 100644 index 00000000000..23a46e89060 --- /dev/null +++ b/.changeset/bright-falcons-move.md @@ -0,0 +1,5 @@ +--- +"@clerk/clerk-js": patch +--- + +Fix bug where session.getToken() was reading a stale organization ID. diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index 6c676057635..7a32e025a5e 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -129,7 +129,7 @@ export class Session extends BaseResource implements SessionResource { // and retrieve it using the session id concatenated with the jwt template name. // 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) { + #getCacheId(template?: string, organizationId?: string | null) { const resolvedOrganizationId = typeof organizationId === 'undefined' ? this.lastActiveOrganizationId : organizationId; return [this.id, template, resolvedOrganizationId, this.updatedAt.getTime()].filter(Boolean).join('-'); @@ -263,7 +263,7 @@ export class Session extends BaseResource implements SessionResource { // 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; + typeof options?.organizationId === 'undefined' ? this.lastActiveOrganizationId : options?.organizationId; if (!template && Number(leewayInSeconds) >= 60) { throw new Error('Leeway can not exceed the token lifespan (60 seconds)'); @@ -273,7 +273,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 && organizationId === Session.clerk.organization?.id; + const shouldDispatchTokenUpdate = !template && organizationId === this.lastActiveOrganizationId; if (cachedEntry) { const cachedToken = await cachedEntry.tokenResolver; diff --git a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts index 03d56d2c11e..b9a3901dcc4 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -169,9 +169,7 @@ describe('Session', () => { }); it('does not dispatch token:update when provided organization ID does not match current active organization', async () => { - BaseResource.clerk = clerkMock({ - organization: new Organization({ id: 'anotherOrganization' } as OrganizationJSON), - }) as any; + BaseResource.clerk = clerkMock() as any; const session = new Session({ status: 'active', @@ -184,7 +182,7 @@ describe('Session', () => { updated_at: new Date().getTime(), } as SessionJSON); - await session.getToken({ organizationId: 'activeOrganization' }); + await session.getToken({ organizationId: 'anotherOrganization' }); expect(dispatchSpy).toHaveBeenCalledTimes(0); }); @@ -229,6 +227,29 @@ describe('Session', () => { expect(token).toEqual(null); }); }); + + it(`uses the current session's lastActiveOrganizationId by default, not clerk.organization.id`, async () => { + BaseResource.clerk = clerkMock({ + organization: new Organization({ id: 'oldActiveOrganization' } as OrganizationJSON), + }) as any; + + const session = new Session({ + status: 'active', + id: 'session_1', + object: 'session', + user: createUser({}), + last_active_organization_id: 'newActiveOrganization', + actor: null, + created_at: new Date().getTime(), + updated_at: new Date().getTime(), + } as SessionJSON); + + await session.getToken(); + + expect((BaseResource.fapiClient.request as jest.Mock).mock.calls[0][0]).toMatchObject({ + body: { organizationId: 'newActiveOrganization' }, + }); + }); }); describe('isAuthorized()', () => {