Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/agentid-oauth-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/shared': minor
'@clerk/ui': patch
---

Add `agentid` to `OAuthProvider` and `OAUTH_PROVIDERS` to support the "Continue with AgentID" OAuth flow. `<ProviderIcon />` renders the AgentID mark via mask-image so the icon follows the foreground color.
6 changes: 6 additions & 0 deletions packages/shared/src/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ export const OAUTH_PROVIDERS: OAuthProviderData[] = [
name: 'Vercel',
docsUrl: 'https://clerk.com/docs/authentication/social-connections/vercel',
},
{
provider: 'agentid',
strategy: 'oauth_agentid',
name: 'AgentID',
docsUrl: 'https://www.agentid.com/docs/clerk',
},
];

interface getOAuthProviderDataProps {
Expand Down
3 changes: 3 additions & 0 deletions packages/shared/src/types/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export type HuggingfaceOAuthProvider = 'huggingface';
/** @inline */
export type VercelOauthProvider = 'vercel';
/** @inline */
export type AgentIDOauthProvider = 'agentid';
/** @inline */
export type CustomOauthProvider = `custom_${string}`;

/** Represents the available OAuth providers. */
Expand Down Expand Up @@ -101,4 +103,5 @@ export type OAuthProvider =
| EnstallOauthProvider
| HuggingfaceOAuthProvider
| VercelOauthProvider
| AgentIDOauthProvider
| CustomOauthProvider;
2 changes: 1 addition & 1 deletion packages/ui/src/common/ProviderIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ProviderInitialIcon } from './ProviderInitialIcon';

type ProviderId = OAuthProvider | Web3Provider | PhoneCodeChannel;

const SUPPORTS_MASK_IMAGE = ['apple', 'github', 'okx_wallet', 'vercel', 'x'] as const;
export const SUPPORTS_MASK_IMAGE = ['agentid', 'apple', 'github', 'okx_wallet', 'vercel', 'x'] as const;

const supportsMaskImage = (id: ProviderId): boolean => {
return (SUPPORTS_MASK_IMAGE as readonly string[]).includes(id);
Expand Down
91 changes: 15 additions & 76 deletions packages/ui/src/common/__tests__/ProviderIcon.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest';
import { bindCreateFixtures } from '@/test/create-fixtures';
import { render, screen } from '@/test/utils';

import { ProviderIcon } from '../ProviderIcon';
import { ProviderIcon, SUPPORTS_MASK_IMAGE } from '../ProviderIcon';

const { createFixtures } = bindCreateFixtures('SignIn');

Expand Down Expand Up @@ -43,94 +43,29 @@ describe('ProviderIcon', () => {
expect(icon).toBeInTheDocument();
});

it('applies mask-image styles for supported providers (apple)', async () => {
const { wrapper } = await createFixtures();

render(
<ProviderIcon
id='apple'
iconUrl='https://example.com/apple-icon.svg'
name='Apple'
/>,
{ wrapper },
);

const icon = screen.getByLabelText('Apple icon');

// Check that mask-image is applied (via inline styles)
expect(icon).toHaveStyle({
display: 'inline-block',
});
});

it('applies mask-image styles for supported providers (github)', async () => {
const { wrapper } = await createFixtures();

render(
<ProviderIcon
id='github'
iconUrl='https://example.com/github-icon.svg'
name='GitHub'
/>,
{ wrapper },
);

const icon = screen.getByLabelText('GitHub icon');
expect(icon).toBeInTheDocument();
it('keeps the mask-image provider list in sync', () => {
expect(SUPPORTS_MASK_IMAGE).toEqual(['agentid', 'apple', 'github', 'okx_wallet', 'vercel', 'x']);
});

it('applies mask-image styles for supported providers (okx_wallet)', async () => {
it.each(SUPPORTS_MASK_IMAGE)('tints supported provider %s with the foreground color via mask-image', async id => {
const { wrapper } = await createFixtures();
const iconUrl = `https://example.com/${id}-icon.svg`;

render(
<ProviderIcon
id='okx_wallet'
iconUrl='https://example.com/okx-icon.svg'
name='OKX Wallet'
id={id}
iconUrl={iconUrl}
name={id}
/>,
{ wrapper },
);

const icon = screen.getByLabelText('OKX Wallet icon');
const icon = screen.getByLabelText(`${id} icon`);
expect(icon).toBeInTheDocument();
});

it('applies mask-image styles for supported providers (x)', async () => {
const { wrapper } = await createFixtures();

render(
<ProviderIcon
id='x'
iconUrl='https://example.com/x-icon.svg'
name='X / Twitter'
/>,
{ wrapper },
);

const icon = screen.getByLabelText('X / Twitter icon');
expect(icon).toBeInTheDocument();

// The mask-image path tints the icon with the foreground color so it stays
// visible in dark mode, instead of painting the raw (black) SVG as a background.
const styles = window.getComputedStyle(icon);
expect(styles.maskImage).toContain('https://example.com/x-icon.svg');
expect(styles.backgroundImage).not.toContain('https://example.com/x-icon.svg');
});

it('applies mask-image styles for supported providers (vercel)', async () => {
const { wrapper } = await createFixtures();

render(
<ProviderIcon
id='vercel'
iconUrl='https://example.com/vercel-icon.svg'
name='Vercel'
/>,
{ wrapper },
);

const icon = screen.getByLabelText('Vercel icon');
expect(icon).toBeInTheDocument();
expect(styles.maskImage).toContain(iconUrl);
expect(styles.backgroundImage).not.toContain(iconUrl);
});

it('applies background-image styles for non-mask-image providers', async () => {
Expand All @@ -147,6 +82,10 @@ describe('ProviderIcon', () => {

const icon = screen.getByLabelText('Google icon');
expect(icon).toBeInTheDocument();

const styles = window.getComputedStyle(icon);
expect(styles.backgroundImage).toContain('https://example.com/google-icon.svg');
expect(styles.maskImage).not.toContain('https://example.com/google-icon.svg');
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('useEnabledThirdPartyProviders', () => {
const { authenticatableOauthStrategies } = result.current;

expect(authenticatableOauthStrategies).toStrictEqual([
'oauth_agentid',
'oauth_apple',
'oauth_atlassian',
'oauth_bitbucket',
Expand Down
Loading