Files
supabase/apps/studio/lib/external-identity-providers.ts
Cemal Kılıç 3667601895 feat(studio): add sign in with ChatGPT (#47772)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Feature

## Summary
Introduce a "Sign in with ChatGPT" option gated by the new
`dashboard_auth:sign_in_with_chatgpt` feature flag and a manual
localStorage rollout switch (`SIGN_IN_CHATGPT_ENABLED`), since the
feature is still WIP.

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

## Summary by CodeRabbit

* **New Features**
  * Added support for signing in with ChatGPT alongside GitHub.
* ChatGPT sign-in now depends on both a feature flag and an additional
rollout setting.
* Updated provider availability so the app can show the correct sign-in
options.

* **Bug Fixes**
* Improved validation and coverage to ensure sign-in options appear only
when fully enabled.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-09 16:32:31 +02:00

130 lines
3.9 KiB
TypeScript

import { BASE_PATH } from './constants'
export type ExternalIdentityProviderConfig = {
id: string
authProvider: string
displayName: string
iconPath: string
scopes?: string
showOnSignIn: boolean
showOnSignUp: boolean
showInAccountPreferences: boolean
}
export type IdentityProviderDisplay = {
id: string
displayName: string
iconPath: string
/** The icon is a single-color mark that should be tinted to the theme's foreground color. */
hasMonochromeIcon?: boolean
}
const BUILT_IN_IDENTITY_PROVIDERS: Record<string, IdentityProviderDisplay> = {
email: {
id: 'email',
displayName: 'Email',
iconPath: `${BASE_PATH}/img/icons/email-icon2.svg`,
},
}
// Statically supported identity providers. To add a new one, declare its config here, gate its
// visibility behind a `dashboard_auth:sign_in_with_*` feature flag in `useEnabledIdentityProviders`,
// and add the matching flag to `packages/common/enabled-features/enabled-features.json`.
export const GITHUB_IDENTITY_PROVIDER: ExternalIdentityProviderConfig = {
id: 'github',
authProvider: 'github',
displayName: 'GitHub',
iconPath: '/img/icons/github-icon.svg',
showOnSignIn: true,
showOnSignUp: true,
showInAccountPreferences: false,
}
export const CHATGPT_IDENTITY_PROVIDER: ExternalIdentityProviderConfig = {
id: 'chatgpt',
authProvider: 'custom:openai',
displayName: 'ChatGPT',
iconPath: '/img/icons/openai-icon.svg',
showOnSignIn: true,
showOnSignUp: true,
showInAccountPreferences: false,
}
// Registry of every known provider, independent of which are currently enabled. Used for config and
// display lookups (e.g. resolving the provider that a mid-flow interstitial was reached with).
const IDENTITY_PROVIDERS: ExternalIdentityProviderConfig[] = [
GITHUB_IDENTITY_PROVIDER,
CHATGPT_IDENTITY_PROVIDER,
]
export function normalizeIconPath(iconPath: string): string {
if (
iconPath.startsWith('http://') ||
iconPath.startsWith('https://') ||
iconPath.startsWith('/')
) {
return iconPath.startsWith('/') ? `${BASE_PATH}${iconPath}` : iconPath
}
return `${BASE_PATH}/${iconPath}`
}
export function getProviderDisplay(provider: string): IdentityProviderDisplay {
const config = IDENTITY_PROVIDERS.find(
({ id, authProvider }) => provider === id || provider === authProvider
)
if (config) {
return {
id: config.id,
displayName: config.displayName,
iconPath: normalizeIconPath(config.iconPath),
hasMonochromeIcon: true,
}
}
if (provider.startsWith('sso')) {
return {
id: provider,
displayName: 'SSO',
iconPath: `${BASE_PATH}/img/icons/saml-icon.svg`,
}
}
return (
BUILT_IN_IDENTITY_PROVIDERS[provider] ?? {
id: provider,
displayName: provider.replaceAll('_', ' '),
iconPath: `${BASE_PATH}/img/icons/saml-icon.svg`,
}
)
}
/**
* Builds the absolute URL an external provider's OAuth flow redirects back to: the MFA-check page
* (`/sign-in-mfa`), tagged with the provider id as the sign-in method and an optional `returnTo`
* destination. Callers should pass the result through `buildPathWithParams` to preserve the current
* location's search params across the OAuth round-trip.
*/
export function buildProviderAuthRedirect(providerId: string, returnTo?: string): string {
const origin =
typeof window !== 'undefined' && process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview'
? window.location.origin
: process.env.NEXT_PUBLIC_SITE_URL
const params = new URLSearchParams({ method: providerId })
if (returnTo) params.set('returnTo', returnTo)
return `${origin}${BASE_PATH}/sign-in-mfa?${params.toString()}`
}
export function getIdentityProviderConfig(
provider: string | undefined
): ExternalIdentityProviderConfig | undefined {
if (!provider) return undefined
return IDENTITY_PROVIDERS.find(
({ id, authProvider }) => provider === id || provider === authProvider
)
}