diff --git a/packages/better-auth/src/api/routes/sign-up.test.ts b/packages/better-auth/src/api/routes/sign-up.test.ts index 09d856bbad..44f0a545ae 100644 --- a/packages/better-auth/src/api/routes/sign-up.test.ts +++ b/packages/better-auth/src/api/routes/sign-up.test.ts @@ -265,13 +265,14 @@ describe("sign-up user enumeration protection", async () => { ); }); - it("should call onExistingUserSignUp when autoSignIn is false", async () => { + it("should call onExistingUserSignUp when autoSignIn is false and requireEmailVerification is true", async () => { const onExistingUserSignUp = vi.fn(); const { auth } = await getTestInstance( { emailAndPassword: { enabled: true, autoSignIn: false, + requireEmailVerification: true, onExistingUserSignUp, }, }, @@ -292,6 +293,33 @@ describe("sign-up user enumeration protection", async () => { expect(onExistingUserSignUp).toHaveBeenCalledTimes(1); }); + it("should not call onExistingUserSignUp when autoSignIn is false without requireEmailVerification", async () => { + const onExistingUserSignUp = vi.fn(); + const { auth } = await getTestInstance( + { + emailAndPassword: { + enabled: true, + autoSignIn: false, + onExistingUserSignUp, + }, + }, + { + disableTestUser: true, + }, + ); + + const body = { + email: "callback-autosignin-no-verify@test.com", + password: "password123", + name: "Callback AutoSignIn No Verify", + }; + + await auth.api.signUpEmail({ body }); + await expect(auth.api.signUpEmail({ body })).rejects.toThrow(); + + expect(onExistingUserSignUp).not.toHaveBeenCalled(); + }); + it("should not call onExistingUserSignUp when enumeration protection is inactive", async () => { const onExistingUserSignUp = vi.fn(); const { auth } = await getTestInstance( @@ -344,7 +372,7 @@ describe("sign-up user enumeration protection", async () => { expect(onExistingUserSignUp).not.toHaveBeenCalled(); }); - it("should return success for existing email when autoSignIn is disabled", async () => { + it("should throw for existing email when autoSignIn is disabled without requireEmailVerification", async () => { const { auth } = await getTestInstance( { emailAndPassword: { @@ -365,10 +393,32 @@ describe("sign-up user enumeration protection", async () => { await auth.api.signUpEmail({ body }); - const duplicatedSignUp = await auth.api.signUpEmail({ body }); + await expect(auth.api.signUpEmail({ body })).rejects.toThrow(); + }); - expect(duplicatedSignUp.token).toBeNull(); - expect(duplicatedSignUp.user.email).toBe(body.email); + it("should return token: null for new sign-up when autoSignIn is disabled", async () => { + const { auth } = await getTestInstance( + { + emailAndPassword: { + enabled: true, + autoSignIn: false, + }, + }, + { + disableTestUser: true, + }, + ); + + const res = await auth.api.signUpEmail({ + body: { + email: "new-auto-signin@test.com", + password: "password123", + name: "New User", + }, + }); + + expect(res.token).toBeNull(); + expect(res.user.email).toBe("new-auto-signin@test.com"); }); }); diff --git a/packages/better-auth/src/api/routes/sign-up.ts b/packages/better-auth/src/api/routes/sign-up.ts index 15f340edd9..5bf210714d 100644 --- a/packages/better-auth/src/api/routes/sign-up.ts +++ b/packages/better-auth/src/api/routes/sign-up.ts @@ -232,8 +232,10 @@ export const signUpEmail = () => ); } const shouldReturnGenericDuplicateResponse = - ctx.context.options.emailAndPassword.autoSignIn === false || ctx.context.options.emailAndPassword.requireEmailVerification; + const shouldSkipAutoSignIn = + ctx.context.options.emailAndPassword.autoSignIn === false || + shouldReturnGenericDuplicateResponse; const additionalUserFields = parseUserInput( ctx.context.options, rest, @@ -391,7 +393,7 @@ export const signUpEmail = () => } } - if (shouldReturnGenericDuplicateResponse) { + if (shouldSkipAutoSignIn) { return ctx.json({ token: null, user: parseUserOutput(ctx.context.options, createdUser) as User<