lib/auth/provider.ts
Rama main · réplica de lectura del repositorio original.
import { betterAuthProvider } from '@/lib/auth/providers/better-auth'
import { noneAuthProvider } from '@/lib/auth/providers/none'
import { supabaseAuthProvider } from '@/lib/auth/providers/supabase'
import type {
AuthCapabilities,
AuthProvider,
AuthProviderName
} from '@/lib/auth/types'
const AUTH_PROVIDER_NAMES: AuthProviderName[] = [
'supabase',
'better-auth',
'none'
]
// Providers register here as they ship.
const providerRegistry: Partial<Record<AuthProviderName, AuthProvider>> = {
supabase: supabaseAuthProvider,
'better-auth': betterAuthProvider,
none: noneAuthProvider
}
function enforceCloudDeploymentGuard(
name: AuthProviderName,
explicitlyConfigured: boolean
): void {
if (process.env.MORPHIC_CLOUD_DEPLOYMENT !== 'true' || name === 'supabase') {
return
}
if (name === 'none' && !explicitlyConfigured) {
// Preserve the historical message for ENABLE_AUTH=false deployments
throw new Error(
'ENABLE_AUTH=false is not allowed in MORPHIC_CLOUD_DEPLOYMENT'
)
}
throw new Error(
`AUTH_PROVIDER=${name} is not allowed in MORPHIC_CLOUD_DEPLOYMENT`
)
}
/**
* Whether sharing is enabled. ENABLE_SHARE is the runtime (non-public)
* flag and works in prebuilt images; NEXT_PUBLIC_ENABLE_SHARE is kept for
* source builds, but Next inlines NEXT_PUBLIC_* at build time even in
* server bundles, so it cannot be flipped at runtime in a published
* image. shareChat enforces the same helper server-side.
*/
export function isShareEnabled(): boolean {
return (
process.env.ENABLE_SHARE === 'true' ||
process.env.NEXT_PUBLIC_ENABLE_SHARE === 'true'
)
}
/**
* Fold the sharing opt-in flag into a provider's capabilities. Must be
* evaluated on the server: client components inline NEXT_PUBLIC_* values
* at build time, which would freeze the setting in prebuilt images.
* shareChat enforces the same rule server-side.
*
* The opt-in applies to Better Auth only: Supabase deployments have
* always shared without an env var, and anonymous mode has no sharing
* capability to fold into.
*/
export function withShareOptIn(
capabilities: AuthCapabilities,
providerName: AuthProviderName
): AuthCapabilities {
if (providerName !== 'better-auth') {
return capabilities
}
return {
...capabilities,
share: capabilities.share && isShareEnabled()
}
}
/**
* Resolve the active auth provider name from configuration.
*
* When `AUTH_PROVIDER` is unset the provider is derived from the existing
* environment so current deployments behave identically:
* - `ENABLE_AUTH=false` -> `none` (anonymous mode)
* - otherwise -> `supabase` (the Supabase provider no-ops to guest behavior
* when Supabase is not configured, exactly as before)
*/
export function resolveAuthProviderName(): AuthProviderName {
const configured = process.env.AUTH_PROVIDER?.trim()
let name: AuthProviderName
if (configured) {
if (!AUTH_PROVIDER_NAMES.includes(configured as AuthProviderName)) {
throw new Error(
`Invalid AUTH_PROVIDER "${configured}". Expected one of: ${AUTH_PROVIDER_NAMES.join(', ')}`
)
}
name = configured as AuthProviderName
} else {
name = process.env.ENABLE_AUTH === 'false' ? 'none' : 'supabase'
}
enforceCloudDeploymentGuard(name, Boolean(configured))
return name
}
/**
* True when the active provider shares a single identity across all visitors
* (anonymous mode). Feature gates must use this instead of reading
* `ENABLE_AUTH` directly, so that `AUTH_PROVIDER=none` and the derived
* `ENABLE_AUTH=false` behavior always agree.
*/
export function isAnonymousMode(): boolean {
return resolveAuthProviderName() === 'none'
}
/** Return the active auth provider implementation. */
export function getAuthProvider(): AuthProvider {
const name = resolveAuthProviderName()
const provider = providerRegistry[name]
if (!provider) {
throw new Error(
`AUTH_PROVIDER=${name} is not available in this version of Morphic.`
)
}
return provider
}