mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 11:24:57 -05:00
fix: chain plugin onRequest hooks properly
This commit is contained in:
@@ -5,6 +5,7 @@ import type {
|
||||
} from "@better-auth/core";
|
||||
import { createAuthMiddleware } from "@better-auth/core/api";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { getTestInstance } from "../test-utils/test-instance";
|
||||
import { getEndpoints } from "./index";
|
||||
|
||||
describe("getEndpoints", () => {
|
||||
@@ -55,3 +56,130 @@ describe("getEndpoints", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("onRequest chain", () => {
|
||||
it("should execute all plugins onRequest handlers in chain", async () => {
|
||||
const onRequestOrder: string[] = [];
|
||||
|
||||
const pluginA: BetterAuthPlugin = {
|
||||
id: "plugin-a",
|
||||
async onRequest(request, _ctx) {
|
||||
onRequestOrder.push("plugin-a");
|
||||
// Return a modified request - this should NOT stop the chain
|
||||
const newHeaders = new Headers(request.headers);
|
||||
newHeaders.set("x-plugin-a", "true");
|
||||
return {
|
||||
request: new Request(request, { headers: newHeaders }),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const pluginB: BetterAuthPlugin = {
|
||||
id: "plugin-b",
|
||||
async onRequest(request, _ctx) {
|
||||
onRequestOrder.push("plugin-b");
|
||||
// This should also execute and see the modified request from plugin-a
|
||||
const newHeaders = new Headers(request.headers);
|
||||
newHeaders.set("x-plugin-b", "true");
|
||||
return {
|
||||
request: new Request(request, { headers: newHeaders }),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const pluginC: BetterAuthPlugin = {
|
||||
id: "plugin-c",
|
||||
async onRequest(_request, _ctx) {
|
||||
onRequestOrder.push("plugin-c");
|
||||
// Just observe, don't modify
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
const { client, testUser } = await getTestInstance({
|
||||
plugins: [pluginA, pluginB, pluginC],
|
||||
});
|
||||
|
||||
await client.signIn.email({
|
||||
email: testUser.email,
|
||||
password: testUser.password,
|
||||
});
|
||||
|
||||
// All three plugins should have their onRequest called
|
||||
expect(onRequestOrder).toEqual(["plugin-a", "plugin-b", "plugin-c"]);
|
||||
});
|
||||
|
||||
it("should pass modified request from previous plugin to next plugin", async () => {
|
||||
let pluginBReceivedHeader: string | null = null;
|
||||
|
||||
const pluginA: BetterAuthPlugin = {
|
||||
id: "plugin-a",
|
||||
async onRequest(request, _ctx) {
|
||||
const newHeaders = new Headers(request.headers);
|
||||
newHeaders.set("x-from-plugin-a", "hello");
|
||||
return {
|
||||
request: new Request(request, { headers: newHeaders }),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const pluginB: BetterAuthPlugin = {
|
||||
id: "plugin-b",
|
||||
async onRequest(request, _ctx) {
|
||||
// Should receive the header set by plugin-a
|
||||
pluginBReceivedHeader = request.headers.get("x-from-plugin-a");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
const { client, testUser } = await getTestInstance({
|
||||
plugins: [pluginA, pluginB],
|
||||
});
|
||||
|
||||
await client.signIn.email({
|
||||
email: testUser.email,
|
||||
password: testUser.password,
|
||||
});
|
||||
|
||||
expect(pluginBReceivedHeader).toBe("hello");
|
||||
});
|
||||
|
||||
it("should stop chain when response is returned", async () => {
|
||||
const onRequestOrder: string[] = [];
|
||||
|
||||
const pluginA: BetterAuthPlugin = {
|
||||
id: "plugin-a",
|
||||
async onRequest(_request, _ctx) {
|
||||
onRequestOrder.push("plugin-a");
|
||||
// Return a response - this SHOULD stop the chain
|
||||
return {
|
||||
response: new Response("Blocked by plugin-a", { status: 403 }),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const pluginB: BetterAuthPlugin = {
|
||||
id: "plugin-b",
|
||||
async onRequest(_request, _ctx) {
|
||||
onRequestOrder.push("plugin-b");
|
||||
return {
|
||||
response: new Response("ok", { status: 200 }),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const { client, testUser } = await getTestInstance({
|
||||
plugins: [pluginA, pluginB],
|
||||
});
|
||||
|
||||
const result = await client.signIn.email({
|
||||
email: testUser.email,
|
||||
password: testUser.password,
|
||||
});
|
||||
|
||||
// Only plugin-a should execute, plugin-b should NOT execute
|
||||
expect(onRequestOrder).toEqual(["plugin-a"]);
|
||||
// Response should be from plugin-a
|
||||
expect(result.error?.status).toBe(403);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -284,25 +284,26 @@ export const router = <Option extends BetterAuthOptions>(
|
||||
if (disabledPaths.includes(normalizedPath)) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
|
||||
let currentRequest = req;
|
||||
for (const plugin of ctx.options.plugins || []) {
|
||||
if (plugin.onRequest) {
|
||||
const response = await plugin.onRequest(req, ctx);
|
||||
const response = await plugin.onRequest(currentRequest, ctx);
|
||||
if (response && "response" in response) {
|
||||
return response.response;
|
||||
}
|
||||
if (response && "request" in response) {
|
||||
const rateLimitResponse = await onRequestRateLimit(
|
||||
response.request,
|
||||
ctx,
|
||||
);
|
||||
if (rateLimitResponse) {
|
||||
return rateLimitResponse;
|
||||
}
|
||||
return response.request;
|
||||
currentRequest = response.request;
|
||||
}
|
||||
}
|
||||
}
|
||||
return onRequestRateLimit(req, ctx);
|
||||
|
||||
const rateLimitResponse = await onRequestRateLimit(currentRequest, ctx);
|
||||
if (rateLimitResponse) {
|
||||
return rateLimitResponse;
|
||||
}
|
||||
|
||||
return currentRequest;
|
||||
},
|
||||
async onResponse(res) {
|
||||
for (const plugin of ctx.options.plugins || []) {
|
||||
|
||||
Reference in New Issue
Block a user