Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 470
chore(clerk-js,shared): Support granular API keys settings#7179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
5364fe8
chore: Use new API keys environment settings
wobsoriano f475e0c
chore: better component guard naming
wobsoriano 402d1f9
chore: clean up component guards
wobsoriano 841257d
test: Add granular settings e2e
wobsoriano 3d40d82
chore: use accurate error code
wobsoriano f2f5eda
chore: use accurate error code
wobsoriano 53447a3
Merge branch 'main' into rob/user-3923-api-keys-updated-env
wobsoriano c83dd6a
chore: add changeset
wobsoriano 0017222
chore: remove deprecated enabled option
wobsoriano cabbef4
chore: apply suggestions
wobsoriano 09a6496
chore: updated changeset
wobsoriano acf15f2
chore: fix unit tests
wobsoriano f302cca
chore: remove unused property
wobsoriano 89f2908
Merge branch 'main' into rob/user-3923-api-keys-updated-env
wobsoriano 24b19d2
chore: fix unit tests
wobsoriano 15f2d15
chore: remove profile based options
wobsoriano 7ec74c1
Merge branch 'main' into rob/user-3923-api-keys-updated-env
wobsoriano 8fcd816
chore: add unit tests for profile based visibility
wobsoriano 396464f
chore: hide nav
wobsoriano 1f08843
chore: clean up
wobsoriano 3742a8a
test: unroute mocked apis
wobsoriano f07c6d5
remove test script
wobsoriano File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| "@clerk/clerk-js": minor | ||
| "@clerk/shared": minor | ||
| --- | ||
| Support granular API keys settings for user and organization profiles |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,32 @@ | ||
| import type { Page } from '@playwright/test'; | ||
| import { expect, test } from '@playwright/test'; | ||
| import { appConfigs } from '../../presets'; | ||
| import type { FakeOrganization, FakeUser } from '../../testUtils'; | ||
| import { createTestUtils, testAgainstRunningApps } from '../../testUtils'; | ||
| const mockAPIKeysEnvironmentSettings = async ( | ||
| page: Page, | ||
| overrides: Partial<{ | ||
| user_api_keys_enabled: boolean; | ||
| orgs_api_keys_enabled: boolean; | ||
| }>, | ||
| ) => { | ||
| await page.route('*/**/v1/environment*', async route => { | ||
| const response = await route.fetch(); | ||
| const json = await response.json(); | ||
| const newJson = { | ||
| ...json, | ||
| api_keys_settings: { | ||
| user_api_keys_enabled: true, | ||
| orgs_api_keys_enabled: true, | ||
| ...overrides, | ||
| }, | ||
| }; | ||
| await route.fulfill({ response, json: newJson }); | ||
| }); | ||
| }; | ||
| testAgainstRunningApps({ | ||
| withEnv: [appConfigs.envs.withAPIKeys], | ||
| withPattern: ['withMachine.next.appRouter'], | ||
| @@ -214,81 +237,111 @@ testAgainstRunningApps({ | ||
| expect(clipboardText).toBe(secret); | ||
| }); | ||
| test('component does not render for orgs when user does not have permissions', async ({ page, context }) => { | ||
| test('UserProfile API keys page visibility', async ({ page, context }) => { | ||
| const u = createTestUtils({ app, page, context }); | ||
| const fakeMember = u.services.users.createFakeUser(); | ||
| const member = await u.services.users.createBapiUser(fakeMember); | ||
| 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.services.clerk.organizations.createOrganizationMembership({ | ||
| organizationId: fakeOrganization.organization.id, | ||
| role: 'org:member', | ||
| userId: member.id, | ||
| }); | ||
| // user_api_keys_enabled: false should hide API keys page | ||
| await mockAPIKeysEnvironmentSettings(u.page, { user_api_keys_enabled: false }); | ||
| await u.po.page.goToRelative('/user'); | ||
| await u.po.userProfile.waitForMounted(); | ||
| await u.po.page.goToRelative('/user#/api-keys'); | ||
| await expect(u.page.locator('.cl-apiKeys')).toBeHidden({ timeout: 2000 }); | ||
| // user_api_keys_enabled: true should show API keys page | ||
| await mockAPIKeysEnvironmentSettings(u.page, { user_api_keys_enabled: true }); | ||
| await page.reload(); | ||
| await u.po.userProfile.waitForMounted(); | ||
| await u.po.page.goToRelative('/user#/api-keys'); | ||
| await expect(u.page.locator('.cl-apiKeys')).toBeVisible({ timeout: 5000 }); | ||
| await u.page.unrouteAll(); | ||
| }); | ||
| test('OrganizationProfile API keys page visibility', async ({ page, context }) => { | ||
| const u = createTestUtils({ app, page, context }); | ||
| 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(); | ||
| // orgs_api_keys_enabled: false should hide API keys page | ||
| await mockAPIKeysEnvironmentSettings(u.page, { orgs_api_keys_enabled: false }); | ||
| await u.po.page.goToRelative('/organization-profile'); | ||
| await u.po.page.goToRelative('/organization-profile#/organization-api-keys'); | ||
| await expect(u.page.locator('.cl-apiKeys')).toBeHidden({ timeout: 2000 }); | ||
| // orgs_api_keys_enabled: true should show API keys page | ||
| await mockAPIKeysEnvironmentSettings(u.page, { orgs_api_keys_enabled: true }); | ||
| await page.reload(); | ||
| await u.po.page.goToRelative('/organization-profile#/organization-api-keys'); | ||
| await expect(u.page.locator('.cl-apiKeys')).toBeVisible({ timeout: 5000 }); | ||
| 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 }); | ||
| await u.po.signIn.goTo(); | ||
| await u.po.signIn.waitForMounted(); | ||
| await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeMember.email, password: fakeMember.password }); | ||
| await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeAdmin.email, password: fakeAdmin.password }); | ||
| await u.po.expect.toBeSignedIn(); | ||
| // user_api_keys_enabled: false should prevent standalone component from rendering | ||
| await mockAPIKeysEnvironmentSettings(u.page, { user_api_keys_enabled: false }); | ||
| let apiKeysRequestWasMade = false; | ||
| u.page.on('request', request => { | ||
| if (request.url().includes('/api_keys')) { | ||
| apiKeysRequestWasMade = true; | ||
| } | ||
| await u.page.route('**/api_keys*', async route => { | ||
| apiKeysRequestWasMade = true; | ||
| await route.abort(); | ||
| }); | ||
| // Check that standalone component is not rendered | ||
| await u.po.page.goToRelative('/api-keys'); | ||
| await expect(u.page.locator('.cl-apiKeys-root')).toBeHidden({ timeout: 1000 }); | ||
| // Check that page is not rendered in OrganizationProfile | ||
| await u.po.page.goToRelative('/organization-profile#/organization-api-keys'); | ||
| await expect(u.page.locator('.cl-apiKeys-root')).toBeHidden({ timeout: 1000 }); | ||
| expect(apiKeysRequestWasMade).toBe(false); | ||
| await fakeMember.deleteIfExists(); | ||
| // user_api_keys_enabled: true should allow standalone component to render | ||
| await mockAPIKeysEnvironmentSettings(u.page, { user_api_keys_enabled: true }); | ||
| await page.reload(); | ||
| await u.po.apiKeys.waitForMounted(); | ||
| await expect(u.page.locator('.cl-apiKeys-root')).toBeVisible(); | ||
| await u.page.unrouteAll(); | ||
| }); | ||
wobsoriano marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| test('user with read permission can view API keys but not manage them', async ({ page, context }) => { | ||
| test('standalone API keys component in org context based on orgs_api_keys_enabled', async ({ page, context }) => { | ||
| const u = createTestUtils({ app, page, context }); | ||
| const fakeViewer = u.services.users.createFakeUser(); | ||
| const viewer = await u.services.users.createBapiUser(fakeViewer); | ||
| await u.services.clerk.organizations.createOrganizationMembership({ | ||
| organizationId: fakeOrganization.organization.id, | ||
| role: 'org:viewer', | ||
| userId: viewer.id, | ||
| }); | ||
| await u.po.signIn.goTo(); | ||
| await u.po.signIn.waitForMounted(); | ||
| await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeViewer.email, password: fakeViewer.password }); | ||
| await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeAdmin.email, password: fakeAdmin.password }); | ||
| await u.po.expect.toBeSignedIn(); | ||
| // orgs_api_keys_enabled: false should prevent standalone component from rendering in org context | ||
| await mockAPIKeysEnvironmentSettings(u.page, { orgs_api_keys_enabled: false }); | ||
| let apiKeysRequestWasMade = false; | ||
| u.page.on('request', request => { | ||
| if (request.url().includes('/api_keys')) { | ||
| apiKeysRequestWasMade = true; | ||
| } | ||
| await u.page.route('**/api_keys*', async route => { | ||
| apiKeysRequestWasMade = true; | ||
| await route.abort(); | ||
| }); | ||
| // Check that standalone component is rendered and user can read API keys | ||
| await u.po.page.goToRelative('/api-keys'); | ||
| await u.po.apiKeys.waitForMounted(); | ||
| await expect(u.page.getByRole('button', { name: /Add new key/i })).toBeHidden(); | ||
| await expect(u.page.getByRole('columnheader', { name: /Actions/i })).toBeHidden(); | ||
| // Check that page is rendered in OrganizationProfile and user can read API keys | ||
| await u.po.page.goToRelative('/organization-profile#/organization-api-keys'); | ||
| await expect(u.page.locator('.cl-apiKeys')).toBeVisible(); | ||
| await expect(u.page.getByRole('button', { name: /Add new key/i })).toBeHidden(); | ||
| await expect(u.page.getByRole('columnheader', { name: /Actions/i })).toBeHidden(); | ||
| await expect(u.page.locator('.cl-apiKeys-root')).toBeHidden({ timeout: 1000 }); | ||
| expect(apiKeysRequestWasMade).toBe(false); | ||
| expect(apiKeysRequestWasMade).toBe(true); | ||
| // orgs_api_keys_enabled: true should allow standalone component to render in org context | ||
| await mockAPIKeysEnvironmentSettings(u.page, { orgs_api_keys_enabled: true }); | ||
| await page.reload(); | ||
| await u.po.apiKeys.waitForMounted(); | ||
| await expect(u.page.locator('.cl-apiKeys-root')).toBeVisible(); | ||
| await fakeViewer.deleteIfExists(); | ||
| await u.page.unrouteAll(); | ||
| }); | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 6 additions & 3 deletions
9 packages/clerk-js/src/core/resources/__tests__/Environment.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 3 additions & 2 deletions
5 packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileNavbar.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 10 additions & 3 deletions
13 packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add explicit return type and JSDoc documentation.
Per coding guidelines, functions should have explicit return types and public/shared functions should have JSDoc documentation.
Apply this diff:
📝 Committable suggestion
🤖 Prompt for AI Agents