diff --git a/.changeset/quiet-scopes-offline.md b/.changeset/quiet-scopes-offline.md new file mode 100644 index 00000000000..8f2b0ac452b --- /dev/null +++ b/.changeset/quiet-scopes-offline.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +`` and `` no longer show an empty permissions list when an OAuth client requests only the `offline_access` scope. diff --git a/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx b/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx index e85b0229dce..b9f6fcd00cb 100644 --- a/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx +++ b/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx @@ -263,22 +263,24 @@ function _OAuthConsent() { onChange={setSelectedOrg} /> )} - - - - - - {displayedScopes.map(item => ( - - {item.description || item.scope || ''} - - ))} - - + {displayedScopes.length > 0 && ( + + + + + + {displayedScopes.map(item => ( + + {item.description || item.scope || ''} + + ))} + + + )} { await waitFor(() => { expect(getByText('Clerk CLI')).toBeVisible(); + expect(getByText('This will allow Clerk CLI access to:')).toBeVisible(); expect(getByText('View your identity')).toBeVisible(); expect(getByText('Access your email address')).toBeVisible(); }); @@ -126,6 +127,27 @@ describe('OAuthConsent', () => { expect(queryByText('Your private metadata')).toBeNull(); }); + it('hides the scope list when only offline_access is requested', async () => { + const { wrapper, fixtures, props } = await createFixtures(f => { + f.withUser({ email_addresses: ['jane@example.com'] }); + }); + + props.setProps({ componentName: 'OAuthConsent' } as any); + mockOAuthApplication(fixtures.clerk, { + getConsentInfo: vi.fn().mockResolvedValue({ + ...fakeConsentInfo, + scopes: [{ scope: 'offline_access', description: 'Offline access', requiresConsent: true }], + }), + }); + + const { getByText, queryByText } = render(, { wrapper }); + + await waitFor(() => { + expect(getByText(/You'll stay signed in until you sign out or revoke access\./)).toBeVisible(); + }); + expect(queryByText('This will allow Clerk CLI access to:')).toBeNull(); + }); + it('supports localizing the private metadata scope description', async () => { const { wrapper: Wrapper, diff --git a/packages/ui/src/components/OAuthDeviceVerification/OAuthDeviceVerification.tsx b/packages/ui/src/components/OAuthDeviceVerification/OAuthDeviceVerification.tsx index bda29d436b3..54a436e6f15 100644 --- a/packages/ui/src/components/OAuthDeviceVerification/OAuthDeviceVerification.tsx +++ b/packages/ui/src/components/OAuthDeviceVerification/OAuthDeviceVerification.tsx @@ -313,22 +313,24 @@ function OAuthDeviceVerificationInternal() { onChange={setSelectedOrg} /> )} - - - - - - {displayedScopes.map(scope => ( - - {scope.description || scope.scope} - - ))} - - + {displayedScopes.length > 0 && ( + + + + + + {displayedScopes.map(scope => ( + + {scope.description || scope.scope} + + ))} + + + )} { expect(getByText("You'll stay signed in until you sign out or revoke access.")).toBeVisible(); }); + it('hides the scope list when only offline_access is requested', async () => { + Object.defineProperty(window, 'location', { + configurable: true, + value: { ...originalLocation, search: '?user_code=BFWS-ZBZM' }, + }); + const { wrapper } = await setup({ + ...verificationInfo, + scopes: [{ scope: 'offline_access', description: 'Offline access', requiresConsent: true }], + }); + const { getByText, queryByText } = render(, { wrapper }); + + await waitFor(() => expect(getByText('Allow TV App to access your account?')).toBeVisible()); + expect(getByText("You'll stay signed in until you sign out or revoke access.")).toBeVisible(); + expect(queryByText('This will allow TV App access to:')).toBeNull(); + }); + it('keeps an invalid URL prefill editable without calling FAPI', async () => { Object.defineProperty(window, 'location', { configurable: true,