better-auth-credentials-plugin
Version:
Generic credentials authentication plugin for Better Auth (To auth with ldap, external API, etc...)
338 lines (337 loc) • 15.2 kB
TypeScript
import { EndpointContext } from "better-call";
import { Account, User } from "better-auth";
import { default as z, ZodTypeAny } from "zod/v3";
import { defaultCredentialsSchema, DefaultCredentialsType } from "./schema.js";
type GetBodyParsed<Z> = Z extends z.ZodTypeAny ? z.infer<Z> : DefaultCredentialsType;
type MaybePromise<T> = T | Promise<T>;
export type CallbackResult<U extends User> = (Partial<U> & {
onSignUp?: (userData: Partial<U>) => MaybePromise<Partial<U> | null>;
onSignIn?: (userData: Partial<U>, user: U, account: Account | null) => MaybePromise<Partial<U> | null>;
onLinkAccount?: (user: U) => MaybePromise<Partial<Account>>;
}) | null | undefined;
export type CredentialOptions<U extends User = User, P extends string = "/sign-in/credentials", Z extends (ZodTypeAny | undefined) = undefined> = {
/**
* Function that receives the credential and password and returns a Promise with the partial user data to be updated.
*
* If the user does not exists it will be created if `autoSignUp` is true, in this case a
* the returned user data will be used to create the user, otherwise, if the user exists, it will be updated with the returned user data.
*
* If a custom inputSchema is set and it hasn't an `email` field, then you should return the `email` field to uniquely identify the user (Better auth can't operate without emails anyway).
*
* The `onSignIn` and `onSignUp` callbacks are optional, but if returned they will be called to handle updating the user data differently based if the user is signing in or signing up.
*
* The `onLinkAccount` callback is called whenever a Account is created or if the user already exists and an account is linked to the user, use it to store custom data on the Account.
*/
callback: (ctx: EndpointContext<string, any>, parsed: GetBodyParsed<Z>) => MaybePromise<CallbackResult<U>>;
/**
* Schema for the input data, if not provided it will use the default schema that mirrors default email and password with rememberMe option.
*
* (Must be a zod/v3 schema)
*/
inputSchema?: Z;
/**
* Whether to sign up the user if they successfully authenticate but do not exist locally
* @default false
*/
autoSignUp?: boolean;
/**
* If is allowed to link an account to an existing user without an Account of this provider (No effect if autoSignUp is false).
*
* Basically, if the user already exists, but with another provider (e.g. email and password), if this is true a
* new Account will be created and linked to this user (as if new login method), otherwise it will throw an error.
* @default false
*/
linkAccountIfExisting?: boolean;
/**
* The Id of the provider to be used for the account created, fallback to "credential", the same used by the email and password flow.
*
* Obs: If you are using this plugin with the email and password plugin enabled and did not change the providerId, users that have a password set will not be able to log in with this credentials plugin.
* @default "credential"
*/
providerId?: string;
/**
* The path for the endpoint
* @default "/sign-in/credentials"
*/
path?: P;
/**
* This is used to infer the User type to be used, never used otherwise. If not provided it will be the default User type.
*
* For example, to add a lastLogin input value:
* @example {} as User & {lastLogin: Date}
*/
UserType?: U;
};
/**
* Customized Credentials plugin for BetterAuth.
*
* The options allow you to customize the input schema, the callback function, and other behaviors.
*
* Summary of the stages of this authentication flow:
* 1. Validate the input data against `inputSchema`
* 2. Call the `callback` function
* - If the callback throws an error, or doesn't return a object with user data, a generic 401 Unauthorized error is thrown.
* 3. Find the user by email (given by callback or parsed input), if exists proceed to [SIGN IN], if not [SIGN UP] (only when `autoSignUp` is true).
*
* **[SIGN IN]**
*
* 4. Find the Account with the providerId
* - If the account is not found, and `linkAccountIfExisting` or `autoSignUp` is false, login fails with a 401 Unauthorized error.
* 5. If provided, Call the `onSignIn` callback function, but yet don't update the user data.
* 6. If no Account was found on step 4. call the `onLinkAccount` callback function to get the account data to be stored, and then create a new Account for the user with the providerId.
* 7. Update the user with the provided data (Either returned by the auth callback function or the `onSignIn` callback function).
*
* **[SIGN UP]**
*
* 4. If provided, call the `onSignUp` callback function to get the user data to be stored.
* 5. Create a new User with the provided data (Either returned by the auth callback function or the `onSignUp` callback function).
* 5. If provided, call the `onLinkAccount` callback function to get the account data to be stored
* 6. Then create a new Account for the user with the providerId.
*
* **[AUTHENTICATED!]**
*
* 6. Create a new session for the user and set the session cookie.
* 7. Return the user data and the session token.
*
* @example
* ```ts
* credentials({
* autoSignUp: true,
* callback: async (ctx, parsed) => {
* // 1. Verify the credentials
*
* // 2. On success, return the user data
* return {
* email: parsed.email
* };
* })
*/
export declare const credentials: <U extends User = User, P extends string = "/sign-in/credentials", Z extends ZodTypeAny = typeof defaultCredentialsSchema>(options: CredentialOptions<U, P, Z>) => {
id: "credentials";
endpoints: {
signInUsername: {
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(...inputCtx: import("better-call").HasRequiredKeys<import("better-call").InputContext<P, {
method: "POST";
body: Z;
metadata: {
openapi: {
summary: string;
description: string;
responses: {
200: {
description: string;
content: {
"application/json": {
schema: {
type: "object";
properties: {
token: {
type: string;
description: string;
};
user: {
$ref: string;
};
};
required: string[];
};
};
};
};
};
};
};
} & {
use: any[];
}>> extends true ? [import("better-call").InferBodyInput<{
method: "POST";
body: Z;
metadata: {
openapi: {
summary: string;
description: string;
responses: {
200: {
description: string;
content: {
"application/json": {
schema: {
type: "object";
properties: {
token: {
type: string;
description: string;
};
user: {
$ref: string;
};
};
required: string[];
};
};
};
};
};
};
};
} & {
use: any[];
}, Z extends import("better-call").StandardSchemaV1<unknown, unknown> ? import("better-call").StandardSchemaV1.InferInput<Z> : undefined> & {
method?: "POST" | undefined;
} & {
query?: Record<string, any> | undefined;
} & import("better-call").InferParamInput<P> & {
request?: Request;
} & {
headers?: HeadersInit;
} & {
asResponse?: boolean;
returnHeaders?: boolean;
use?: import("better-call").Middleware[];
path?: string;
} & {
asResponse?: AsResponse | undefined;
returnHeaders?: ReturnHeaders | undefined;
}] : [((import("better-call").InferBodyInput<{
method: "POST";
body: Z;
metadata: {
openapi: {
summary: string;
description: string;
responses: {
200: {
description: string;
content: {
"application/json": {
schema: {
type: "object";
properties: {
token: {
type: string;
description: string;
};
user: {
$ref: string;
};
};
required: string[];
};
};
};
};
};
};
};
} & {
use: any[];
}, Z extends import("better-call").StandardSchemaV1<unknown, unknown> ? import("better-call").StandardSchemaV1.InferInput<Z> : undefined> & {
method?: "POST" | undefined;
} & {
query?: Record<string, any> | undefined;
} & import("better-call").InferParamInput<P> & {
request?: Request;
} & {
headers?: HeadersInit;
} & {
asResponse?: boolean;
returnHeaders?: boolean;
use?: import("better-call").Middleware[];
path?: string;
} & {
asResponse?: AsResponse | undefined;
returnHeaders?: ReturnHeaders | undefined;
}) | undefined)?]): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? {
headers: Headers;
response: {
token: null;
user: {
id: string;
email: string;
name: string;
image: string | null | undefined;
emailVerified: false;
createdAt: Date;
updatedAt: Date;
};
} | {
token: string;
user: {
id: string;
email: string;
name: string;
image: string | null | undefined;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
};
};
} : {
token: null;
user: {
id: string;
email: string;
name: string;
image: string | null | undefined;
emailVerified: false;
createdAt: Date;
updatedAt: Date;
};
} | {
token: string;
user: {
id: string;
email: string;
name: string;
image: string | null | undefined;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
};
}>;
options: {
method: "POST";
body: Z;
metadata: {
openapi: {
summary: string;
description: string;
responses: {
200: {
description: string;
content: {
"application/json": {
schema: {
type: "object";
properties: {
token: {
type: string;
description: string;
};
user: {
$ref: string;
};
};
required: string[];
};
};
};
};
};
};
};
} & {
use: any[];
};
path: P;
};
};
$ERROR_CODES: {
INVALID_CREDENTIALS: string;
EMAIL_REQUIRED: string;
EMAIL_NOT_VERIFIED: string;
UNEXPECTED_ERROR: string;
USERNAME_IS_ALREADY_TAKEN: string;
};
};
export {};