@carere/gel-better-auth
Version:
Adapter for Better Auth and Gel/EdgeDB
1,571 lines (1,556 loc) • 55.5 kB
TypeScript
import { PostgresPool, MysqlPool, Dialect, Kysely, Migration } from 'kysely';
import * as better_call from 'better-call';
import { CookieOptions, EndpointContext, Endpoint, Middleware, InputContext } from 'better-call';
import * as z from 'zod/v4';
import { ZodSchema } from 'zod/v4';
import { L, e, D, O } from './better-auth.ZSfSbnQT.js';
import { S, a, b } from './better-auth.ClXlabtY.js';
import { Database } from 'better-sqlite3';
import { Database as Database$1 } from 'bun:sqlite';
import { AdapterDebugLogs as AdapterDebugLogs$1 } from 'better-auth/adapters';
import { Client } from 'gel';
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 HookEndpointContext = EndpointContext<string, any> & Omit<InputContext<string, any>, "method"> & {
context: AuthContext & {
returned?: unknown;
responseHeaders?: Headers;
};
headers?: Headers;
};
type GenericEndpointContext = EndpointContext<string, any> & {
context: AuthContext;
};
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>;
type LogLevel = "info" | "success" | "warn" | "error" | "debug";
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;
declare const createLogger: (options?: Logger) => Record<LogLevel, (...params: LogHandlerParams) => void>;
declare function checkPassword(userId: string, c: GenericEndpointContext): Promise<boolean>;
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: a[];
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: e<Models, string>;
size?: number;
}) => string;
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: ReturnType<typeof getAuthTables>;
runMigrations: () => Promise<void>;
};
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>;
};
type AuthMiddleware = ReturnType<typeof createAuthMiddleware>;
type FieldType = "string" | "number" | "boolean" | "date" | `${"string" | "number"}[]` | Array<L>;
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);
/**
* 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?: ZodSchema;
output?: ZodSchema;
};
/**
* 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>;
type AuthPluginSchema = {
[table in string]: {
fields: {
[field in string]: FieldAttribute;
};
disableMigration?: boolean;
modelName?: string;
};
};
type BetterAuthPlugin = {
id: L;
/**
* 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?: D<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>;
/**
* 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>;
};
/**
* Adapter where clause
*/
type Where = {
operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "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?: Record<string, any>;
};
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<string | null> | string | null;
set: (
/**
* Key to store
*/
key: string,
/**
* Value to store
*/
value: string,
/**
* Time to live in seconds
*/
ttl?: number) => Promise<void | null | string> | void;
/**
*
* @param key - Key to delete
*/
delete: (key: string) => Promise<void | null | string> | void;
}
type KyselyDatabaseType = "postgres" | "mysql" | "sqlite" | "mssql";
declare const accountSchema: z.ZodObject<{
id: z.ZodString;
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>>;
createdAt: z.ZodDefault<z.ZodDate>;
updatedAt: z.ZodDefault<z.ZodDate>;
}, z.core.$strip>;
declare const userSchema: z.ZodObject<{
id: z.ZodString;
email: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
emailVerified: z.ZodDefault<z.ZodBoolean>;
name: z.ZodString;
image: z.ZodOptional<z.ZodNullable<z.ZodString>>;
createdAt: z.ZodDefault<z.ZodDate>;
updatedAt: z.ZodDefault<z.ZodDate>;
}, z.core.$strip>;
declare const sessionSchema: z.ZodObject<{
id: z.ZodString;
userId: z.ZodCoercedString<unknown>;
expiresAt: z.ZodDate;
createdAt: z.ZodDefault<z.ZodDate>;
updatedAt: z.ZodDefault<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;
value: z.ZodString;
createdAt: z.ZodDefault<z.ZodDate>;
updatedAt: z.ZodDefault<z.ZodDate>;
expiresAt: z.ZodDate;
identifier: z.ZodString;
}, z.core.$strip>;
type Models = "user" | "account" | "session" | "verification" | "rate-limit" | "organization" | "member" | "invitation" | "jwks" | "passkey" | "two-factor";
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 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;
};
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 | {
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: string) => Promise<string>;
verify?: (data: {
hash: string;
password: string;
}) => Promise<boolean>;
};
/**
* Automatically sign in the user after sign up
*
* @default true
*/
autoSignIn?: boolean;
/**
* Whether to revoke all other sessions when resetting password
* @default false
*/
revokeSessionsOnPasswordReset?: boolean;
};
/**
* list of social providers
*/
socialProviders?: S;
/**
* List of Better Auth plugins
*/
plugins?: BetterAuthPlugin[];
/**
* User configuration
*/
user?: {
/**
* The model name for the user. Defaults to "user".
*/
modelName?: string;
/**
* Map fields
*
* @example
* ```ts
* {
* userId: "user_id"
* }
* ```
*/
fields?: Partial<Record<keyof O<User>, string>>;
/**
* Additional fields for the session
*/
additionalFields?: {
[key: string]: FieldAttribute;
};
/**
* Changing email configuration
*/
changeEmail?: {
/**
* Enable changing email
* @default false
*/
enabled: boolean;
/**
* Send a verification email when the user changes their email.
* @param data the data object
* @param request the request object
*/
sendChangeEmailVerification?: (data: {
user: User;
newEmail: string;
url: string;
token: string;
}, request?: Request) => Promise<void>;
};
/**
* User deletion configuration
*/
deleteUser?: {
/**
* Enable user deletion
*/
enabled?: boolean;
/**
* Send a verification email when the user deletes their account.
*
* if this is not set, the user will be deleted immediately.
* @param data the data object
* @param request the request object
*/
sendDeleteAccountVerification?: (data: {
user: User;
url: string;
token: string;
}, request?: Request) => Promise<void>;
/**
* A function that is called before a user is deleted.
*
* to interrupt with error you can throw `APIError`
*/
beforeDelete?: (user: User, request?: Request) => Promise<void>;
/**
* A function that is called after a user is deleted.
*
* This is useful for cleaning up user data
*/
afterDelete?: (user: User, request?: Request) => Promise<void>;
/**
* The expiration time for the delete token.
*
* @default 1 day (60 * 60 * 24) in seconds
*/
deleteTokenExpiresIn?: number;
};
};
session?: {
/**
* The model name for the session.
*
* @default "session"
*/
modelName?: string;
/**
* Map fields
*
* @example
* ```ts
* {
* userId: "user_id"
* }
*/
fields?: Partial<Record<keyof O<Session>, string>>;
/**
* Expiration time for the session token. The value
* should be in seconds.
* @default 7 days (60 * 60 * 24 * 7)
*/
expiresIn?: number;
/**
* How often the session should be refreshed. The value
* should be in seconds.
* If set 0 the session will be refreshed every time it is used.
* @default 1 day (60 * 60 * 24)
*/
updateAge?: number;
/**
* Disable session refresh so that the session is not updated
* regardless of the `updateAge` option.
*
* @default false
*/
disableSessionRefresh?: boolean;
/**
* Additional fields for the session
*/
additionalFields?: {
[key: string]: FieldAttribute;
};
/**
* By default if secondary storage is provided
* the session is stored in the secondary storage.
*
* Set this to true to store the session in the database
* as well.
*
* Reads are always done from the secondary storage.
*
* @default false
*/
storeSessionInDatabase?: boolean;
/**
* By default, sessions are deleted from the database when secondary storage
* is provided when session is revoked.
*
* Set this to true to preserve session records in the database,
* even if they are deleted from the secondary storage.
*
* @default false
*/
preserveSessionInDatabase?: boolean;
/**
* Enable caching session in cookie
*/
cookieCache?: {
/**
* max age of the cookie
* @default 5 minutes (5 * 60)
*/
maxAge?: number;
/**
* Enable caching session in cookie
* @default false
*/
enabled?: boolean;
};
/**
* The age of the session to consider it fresh.
*
* This is used to check if the session is fresh
* for sensitive operations. (e.g. deleting an account)
*
* If the session is not fresh, the user should be prompted
* to sign in again.
*
* If set to 0, the session will be considered fresh every time. (⚠︎ not recommended)
*
* @default 1 day (60 * 60 * 24)
*/
freshAge?: number;
};
account?: {
/**
* The model name for the account. Defaults to "account".
*/
modelName?: string;
/**
* Map fields
*/
fields?: Partial<Record<keyof O<Account>, string>>;
/**
* When enabled (true), the user account data (accessToken, idToken, refreshToken, etc.)
* will be updated on sign in with the latest data from the provider.
*
* @default true
*/
updateAccountOnSignIn?: boolean;
/**
* Configuration for account linking.
*/
accountLinking?: {
/**
* Enable account linking
*
* @default true
*/
enabled?: boolean;
/**
* List of trusted providers
*/
trustedProviders?: Array<e<b[number] | "email-password", string>>;
/**
* If enabled (true), this will allow users to manually linking accounts with different email addresses than the main user.
*
* @default false
*
* ⚠️ Warning: enabling this might lead to account takeovers, so proceed with caution.
*/
allowDifferentEmails?: boolean;
/**
* If enabled (true), this will allow users to unlink all accounts.
*
* @default false
*/
allowUnlinkingAll?: boolean;
/**
* If enabled (true), this will update the user information based on the newly linked account
*
* @default false
*/
updateUserInfoOnLink?: boolean;
};
/**
* Encrypt OAuth tokens
*
* By default, OAuth tokens (access tokens, refresh tokens, ID tokens) are stored in plain text in the database.
* This poses a security risk if your database is compromised, as attackers could gain access to user accounts
* on external services.
*
* When enabled, tokens are encrypted using AES-256-GCM before storage, providing protection against:
* - Database breaches and unauthorized access to raw token data
* - Internal threats from database administrators or compromised credentials
* - Token exposure in database backups and logs
* @default false
*/
encryptOAuthTokens?: boolean;
};
/**
* Verification configuration
*/
verification?: {
/**
* Change the modelName of the verification table
*/
modelName?: string;
/**
* Map verification fields
*/
fields?: Partial<Record<keyof O<Verification>, string>>;
/**
* disable cleaning up expired values when a verification value is
* fetched
*/
disableCleanup?: boolean;
};
/**
* List of trusted origins.
*/
trustedOrigins?: string[] | ((request: Request) => string[] | Promise<string[]>);
/**
* Rate limiting configuration
*/
rateLimit?: {
/**
* By default, rate limiting is only
* enabled on production.
*/
enabled?: boolean;
/**
* Default window to use for rate limiting. The value
* should be in seconds.
*
* @default 10 seconds
*/
window?: number;
/**
* The default maximum number of requests allowed within the window.
*
* @default 100 requests
*/
max?: number;
/**
* Custom rate limit rules to apply to
* specific paths.
*/
customRules?: {
[key: string]: {
/**
* The window to use for the custom rule.
*/
window: number;
/**
* The maximum number of requests allowed within the window.
*/
max: number;
} | ((request: Request) => {
window: number;
max: number;
} | Promise<{
window: number;
max: number;
}>);
};
/**
* Storage configuration
*
* By default, rate limiting is stored in memory. If you passed a
* secondary storage, rate limiting will be stored in the secondary
* storage.
*
* @default "memory"
*/
storage?: "memory" | "database" | "secondary-storage";
/**
* If database is used as storage, the name of the table to
* use for rate limiting.
*
* @default "rateLimit"
*/
modelName?: string;
/**
* Custom field names for the rate limit table
*/
fields?: Record<keyof RateLimit, string>;
/**
* custom storage configuration.
*
* NOTE: If custom storage is used storage
* is ignored
*/
customStorage?: {
get: (key: string) => Promise<RateLimit | undefined>;
set: (key: string, value: RateLimit) => Promise<void>;
};
};
/**
* Advanced options
*/
advanced?: {
/**
* Ip address configuration
*/
ipAddress?: {
/**
* List of headers to use for ip address
*
* Ip address is used for rate limiting and session tracking
*
* @example ["x-client-ip", "x-forwarded-for", "cf-connecting-ip"]
*
* @default
* @link https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/utils/get-request-ip.ts#L8
*/
ipAddressHeaders?: string[];
/**
* Disable ip tracking
*
* ⚠︎ This is a security risk and it may expose your application to abuse
*/
disableIpTracking?: boolean;
};
/**
* Use secure cookies
*
* @default false
*/
useSecureCookies?: boolean;
/**
* Disable trusted origins check
*
* ⚠︎ This is a security risk and it may expose your application to CSRF attacks
*/
disableCSRFCheck?: boolean;
/**
* Configure cookies to be cross subdomains
*/
crossSubDomainCookies?: {
/**
* Enable cross subdomain cookies
*/
enabled: boolean;
/**
* Additional cookies to be shared across subdomains
*/
additionalCookies?: string[];
/**
* The domain to use for the cookies
*
* By default, the domain will be the root
* domain from the base URL.
*/
domain?: string;
};
cookies?: {
[key: string]: {
name?: string;
attributes?: CookieOptions;
};
};
defaultCookieAttributes?: CookieOptions;
/**
* Prefix for cookies. If a cookie name is provided
* in cookies config, this will be overridden.
*
* @default
* ```txt
* "appName" -> which defaults to "better-auth"
* ```
*/
cookiePrefix?: string;
/**
* Database configuration.
*/
database?: {
/**
* The default number of records to return from the database
* when using the `findMany` adapter method.
*
* @default 100
*/
defaultFindManyLimit?: number;
/**
* If your database auto increments number ids, set this to `true`.
*
* Note: If enabled, we will not handle ID generation (including if you use `generateId`), and it would be expected that your database will provide the ID automatically.
*
* @default false
*/
useNumberId?: boolean;
/**
* Custom generateId function.
*
* If not provided, random ids will be generated.
* If set to false, the database's auto generated id will be used.
*/
generateId?: ((options: {
model: e<Models, string>;
size?: number;
}) => string) | false;
};
/**
* Custom generateId function.
*
* If not provided, random ids will be generated.
* If set to false, the database's auto generated id will be used.
*
* @deprecated Please use `database.generateId` instead. This will be potentially removed in future releases.
*/
generateId?: ((options: {
model: e<Models, string>;
size?: number;
}) => string) | false;
};
logger?: Logger;
/**
* allows you to define custom hooks that can be
* executed during lifecycle of core database
* operations.
*/
databaseHooks?: {
/**
* User hooks
*/
user?: {
create?: {
/**
* Hook that is called before a user is created.
* if the hook returns false, the user will not be created.
* If the hook returns an object, it'll be used instead of the original data
*/
before?: (user: User, context?: GenericEndpointContext) => Promise<boolean | void | {
data: Partial<User> & Record<string, any>;
}>;
/**
* Hook that is called after a user is created.
*/
after?: (user: User, context?: GenericEndpointContext) => Promise<void>;
};
update?: {
/**
* Hook that is called before a user is updated.
* if the hook returns false, the user will not be updated.
* If the hook returns an object, it'll be used instead of the original data
*/
before?: (user: Partial<User>, context?: GenericEndpointContext) => Promise<boolean | void | {
data: Partial<User & Record<string, any>>;
}>;
/**
* Hook that is called after a user is updated.
*/
after?: (user: User, context?: GenericEndpointContext) => Promise<void>;
};
};
/**
* Session Hook
*/
session?: {
create?: {
/**
* Hook that is called before a session is created.
* if the hook returns false, the session will not be created.
* If the hook returns an object, it'll be used instead of the original data
*/
before?: (session: Session, context?: GenericEndpointContext) => Promise<boolean | void | {
data: Partial<Session> & Record<string, any>;
}>;
/**
* Hook that is called after a session is created.
*/
after?: (session: Session, context?: GenericEndpointContext) => Promise<void>;
};
/**
* Update hook
*/
update?: {
/**
* Hook that is called before a user is updated.
* if the hook returns false, the session will not be updated.
* If the hook returns an object, it'll be used instead of the original data
*/
before?: (session: Partial<Session>, context?: GenericEndpointContext) => Promise<boolean | void | {
data: Session & Record<string, any>;
}>;
/**
* Hook that is called after a session is updated.
*/
after?: (session: Session, context?: GenericEndpointContext) => Promise<void>;
};
};
/**
* Account Hook
*/
account?: {
create?: {
/**
* Hook that is called before a account is created.
* If the hook returns false, the account will not be created.
* If the hook returns an object, it'll be used instead of the original data
*/
before?: (account: Account, context?: GenericEndpointContext) => Promise<boolean | void | {
data: Partial<Account> & Record<string, any>;
}>;
/**
* Hook that is called after a account is created.
*/
after?: (account: Account, context?: GenericEndpointContext) => Promise<void>;
};
/**
* Update hook
*/
update?: {
/**
* Hook that is called before a account is update.
* If the hook returns false, the user will not be updated.
* If the hook returns an object, it'll be used instead of the original data
*/
before?: (account: Partial<Account>, context?: GenericEndpointContext) => Promise<boolean | void | {
data: Partial<Account & Record<string, any>>;
}>;
/**
* Hook that is called after a account is updated.
*/
after?: (account: Account, context?: GenericEndpointContext) => Promise<void>;
};
};
/**
* Verification Hook
*/
verification?: {
create?: {
/**
* Hook that is called before a verification is created.
* if the hook returns false, the verification will not be created.
* If the hook returns an object, it'll be used instead of the original data
*/
before?: (verification: Verification, context?: GenericEndpointContext) => Promise<boolean | void | {
data: Partial<Verification> & Record<string, any>;
}>;
/**
* Hook that is called after a verification is created.
*/
after?: (verification: Verification, context?: GenericEndpointContext) => Promise<void>;
};
update?: {
/**
* Hook that is called before a verification is updated.
* if the hook returns false, the verification will not be updated.
* If the hook returns an object, it'll be used instead of the original data
*/
before?: (verification: Partial<Verification>, context?: GenericEndpointContext) => Promise<boolean | void | {
data: Partial<Verification & Record<string, any>>;
}>;
/**
* Hook that is called after a verification is updated.
*/
after?: (verification: Verification, context?: GenericEndpointContext) => Promise<void>;
};
};
};
/**
* API error handling
*/
onAPIError?: {
/**
* Throw an error on API error
*
* @default false
*/
throw?: boolean;
/**
* Custom error handler
*
* @param error
* @param ctx - Auth context
*/
onError?: (error: unknown, ctx: AuthContext) => void | Promise<void>;
/**
* The URL to redirect to on error
*
* When errorURL is provided, the error will be added to the URL as a query parameter
* and the user will be redirected to the errorURL.
*
* @default - "/api/auth/error"
*/
errorURL?: string;
};
/**
* Hooks
*/
hooks?: {
/**
* Before a request is processed
*/
before?: AuthMiddleware;
/**
* After a request is processed
*/
after?: AuthMiddleware;
};
/**
* Disabled paths
*
* Paths you want to disable.
*/
disabledPaths?: string[];
};
declare const createInternalAdapter: (adapter: Adapter, ctx: {
options: BetterAuthOptions;
hooks: Exclude<BetterAuthOptions["databaseHooks"], undefined>[];
generateId: AuthContext["generateId"];
}) => {
createOAuthUser: (user: Omit<User, "id" | "createdAt" | "updatedAt"> & Partial<User>, account: Omit<Account, "userId" | "id" | "createdAt" | "updatedAt"> & Partial<Account>, context?: GenericEndpointContext) => Promise<{
user: any;
account: any;
}>;
createUser: <T>(user: Omit<User, "id" | "createdAt" | "updatedAt" | "emailVerified"> & Partial<User> & Record<string, any>, context?: GenericEndpointContext) => Promise<T & {
id: string;
email: string;
emailVerified: boolean;
name: string;
createdAt: Date;
updatedAt: Date;
image?: string | null | undefined;
}>;
createAccount: <T>(account: Omit<Account, "id" | "createdAt" | "updatedAt"> & Partial<Account> & Record<string, any>, context?: GenericEndpointContext) => Promise<T & {
id: string;
providerId: string;
accountId: string;
userId: string;
createdAt: Date;
updatedAt: Date;
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;
}>;
listSessions: (userId: string) => Promise<{
id: string;
userId: string;
expiresAt: Date;
createdAt: Date;
updatedAt: Date;
token: string;
ipAddress?: string | null | undefined;
userAgent?: string | null | undefined;
}[]>;
listUsers: (limit?: number, offset?: number, sortBy?: {
field: string;
direction: "asc" | "desc";
}, where?: Where[]) => Promise<{
id: string;
email: string;
emailVerified: boolean;
name: string;
createdAt: Date;
updatedAt: Date;
image?: string | null | undefined;
}[]>;
countTotalUsers: (where?: Where[]) => Promise<number>;
deleteUser: (userId: string) => Promise<void>;
createSession: (userId: string, ctx: GenericEndpointContext, dontRememberMe?: boolean, override?: Partial<Session> & Record<string, any>, overrideAll?: boolean) => Promise<{
id: string;
userId: string;
expiresAt: Date;
createdAt: Date;
updatedAt: Date;
token: string;
ipAddress?: string | null | undefined;
userAgent?: string | null | undefined;
}>;
findSession: (token: string) => Promise<{
session: Session & Record<string, any>;
user: User & Record<string, any>;
} | null>;
findSessions: (sessionTokens: string[]) => Promise<{
session: Session;
user: User;
}[]>;
updateSession: (sessionToken: string, session: Partial<Session> & Record<string, any>, context?: GenericEndpointContext) => Promise<any>;
deleteSession: (token: string) => Promise<void>;
deleteAccounts: (userId: string) => Promise<void>;
deleteAccount: (accountId: string) => Promise<void>;
deleteSessions: (userIdOrSessionTokens: string | string[]) => Promise<void>;
findOAuthUser: (email: string, accountId: string, providerId: string) => Promise<{
user: {
id: string;
email: string;
emailVerified: boolean;
name: string;
createdAt: Date;
updatedAt: Date;
image?: string | null | undefined;
};
accounts: {
id: string;
providerId: string;
accountId: string;
userId: string;
createdAt: Date;
updatedAt: Date;
accessToken?: string | null | undefined;
refreshToken?: string | null | undefined;
idToken?: string | null | undefined;
accessTokenExpiresAt?: Date | null | undefined;
refreshTokenExpiresAt?: Date | null | undefined;
scope?: string | null | undefined;