feat: Migrate from NextAuth to Better Auth

Backend changes:
- Add better-auth and pg packages
- Create Better Auth instance with PostgreSQL adapter
- Add Better Auth route handler at /api/auth/*
- Create migration script for Better Auth database schema
- Update main index to use Better Auth routes instead of custom auth
- Configure email/password and OAuth (GitHub/Google) providers

Frontend changes:
- Add better-auth client
- Create Better Auth client instance configuration
- Update lib/auth.ts to use Better Auth session
- Rewrite sign-in page with Better Auth methods
- Rewrite sign-up page with Better Auth methods
- Remove NextAuth route handler

Documentation:
- Add comprehensive migration guide with setup instructions
- Document environment variables and API endpoints
- Include testing checklist and rollback plan

Benefits:
- Unified authentication for both Elysia backend and Next.js frontend
- Database-backed sessions (more secure than JWT)
- Better TypeScript support
- Extensible plugin system for future features
- Active development and frequent updates
This commit is contained in:
Francesco
2026-02-20 04:13:26 +00:00
parent 73282c71af
commit f8356e0945
12 changed files with 583 additions and 154 deletions

35
backend/src/auth.ts Normal file
View File

@@ -0,0 +1,35 @@
import { betterAuth } from "better-auth";
import { Pool } from "pg";
export const auth = betterAuth({
database: new Pool({
connectionString: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/fiscal',
}),
emailAndPassword: {
enabled: true,
},
socialProviders: {
github: {
clientId: process.env.GITHUB_ID as string,
clientSecret: process.env.GITHUB_SECRET as string,
},
google: {
clientId: process.env.GOOGLE_ID as string,
clientSecret: process.env.GOOGLE_SECRET as string,
},
},
user: {
modelName: "users",
additionalFields: {
name: {
type: "string",
required: false,
},
},
},
advanced: {
database: {
generateId: false, // Use PostgreSQL serial for users table
},
},
});

View File

@@ -0,0 +1,109 @@
import { db } from './db';
async function migrateToBetterAuth() {
console.log('Migrating to Better Auth schema...');
try {
// Add Better Auth columns to users table
await db`
ALTER TABLE users
ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT FALSE
`;
await db`
ALTER TABLE users
ADD COLUMN IF NOT EXISTS image TEXT
`;
console.log('✅ Added Better Auth columns to users table');
// Create session table
await db`
CREATE TABLE IF NOT EXISTS session (
id TEXT PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
token TEXT NOT NULL UNIQUE,
expires_at TIMESTAMP NOT NULL,
ip_address TEXT,
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
console.log('✅ Created session table');
// Create account table
await db`
CREATE TABLE IF NOT EXISTS account (
id TEXT PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
account_id TEXT NOT NULL,
provider_id TEXT NOT NULL,
access_token TEXT,
refresh_token TEXT,
access_token_expires_at TIMESTAMP,
refresh_token_expires_at TIMESTAMP,
scope TEXT,
id_token TEXT,
password TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, provider_id, account_id)
)
`;
console.log('✅ Created account table');
// Create verification table
await db`
CREATE TABLE IF NOT EXISTS verification (
id TEXT PRIMARY KEY,
identifier TEXT NOT NULL,
value TEXT NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
console.log('✅ Created verification table');
// Create indexes
await db`CREATE INDEX IF NOT EXISTS idx_session_user_id ON session(user_id)`;
await db`CREATE INDEX IF NOT EXISTS idx_session_token ON session(token)`;
await db`CREATE INDEX IF NOT EXISTS idx_session_expires_at ON session(expires_at)`;
await db`CREATE INDEX IF NOT EXISTS idx_account_user_id ON account(user_id)`;
await db`CREATE INDEX IF NOT EXISTS idx_account_provider_id ON account(provider_id)`;
await db`CREATE INDEX IF NOT EXISTS idx_verification_identifier ON verification(identifier)`;
await db`CREATE INDEX IF NOT EXISTS idx_verification_expires_at ON verification(expires_at)`;
console.log('✅ Created indexes');
// Migrate existing users to account table for credential auth
await db`
INSERT INTO account (id, user_id, account_id, provider_id, password, created_at, updated_at)
SELECT
gen_random_uuid(),
id,
id::text,
'credential',
password,
created_at,
updated_at
FROM users
WHERE password IS NOT NULL
ON CONFLICT DO NOTHING
`;
console.log('✅ Migrated existing users to account table');
console.log('✅ Better Auth migration completed!');
process.exit(0);
} catch (error) {
console.error('❌ Migration failed:', error);
process.exit(1);
}
}
migrateToBetterAuth();

View File

@@ -9,8 +9,8 @@ import { db } from './db';
import { filingsRoutes } from './routes/filings';
import { portfolioRoutes } from './routes/portfolio';
import { openclawRoutes } from './routes/openclaw';
import { authRoutes } from './routes/auth';
import { watchlistRoutes } from './routes/watchlist';
import { betterAuthRoutes } from './routes/better-auth';
const app = new Elysia({
prefix: '/api'
@@ -29,7 +29,7 @@ const app = new Elysia({
}
}
}))
.use(authRoutes)
.use(betterAuthRoutes)
.use(filingsRoutes)
.use(portfolioRoutes)
.use(watchlistRoutes)

View File

@@ -0,0 +1,7 @@
import { Elysia } from 'elysia';
import { auth } from '../auth';
export const betterAuthRoutes = new Elysia()
.all('/api/auth/*', async ({ request }) => {
return auth.handler(request);
});