@better-auth-ui/core
Version:
Authentication components and data utilities for [Better Auth](https://better-auth.com), available for React and Solid.
202 lines (194 loc) • 6.19 kB
text/typescript
import type { AuthClient } from "../lib/auth-client"
import {
type AuthLocale,
defaultAuthLocale,
localizeAuthPlugins
} from "../lib/auth-locale"
import type { AuthPlugin } from "../lib/auth-plugin"
import { type BasePaths, basePaths } from "../lib/base-paths"
import type { DeepPartial } from "../lib/deep-partial"
import { type Localization, localization } from "../lib/localization"
import { deepmerge, resizeAvatar } from "../lib/utils"
import { type ViewPaths, viewPaths } from "../lib/view-paths"
import type {
AdditionalField,
AdditionalFields
} from "./additional-fields-config"
import type { AvatarConfig } from "./avatar-config"
import type { EmailAndPasswordConfig } from "./email-and-password-config"
import type { AuthSocialProvider } from "./social-provider-config"
export type SocialSignInMode = "redirect" | "popup"
/**
* Core authentication configuration interface.
*
* Defines the base structure for authentication settings including paths,
* providers, navigation functions, and feature flags.
*/
export interface AuthConfig<TAuthClient extends AuthClient = AuthClient> {
/**
* The Better Auth client instance used for authentication operations.
*/
authClient: TAuthClient
/**
* Additional user fields rendered on sign-up and the user profile.
* @remarks `AdditionalFields`
*/
additionalFields?: AdditionalFields
/**
* Avatar upload, optimization, and deletion configuration.
* @remarks `AvatarConfig`
* @default { enabled: true, resize: resizeAvatar, size: 256, extension: "png" }
*/
avatar: AvatarConfig
/**
* Base paths for different application sections
* @remarks `BasePaths`
*/
basePaths: BasePaths
/**
* Base URL for API endpoints (optional)
* @default ""
*/
baseURL: string
/**
* Email and password authentication configuration
* @remarks `EmailAndPasswordConfig`
*/
emailAndPassword: EmailAndPasswordConfig
/**
* Localization strings for UI components
* @remarks `Localization`
*/
localization: Localization
/**
* Language metadata and translated messages for all auth components.
* @remarks `AuthLocale`
* @default `defaultAuthLocale`
*/
locale: AuthLocale
/**
* Registered auth plugins. UI packages widen the element type via the
* `AuthPluginRegister` module-augmentation slot.
* @remarks `AuthPlugin[]`
* @default []
*/
plugins: AuthPlugin[]
/**
* Default redirect path after successful authentication
* @default "/"
*/
redirectTo: string
/**
* Allow users to link multiple accounts from the same social provider.
* When false, providers already linked to the account are hidden from the available-to-link list.
* @default true
*/
multipleAccountsPerProvider?: boolean
/**
* Allow the UI to unlink the final sign-in account.
*
* Enable this only when Better Auth's
* `account.accountLinking.allowUnlinkingAll` option is also enabled.
* @default false
*/
allowUnlinkingAllAccounts?: boolean
/**
* List of enabled social authentication providers
* Built-in providers can use their string ID. Custom and Generic OAuth
* providers use `{ id, label, icon }`.
* @remarks `AuthSocialProvider[]`
*/
socialProviders?: AuthSocialProvider[]
/**
* How social sign-in opens the provider flow.
* Popup mode requires Better Auth's experimental `oauthPopupClient` action.
* @default "redirect"
*/
socialSignInMode: SocialSignInMode
/**
* View path mappings for different authentication views
* @remarks `ViewPaths`
*/
viewPaths: ViewPaths
/**
* Function to navigate to a new path
* @param options - Navigation options with href and optional replace flag
* @default window.location.href = href (or window.location.replace if replace: true)
* @example
* // TanStack Router
* navigate={navigate}
* // Next.js
* navigate={({href, replace}) => replace ? router.replace(href) : router.push(href)}
*/
navigate: (options: { to: string; replace?: boolean }) => void
}
export const defaultAuthConfig: Omit<AuthConfig, "authClient"> = {
allowUnlinkingAllAccounts: false,
avatar: {
enabled: true,
resize: resizeAvatar,
size: 256,
extension: "png"
},
basePaths,
baseURL: "",
emailAndPassword: {
enabled: true,
forgotPassword: true,
name: true,
rememberMe: false,
strengthMeter: true,
minPasswordLength: 8,
maxPasswordLength: 128
},
plugins: [],
redirectTo: "/",
socialSignInMode: "redirect",
viewPaths,
localization,
locale: defaultAuthLocale,
navigate: ({ to, replace }) => {
if (replace) {
window.location.replace(to)
} else {
window.location.href = to
}
}
}
export type AuthConfigOptions<TAuthClient extends AuthClient = AuthClient> =
DeepPartial<Omit<AuthConfig<TAuthClient>, "authClient" | "locale">> & {
authClient: TAuthClient
/** Imported locale bundle. Defaults to English (`en-US`). */
locale?: AuthLocale
}
/** Resolves defaults, locale messages, plugin messages, and consumer overrides. */
export function resolveAuthConfig<TAuthClient extends AuthClient = AuthClient>(
options: AuthConfigOptions<TAuthClient>
): AuthConfig<TAuthClient> {
const locale = options.locale ?? defaultAuthLocale
const resolvedLocalization = deepmerge(
deepmerge(localization, locale.localization),
options.localization ?? {}
)
const plugins = localizeAuthPlugins(options.plugins ?? [], locale)
const mergedConfig = deepmerge<Omit<AuthConfig<TAuthClient>, "authClient">>(
defaultAuthConfig as Omit<AuthConfig<TAuthClient>, "authClient">,
{
...options,
locale,
localization: resolvedLocalization,
plugins
}
)
const fieldsByName = new Map<string, AdditionalField>()
for (const plugin of plugins) {
for (const field of plugin.additionalFields ?? []) {
fieldsByName.set(field.name, field)
}
}
for (const field of mergedConfig.additionalFields ?? []) {
fieldsByName.set(field.name, field)
}
mergedConfig.additionalFields = Array.from(fieldsByName.values())
return { ...mergedConfig, authClient: options.authClient }
}