mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 11:24:57 -05:00
fix(magic-link): redirect to errorCallbackURL on session creation errors
The verify endpoint already forwards an APIError from a rejected user.create.before hook to errorCallbackURL, but the createSession call right after it was unguarded. A session.create.before hook that throws an APIError (e.g. the admin plugin's BANNED_USER) escaped as a raw error response instead of redirecting, unlike the createUser path a few lines above. Wrap createSession in the same try/catch pattern used for createUser.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"better-auth": patch
|
||||
---
|
||||
|
||||
Fix `magic-link` verify redirecting to `errorCallbackURL` when a `session.create.before` hook rejects with an `APIError` (e.g. the admin plugin's `BANNED_USER`), instead of letting the raw error response escape the auth server.
|
||||
@@ -444,9 +444,19 @@ export const magicLink = (options: MagicLinkOptions) => {
|
||||
user = promotedUser;
|
||||
}
|
||||
|
||||
const session = await ctx.context.internalAdapter.createSession(
|
||||
user.id,
|
||||
);
|
||||
let session: Awaited<
|
||||
ReturnType<typeof ctx.context.internalAdapter.createSession>
|
||||
> | null;
|
||||
try {
|
||||
session = await ctx.context.internalAdapter.createSession(user.id);
|
||||
} catch (e) {
|
||||
// Browser flow: forward a gate rejection's code to the error
|
||||
// URL instead of surfacing a raw API error.
|
||||
if (isAPIError(e) && e.body?.code) {
|
||||
redirectWithError(e.body.code, e.body.message);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
redirectWithError("failed_to_create_session");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createAuthClient } from "../../client";
|
||||
import { getTestInstance } from "../../test-utils/test-instance";
|
||||
import { admin } from "../admin";
|
||||
import { magicLink } from ".";
|
||||
import { magicLinkClient } from "./client";
|
||||
import { defaultKeyHasher } from "./utils";
|
||||
@@ -604,6 +605,61 @@ describe("magic link verify origin validation", async () => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A `session.create.before` hook that rejects with an `APIError` (e.g. the
|
||||
* admin plugin banning the user) must be forwarded to `errorCallbackURL` the
|
||||
* same way a `user.create.before` rejection already is on this endpoint,
|
||||
* instead of escaping verify as a raw error response from the auth server.
|
||||
*
|
||||
* @see https://github.com/better-auth/better-auth/issues/7406
|
||||
*/
|
||||
describe("magic link verify session creation errors", async () => {
|
||||
it("redirects to errorCallbackURL when session creation is rejected", async () => {
|
||||
let verificationEmail: VerificationEmail = {
|
||||
email: "",
|
||||
token: "",
|
||||
url: "",
|
||||
};
|
||||
const { auth, testUser, db } = await getTestInstance({
|
||||
trustedOrigins: ["http://localhost:5173"],
|
||||
plugins: [
|
||||
admin(),
|
||||
magicLink({
|
||||
async sendMagicLink(data) {
|
||||
verificationEmail = data;
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const user = await db.findOne<{ id: string }>({
|
||||
model: "user",
|
||||
where: [{ field: "email", value: testUser.email }],
|
||||
});
|
||||
await db.update({
|
||||
model: "user",
|
||||
where: [{ field: "id", value: user!.id }],
|
||||
update: { banned: true },
|
||||
});
|
||||
|
||||
await auth.api.signInMagicLink({
|
||||
body: {
|
||||
email: testUser.email,
|
||||
callbackURL: "http://localhost:5173/",
|
||||
errorCallbackURL: "http://localhost:5173/sign-in",
|
||||
},
|
||||
headers: new Headers(),
|
||||
});
|
||||
|
||||
const res = await auth.handler(new Request(verificationEmail.url));
|
||||
expect(res.status).toBe(302);
|
||||
expect(res.headers.get("location")).toContain(
|
||||
"http://localhost:5173/sign-in",
|
||||
);
|
||||
expect(res.headers.get("location")).toContain("error=BANNED_USER");
|
||||
});
|
||||
});
|
||||
|
||||
describe("magic link storeToken", async () => {
|
||||
it("should store token in hashed", async () => {
|
||||
let verificationEmail: VerificationEmail = {
|
||||
|
||||
Reference in New Issue
Block a user