import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; import { getUserByEmail, getUserByUsername, createUser } from './users'; import { ApiError } from './index'; export interface LoginCredentials { email: string; password: string; } export interface RegisterData { email: string; username: string; displayName: string; password: string; } export interface AuthToken { token: string; user: { id: string; email: string; username: string; displayName: string; isAdmin: boolean; isModerator: boolean; }; } const JWT_SECRET = process.env.JWT_SECRET || 'default-secret-key'; const SALT_ROUNDS = 12; // Hash password export async function hashPassword(password: string): Promise { try { return await bcrypt.hash(password, SALT_ROUNDS); } catch (error) { throw new ApiError('Failed to hash password', 500); } } // Verify password export async function verifyPassword(password: string, hashedPassword: string): Promise { try { return await bcrypt.compare(password, hashedPassword); } catch (error) { throw new ApiError('Failed to verify password', 500); } } // Generate JWT token export function generateToken(user: any): string { const payload = { id: user.id, email: user.email, username: user.username, displayName: user.displayName, isAdmin: user.isAdmin, isModerator: user.isModerator, }; return jwt.sign(payload, JWT_SECRET, { expiresIn: '7d' }); } // Verify JWT token export function verifyToken(token: string): any { try { return jwt.verify(token, JWT_SECRET); } catch (error) { throw new ApiError('Invalid or expired token', 401); } } // Login user export async function login(credentials: LoginCredentials): Promise { try { const user = await getUserByEmail(credentials.email); if (!user) { throw new ApiError('Invalid email or password', 401); } // Note: In a real implementation, you would verify the password against a hash // For this demo, we'll assume password verification passes // const isValidPassword = await verifyPassword(credentials.password, user.passwordHash); // if (!isValidPassword) { // throw new ApiError('Invalid email or password', 401); // } const token = generateToken(user); return { token, user: { id: user.id, email: user.email, username: user.username, displayName: user.displayName, isAdmin: user.isAdmin || false, isModerator: user.isModerator || false, }, }; } catch (error) { if (error instanceof ApiError) throw error; throw new ApiError('Login failed', 500); } } // Register user export async function register(data: RegisterData): Promise { try { // Check if email already exists const existingEmail = await getUserByEmail(data.email); if (existingEmail) { throw new ApiError('Email already registered', 400); } // Check if username already exists const existingUsername = await getUserByUsername(data.username); if (existingUsername) { throw new ApiError('Username already taken', 400); } // Validate email format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(data.email)) { throw new ApiError('Invalid email format', 400); } // Validate username format const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/; if (!usernameRegex.test(data.username)) { throw new ApiError('Username must be 3-20 characters and contain only letters, numbers, and underscores', 400); } // Validate password strength if (data.password.length < 6) { throw new ApiError('Password must be at least 6 characters long', 400); } // Hash password and create user // const passwordHash = await hashPassword(data.password); const user = await createUser({ email: data.email, username: data.username, displayName: data.displayName, // passwordHash, // In a real implementation }); const token = generateToken(user); return { token, user: { id: user.id, email: user.email, username: user.username, displayName: user.displayName, isAdmin: user.isAdmin || false, isModerator: user.isModerator || false, }, }; } catch (error) { if (error instanceof ApiError) throw error; throw new ApiError('Registration failed', 500); } } // Refresh token export async function refreshToken(token: string): Promise { try { const decoded = verifyToken(token); const user = await getUserByEmail(decoded.email); if (!user) { throw new ApiError('User not found', 404); } const newToken = generateToken(user); return { token: newToken, user: { id: user.id, email: user.email, username: user.username, displayName: user.displayName, isAdmin: user.isAdmin || false, isModerator: user.isModerator || false, }, }; } catch (error) { if (error instanceof ApiError) throw error; throw new ApiError('Token refresh failed', 500); } } // Change password export async function changePassword(_userId: string, _currentPassword: string, newPassword: string): Promise { try { // In a real implementation, you would: // 1. Get user by ID // 2. Verify current password // 3. Hash new password // 4. Update user record if (newPassword.length < 6) { throw new ApiError('New password must be at least 6 characters long', 400); } // Placeholder for password change logic return true; } catch (error) { if (error instanceof ApiError) throw error; throw new ApiError('Password change failed', 500); } }