🔧 Debugging Improvements: - Optimize setup-auth endpoint to avoid timeouts - Add table existence checks before operations - Simplify permission system for faster setup - Add environment variable validation - Create test-db endpoint for connection debugging 🚀 Performance Optimizations: - Break down large SQL queries into smaller chunks - Add early returns for already-setup scenarios - Reduce permission complexity during initial setup - Better error messages for troubleshooting 🧪 New Debug Endpoints: - /api/test-db - Test database connectivity - /api/setup-auth - Optimized schema setup These changes should resolve timeout issues and provide better debugging information for authentication problems.
205 lines
No EOL
5.6 KiB
TypeScript
205 lines
No EOL
5.6 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { Pool } from 'pg';
|
|
import bcrypt from 'bcryptjs';
|
|
import jwt from 'jsonwebtoken';
|
|
|
|
const pool = new Pool({
|
|
connectionString: process.env.DATABASE_URL,
|
|
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
|
});
|
|
|
|
interface RegisterRequest {
|
|
username: string;
|
|
email: string;
|
|
password: string;
|
|
firstName?: string;
|
|
lastName?: string;
|
|
}
|
|
|
|
export default async function handler(req: NextRequest) {
|
|
if (req.method !== 'POST') {
|
|
return new NextResponse(JSON.stringify({ error: 'Method not allowed' }), {
|
|
status: 405,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
// Check environment variables
|
|
if (!process.env.DATABASE_URL) {
|
|
return new NextResponse(JSON.stringify({
|
|
error: 'Database configuration missing',
|
|
details: 'DATABASE_URL environment variable not set'
|
|
}), {
|
|
status: 500,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
if (!process.env.JWT_SECRET) {
|
|
console.warn('JWT_SECRET not set, using fallback');
|
|
}
|
|
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
// Parse request body
|
|
let body: RegisterRequest;
|
|
try {
|
|
const bodyText = await req.text();
|
|
body = JSON.parse(bodyText);
|
|
} catch (parseError) {
|
|
return new NextResponse(JSON.stringify({
|
|
error: 'Invalid JSON in request body'
|
|
}), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
const { username, email, password, firstName, lastName } = body;
|
|
|
|
// Validate input
|
|
if (!username || !email || !password) {
|
|
return new NextResponse(JSON.stringify({
|
|
error: 'Username, email, and password are required'
|
|
}), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
if (password.length < 6) {
|
|
return new NextResponse(JSON.stringify({
|
|
error: 'Password must be at least 6 characters long'
|
|
}), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
// Check if users table exists
|
|
const tableCheck = await client.query(`
|
|
SELECT EXISTS (
|
|
SELECT FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = 'users'
|
|
);
|
|
`);
|
|
|
|
if (!tableCheck.rows[0].exists) {
|
|
return new NextResponse(JSON.stringify({
|
|
error: 'Database not initialized',
|
|
details: 'Please run the setup-auth endpoint first'
|
|
}), {
|
|
status: 500,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
// Check if user already exists
|
|
const existingUser = await client.query(
|
|
'SELECT id FROM users WHERE username = $1 OR email = $2',
|
|
[username, email]
|
|
);
|
|
|
|
if (existingUser.rows.length > 0) {
|
|
return new NextResponse(JSON.stringify({
|
|
error: 'Username or email already exists'
|
|
}), {
|
|
status: 409,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
// Hash password
|
|
const saltRounds = 12;
|
|
const passwordHash = await bcrypt.hash(password, saltRounds);
|
|
|
|
// Create user
|
|
const userResult = await client.query(`
|
|
INSERT INTO users (username, email, password_hash, first_name, last_name)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
RETURNING id, username, email, first_name, last_name, created_at
|
|
`, [username, email, passwordHash, firstName || null, lastName || null]);
|
|
|
|
const user = userResult.rows[0];
|
|
|
|
// Assign default 'user' role
|
|
const roleResult = await client.query(
|
|
'SELECT id FROM roles WHERE name = $1',
|
|
['user']
|
|
);
|
|
|
|
if (roleResult.rows.length > 0) {
|
|
await client.query(
|
|
'INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2)',
|
|
[user.id, roleResult.rows[0].id]
|
|
);
|
|
}
|
|
|
|
// Generate JWT token
|
|
const jwtSecret = process.env.JWT_SECRET || 'fallback-secret-change-in-production';
|
|
const token = jwt.sign(
|
|
{
|
|
userId: user.id,
|
|
username: user.username,
|
|
email: user.email
|
|
},
|
|
jwtSecret,
|
|
{ expiresIn: '7d' }
|
|
);
|
|
|
|
// Store session
|
|
const tokenHash = await bcrypt.hash(token, 10);
|
|
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
|
|
|
|
await client.query(`
|
|
INSERT INTO user_sessions (user_id, token_hash, expires_at, user_agent, ip_address)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
`, [
|
|
user.id,
|
|
tokenHash,
|
|
expiresAt,
|
|
req.headers.get('user-agent') || null,
|
|
req.headers.get('x-forwarded-for') || req.headers.get('x-real-ip') || null
|
|
]);
|
|
|
|
// Get user roles for response
|
|
const userRoles = await client.query(`
|
|
SELECT r.name, r.description
|
|
FROM roles r
|
|
JOIN user_roles ur ON r.id = ur.role_id
|
|
WHERE ur.user_id = $1
|
|
`, [user.id]);
|
|
|
|
return new NextResponse(JSON.stringify({
|
|
success: true,
|
|
message: 'User registered successfully',
|
|
user: {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
firstName: user.first_name,
|
|
lastName: user.last_name,
|
|
roles: userRoles.rows.map(r => r.name),
|
|
createdAt: user.created_at
|
|
},
|
|
token
|
|
}), {
|
|
status: 201,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Registration error:', error);
|
|
return new NextResponse(JSON.stringify({
|
|
error: 'Registration failed',
|
|
details: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined
|
|
}), {
|
|
status: 500,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|