diff --git a/packages/better-auth/src/api/routes/sign-up.ts b/packages/better-auth/src/api/routes/sign-up.ts index 633273e632..3be303986d 100644 --- a/packages/better-auth/src/api/routes/sign-up.ts +++ b/packages/better-auth/src/api/routes/sign-up.ts @@ -11,6 +11,7 @@ import type { import type { toZod } from "../../types/to-zod"; import { parseUserInput } from "../../db/schema"; import { BASE_ERROR_CODES } from "../../error/codes"; +import { isDevelopment } from "../../utils/env"; export const signUpEmail = () => createAuthEndpoint( @@ -161,7 +162,9 @@ export const signUpEmail = () => }); } } catch (e) { - ctx.context.logger.error("Failed to create user", e); + if (isDevelopment) { + ctx.context.logger.error("Failed to create user", e); + } throw new APIError("UNPROCESSABLE_ENTITY", { message: BASE_ERROR_CODES.FAILED_TO_CREATE_USER, details: e, diff --git a/packages/better-auth/src/db/schema.ts b/packages/better-auth/src/db/schema.ts index 9463738aa8..046fe4f481 100644 --- a/packages/better-auth/src/db/schema.ts +++ b/packages/better-auth/src/db/schema.ts @@ -148,6 +148,10 @@ export function parseInputData>( parsedData[key] = fields[key].validator.input.parse(data[key]); continue; } + if (fields[key].transform?.input && data[key] !== undefined) { + parsedData[key] = fields[key].transform?.input(data[key]); + continue; + } parsedData[key] = data[key]; continue; } diff --git a/packages/better-auth/src/plugins/username/index.ts b/packages/better-auth/src/plugins/username/index.ts index d18ff54b67..6239179510 100644 --- a/packages/better-auth/src/plugins/username/index.ts +++ b/packages/better-auth/src/plugins/username/index.ts @@ -13,6 +13,7 @@ export const username = () => { INVALID_USERNAME_OR_PASSWORD: "invalid username or password", EMAIL_NOT_VERIFIED: "email not verified", UNEXPECTED_ERROR: "unexpected error", + USERNAME_IS_ALREADY_TAKEN: "username is already taken. please try another.", }; return { id: "username", @@ -163,10 +164,43 @@ export const username = () => { required: false, unique: true, returned: true, + transform: { + input(value) { + return value?.toString().toLowerCase(); + }, + }, }, }, }, }, + hooks: { + before: [ + { + matcher(context) { + return context.path === "/sign-up/email"; + }, + async handler(ctx) { + const username = ctx.body.username; + if (username) { + const user = await ctx.context.adapter.findOne({ + model: "user", + where: [ + { + field: "username", + value: username.toLowerCase(), + }, + ], + }); + if (user) { + throw new APIError("UNPROCESSABLE_ENTITY", { + message: ERROR_CODES.USERNAME_IS_ALREADY_TAKEN, + }); + } + } + }, + }, + ], + }, $ERROR_CODES: TWO_FACTOR_ERROR_CODES, } satisfies BetterAuthPlugin; }; diff --git a/packages/better-auth/src/plugins/username/username.test.ts b/packages/better-auth/src/plugins/username/username.test.ts index 390de9b1d0..63dfeb7a39 100644 --- a/packages/better-auth/src/plugins/username/username.test.ts +++ b/packages/better-auth/src/plugins/username/username.test.ts @@ -65,4 +65,14 @@ describe("username", async (it) => { }); expect(session?.user.username).toBe("new-username-2"); }); + + it("should fail on duplicate username", async () => { + const res = await client.signUp.email({ + email: "new-email-2@gamil.com", + username: "New-username-2", + password: "new-password", + name: "new-name", + }); + expect(res.error?.status).toBe(422); + }); });