mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-08 20:37:44 +00:00
feat: hooks (#916)
This commit is contained in:
@@ -233,6 +233,26 @@ export const contents: Content[] = [
|
|||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
href: "/docs/concepts/hooks",
|
||||||
|
title: "Hooks",
|
||||||
|
icon: (props?: SVGProps<any>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 65 64"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M30.719 27.2c-6.18-3.098-8.44-7.209-6.946-12.241c1.316-4.435 5.752-7.337 10.27-6.655c2.253.34 4.235 1.263 5.78 3.023c2.337 2.667 2.865 5.78 2.151 9.246l2.135.579l3.201.868c2.273-6.234-.393-13.518-6.214-17.258c-6.061-3.893-13.914-3.071-19.062 2c-2.687 2.649-4.158 5.88-4.5 9.62c-.483 5.29 1.703 9.558 5.375 13.21l-5.84 9.793q-.283.02-.502.033c-.269.016-.48.029-.688.058c-3.839.544-6.54 3.958-5.895 7.44c.73 3.933 4.309 6.348 7.983 5.385c3.896-1.02 5.97-4.78 4.5-8.644c-.532-1.398-.203-2.294.463-3.394c1.877-3.101 3.727-6.219 5.61-9.394zm13.222 4.686l-5.647-9.96q.14-.349.272-.665v-.001c.185-.448.354-.858.495-1.277c.747-2.21.296-4.228-1.122-6.02c-1.736-2.194-4.764-2.991-7.345-2.004c-2.605.997-4.272 3.554-4.158 6.383c.115 2.86 2.034 5.414 5.008 5.929c1.78.308 2.652 1.154 3.442 2.61c1.68 3.1 3.42 6.165 5.162 9.233v.001q1.033 1.817 2.061 3.64c5.832-3.888 10.657-3.764 14.26.285c3.12 3.51 3.186 8.854.153 12.438c-3.557 4.201-8.348 4.368-13.826.82l-4.352 3.642c5.546 5.536 13.463 6.272 19.723 1.963c6.099-4.199 8.222-12.258 5.116-19.063c-2.57-5.633-9.737-10.895-19.242-7.954m-12.623 16.99H42.76q.238.321.455.63c.303.428.592.834.928 1.195c2.424 2.592 6.516 2.72 9.106.315c2.685-2.492 2.807-6.68.27-9.281c-2.483-2.547-6.725-2.79-9.03-.094c-1.4 1.639-2.835 1.831-4.694 1.802c-3.397-.052-6.795-.042-10.193-.032q-2.045.007-4.088.008c.309 6.695-2.222 10.867-7.242 11.858c-4.916.97-9.443-1.538-11.037-6.114c-1.81-5.2.428-9.359 6.898-12.66c-.487-1.763-.98-3.548-1.466-5.315C5.617 32.724.327 39.565.872 47.26c.483 6.793 5.963 12.827 12.665 13.907c3.64.588 7.06-.022 10.233-1.822c4.082-2.316 6.451-5.958 7.548-10.47"
|
||||||
|
clipRule="evenodd"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
href: "/docs/concepts/plugins",
|
href: "/docs/concepts/plugins",
|
||||||
title: "Plugins",
|
title: "Plugins",
|
||||||
|
|||||||
161
docs/content/docs/concepts/hooks.mdx
Normal file
161
docs/content/docs/concepts/hooks.mdx
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
---
|
||||||
|
title: Hooks
|
||||||
|
description: Better Auth Hooks let you customize BetterAuth's behavior
|
||||||
|
---
|
||||||
|
|
||||||
|
Hooks in Better Auth let you "hook into" the lifecycle and execute custom logic. They provide a way to customize Better Auth's behavior without writing a full plugin.
|
||||||
|
|
||||||
|
## Before Hooks
|
||||||
|
|
||||||
|
**Before hooks** run *before* an endpoint is executed. Use them to modify requests, pre validate data, or return early.
|
||||||
|
|
||||||
|
### Example: Enforce Email Domain Restriction
|
||||||
|
|
||||||
|
This hook ensures that users can only sign up if their email ends with `@example.com`:
|
||||||
|
|
||||||
|
```ts title="auth.ts"
|
||||||
|
import { betterAuth } from "better-auth";
|
||||||
|
import { createAuthMiddleware } from "better-auth/api";
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
hooks: {
|
||||||
|
before: createAuthMiddleware(async (ctx) => {
|
||||||
|
if (ctx.path !== "/sign-up/email") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ctx.body?.email.endsWith("@example.com")) {
|
||||||
|
throw new APIError("BAD_REQUEST", {
|
||||||
|
message: "Email must end with @example.com",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Modify Request Context
|
||||||
|
|
||||||
|
To adjust the request context before proceeding:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { betterAuth } from "better-auth";
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
hooks: {
|
||||||
|
before: createAuthMiddleware(async (ctx) => {
|
||||||
|
if (ctx.path === "/sign-up/email") {
|
||||||
|
return {
|
||||||
|
...ctx,
|
||||||
|
body: {
|
||||||
|
...ctx.body,
|
||||||
|
name: "John Doe",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## After Hooks
|
||||||
|
|
||||||
|
**After hooks** run *after* an endpoint is executed. Use them to modify responses.
|
||||||
|
|
||||||
|
### Example: Add a Custom Header
|
||||||
|
|
||||||
|
This hook adds a custom header to the response:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { betterAuth } from "better-auth";
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
hooks: {
|
||||||
|
after: createAuthMiddleware(async (ctx) => {
|
||||||
|
ctx.response.headers.set("X-Custom-Header", "Hello World");
|
||||||
|
return {
|
||||||
|
responseHeader: ctx.responseHeader // return the updated response headers
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Context Object
|
||||||
|
|
||||||
|
The `ctx` object provides:
|
||||||
|
|
||||||
|
- **Path:** `ctx.path` to get the current endpoint path.
|
||||||
|
- **Body:** `ctx.body` for parsed request body (available for POST requests).
|
||||||
|
- **Headers:** `ctx.headers` to access request headers.
|
||||||
|
- **Request:** `ctx.request` to access the request object (may not exist in server-only endpoints).
|
||||||
|
- **Query Parameters:** `ctx.query` to access query parameters.
|
||||||
|
|
||||||
|
and more.
|
||||||
|
|
||||||
|
### JSON Responses
|
||||||
|
|
||||||
|
Use `ctx.json` to send JSON responses:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const hook = createAuthMiddleware(async (ctx) => {
|
||||||
|
return ctx.json({
|
||||||
|
message: "Hello World",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redirects
|
||||||
|
|
||||||
|
Use `ctx.redirect` to redirect users:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const hook = createAuthMiddleware(async (ctx) => {
|
||||||
|
throw ctx.redirect("/sign-up/name");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cookies
|
||||||
|
|
||||||
|
- Set cookies: `ctx.setCookies` or `ctx.setSignedCookie`.
|
||||||
|
- Get cookies: `ctx.getCookies` or `ctx.getSignedCookies`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const hook = createAuthMiddleware(async (ctx) => {
|
||||||
|
ctx.setCookies("my-cookie", "value");
|
||||||
|
await ctx.setSignedCookie("my-signed-cookie", "value", ctx.context.secret, {
|
||||||
|
maxAge: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cookie = ctx.getCookies("my-cookie");
|
||||||
|
const signedCookie = await ctx.getSignedCookies("my-signed-cookie");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Predefined Auth Cookies
|
||||||
|
|
||||||
|
Access BetterAuth’s predefined cookie properties:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const hook = createAuthMiddleware(async (ctx) => {
|
||||||
|
const cookieName = ctx.context.authCookies.sessionToken.name;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Errors
|
||||||
|
|
||||||
|
Throw errors with `APIError` for a specific status code and message:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const hook = createAuthMiddleware(async (ctx) => {
|
||||||
|
throw new APIError("BAD_REQUEST", {
|
||||||
|
message: "Invalid request",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reusable Hooks
|
||||||
|
|
||||||
|
If you need to reuse a hook across multiple endpoints, consider creating a plugin. Learn more in the [Plugins Documentation](/docs/concepts/plugins).
|
||||||
|
|
||||||
@@ -10,13 +10,14 @@ import { init } from "../init";
|
|||||||
import type { BetterAuthOptions, BetterAuthPlugin } from "../types";
|
import type { BetterAuthOptions, BetterAuthPlugin } from "../types";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { createAuthClient } from "../client";
|
import { createAuthClient } from "../client";
|
||||||
import { convertSetCookieToCookie } from "../test-utils/headers";
|
|
||||||
|
|
||||||
describe("call", async () => {
|
describe("call", async () => {
|
||||||
const q = z.optional(
|
const q = z.optional(
|
||||||
z.object({
|
z.object({
|
||||||
testBeforeHook: z.string().optional(),
|
testBeforeHook: z.string().optional(),
|
||||||
|
testBeforeGlobal: z.string().optional(),
|
||||||
testAfterHook: z.string().optional(),
|
testAfterHook: z.string().optional(),
|
||||||
|
testAfterGlobal: z.string().optional(),
|
||||||
testContext: z.string().optional(),
|
testContext: z.string().optional(),
|
||||||
message: z.string().optional(),
|
message: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
@@ -180,6 +181,28 @@ describe("call", async () => {
|
|||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
hooks: {
|
||||||
|
before: createAuthMiddleware(async (ctx) => {
|
||||||
|
if (ctx.path === "/sign-up/email") {
|
||||||
|
return {
|
||||||
|
context: {
|
||||||
|
body: {
|
||||||
|
...ctx.body,
|
||||||
|
email: "changed@email.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (ctx.query?.testBeforeGlobal) {
|
||||||
|
return ctx.json({ before: "global" });
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
after: createAuthMiddleware(async (ctx) => {
|
||||||
|
if (ctx.query?.testAfterGlobal) {
|
||||||
|
return ctx.json({ after: "global" });
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
} satisfies BetterAuthOptions;
|
} satisfies BetterAuthOptions;
|
||||||
const authContext = init(options);
|
const authContext = init(options);
|
||||||
const { api } = getEndpoints(authContext, options);
|
const { api } = getEndpoints(authContext, options);
|
||||||
@@ -343,6 +366,39 @@ describe("call", async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should intercept on global before hook", async () => {
|
||||||
|
const response = await api.test({
|
||||||
|
query: {
|
||||||
|
testBeforeGlobal: "true",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response).toMatchObject({
|
||||||
|
before: "global",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should intercept on global after hook", async () => {
|
||||||
|
const response = await api.test({
|
||||||
|
query: {
|
||||||
|
testAfterGlobal: "true",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response).toMatchObject({
|
||||||
|
after: "global",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("global before hook should change the context", async (ctx) => {
|
||||||
|
const response = await api.signUpEmail({
|
||||||
|
body: {
|
||||||
|
email: "my-email@test.com",
|
||||||
|
password: "password",
|
||||||
|
name: "test",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response.email).toBe("changed@email.com");
|
||||||
|
});
|
||||||
|
|
||||||
it("should fetch using a client", async () => {
|
it("should fetch using a client", async () => {
|
||||||
const response = await client.$fetch("/test");
|
const response = await client.$fetch("/test");
|
||||||
expect(response.data).toMatchObject({
|
expect(response.data).toMatchObject({
|
||||||
|
|||||||
@@ -195,21 +195,49 @@ export function getEndpoints<
|
|||||||
};
|
};
|
||||||
|
|
||||||
const plugins = options.plugins || [];
|
const plugins = options.plugins || [];
|
||||||
for (const plugin of plugins) {
|
const beforeHooks = plugins
|
||||||
const beforeHooks = plugin.hooks?.before ?? [];
|
.map((plugin) => {
|
||||||
for (const hook of beforeHooks) {
|
if (plugin.hooks?.before) {
|
||||||
if (!hook.matcher(internalContext)) continue;
|
return plugin.hooks.before;
|
||||||
const hookRes = await hook.handler(internalContext);
|
|
||||||
if (hookRes && "context" in hookRes) {
|
|
||||||
// modify the context with the response from the hook
|
|
||||||
internalContext = defu(internalContext, hookRes.context);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.filter((plugin) => plugin !== undefined)
|
||||||
|
.flat();
|
||||||
|
const afterHooks = plugins
|
||||||
|
.map((plugin) => {
|
||||||
|
if (plugin.hooks?.after) {
|
||||||
|
return plugin.hooks.after;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((plugin) => plugin !== undefined)
|
||||||
|
.flat();
|
||||||
|
if (options.hooks?.before) {
|
||||||
|
beforeHooks.push({
|
||||||
|
matcher: () => true,
|
||||||
|
handler: options.hooks.before,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (options.hooks?.after) {
|
||||||
|
afterHooks.push({
|
||||||
|
matcher: () => true,
|
||||||
|
handler: options.hooks.after,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const hook of beforeHooks) {
|
||||||
|
if (!hook.matcher(internalContext)) continue;
|
||||||
|
const hookRes = await hook.handler(internalContext);
|
||||||
|
if (hookRes && "context" in hookRes) {
|
||||||
|
// modify the context with the response from the hook
|
||||||
|
internalContext = {
|
||||||
|
...internalContext,
|
||||||
|
...hookRes.context,
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (hookRes) {
|
if (hookRes) {
|
||||||
// return with the response from the hook
|
// return with the response from the hook
|
||||||
return hookRes;
|
return hookRes;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,25 +253,16 @@ export function getEndpoints<
|
|||||||
internalContext.context.newSession = newSession;
|
internalContext.context.newSession = newSession;
|
||||||
}
|
}
|
||||||
if (e instanceof APIError) {
|
if (e instanceof APIError) {
|
||||||
const afterPlugins = options.plugins
|
|
||||||
?.map((plugin) => {
|
|
||||||
if (plugin.hooks?.after) {
|
|
||||||
return plugin.hooks.after;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter((plugin) => plugin !== undefined)
|
|
||||||
.flat();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there are no after plugins, we can directly throw the error
|
* If there are no after plugins, we can directly throw the error
|
||||||
*/
|
*/
|
||||||
if (!afterPlugins?.length) {
|
if (!afterHooks?.length) {
|
||||||
e.headers = endpoint.headers;
|
e.headers = endpoint.headers;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
internalContext.context.returned = e;
|
internalContext.context.returned = e;
|
||||||
internalContext.context.returned.headers = endpoint.headers;
|
internalContext.context.returned.headers = endpoint.headers;
|
||||||
for (const hook of afterPlugins || []) {
|
for (const hook of afterHooks || []) {
|
||||||
const match = hook.matcher(internalContext);
|
const match = hook.matcher(internalContext);
|
||||||
if (match) {
|
if (match) {
|
||||||
try {
|
try {
|
||||||
@@ -272,29 +291,25 @@ export function getEndpoints<
|
|||||||
}
|
}
|
||||||
internalContext.context.returned = endpointRes;
|
internalContext.context.returned = endpointRes;
|
||||||
internalContext.responseHeader = endpoint.headers;
|
internalContext.responseHeader = endpoint.headers;
|
||||||
for (const plugin of options.plugins || []) {
|
for (const hook of afterHooks) {
|
||||||
if (plugin.hooks?.after) {
|
const match = hook.matcher(internalContext);
|
||||||
for (const hook of plugin.hooks.after) {
|
if (match) {
|
||||||
const match = hook.matcher(internalContext);
|
try {
|
||||||
if (match) {
|
const hookRes = await hook.handler(internalContext);
|
||||||
try {
|
if (hookRes) {
|
||||||
const hookRes = await hook.handler(internalContext);
|
if ("responseHeader" in hookRes) {
|
||||||
if (hookRes) {
|
const headers = hookRes.responseHeader as Headers;
|
||||||
if ("responseHeader" in hookRes) {
|
internalContext.responseHeader = headers;
|
||||||
const headers = hookRes.responseHeader as Headers;
|
} else {
|
||||||
internalContext.responseHeader = headers;
|
internalContext.context.returned = hookRes;
|
||||||
} else {
|
|
||||||
internalContext.context.returned = hookRes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof APIError) {
|
|
||||||
internalContext.context.returned = e;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof APIError) {
|
||||||
|
internalContext.context.returned = e;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { BetterAuthOptions } from ".";
|
import type { GenericEndpointContext } from "./context";
|
||||||
|
import type { BetterAuthOptions } from "./options";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter where clause
|
* Adapter where clause
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import type { Dialect, Kysely, MysqlPool, PostgresPool } from "kysely";
|
import type { Dialect, Kysely, MysqlPool, PostgresPool } from "kysely";
|
||||||
import type { Account, Session, User, Verification } from "../db/schema";
|
import type { Account, Session, User, Verification } from "../db/schema";
|
||||||
import type { BetterAuthPlugin } from ".";
|
import type {
|
||||||
|
BetterAuthPlugin,
|
||||||
|
HookAfterHandler,
|
||||||
|
HookBeforeHandler,
|
||||||
|
} from "./plugins";
|
||||||
import type { SocialProviderList, SocialProviders } from "../social-providers";
|
import type { SocialProviderList, SocialProviders } from "../social-providers";
|
||||||
import type { AdapterInstance, SecondaryStorage } from ".";
|
import type { AdapterInstance, SecondaryStorage, Where } from "./adapter";
|
||||||
import type { KyselyDatabaseType } from "../adapters/kysely-adapter/types";
|
import type { KyselyDatabaseType } from "../adapters/kysely-adapter/types";
|
||||||
import type { FieldAttribute } from "../db";
|
import type { FieldAttribute } from "../db";
|
||||||
import type { Models, RateLimit } from "./models";
|
import type { Models, RateLimit } from "./models";
|
||||||
@@ -586,8 +590,11 @@ export type BetterAuthOptions = {
|
|||||||
* operations.
|
* operations.
|
||||||
*/
|
*/
|
||||||
databaseHooks?: {
|
databaseHooks?: {
|
||||||
|
/**
|
||||||
|
* User hooks
|
||||||
|
*/
|
||||||
user?: {
|
user?: {
|
||||||
[key in "create" | "update"]?: {
|
create?: {
|
||||||
/**
|
/**
|
||||||
* Hook that is called before a user is created.
|
* Hook that is called before a user is created.
|
||||||
* if the hook returns false, the user will not be created.
|
* if the hook returns false, the user will not be created.
|
||||||
@@ -605,12 +612,33 @@ export type BetterAuthOptions = {
|
|||||||
*/
|
*/
|
||||||
after?: (user: User) => Promise<void>;
|
after?: (user: User) => Promise<void>;
|
||||||
};
|
};
|
||||||
};
|
update?: {
|
||||||
session?: {
|
|
||||||
[key in "create" | "update"]?: {
|
|
||||||
/**
|
/**
|
||||||
* Hook that is called before a user is created.
|
* Hook that is called before a user is updated.
|
||||||
* if the hook returns false, the user will not be created.
|
* 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>) => Promise<
|
||||||
|
| boolean
|
||||||
|
| void
|
||||||
|
| {
|
||||||
|
data: User & Record<string, any>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
/**
|
||||||
|
* Hook that is called after a user is updated.
|
||||||
|
*/
|
||||||
|
after?: (user: User) => Promise<void>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Session Hook
|
||||||
|
*/
|
||||||
|
session?: {
|
||||||
|
create?: {
|
||||||
|
/**
|
||||||
|
* Hook that is called before a session 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
|
* If the hook returns an object, it'll be used instead of the original data
|
||||||
*/
|
*/
|
||||||
before?: (session: Session) => Promise<
|
before?: (session: Session) => Promise<
|
||||||
@@ -621,16 +649,40 @@ export type BetterAuthOptions = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
/**
|
/**
|
||||||
* Hook that is called after a user is created.
|
* Hook that is called after a session is updated.
|
||||||
|
*/
|
||||||
|
after?: (session: Session) => 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>) => Promise<
|
||||||
|
| boolean
|
||||||
|
| void
|
||||||
|
| {
|
||||||
|
data: Session & Record<string, any>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
/**
|
||||||
|
* Hook that is called after a session is updated.
|
||||||
*/
|
*/
|
||||||
after?: (session: Session) => Promise<void>;
|
after?: (session: Session) => Promise<void>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Account Hook
|
||||||
|
*/
|
||||||
account?: {
|
account?: {
|
||||||
[key in "create" | "update"]?: {
|
create?: {
|
||||||
/**
|
/**
|
||||||
* Hook that is called before a user is created.
|
* Hook that is called before a account is created.
|
||||||
* If the hook returns false, the user will not be 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
|
* If the hook returns an object, it'll be used instead of the original data
|
||||||
*/
|
*/
|
||||||
before?: (account: Account) => Promise<
|
before?: (account: Account) => Promise<
|
||||||
@@ -641,16 +693,40 @@ export type BetterAuthOptions = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
/**
|
/**
|
||||||
* Hook that is called after a user is created.
|
* Hook that is called after a account is created.
|
||||||
|
*/
|
||||||
|
after?: (account: Account) => 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>) => Promise<
|
||||||
|
| boolean
|
||||||
|
| void
|
||||||
|
| {
|
||||||
|
data: Account & Record<string, any>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
/**
|
||||||
|
* Hook that is called after a account is updated.
|
||||||
*/
|
*/
|
||||||
after?: (account: Account) => Promise<void>;
|
after?: (account: Account) => Promise<void>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Verification Hook
|
||||||
|
*/
|
||||||
verification?: {
|
verification?: {
|
||||||
[key in "create" | "update"]: {
|
create?: {
|
||||||
/**
|
/**
|
||||||
* Hook that is called before a user is created.
|
* Hook that is called before a verification is created.
|
||||||
* if the hook returns false, the user will not be 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
|
* If the hook returns an object, it'll be used instead of the original data
|
||||||
*/
|
*/
|
||||||
before?: (verification: Verification) => Promise<
|
before?: (verification: Verification) => Promise<
|
||||||
@@ -661,7 +737,25 @@ export type BetterAuthOptions = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
/**
|
/**
|
||||||
* Hook that is called after a user is created.
|
* Hook that is called after a verification is created.
|
||||||
|
*/
|
||||||
|
after?: (verification: Verification) => 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>) => Promise<
|
||||||
|
| boolean
|
||||||
|
| void
|
||||||
|
| {
|
||||||
|
data: Verification & Record<string, any>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
/**
|
||||||
|
* Hook that is called after a verification is updated.
|
||||||
*/
|
*/
|
||||||
after?: (verification: Verification) => Promise<void>;
|
after?: (verification: Verification) => Promise<void>;
|
||||||
};
|
};
|
||||||
@@ -685,4 +779,17 @@ export type BetterAuthOptions = {
|
|||||||
*/
|
*/
|
||||||
onError?: (error: unknown, ctx: AuthContext) => void | Promise<void>;
|
onError?: (error: unknown, ctx: AuthContext) => void | Promise<void>;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
hooks?: {
|
||||||
|
/**
|
||||||
|
* Before a request is processed
|
||||||
|
*/
|
||||||
|
before?: HookBeforeHandler;
|
||||||
|
/**
|
||||||
|
* After a request is processed
|
||||||
|
*/
|
||||||
|
after?: HookAfterHandler;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,32 @@ export type PluginSchema = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type HookBeforeHandler = (context: HookEndpointContext) => Promise<
|
||||||
|
| void
|
||||||
|
| {
|
||||||
|
context?: Partial<HookEndpointContext>;
|
||||||
|
}
|
||||||
|
| Response
|
||||||
|
| {
|
||||||
|
response: Record<string, any>;
|
||||||
|
body: any;
|
||||||
|
_flag: "json";
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type HookAfterHandler = (context: HookEndpointContext) => Promise<
|
||||||
|
| void
|
||||||
|
| {
|
||||||
|
responseHeader?: Headers;
|
||||||
|
}
|
||||||
|
| Response
|
||||||
|
| {
|
||||||
|
response: Record<string, any>;
|
||||||
|
body: any;
|
||||||
|
_flag: "json";
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
export type BetterAuthPlugin = {
|
export type BetterAuthPlugin = {
|
||||||
id: LiteralString;
|
id: LiteralString;
|
||||||
/**
|
/**
|
||||||
@@ -55,18 +81,7 @@ export type BetterAuthPlugin = {
|
|||||||
hooks?: {
|
hooks?: {
|
||||||
before?: {
|
before?: {
|
||||||
matcher: (context: HookEndpointContext) => boolean;
|
matcher: (context: HookEndpointContext) => boolean;
|
||||||
handler: (context: HookEndpointContext) => Promise<
|
handler: HookBeforeHandler;
|
||||||
| void
|
|
||||||
| {
|
|
||||||
context?: Partial<HookEndpointContext>;
|
|
||||||
}
|
|
||||||
| Response
|
|
||||||
| {
|
|
||||||
response: Record<string, any>;
|
|
||||||
body: any;
|
|
||||||
_flag: "json";
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
}[];
|
}[];
|
||||||
after?: {
|
after?: {
|
||||||
matcher: (
|
matcher: (
|
||||||
@@ -75,18 +90,7 @@ export type BetterAuthPlugin = {
|
|||||||
endpoint: Endpoint;
|
endpoint: Endpoint;
|
||||||
}>,
|
}>,
|
||||||
) => boolean;
|
) => boolean;
|
||||||
handler: (context: HookEndpointContext) => Promise<
|
handler: HookAfterHandler;
|
||||||
| void
|
|
||||||
| {
|
|
||||||
responseHeader?: Headers;
|
|
||||||
}
|
|
||||||
| Response
|
|
||||||
| {
|
|
||||||
response: Record<string, any>;
|
|
||||||
body: any;
|
|
||||||
_flag: "json";
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user