From cdd897abbc89c96c0e2c13bcad3ab33fba1f6b2f Mon Sep 17 00:00:00 2001 From: Lorentz Date: Mon, 16 Feb 2026 12:55:57 +0000 Subject: [PATCH] feat: implement custom login system to replace Better Auth - Add custom login API route with bcrypt password verification - Update auth client to use custom endpoints - Simplify login page to use new auth client - Remove Better Auth dependencies that were causing issues --- src/app/(auth)/login/page.tsx | 22 ++----- src/app/api/auth/login/route.ts | 109 ++++++++++++++++++++++++++++++++ src/lib/auth-client.ts | 31 +++++++-- src/lib/auth.ts | 9 +-- 4 files changed, 142 insertions(+), 29 deletions(-) create mode 100644 src/app/api/auth/login/route.ts diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index f7cf4df..5d6a802 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -29,25 +29,13 @@ export default function LoginPage() { setIsLoading(true); try { - const result = await signIn.email({ - email, - password, - callbackURL: '/dashboard', - }); - - if (result.error) { - toast({ - title: 'Login failed', - description: result.error.message || 'Invalid email or password', - variant: 'destructive', - }); - } else { - router.push('/dashboard'); - } + await signIn(email, password); + router.push('/dashboard'); } catch (error) { toast({ - title: 'Error', - description: 'An error occurred during login. Please try again.', + title: 'Login failed', + description: + error instanceof Error ? error.message : 'Invalid email or password', variant: 'destructive', }); } finally { diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts new file mode 100644 index 0000000..ce2e0b2 --- /dev/null +++ b/src/app/api/auth/login/route.ts @@ -0,0 +1,109 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { db } from '@/lib/db'; +import bcrypt from 'bcryptjs'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { email, password } = body; + + if (!email || !password) { + return NextResponse.json( + { error: 'Email and password are required' }, + { status: 400 } + ); + } + + // Find user + const user = await db.auth_user.findUnique({ + where: { email }, + include: { + quest_user: { + include: { + companies: { + include: { + company: true, + }, + }, + }, + }, + }, + }); + + if (!user) { + return NextResponse.json( + { error: 'Invalid credentials' }, + { status: 401 } + ); + } + + // Get password from auth_account + const account = await db.auth_account.findFirst({ + where: { + user_id: user.id, + provider_id: 'credential', + }, + }); + + if (!account || !account.password) { + return NextResponse.json( + { error: 'Invalid credentials' }, + { status: 401 } + ); + } + + // Verify password + const valid = await bcrypt.compare(password, account.password); + if (!valid) { + return NextResponse.json( + { error: 'Invalid credentials' }, + { status: 401 } + ); + } + + // Create session + const token = crypto.randomUUID(); + const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days + + const session = await db.auth_session.create({ + data: { + user_id: user.id, + token, + expires_at: expiresAt, + ip_address: request.headers.get('x-forwarded-for') || 'unknown', + user_agent: request.headers.get('user-agent') || 'unknown', + }, + }); + + // Set cookie + const response = NextResponse.json( + { + success: true, + user: { + id: user.id, + email: user.email, + name: user.name, + }, + }, + { status: 200 } + ); + + response.cookies.set({ + name: 'session_token', + value: token, + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + expires: expiresAt, + path: '/', + }); + + return response; + } catch (error) { + console.error('Login error:', error); + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 } + ); + } +} diff --git a/src/lib/auth-client.ts b/src/lib/auth-client.ts index 0bdb097..c24f2d8 100644 --- a/src/lib/auth-client.ts +++ b/src/lib/auth-client.ts @@ -1,9 +1,30 @@ 'use client'; -import { createAuthClient } from 'better-auth/react'; +export async function signIn(email: string, password: string) { + const response = await fetch('/api/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email, password }), + credentials: 'include', + }); -export const authClient = createAuthClient({ - baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL || 'http://localhost:3000', -}); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Login failed'); + } -export const { signIn, signOut, signUp, useSession } = authClient; + return await response.json(); +} + +export async function signOut() { + const response = await fetch('/api/auth/logout', { + method: 'POST', + credentials: 'include', + }); + + if (response.ok) { + window.location.href = '/login'; + } +} diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 4ef1de7..dc1d0ce 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -2,16 +2,11 @@ import { betterAuth } from 'better-auth'; import { prismaAdapter } from 'better-auth/adapters/prisma'; import { db } from './db'; +// Use Prisma adapter with our alias models (user, session, account) +// These map to auth_user, auth_session, auth_account tables export const auth = betterAuth({ database: prismaAdapter(db, { provider: 'postgresql', - // Map our custom table names to Better Auth models - schema: { - user: 'auth_user', - session: 'auth_session', - account: 'auth_account', - verification: 'auth_verification', - }, }), emailAndPassword: { enabled: true,