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