UNPKG

better-auth

Version:

The most comprehensive authentication library for TypeScript.

1,488 lines (1,471 loc) 728 kB
import { Migration, PostgresPool, MysqlPool, Dialect, Kysely } from 'kysely'; import * as better_call from 'better-call'; import { EndpointContext, InputContext, CookieOptions, Endpoint, Middleware } from 'better-call'; import * as z from 'zod/v4'; import { ZodType } from 'zod/v4'; import { b as LiteralUnion, L as LiteralString, D as DeepPartial, U as UnionToIntersection, a as Prettify, S as StripEmptyObjects, O as OmitId, P as PrettifyDeep, E as Expand } from './better-auth.DTtXpZYr.cjs'; import { b as OAuthProvider, S as SocialProviders, c as SocialProviderList, O as OAuth2Tokens, a as OAuth2UserInfo } from './better-auth.BaMSx6K3.cjs'; import * as zod_v4_core from 'zod/v4/core'; import * as zod from 'zod'; import { Database } from 'better-sqlite3'; import { Database as Database$1 } from 'bun:sqlite'; import { DatabaseSync } from 'node:sqlite'; type HookEndpointContext = EndpointContext<string, any> & Omit<InputContext<string, any>, "method"> & { context: AuthContext & { returned?: unknown; responseHeaders?: Headers; }; headers?: Headers; }; type GenericEndpointContext = EndpointContext<string, any> & { context: AuthContext; }; interface CookieAttributes { value: string; "max-age"?: number; expires?: Date; domain?: string; path?: string; secure?: boolean; httponly?: boolean; samesite?: "strict" | "lax" | "none"; [key: string]: any; } declare function parseSetCookieHeader(setCookie: string): Map<string, CookieAttributes>; declare function setCookieToHeader(headers: Headers): (context: { response: Response; }) => void; declare function createCookieGetter(options: BetterAuthOptions): (cookieName: string, overrideAttributes?: Partial<CookieOptions>) => { name: string; attributes: CookieOptions; }; declare function getCookies(options: BetterAuthOptions): { sessionToken: { name: string; options: CookieOptions; }; /** * This cookie is used to store the session data in the cookie * This is useful for when you want to cache the session in the cookie */ sessionData: { name: string; options: CookieOptions; }; dontRememberToken: { name: string; options: CookieOptions; }; }; type BetterAuthCookies = ReturnType<typeof getCookies>; declare function setCookieCache(ctx: GenericEndpointContext, session: { session: Session & Record<string, any>; user: User; }): Promise<void>; declare function setSessionCookie(ctx: GenericEndpointContext, session: { session: Session & Record<string, any>; user: User; }, dontRememberMe?: boolean, overrides?: Partial<CookieOptions>): Promise<void>; declare function deleteSessionCookie(ctx: GenericEndpointContext, skipDontRememberMe?: boolean): void; declare function parseCookies(cookieHeader: string): Map<string, string>; type EligibleCookies = (string & {}) | (keyof BetterAuthCookies & {}); declare const getSessionCookie: (request: Request | Headers, config?: { cookiePrefix?: string; cookieName?: string; path?: string; }) => string | null; declare const getCookieCache: <S extends { session: Session & Record<string, any>; user: User & Record<string, any>; }>(request: Request | Headers, config?: { cookiePrefix?: string; cookieName?: string; isSecure?: boolean; secret?: string; }) => Promise<S | null>; type LogLevel = "info" | "success" | "warn" | "error" | "debug"; declare const levels: readonly ["info", "success", "warn", "error", "debug"]; declare function shouldPublishLog(currentLogLevel: LogLevel, logLevel: LogLevel): boolean; interface Logger { disabled?: boolean; level?: Exclude<LogLevel, "success">; log?: (level: Exclude<LogLevel, "success">, message: string, ...args: any[]) => void; } type LogHandlerParams = Parameters<NonNullable<Logger["log"]>> extends [ LogLevel, ...infer Rest ] ? Rest : never; type InternalLogger = { [K in LogLevel]: (...params: LogHandlerParams) => void; } & { get level(): LogLevel; }; declare const createLogger: (options?: Logger) => InternalLogger; declare const logger: InternalLogger; declare function checkPassword(userId: string, c: GenericEndpointContext): Promise<boolean>; interface TelemetryEvent { type: string; anonymousId?: string; payload: Record<string, any>; } interface TelemetryContext { customTrack?: (event: TelemetryEvent) => Promise<void>; database?: string; adapter?: string; skipTestCheck?: boolean; } declare const init: (options: BetterAuthOptions) => Promise<AuthContext>; type AuthContext = { options: BetterAuthOptions; appName: string; baseURL: string; trustedOrigins: string[]; /** * New session that will be set after the request * meaning: there is a `set-cookie` header that will set * the session cookie. This is the fetched session. And it's set * by `setNewSession` method. */ newSession: { session: Session & Record<string, any>; user: User & Record<string, any>; } | null; session: { session: Session & Record<string, any>; user: User & Record<string, any>; } | null; setNewSession: (session: { session: Session & Record<string, any>; user: User & Record<string, any>; } | null) => void; socialProviders: OAuthProvider[]; authCookies: BetterAuthCookies; logger: ReturnType<typeof createLogger>; rateLimit: { enabled: boolean; window: number; max: number; storage: "memory" | "database" | "secondary-storage"; } & BetterAuthOptions["rateLimit"]; adapter: Adapter; internalAdapter: ReturnType<typeof createInternalAdapter>; createAuthCookie: ReturnType<typeof createCookieGetter>; secret: string; sessionConfig: { updateAge: number; expiresIn: number; freshAge: number; }; generateId: (options: { model: LiteralUnion<Models, string>; size?: number; }) => string | false; secondaryStorage: SecondaryStorage | undefined; password: { hash: (password: string) => Promise<string>; verify: (data: { password: string; hash: string; }) => Promise<boolean>; config: { minPasswordLength: number; maxPasswordLength: number; }; checkPassword: typeof checkPassword; }; tables: BetterAuthDbSchema; runMigrations: () => Promise<void>; publishTelemetry: (event: TelemetryEvent) => Promise<void>; }; declare const optionsMiddleware: <InputCtx extends better_call.MiddlewareInputContext<better_call.MiddlewareOptions>>(inputContext: InputCtx) => Promise<AuthContext>; declare const createAuthMiddleware: { <Options extends better_call.MiddlewareOptions, R>(options: Options, handler: (ctx: better_call.MiddlewareContext<Options, AuthContext & { returned?: unknown; responseHeaders?: Headers; }>) => Promise<R>): (inputContext: better_call.MiddlewareInputContext<Options>) => Promise<R>; <Options extends better_call.MiddlewareOptions, R_1>(handler: (ctx: better_call.MiddlewareContext<Options, AuthContext & { returned?: unknown; responseHeaders?: Headers; }>) => Promise<R_1>): (inputContext: better_call.MiddlewareInputContext<Options>) => Promise<R_1>; }; declare const createAuthEndpoint: <Path extends string, Opts extends better_call.EndpointOptions, R>(path: Path, options: Opts, handler: (ctx: better_call.EndpointContext<Path, Opts, AuthContext>) => Promise<R>) => { <AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(...inputCtx: better_call.HasRequiredKeys<better_call.InputContext<Path, Opts & { use: any[]; }>> extends true ? [better_call.InferBodyInput<Opts & { use: any[]; }, (Opts & { use: any[]; })["metadata"] extends { $Infer: { body: infer B; }; } ? B : (Opts & { use: any[]; })["body"] extends better_call.StandardSchemaV1<unknown, unknown> ? better_call.StandardSchemaV1.InferInput<(Opts & { use: any[]; })["body"]> : undefined> & better_call.InferInputMethod<Opts & { use: any[]; }, (Opts & { use: any[]; })["method"] extends any[] ? (Opts & { use: any[]; })["method"][number] : (Opts & { use: any[]; })["method"] extends "*" ? better_call.HTTPMethod : (Opts & { use: any[]; })["method"] | undefined> & better_call.InferQueryInput<Opts & { use: any[]; }, (Opts & { use: any[]; })["metadata"] extends { $Infer: { query: infer Query; }; } ? Query : (Opts & { use: any[]; })["query"] extends better_call.StandardSchemaV1<unknown, unknown> ? better_call.StandardSchemaV1.InferInput<(Opts & { use: any[]; })["query"]> : Record<string, any> | undefined> & better_call.InferParamInput<Path> & better_call.InferRequestInput<Opts & { use: any[]; }> & better_call.InferHeadersInput<Opts & { use: any[]; }> & { asResponse?: boolean; returnHeaders?: boolean; use?: better_call.Middleware[]; path?: string; } & { asResponse?: AsResponse | undefined; returnHeaders?: ReturnHeaders | undefined; }] : [((better_call.InferBodyInput<Opts & { use: any[]; }, (Opts & { use: any[]; })["metadata"] extends { $Infer: { body: infer B_1; }; } ? B_1 : (Opts & { use: any[]; })["body"] extends better_call.StandardSchemaV1<unknown, unknown> ? better_call.StandardSchemaV1.InferInput<(Opts & { use: any[]; })["body"]> : undefined> & better_call.InferInputMethod<Opts & { use: any[]; }, (Opts & { use: any[]; })["method"] extends any[] ? (Opts & { use: any[]; })["method"][number] : (Opts & { use: any[]; })["method"] extends "*" ? better_call.HTTPMethod : (Opts & { use: any[]; })["method"] | undefined> & better_call.InferQueryInput<Opts & { use: any[]; }, (Opts & { use: any[]; })["metadata"] extends { $Infer: { query: infer Query_1; }; } ? Query_1 : (Opts & { use: any[]; })["query"] extends better_call.StandardSchemaV1<unknown, unknown> ? better_call.StandardSchemaV1.InferInput<(Opts & { use: any[]; })["query"]> : Record<string, any> | undefined> & better_call.InferParamInput<Path> & better_call.InferRequestInput<Opts & { use: any[]; }> & better_call.InferHeadersInput<Opts & { use: any[]; }> & { asResponse?: boolean; returnHeaders?: boolean; use?: better_call.Middleware[]; path?: string; } & { asResponse?: AsResponse | undefined; returnHeaders?: ReturnHeaders | undefined; }) | undefined)?]): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? { headers: Headers; response: R; } : R>; options: Opts & { use: any[]; }; path: Path; }; type AuthEndpoint = ReturnType<typeof createAuthEndpoint>; type AuthMiddleware = ReturnType<typeof createAuthMiddleware>; type FieldType = "string" | "number" | "boolean" | "date" | "json" | `${"string" | "number"}[]` | Array<LiteralString>; type Primitive = string | number | boolean | Date | null | undefined | string[] | number[]; type FieldAttributeConfig<T extends FieldType = FieldType> = { /** * If the field should be required on a new record. * @default true */ required?: boolean; /** * If the value should be returned on a response body. * @default true */ returned?: boolean; /** * If a value should be provided when creating a new record. * @default true */ input?: boolean; /** * Default value for the field * * Note: This will not create a default value on the database level. It will only * be used when creating a new record. */ defaultValue?: Primitive | (() => Primitive); /** * Update value for the field * * Note: This will create an onUpdate trigger on the database level for supported adapters. * It will be called when updating a record. */ onUpdate?: () => Primitive; /** * transform the value before storing it. */ transform?: { input?: (value: Primitive) => Primitive | Promise<Primitive>; output?: (value: Primitive) => Primitive | Promise<Primitive>; }; /** * Reference to another model. */ references?: { /** * The model to reference. */ model: string; /** * The field on the referenced model. */ field: string; /** * The action to perform when the reference is deleted. * @default "cascade" */ onDelete?: "no action" | "restrict" | "cascade" | "set null" | "set default"; }; unique?: boolean; /** * If the field should be a bigint on the database instead of integer. */ bigint?: boolean; /** * A zod schema to validate the value. */ validator?: { input?: ZodType; output?: ZodType; }; /** * The name of the field on the database. */ fieldName?: string; /** * If the field should be sortable. * * applicable only for `text` type. * It's useful to mark fields varchar instead of text. */ sortable?: boolean; }; type FieldAttribute<T extends FieldType = FieldType> = { type: T; } & FieldAttributeConfig<T>; declare const createFieldAttribute: <T extends FieldType, C extends Omit<FieldAttributeConfig<T>, "type">>(type: T, config?: C) => { bigint?: boolean; input?: boolean; transform?: { input?: (value: Primitive) => Primitive | Promise<Primitive>; output?: (value: Primitive) => Primitive | Promise<Primitive>; }; returned?: boolean; required?: boolean; defaultValue?: Primitive | (() => Primitive); onUpdate?: () => Primitive; references?: { /** * The model to reference. */ model: string; /** * The field on the referenced model. */ field: string; /** * The action to perform when the reference is deleted. * @default "cascade" */ onDelete?: "no action" | "restrict" | "cascade" | "set null" | "set default"; }; unique?: boolean; validator?: { input?: ZodType; output?: ZodType; }; fieldName?: string; sortable?: boolean; type: T; }; type InferValueType<T extends FieldType> = T extends "string" ? string : T extends "number" ? number : T extends "boolean" ? boolean : T extends "date" ? Date : T extends `${infer T}[]` ? T extends "string" ? string[] : number[] : T extends Array<any> ? T[number] : never; type InferFieldsOutput<Field> = Field extends Record<infer Key, FieldAttribute> ? { [key in Key as Field[key]["required"] extends false ? Field[key]["defaultValue"] extends boolean | string | number | Date ? key : never : key]: InferFieldOutput<Field[key]>; } & { [key in Key as Field[key]["returned"] extends false ? never : key]?: InferFieldOutput<Field[key]> | null; } : {}; type InferFieldsInput<Field> = Field extends Record<infer Key, FieldAttribute> ? { [key in Key as Field[key]["required"] extends false ? never : Field[key]["defaultValue"] extends string | number | boolean | Date ? never : Field[key]["input"] extends false ? never : key]: InferFieldInput<Field[key]>; } & { [key in Key as Field[key]["input"] extends false ? never : key]?: InferFieldInput<Field[key]> | undefined | null; } : {}; /** * For client will add "?" on optional fields */ type InferFieldsInputClient<Field> = Field extends Record<infer Key, FieldAttribute> ? { [key in Key as Field[key]["required"] extends false ? never : Field[key]["defaultValue"] extends string | number | boolean | Date ? never : Field[key]["input"] extends false ? never : key]: InferFieldInput<Field[key]>; } & { [key in Key as Field[key]["input"] extends false ? never : Field[key]["required"] extends false ? key : Field[key]["defaultValue"] extends string | number | boolean | Date ? key : never]?: InferFieldInput<Field[key]> | undefined | null; } : {}; type InferFieldOutput<T extends FieldAttribute> = T["returned"] extends false ? never : T["required"] extends false ? InferValueType<T["type"]> | undefined | null : InferValueType<T["type"]>; /** * Converts a Record<string, FieldAttribute> to an object type * with keys and value types inferred from FieldAttribute["type"]. */ type FieldAttributeToObject<Fields extends Record<string, FieldAttribute>> = AddOptionalFields<{ [K in keyof Fields]: InferValueType<Fields[K]["type"]>; }, Fields>; type AddOptionalFields<T extends Record<string, any>, Fields extends Record<keyof T, FieldAttribute>> = { [K in keyof T as Fields[K] extends { required: true; } ? K : never]: T[K]; } & { [K in keyof T as Fields[K] extends { required: true; } ? never : K]?: T[K]; }; /** * Infer the additional fields from the plugin options. * For example, you can infer the additional fields of the org plugin's organization schema like this: * ```ts * type AdditionalFields = InferAdditionalFieldsFromPluginOptions<"organization", OrganizationOptions> * ``` */ type InferAdditionalFieldsFromPluginOptions<SchemaName extends string, Options extends { schema?: { [key in SchemaName]?: { additionalFields?: Record<string, FieldAttribute>; }; }; }, isClientSide extends boolean = true> = Options["schema"] extends { [key in SchemaName]?: { additionalFields: infer Field extends Record<string, FieldAttribute>; }; } ? isClientSide extends true ? FieldAttributeToObject<RemoveFieldsWithInputFalse<Field>> : FieldAttributeToObject<Field> : {}; type RemoveFieldsWithInputFalse<T extends Record<string, FieldAttribute>> = { [K in keyof T as T[K]["input"] extends false ? never : K]: T[K]; }; type InferFieldInput<T extends FieldAttribute> = InferValueType<T["type"]>; type PluginFieldAttribute = Omit<FieldAttribute, "transform" | "defaultValue" | "hashValue">; type InferFieldsFromPlugins<Options extends BetterAuthOptions, Key extends string, Format extends "output" | "input" = "output"> = Options["plugins"] extends Array<infer T> ? T extends { schema: { [key in Key]: { fields: infer Field; }; }; } ? Format extends "output" ? InferFieldsOutput<Field> : InferFieldsInput<Field> : {} : {}; type InferFieldsFromOptions<Options extends BetterAuthOptions, Key extends "session" | "user", Format extends "output" | "input" = "output"> = Options[Key] extends { additionalFields: infer Field; } ? Format extends "output" ? InferFieldsOutput<Field> : InferFieldsInput<Field> : {}; type AuthPluginSchema = { [table in string]: { fields: { [field in string]: FieldAttribute; }; disableMigration?: boolean; modelName?: string; }; }; type BetterAuthPlugin = { id: LiteralString; /** * The init function is called when the plugin is initialized. * You can return a new context or modify the existing context. */ init?: (ctx: AuthContext) => { context?: DeepPartial<Omit<AuthContext, "options">>; options?: Partial<BetterAuthOptions>; } | void; endpoints?: { [key: string]: Endpoint; }; middlewares?: { path: string; middleware: Middleware; }[]; onRequest?: (request: Request, ctx: AuthContext) => Promise<{ response: Response; } | { request: Request; } | void>; onResponse?: (response: Response, ctx: AuthContext) => Promise<{ response: Response; } | void>; hooks?: { before?: { matcher: (context: HookEndpointContext) => boolean; handler: AuthMiddleware; }[]; after?: { matcher: (context: HookEndpointContext) => boolean; handler: AuthMiddleware; }[]; }; /** * Schema the plugin needs * * This will also be used to migrate the database. If the fields are dynamic from the plugins * configuration each time the configuration is changed a new migration will be created. * * NOTE: If you want to create migrations manually using * migrations option or any other way you * can disable migration per table basis. * * @example * ```ts * schema: { * user: { * fields: { * email: { * type: "string", * }, * emailVerified: { * type: "boolean", * defaultValue: false, * }, * }, * } * } as AuthPluginSchema * ``` */ schema?: AuthPluginSchema; /** * The migrations of the plugin. If you define schema that will automatically create * migrations for you. * * ⚠️ Only uses this if you dont't want to use the schema option and you disabled migrations for * the tables. */ migrations?: Record<string, Migration>; /** * The options of the plugin */ options?: Record<string, any> | undefined; /** * types to be inferred */ $Infer?: Record<string, any>; /** * The rate limit rules to apply to specific paths. */ rateLimit?: { window: number; max: number; pathMatcher: (path: string) => boolean; }[]; /** * The error codes returned by the plugin */ $ERROR_CODES?: Record<string, string>; }; type InferOptionSchema<S extends AuthPluginSchema> = S extends Record<string, { fields: infer Fields; }> ? { [K in keyof S]?: { modelName?: string; fields?: { [P in keyof Fields]?: string; }; }; } : never; type InferPluginErrorCodes<O extends BetterAuthOptions> = O["plugins"] extends Array<infer P> ? UnionToIntersection<P extends BetterAuthPlugin ? P["$ERROR_CODES"] extends Record<string, any> ? P["$ERROR_CODES"] : {} : {}> : {}; type BetterAuthDbSchema = Record<string, { /** * The name of the table in the database */ modelName: string; /** * The fields of the table */ fields: Record<string, FieldAttribute>; /** * Whether to disable migrations for this table * @default false */ disableMigrations?: boolean; /** * The order of the table */ order?: number; }>; declare const getAuthTables: (options: BetterAuthOptions) => BetterAuthDbSchema; type AdapterDebugLogs = boolean | { /** * Useful when you want to log only certain conditions. */ logCondition?: (() => boolean) | undefined; create?: boolean; update?: boolean; updateMany?: boolean; findOne?: boolean; findMany?: boolean; delete?: boolean; deleteMany?: boolean; count?: boolean; } | { /** * Only used for adapter tests to show debug logs if a test fails. * * @deprecated Not actually deprecated. Doing this for IDEs to show this option at the very bottom and stop end-users from using this. */ isRunningAdapterTests: boolean; }; interface AdapterConfig { /** * Use plural table names. * * All tables will be named with an `s` at the end. * * @default false */ usePlural?: boolean; /** * Enable debug logs. * * @default false */ debugLogs?: AdapterDebugLogs; /** * Name of the adapter. * * This is used to identify the adapter in the debug logs. * * @default `adapterId` */ adapterName?: string; /** * Adapter id */ adapterId: string; /** * If the database supports numeric ids, set this to `true`. * * @default true */ supportsNumericIds?: boolean; /** * If the database doesn't support JSON columns, set this to `false`. * * We will handle the translation between using `JSON` columns, and saving `string`s to the database. * * @default false */ supportsJSON?: boolean; /** * If the database doesn't support dates, set this to `false`. * * We will handle the translation between using `Date` objects, and saving `string`s to the database. * * @default true */ supportsDates?: boolean; /** * If the database doesn't support booleans, set this to `false`. * * We will handle the translation between using `boolean`s, and saving `0`s and `1`s to the database. * * @default true */ supportsBooleans?: boolean; /** * Disable id generation for the `create` method. * * This is useful for databases that don't support custom id values and would auto-generate them for you. * * @default false */ disableIdGeneration?: boolean; /** * Map the keys of the input data. * * This is useful for databases that expect a different key name for a given situation. * * For example, MongoDB uses `_id` while in Better-Auth we use `id`. * * * @example * Each key represents the old key to replace. * The value represents the new key * * This can be a partial object that only transforms some keys. * * ```ts * mapKeysTransformInput: { * id: "_id" // We want to replace `id` to `_id` to save into MongoDB * } * ``` */ mapKeysTransformInput?: Record<string, string>; /** * Map the keys of the output data. * * This is useful for databases that expect a different key name for a given situation. * * For example, MongoDB uses `_id` while in Better-Auth we use `id`. * * @example * Each key represents the old key to replace. * The value represents the new key * * This can be a partial object that only transforms some keys. * * ```ts * mapKeysTransformOutput: { * _id: "id" // In MongoDB, we save `id` as `_id`. So we want to replace `_id` with `id` when we get the data back. * } * ``` */ mapKeysTransformOutput?: Record<string, string>; /** * Custom transform input function. * * This function is used to transform the input data before it is saved to the database. */ customTransformInput?: (props: { data: any; /** * The fields of the model. */ fieldAttributes: FieldAttribute; /** * The field to transform. */ field: string; /** * The action which was called from the adapter. */ action: "create" | "update"; /** * The model name. */ model: string; /** * The schema of the user's Better-Auth instance. */ schema: BetterAuthDbSchema; /** * The options of the user's Better-Auth instance. */ options: BetterAuthOptions; }) => any; /** * Custom transform output function. * * This function is used to transform the output data before it is returned to the user. */ customTransformOutput?: (props: { data: any; /** * The fields of the model. */ fieldAttributes: FieldAttribute; /** * The field to transform. */ field: string; /** * The fields to select. */ select: string[]; /** * The model name. */ model: string; /** * The schema of the user's Better-Auth instance. */ schema: BetterAuthDbSchema; /** * The options of the user's Better-Auth instance. */ options: BetterAuthOptions; }) => any; /** * Custom ID generator function. * * By default, we can handle ID generation for you, however if the database your adapter is for only supports a specific custom id generation, * then you can use this function to generate your own IDs. * * * Notes: * - If the user enabled `useNumberId`, then this option will be ignored. Unless this adapter config has `supportsNumericIds` set to `false`. * - If `generateId` is `false` in the user's Better-Auth config, then this option will be ignored. * - If `generateId` is a function, then it will override this option. * * @example * * ```ts * customIdGenerator: ({ model }) => { * return "my-super-unique-id"; * } * ``` */ customIdGenerator?: (props: { model: string; }) => string; } type CreateCustomAdapter = ({ options, debugLog, schema, getDefaultModelName, getDefaultFieldName, }: { options: BetterAuthOptions; /** * The schema of the user's Better-Auth instance. */ schema: BetterAuthDbSchema; /** * The debug log function. * * If the config has defined `debugLogs` as `false`, no logs will be shown. */ debugLog: (...args: any[]) => void; /** * Get the model name which is expected to be saved in the database based on the user's schema. */ getModelName: (model: string) => string; /** * Get the field name which is expected to be saved in the database based on the user's schema. */ getFieldName: ({ model, field }: { model: string; field: string; }) => string; /** * This function helps us get the default model name from the schema defined by devs. * Often times, the user will be using the `modelName` which could had been customized by the users. * This function helps us get the actual model name useful to match against the schema. (eg: schema[model]) * * If it's still unclear what this does: * * 1. User can define a custom modelName. * 2. When using a custom modelName, doing something like `schema[model]` will not work. * 3. Using this function helps us get the actual model name based on the user's defined custom modelName. * 4. Thus allowing us to use `schema[model]`. */ getDefaultModelName: (model: string) => string; /** * This function helps us get the default field name from the schema defined by devs. * Often times, the user will be using the `fieldName` which could had been customized by the users. * This function helps us get the actual field name useful to match against the schema. (eg: schema[model].fields[field]) * * If it's still unclear what this does: * * 1. User can define a custom fieldName. * 2. When using a custom fieldName, doing something like `schema[model].fields[field]` will not work. * */ getDefaultFieldName: ({ model, field, }: { model: string; field: string; }) => string; /** * Get the field attributes for a given model and field. * * Note: any model name or field name is allowed, whether default to schema or not. */ getFieldAttributes: ({ model, field, }: { model: string; field: string; }) => FieldAttribute; }) => CustomAdapter; interface CustomAdapter { create: <T extends Record<string, any>>({ data, model, select, }: { model: string; data: T; select?: string[]; }) => Promise<T>; update: <T>(data: { model: string; where: CleanedWhere[]; update: T; }) => Promise<T | null>; updateMany: (data: { model: string; where: CleanedWhere[]; update: Record<string, any>; }) => Promise<number>; findOne: <T>({ model, where, select, }: { model: string; where: CleanedWhere[]; select?: string[]; }) => Promise<T | null>; findMany: <T>({ model, where, limit, sortBy, offset, }: { model: string; where?: CleanedWhere[]; limit: number; sortBy?: { field: string; direction: "asc" | "desc"; }; offset?: number; }) => Promise<T[]>; delete: ({ model, where, }: { model: string; where: CleanedWhere[]; }) => Promise<void>; deleteMany: ({ model, where, }: { model: string; where: CleanedWhere[]; }) => Promise<number>; count: ({ model, where, }: { model: string; where?: CleanedWhere[]; }) => Promise<number>; createSchema?: (props: { /** * The file the user may have passed in to the `generate` command as the expected schema file output path. */ file?: string; /** * The tables from the user's Better-Auth instance schema. */ tables: BetterAuthDbSchema; }) => Promise<AdapterSchemaCreation>; /** * Your adapter's options. */ options?: Record<string, any> | undefined; } type CleanedWhere = Prettify<Required<Where>>; type AdapterTestDebugLogs = { resetDebugLogs: () => void; printDebugLogs: () => void; }; /** * Adapter where clause */ type Where = { operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with"; value: string | number | boolean | string[] | number[] | Date | null; field: string; connector?: "AND" | "OR"; }; /** * Adapter Interface */ type Adapter = { id: string; create: <T extends Record<string, any>, R = T>(data: { model: string; data: Omit<T, "id">; select?: string[]; /** * By default, any `id` provided in `data` will be ignored. * * If you want to force the `id` to be the same as the `data.id`, set this to `true`. */ forceAllowId?: boolean; }) => Promise<R>; findOne: <T>(data: { model: string; where: Where[]; select?: string[]; }) => Promise<T | null>; findMany: <T>(data: { model: string; where?: Where[]; limit?: number; sortBy?: { field: string; direction: "asc" | "desc"; }; offset?: number; }) => Promise<T[]>; count: (data: { model: string; where?: Where[]; }) => Promise<number>; /** * ⚠︎ Update may not return the updated data * if multiple where clauses are provided */ update: <T>(data: { model: string; where: Where[]; update: Record<string, any>; }) => Promise<T | null>; updateMany: (data: { model: string; where: Where[]; update: Record<string, any>; }) => Promise<number>; delete: <T>(data: { model: string; where: Where[]; }) => Promise<void>; deleteMany: (data: { model: string; where: Where[]; }) => Promise<number>; /** * * @param options * @param file - file path if provided by the user */ createSchema?: (options: BetterAuthOptions, file?: string) => Promise<AdapterSchemaCreation>; options?: { adapterConfig: AdapterConfig; } & CustomAdapter["options"]; }; type AdapterSchemaCreation = { /** * Code to be inserted into the file */ code: string; /** * Path to the file, including the file name and extension. * Relative paths are supported, with the current working directory of the developer's project as the base. */ path: string; /** * Append the file if it already exists. * Note: This will not apply if `overwrite` is set to true. */ append?: boolean; /** * Overwrite the file if it already exists */ overwrite?: boolean; }; interface AdapterInstance { (options: BetterAuthOptions): Adapter; } interface SecondaryStorage { /** * * @param key - Key to get * @returns - Value of the key */ get: (key: string) => Promise<unknown> | unknown; set: ( /** * Key to store */ key: string, /** * Value to store */ value: string, /** * Time to live in seconds */ ttl?: number) => Promise<void | null | unknown> | void; /** * * @param key - Key to delete */ delete: (key: string) => Promise<void | null | string> | void; } type KyselyDatabaseType = "postgres" | "mysql" | "sqlite" | "mssql"; declare const coreSchema: z.ZodObject<{ id: z.ZodString; createdAt: z.ZodDefault<z.ZodDate>; updatedAt: z.ZodDefault<z.ZodDate>; }, z.core.$strip>; declare const accountSchema: z.ZodObject<{ id: z.ZodString; createdAt: z.ZodDefault<z.ZodDate>; updatedAt: z.ZodDefault<z.ZodDate>; providerId: z.ZodString; accountId: z.ZodString; userId: z.ZodCoercedString<unknown>; accessToken: z.ZodOptional<z.ZodNullable<z.ZodString>>; refreshToken: z.ZodOptional<z.ZodNullable<z.ZodString>>; idToken: z.ZodOptional<z.ZodNullable<z.ZodString>>; accessTokenExpiresAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>; refreshTokenExpiresAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>; scope: z.ZodOptional<z.ZodNullable<z.ZodString>>; password: z.ZodOptional<z.ZodNullable<z.ZodString>>; }, z.core.$strip>; declare const userSchema: z.ZodObject<{ id: z.ZodString; createdAt: z.ZodDefault<z.ZodDate>; updatedAt: z.ZodDefault<z.ZodDate>; email: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>; emailVerified: z.ZodDefault<z.ZodBoolean>; name: z.ZodString; image: z.ZodOptional<z.ZodNullable<z.ZodString>>; }, z.core.$strip>; declare const sessionSchema: z.ZodObject<{ id: z.ZodString; createdAt: z.ZodDefault<z.ZodDate>; updatedAt: z.ZodDefault<z.ZodDate>; userId: z.ZodCoercedString<unknown>; expiresAt: z.ZodDate; token: z.ZodString; ipAddress: z.ZodOptional<z.ZodNullable<z.ZodString>>; userAgent: z.ZodOptional<z.ZodNullable<z.ZodString>>; }, z.core.$strip>; declare const verificationSchema: z.ZodObject<{ id: z.ZodString; createdAt: z.ZodDefault<z.ZodDate>; updatedAt: z.ZodDefault<z.ZodDate>; value: z.ZodString; expiresAt: z.ZodDate; identifier: z.ZodString; }, z.core.$strip>; declare function parseOutputData<T extends Record<string, any>>(data: T, schema: { fields: Record<string, FieldAttribute>; }): T; declare function getAllFields(options: BetterAuthOptions, table: string): Record<string, FieldAttribute>; declare function parseUserOutput(options: BetterAuthOptions, user: User): { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; }; declare function parseAccountOutput(options: BetterAuthOptions, account: Account): { id: string; createdAt: Date; updatedAt: Date; providerId: string; accountId: string; userId: string; accessToken?: string | null | undefined; refreshToken?: string | null | undefined; idToken?: string | null | undefined; accessTokenExpiresAt?: Date | null | undefined; refreshTokenExpiresAt?: Date | null | undefined; scope?: string | null | undefined; password?: string | null | undefined; }; declare function parseSessionOutput(options: BetterAuthOptions, session: Session): { id: string; createdAt: Date; updatedAt: Date; userId: string; expiresAt: Date; token: string; ipAddress?: string | null | undefined; userAgent?: string | null | undefined; }; declare function parseInputData<T extends Record<string, any>>(data: T, schema: { fields: Record<string, FieldAttribute>; action?: "create" | "update"; }): Partial<T>; declare function parseUserInput(options: BetterAuthOptions, user?: Record<string, any>, action?: "create" | "update"): Partial<Record<string, any>>; declare function parseAdditionalUserInput(options: BetterAuthOptions, user?: Record<string, any>): Partial<Record<string, any>>; declare function parseAccountInput(options: BetterAuthOptions, account: Partial<Account>): Partial<Partial<{ id: string; createdAt: Date; updatedAt: Date; providerId: string; accountId: string; userId: string; accessToken?: string | null | undefined; refreshToken?: string | null | undefined; idToken?: string | null | undefined; accessTokenExpiresAt?: Date | null | undefined; refreshTokenExpiresAt?: Date | null | undefined; scope?: string | null | undefined; password?: string | null | undefined; }>>; declare function parseSessionInput(options: BetterAuthOptions, session: Partial<Session>): Partial<Partial<{ id: string; createdAt: Date; updatedAt: Date; userId: string; expiresAt: Date; token: string; ipAddress?: string | null | undefined; userAgent?: string | null | undefined; }>>; declare function mergeSchema<S extends AuthPluginSchema>(schema: S, newSchema?: { [K in keyof S]?: { modelName?: string; fields?: { [P: string]: string; }; }; }): S; type Models = "user" | "account" | "session" | "verification" | "rate-limit" | "organization" | "member" | "invitation" | "jwks" | "passkey" | "two-factor"; type AdditionalUserFieldsInput<Options extends BetterAuthOptions> = InferFieldsFromPlugins<Options, "user", "input"> & InferFieldsFromOptions<Options, "user", "input">; type AdditionalUserFieldsOutput<Options extends BetterAuthOptions> = InferFieldsFromPlugins<Options, "user"> & InferFieldsFromOptions<Options, "user">; type AdditionalSessionFieldsInput<Options extends BetterAuthOptions> = InferFieldsFromPlugins<Options, "session", "input"> & InferFieldsFromOptions<Options, "session", "input">; type AdditionalSessionFieldsOutput<Options extends BetterAuthOptions> = InferFieldsFromPlugins<Options, "session"> & InferFieldsFromOptions<Options, "session">; type InferUser<O extends BetterAuthOptions | Auth> = UnionToIntersection<StripEmptyObjects<User & (O extends BetterAuthOptions ? AdditionalUserFieldsOutput<O> : O extends Auth ? AdditionalUserFieldsOutput<O["options"]> : {})>>; type InferSession<O extends BetterAuthOptions | Auth> = UnionToIntersection<StripEmptyObjects<Session & (O extends BetterAuthOptions ? AdditionalSessionFieldsOutput<O> : O extends Auth ? AdditionalSessionFieldsOutput<O["options"]> : {})>>; type InferPluginTypes<O extends BetterAuthOptions> = O["plugins"] extends Array<infer P> ? UnionToIntersection<P extends BetterAuthPlugin ? P["$Infer"] extends Record<string, any> ? P["$Infer"] : {} : {}> : {}; interface RateLimit { /** * The key to use for rate limiting */ key: string; /** * The number of requests made */ count: number; /** * The last request time in milliseconds */ lastRequest: number; } type User = z.infer<typeof userSchema>; type Account = z.infer<typeof accountSchema>; type Session = z.infer<typeof sessionSchema>; type Verification = z.infer<typeof verificationSchema>; type BetterAuthOptions = { /** * The name of the application * * process.env.APP_NAME * * @default "Better Auth" */ appName?: string; /** * Base URL for the Better Auth. This is typically the * root URL where your application server is hosted. * If not explicitly set, * the system will check the following environment variable: * * process.env.BETTER_AUTH_URL * * If not set it will throw an error. */ baseURL?: string; /** * Base path for the Better Auth. This is typically * the path where the * Better Auth routes are mounted. * * @default "/api/auth" */ basePath?: string; /** * The secret to use for encryption, * signing and hashing. * * By default Better Auth will look for * the following environment variables: * process.env.BETTER_AUTH_SECRET, * process.env.AUTH_SECRET * If none of these environment * variables are set, * it will default to * "better-auth-secret-123456789". * * on production if it's not set * it will throw an error. * * you can generate a good secret * using the following command: * @example * ```bash * openssl rand -base64 32 * ``` */ secret?: string; /** * Database configuration */ database?: PostgresPool | MysqlPool | Database | Dialect | AdapterInstance | Database$1 | DatabaseSync | { dialect: Dialect; type: KyselyDatabaseType; /** * casing for table names * * @default "camel" */ casing?: "snake" | "camel"; /** * Enable debug logs for the adapter * * @default false */ debugLogs?: AdapterDebugLogs; } | { /** * Kysely instance */ db: Kysely<any>; /** * Database type between postgres, mysql and sqlite */ type: KyselyDatabaseType; /** * casing for table names * * @default "camel" */ casing?: "snake" | "camel"; /** * Enable debug logs for the adapter * * @default false */ debugLogs?: AdapterDebugLogs; }; /** * Secondary storage configuration * * This is used to store session and rate limit data. */ secondaryStorage?: SecondaryStorage; /** * Email verification configuration */ emailVerification?: { /** * Send a verification email * @param data the data object * @param request the request object */ sendVerificationEmail?: ( /** * @param user the user to send the * verification email to * @param url the URL to send the verification email to * it contains the token as well * @param token the token to send the verification email to */ data: { user: User; url: string; token: string; }, /** * The request object */ request?: Request) => Promise<void>; /** * Send a verification email automatically * after sign up * * @default false */ sendOnSignUp?: boolean; /** * Send a verification email automatically * on sign in when the user's email is not verified * * @default false */ sendOnSignIn?: boolean; /** * Auto signin the user after they verify their email */ autoSignInAfterVerification?: boolean; /** * Number of seconds the verification token is * valid for. * @default 3600 seconds (1 hour) */ expiresIn?: number; /** * A function that is called when a user verifies their email * @param user the user that verified their email * @param request the request object */ onEmailVerification?: (user: User, request?: Request) => Promise<void>; /** * A function that is called when a user's email is updated to verified * @param user the user that verified their email * @param request the request object */ afterEmailVerification?: (user: User, request?: Request) => Promise<void>; }; /** * Email and password authentication */ emailAndPassword?: { /** * Enable email and password authentication * * @default false */ enabled: boolean; /** * Disable email and password sign up * * @default false */ disableSignUp?: boolean; /** * Require email verification before a session * can be created for the user. * * if the user is not verified, the user will not be able to sign in * and on sign in attempts, the user will be prompted to verify their email. */ requireEmailVerification?: boolean; /** * The maximum length of the password. * * @default 128 */ maxPasswordLength?: number; /** * The minimum length of the password. * * @default 8 */ minPasswordLength?: number; /** * send reset password */ sendResetPassword?: ( /** * @param user the user to send the * reset password email to * @param url the URL to send the reset password email to * @param token the token to send to the user (could be used instead of sending the url * if you need to redirect the user to custom route) */ data: { user: User; url: string; token: string; }, /** * The request object */ request?: Request) => Promise<void>; /** * Number of seconds the reset password token is * valid for. * @default 1 hour (60 * 60) */ resetPasswordTokenExpiresIn?: number; /** * A callback function that is triggered * when a user's password is changed successfully. */ onPasswordReset?: (data: { user: User; }, request?: Request) => Promise<void>; /** * Password hashing and verification * * By default Scrypt is used for password hashing and * verification. You can provide your own hashing and * verification function. if you want to use a * different algorithm. */ password?: { hash?: (password