Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/quiet-scopes-offline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/ui': patch
---

`<OAuthConsent />` and `<OAuthDeviceVerification />` no longer show an empty permissions list when an OAuth client requests only the `offline_access` scope.
34 changes: 18 additions & 16 deletions packages/ui/src/components/OAuthConsent/OAuthConsent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,22 +263,24 @@ function _OAuthConsent() {
onChange={setSelectedOrg}
/>
)}
<ListGroup>
<ListGroupHeader>
<ListGroupHeaderTitle
localizationKey={localizationKeys('oauthConsent.scopeList.title', {
applicationName: oauthApplicationName,
})}
/>
</ListGroupHeader>
<ListGroupContent>
{displayedScopes.map(item => (
<ListGroupItem key={item.scope}>
<ListGroupItemLabel>{item.description || item.scope || ''}</ListGroupItemLabel>
</ListGroupItem>
))}
</ListGroupContent>
</ListGroup>
{displayedScopes.length > 0 && (
<ListGroup>
<ListGroupHeader>
<ListGroupHeaderTitle
localizationKey={localizationKeys('oauthConsent.scopeList.title', {
applicationName: oauthApplicationName,
})}
/>
</ListGroupHeader>
<ListGroupContent>
{displayedScopes.map(item => (
<ListGroupItem key={item.scope}>
<ListGroupItemLabel>{item.description || item.scope || ''}</ListGroupItemLabel>
</ListGroupItem>
))}
</ListGroupContent>
</ListGroup>
)}
<Alert colorScheme='warning'>
<Text
colorScheme='warning'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ describe('OAuthConsent', () => {

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();
});
Expand Down Expand Up @@ -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(<OAuthConsent />, { 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,22 +313,24 @@ function OAuthDeviceVerificationInternal() {
onChange={setSelectedOrg}
/>
)}
<ListGroup>
<ListGroupHeader>
<ListGroupHeaderTitle
localizationKey={localizationKeys('oauthDeviceVerification.confirmation.scopeListTitle', {
applicationName: data.oauthApplicationName,
})}
/>
</ListGroupHeader>
<ListGroupContent>
{displayedScopes.map(scope => (
<ListGroupItem key={scope.scope}>
<ListGroupItemLabel>{scope.description || scope.scope}</ListGroupItemLabel>
</ListGroupItem>
))}
</ListGroupContent>
</ListGroup>
{displayedScopes.length > 0 && (
<ListGroup>
<ListGroupHeader>
<ListGroupHeaderTitle
localizationKey={localizationKeys('oauthDeviceVerification.confirmation.scopeListTitle', {
applicationName: data.oauthApplicationName,
})}
/>
</ListGroupHeader>
<ListGroupContent>
{displayedScopes.map(scope => (
<ListGroupItem key={scope.scope}>
<ListGroupItemLabel>{scope.description || scope.scope}</ListGroupItemLabel>
</ListGroupItem>
))}
</ListGroupContent>
</ListGroup>
)}
<Alert colorScheme='warning'>
<Text
colorScheme='warning'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,22 @@ describe('OAuthDeviceVerification', () => {
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(<OAuthDeviceVerification />, { 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,
Expand Down
Loading