Files
supabase/apps/studio/lib/external-identity-providers.test.ts
T
Alaister YoungandAlaister Young 6073c7a7e7 [FE-4193] fix(studio): show proper names for custom identity providers (#49182)
Unregistered `custom:*` identity providers (e.g. white-label
deployments' own OAuth providers) rendered their raw id — the account
preferences "Sign-in methods" list showed something like `Custom:Acme`
instead of `Acme`. `getProviderDisplay()` now derives a proper
title-cased name from any `custom:*` id, so this works generically for
every custom provider.

**Changed:**
- `getProviderDisplay()` derives a title-cased display name for
unregistered `custom:*` providers (`custom:acme` → "Acme",
`custom:my_provider` → "My Provider"), case-insensitively. Registered
ones (e.g. `custom:openai` → ChatGPT) are unaffected.
- `SignInWithCustom` reuses `getProviderDisplay()` instead of its own
`formatProviderName`, which only stripped a lowercase `custom:` prefix —
the display name also now flows into its error toast.
- Added unit tests for the new fallback branch.

## To test

- On a deployment with a custom provider (or by temporarily hardcoding
an identity with `provider: 'custom:acme'` in `AccountIdentities`),
check `/account/me` → Sign-in methods shows "Acme", not "Custom:Acme"
- Unlink dialog/toast for that identity should also say "Acme"
- Sign-in page with a custom provider configured should show "Continue
with Acme"
- `pnpm vitest run lib/external-identity-providers.test.ts` in
`apps/studio` passes

Addresses [FE-4193](https://linear.app/supabase/issue/FE-4193)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added support for unregistered custom identity providers.
* Custom provider names are now displayed in a clearer, title-cased
format with underscores converted to spaces.
* Matching providers use the SAML icon while preserving their configured
display names.

* **Bug Fixes**
* Improved sign-in error messages and button labels for custom
providers.
  * Provider identifiers are now handled case-insensitively.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-08-18 15:33:04 +08:00

83 lines
3.4 KiB
TypeScript

import { afterEach, describe, expect, test, vi } from 'vitest'
import {
buildProviderAuthRedirect,
getIdentityProviderConfig,
getProviderDisplay,
normalizeIconPath,
} from './external-identity-providers'
describe('external identity providers', () => {
test('normalizes relative icon paths against the dashboard base path', () => {
expect(normalizeIconPath('/img/icons/github-icon.svg')).toBe('/img/icons/github-icon.svg')
expect(normalizeIconPath('img/icons/github-icon.svg')).toBe('/img/icons/github-icon.svg')
expect(normalizeIconPath('https://example.com/icon.svg')).toBe('https://example.com/icon.svg')
})
test('resolves static provider config by provider id or auth provider', () => {
expect(getIdentityProviderConfig('github')?.displayName).toBe('GitHub')
expect(getIdentityProviderConfig('unknown')).toBeUndefined()
})
test('returns display metadata for static, built-in, and fallback providers', () => {
expect(getProviderDisplay('github').displayName).toBe('GitHub')
expect(getProviderDisplay('email').displayName).toBe('Email')
expect(getProviderDisplay('sso:test').displayName).toBe('SSO')
expect(getProviderDisplay('my_provider').displayName).toBe('my provider')
})
test('derives a title-cased name for unregistered custom providers', () => {
expect(getProviderDisplay('custom:acme').displayName).toBe('Acme')
expect(getProviderDisplay('Custom:Acme').displayName).toBe('Acme')
expect(getProviderDisplay('custom:my_provider').displayName).toBe('My Provider')
expect(getProviderDisplay('custom:MY_PROVIDER').displayName).toBe('My Provider')
// registered custom providers keep their configured display name
expect(getProviderDisplay('custom:openai').displayName).toBe('ChatGPT')
})
test('marks static provider icons as monochrome but not built-in or fallback icons', () => {
expect(getProviderDisplay('github').hasMonochromeIcon).toBe(true)
expect(getProviderDisplay('email').hasMonochromeIcon).toBeUndefined()
expect(getProviderDisplay('sso:test').hasMonochromeIcon).toBeUndefined()
})
describe('buildProviderAuthRedirect', () => {
afterEach(() => {
vi.unstubAllEnvs()
})
test('builds the MFA-check URL against the configured site URL', () => {
vi.stubEnv('NEXT_PUBLIC_SITE_URL', 'https://supabase.com/dashboard')
expect(buildProviderAuthRedirect('github')).toBe(
'https://supabase.com/dashboard/sign-in-mfa?method=github'
)
})
test('URL-encodes custom provider ids in the method param', () => {
vi.stubEnv('NEXT_PUBLIC_SITE_URL', 'https://supabase.com/dashboard')
expect(buildProviderAuthRedirect('custom:example')).toBe(
'https://supabase.com/dashboard/sign-in-mfa?method=custom%3Aexample'
)
})
test('appends an encoded returnTo destination when provided', () => {
vi.stubEnv('NEXT_PUBLIC_SITE_URL', 'https://supabase.com/dashboard')
expect(buildProviderAuthRedirect('custom:example', '/account/me')).toBe(
'https://supabase.com/dashboard/sign-in-mfa?method=custom%3Aexample&returnTo=%2Faccount%2Fme'
)
})
test('uses the current origin on Vercel preview deployments', () => {
vi.stubEnv('NEXT_PUBLIC_VERCEL_ENV', 'preview')
vi.stubEnv('NEXT_PUBLIC_SITE_URL', 'https://supabase.com/dashboard')
expect(buildProviderAuthRedirect('github')).toBe(
`${location.origin}/sign-in-mfa?method=github`
)
})
})
})