Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
35 changes: 35 additions & 0 deletions integration/tests/machine-auth/component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,41 @@ 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();

// Capture the subject parameter
let capturedSubject: string | null = null;
await u.page.route('**/api_keys*', async route => {
const url = new URL(route.request().url());
capturedSubject = url.searchParams.get('subject');
await route.continue();
});

await u.po.page.goToRelative('/user');
await u.po.userProfile.waitForMounted();
await u.po.userProfile.switchToAPIKeysTab();

// Verify the subject parameter is the user ID, not the organization ID
expect(capturedSubject).toBe(userId);
expect(capturedSubject).not.toBe(fakeOrganization.organization.id);

await u.page.unrouteAll();
});

test('standalone API keys component in user context based on user_api_keys_enabled', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });

Expand Down
4 changes: 3 additions & 1 deletion packages/clerk-js/src/ui/components/APIKeys/APIKeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ const _APIKeys = () => {
// Do not use `useOrganization` to avoid triggering the in-app enable organizations prompt in development instance
const organizationCtx = useOrganizationContext();

const subject = organizationCtx?.organization?.id ?? user?.id ?? '';
// Use subject from context if provided (set by UserProfile/OrganizationProfile),
// otherwise fall back to organization ID if active, or user ID
const subject = ctx.subject ?? organizationCtx?.organization?.id ?? user?.id ?? '';

return (
<Flow.Root
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const OrganizationAPIKeysPage = () => {
textVariant='h2'
/>
</Header.Root>
<APIKeysContext.Provider value={{ ...apiKeysProps, componentName: 'APIKeys' }}>
<APIKeysContext.Provider value={{ ...apiKeysProps, componentName: 'APIKeys', subject: organization.id }}>
<APIKeysPage
subject={organization.id}
revokeModalRoot={contentRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const APIKeysPage = () => {
textVariant='h2'
/>
</Header.Root>
<APIKeysContext.Provider value={{ componentName: 'APIKeys', ...apiKeysProps }}>
<APIKeysContext.Provider value={{ componentName: 'APIKeys', ...apiKeysProps, subject: user.id }}>
<APIKeysPageInternal
subject={user.id}
revokeModalRoot={contentRef}
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/types/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2001,6 +2001,10 @@ export type APIKeysProps = {
* @default false
*/
showDescription?: boolean;
/**
* @internal
*/
subject?: string;
};

export type GetAPIKeysParams = ClerkPaginationParams<{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' });
},
Expand Down
Loading