mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 04:19:26 +00:00
fix: better logs on validation fail for oauth (#100)
* fix: better errors on phone number plugin * fix: add validation log on oauth
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { organization, passkey, twoFactor } from "better-auth/plugins";
|
||||
import {
|
||||
organization,
|
||||
passkey,
|
||||
phoneNumber,
|
||||
twoFactor,
|
||||
} from "better-auth/plugins";
|
||||
import { reactInvitationEmail } from "./email/invitation";
|
||||
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
||||
import { reactResetPasswordEmail } from "./email/rest-password";
|
||||
@@ -24,6 +29,7 @@ export const auth = betterAuth({
|
||||
},
|
||||
sendEmailVerificationOnSignUp: true,
|
||||
async sendVerificationEmail(email, url) {
|
||||
console.log("Sending verification email to", email);
|
||||
const res = await resend.emails.send({
|
||||
from,
|
||||
to: to || email,
|
||||
@@ -34,6 +40,14 @@ export const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
phoneNumber({
|
||||
otp: {
|
||||
sendOTP(phoneNumber, code) {
|
||||
console.log(`Sending OTP to ${phoneNumber}: ${code}`);
|
||||
},
|
||||
sendOTPonSignUp: true,
|
||||
},
|
||||
}),
|
||||
organization({
|
||||
async sendInvitationEmail(data) {
|
||||
const res = await resend.emails.send({
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.0",
|
||||
"consola": "^3.2.3",
|
||||
"date-fns": "^3.6.0",
|
||||
"embla-carousel-react": "^8.2.1",
|
||||
"framer-motion": "^11.5.4",
|
||||
|
||||
@@ -79,6 +79,7 @@ export const callbackOAuth = createAuthEndpoint(
|
||||
const { callbackURL, currentURL, dontRememberMe } = parsedState.data;
|
||||
|
||||
if (!user || data.success === false) {
|
||||
logger.error("Unable to get user info", data.error);
|
||||
throw c.redirect(
|
||||
`${c.context.baseURL}/error?error=oauth_validation_failed`,
|
||||
);
|
||||
|
||||
@@ -23,53 +23,93 @@ export const signUpEmail = createAuthEndpoint(
|
||||
},
|
||||
async (ctx) => {
|
||||
if (!ctx.context.options.emailAndPassword?.enabled) {
|
||||
return ctx.json(null, {
|
||||
return ctx.json(
|
||||
{
|
||||
user: null,
|
||||
session: null,
|
||||
error: {
|
||||
message: "Email and password is not enabled",
|
||||
},
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
body: {
|
||||
message: "Email and password is not enabled",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
const { name, email, password, image } = ctx.body;
|
||||
const isValidEmail = z.string().email().safeParse(email);
|
||||
if (!isValidEmail.success) {
|
||||
return ctx.json(null, {
|
||||
return ctx.json(
|
||||
{
|
||||
user: null,
|
||||
session: null,
|
||||
error: {
|
||||
message: "Invalid email address",
|
||||
},
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
body: {
|
||||
message: "Invalid email address",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
||||
if (password.length < minPasswordLength) {
|
||||
ctx.context.logger.error("Password is too short");
|
||||
return ctx.json(null, {
|
||||
return ctx.json(
|
||||
{
|
||||
user: null,
|
||||
session: null,
|
||||
error: {
|
||||
message: "Password is too short",
|
||||
},
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
body: { message: "Password is too short" },
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
|
||||
if (password.length > maxPasswordLength) {
|
||||
ctx.context.logger.error("Password is too long");
|
||||
return ctx.json(null, {
|
||||
return ctx.json(
|
||||
{
|
||||
user: null,
|
||||
session: null,
|
||||
error: {
|
||||
message: "Password is too long",
|
||||
},
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
body: { message: "Password is too long" },
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const dbUser = await ctx.context.internalAdapter.findUserByEmail(email);
|
||||
/**
|
||||
* hash first to avoid timing attacks
|
||||
*/
|
||||
const hash = await ctx.context.password.hash(password);
|
||||
if (dbUser?.user) {
|
||||
return ctx.json(null, {
|
||||
return ctx.json(
|
||||
{
|
||||
user: null,
|
||||
session: null,
|
||||
error: {
|
||||
message: "User already exists",
|
||||
},
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
body: {
|
||||
message: "User already exists",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
const createdUser = await ctx.context.internalAdapter.createUser({
|
||||
id: generateRandomString(32, alphabet("a-z", "0-9", "A-Z")),
|
||||
@@ -81,16 +121,26 @@ export const signUpEmail = createAuthEndpoint(
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
if (!createdUser) {
|
||||
return ctx.json(null, {
|
||||
return ctx.json(
|
||||
{
|
||||
user: null,
|
||||
session: null,
|
||||
error: {
|
||||
message: "Could not create user",
|
||||
},
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
body: {
|
||||
message: "Could not create user",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Link the account to the user
|
||||
*/
|
||||
const hash = await ctx.context.password.hash(password);
|
||||
await ctx.context.internalAdapter.linkAccount({
|
||||
id: generateRandomString(32, alphabet("a-z", "0-9", "A-Z")),
|
||||
userId: createdUser.id,
|
||||
@@ -103,12 +153,21 @@ export const signUpEmail = createAuthEndpoint(
|
||||
ctx.request,
|
||||
);
|
||||
if (!session) {
|
||||
return ctx.json(null, {
|
||||
return ctx.json(
|
||||
{
|
||||
user: null,
|
||||
session: null,
|
||||
error: {
|
||||
message: "Could not create session",
|
||||
},
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
body: {
|
||||
message: "Could not create session",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
await setSessionCookie(ctx, session.id);
|
||||
if (ctx.context.options.emailAndPassword.sendEmailVerificationOnSignUp) {
|
||||
@@ -131,6 +190,7 @@ export const signUpEmail = createAuthEndpoint(
|
||||
{
|
||||
user: createdUser,
|
||||
session,
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
body: ctx.body.callbackURL
|
||||
|
||||
@@ -10,13 +10,7 @@ import { getDate } from "../../utils/date";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { setSessionCookie } from "../../utils/cookies";
|
||||
|
||||
interface OTP {
|
||||
code: string;
|
||||
phoneNumber: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
interface UserWithPhoneNumber extends User {
|
||||
export interface UserWithPhoneNumber extends User {
|
||||
phoneNumber: string;
|
||||
phoneNumberVerified: boolean;
|
||||
}
|
||||
@@ -46,6 +40,12 @@ export const phoneNumber = (options?: {
|
||||
expiresIn?: number;
|
||||
};
|
||||
enableAutoSignIn?: boolean;
|
||||
/**
|
||||
* Function to validate phone number
|
||||
*
|
||||
* by default any string is accepted
|
||||
*/
|
||||
phoneNumberValidator?: (phoneNumber: string) => boolean;
|
||||
}) => {
|
||||
const opts = {
|
||||
phoneNumber: "phoneNumber",
|
||||
@@ -171,19 +171,77 @@ export const phoneNumber = (options?: {
|
||||
}),
|
||||
},
|
||||
async (ctx) => {
|
||||
if (
|
||||
options?.phoneNumberValidator &&
|
||||
!options.phoneNumberValidator(ctx.body.phoneNumber)
|
||||
) {
|
||||
return ctx.json(
|
||||
{
|
||||
user: null,
|
||||
session: null,
|
||||
error: {
|
||||
message: "Invalid phone number",
|
||||
},
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
body: {
|
||||
message: "Invalid phone number",
|
||||
status: 400,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const existing = await ctx.context.adapter.findOne<User>({
|
||||
model: ctx.context.tables.user.tableName,
|
||||
where: [
|
||||
{
|
||||
field: opts.phoneNumber,
|
||||
value: ctx.body.phoneNumber,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (existing) {
|
||||
return ctx.json(
|
||||
{
|
||||
user: null,
|
||||
session: null,
|
||||
error: {
|
||||
message: "Phone number already exists",
|
||||
},
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
body: {
|
||||
message: "Phone number already exists",
|
||||
status: 400,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
const res = await signUpEmail({
|
||||
...ctx,
|
||||
//@ts-expect-error
|
||||
options: {
|
||||
...ctx.context.options,
|
||||
},
|
||||
_flag: undefined,
|
||||
});
|
||||
if (!res) {
|
||||
return ctx.json(null, {
|
||||
if (res.error) {
|
||||
return ctx.json(
|
||||
{
|
||||
user: null,
|
||||
session: null,
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
body: {
|
||||
message: "Sign up failed",
|
||||
message: res.error.message,
|
||||
status: 400,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
if (options?.otp?.sendOTPonSignUp) {
|
||||
if (!options.otp.sendOTP) {
|
||||
@@ -200,6 +258,7 @@ export const phoneNumber = (options?: {
|
||||
});
|
||||
await options.otp.sendOTP(ctx.body.phoneNumber, code);
|
||||
}
|
||||
|
||||
const updated = await ctx.context.internalAdapter.updateUserByEmail(
|
||||
res.user.email,
|
||||
{
|
||||
|
||||
@@ -117,8 +117,6 @@ export const github = (options: GithubOptions) => {
|
||||
email: profile.email,
|
||||
image: profile.avatar_url,
|
||||
emailVerified,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
data: profile,
|
||||
};
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -170,6 +170,9 @@ importers:
|
||||
cmdk:
|
||||
specifier: 1.0.0
|
||||
version: 1.0.0(@types/react-dom@18.3.0)(@types/react@18.3.9)(react-dom@19.0.0-rc-7771d3a7-20240827)(react@19.0.0-rc-7771d3a7-20240827)
|
||||
consola:
|
||||
specifier: ^3.2.3
|
||||
version: 3.2.3
|
||||
date-fns:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
|
||||
Reference in New Issue
Block a user