diff --git a/.changeset/friendly-adults-bathe.md b/.changeset/friendly-adults-bathe.md new file mode 100644 index 00000000000..d0439fad4e6 --- /dev/null +++ b/.changeset/friendly-adults-bathe.md @@ -0,0 +1,6 @@ +--- +"@clerk/shared": patch +"@clerk/testing": patch +--- + +Fixed an issue where API keys in `` are showing organization API keys. diff --git a/integration/tests/machine-auth/component.test.ts b/integration/tests/machine-auth/component.test.ts index f03881e4632..b37527e0f36 100644 --- a/integration/tests/machine-auth/component.test.ts +++ b/integration/tests/machine-auth/component.test.ts @@ -285,6 +285,43 @@ testAgainstRunningApps({ await u.page.unrouteAll(); }); + test('UserProfile API keys uses user ID as subject even when organization is active', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + const admin = await u.services.users.getUser({ email: fakeAdmin.email }); + expect(admin).toBeDefined(); + const userId = admin.id; + + await u.po.signIn.goTo(); + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeAdmin.email, password: fakeAdmin.password }); + await u.po.expect.toBeSignedIn(); + + await u.po.organizationSwitcher.goTo(); + await u.po.organizationSwitcher.waitForMounted(); + await u.po.organizationSwitcher.waitForAnOrganizationToSelected(); + + let capturedSubject: string | null = null; + const apiKeyRequestPromise = u.page.waitForRequest(request => { + if (request.url().includes('api_keys')) { + const url = new URL(request.url()); + capturedSubject = url.searchParams.get('subject'); + return true; + } + return false; + }); + + await u.po.page.goToRelative('/user'); + await u.po.userProfile.waitForMounted(); + await u.po.userProfile.switchToAPIKeysTab(); + + await apiKeyRequestPromise; + + // Verify the subject parameter is the user ID, not the organization ID + expect(capturedSubject).toBe(userId); + expect(capturedSubject).not.toBe(fakeOrganization.organization.id); + }); + test('standalone API keys component in user context based on user_api_keys_enabled', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); diff --git a/packages/shared/src/react/hooks/useAPIKeys.rq.tsx b/packages/shared/src/react/hooks/useAPIKeys.rq.tsx index 068c139a9c2..7423a4bc59b 100644 --- a/packages/shared/src/react/hooks/useAPIKeys.rq.tsx +++ b/packages/shared/src/react/hooks/useAPIKeys.rq.tsx @@ -95,7 +95,9 @@ export function useAPIKeys(params?: T): UseAPIKeysRe const isEnabled = (safeValues.enabled ?? true) && clerk.loaded; return usePagesOrInfinite({ - fetcher: clerk.apiKeys?.getAll ? (params: GetAPIKeysParams) => clerk.apiKeys.getAll(params) : undefined, + fetcher: clerk.apiKeys?.getAll + ? (params: GetAPIKeysParams) => clerk.apiKeys.getAll({ ...params, subject: safeValues.subject }) + : undefined, config: { keepPreviousData: safeValues.keepPreviousData, infinite: safeValues.infinite, diff --git a/packages/shared/src/react/hooks/useAPIKeys.swr.tsx b/packages/shared/src/react/hooks/useAPIKeys.swr.tsx index dc7037ab621..7780f8e85a1 100644 --- a/packages/shared/src/react/hooks/useAPIKeys.swr.tsx +++ b/packages/shared/src/react/hooks/useAPIKeys.swr.tsx @@ -98,7 +98,9 @@ export function useAPIKeys(params?: T): UseAPIKeysRe const isEnabled = (safeValues.enabled ?? true) && clerk.loaded; const result = usePagesOrInfinite({ - fetcher: clerk.apiKeys?.getAll ? (params: GetAPIKeysParams) => clerk.apiKeys.getAll(params) : undefined, + fetcher: clerk.apiKeys?.getAll + ? (params: GetAPIKeysParams) => clerk.apiKeys.getAll({ ...params, subject: safeValues.subject }) + : undefined, config: { keepPreviousData: safeValues.keepPreviousData, infinite: safeValues.infinite, diff --git a/packages/testing/src/playwright/unstable/page-objects/userProfile.ts b/packages/testing/src/playwright/unstable/page-objects/userProfile.ts index f80b591e7c3..bfc0a442319 100644 --- a/packages/testing/src/playwright/unstable/page-objects/userProfile.ts +++ b/packages/testing/src/playwright/unstable/page-objects/userProfile.ts @@ -17,6 +17,9 @@ export const createUserProfileComponentPageObject = (testArgs: { page: EnhancedP switchToBillingTab: async () => { await page.getByText(/Billing/i).click(); }, + switchToAPIKeysTab: async () => { + await page.getByText(/API keys/i).click(); + }, waitForMounted: () => { return page.waitForSelector('.cl-userProfile-root', { state: 'attached' }); },