better-auth
Version:
The most comprehensive authentication library for TypeScript.
1,488 lines (1,471 loc) • 728 kB
text/typescript
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