deckhearth/api/auth/register.ts
Randall Stillwell 07b3dda6fa Fix Vercel function request body parsing
🐛 Bug Fix:
- Replace req.json() with req.text() + JSON.parse() for Vercel compatibility
- Add proper error handling for malformed JSON requests
- Fix authentication endpoints (login/register) body parsing
- Fix admin users endpoint body parsing

🔧 Technical Details:
- Vercel functions don't support req.json() method directly
- Use req.text() to get raw body content then parse manually
- Add try/catch blocks for JSON parsing errors
- Maintain same API interface and error responses

 Endpoints Fixed:
- /api/auth/register - User registration
- /api/auth/login - User authentication
- /api/admin/users - Admin user management

This resolves the 'req.json is not a function' error in production.
2025-07-21 21:11:13 -05:00

171 lines
No EOL
4.7 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' },
});
}
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 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();
}
}