import { getUserByUsername, checkRateLimit, resetRateLimit, createSession, isAccountLocked, incrementFailedAttempts, resetFailedAttempts } from '~/server/utils/database' import { setAuthCookie, generateSessionToken } from '~/server/utils/auth' import { generateCsrfToken, setCsrfCookie } from '~/server/utils/csrf' import bcrypt from 'bcrypt' export default defineEventHandler(async (event) => { // Get real client IP from proxy headers (prioritize x-real-ip for NPM) const xRealIp = getHeader(event, 'x-real-ip') const xForwardedFor = getHeader(event, 'x-forwarded-for') const cfConnectingIp = getHeader(event, 'cf-connecting-ip') // Use x-real-ip first (set by NPM), then x-forwarded-for, then cf-connecting-ip, then fallback const clientIp = xRealIp || (xForwardedFor ? xForwardedFor.split(',')[0].trim() : null) || cfConnectingIp || getRequestIP(event) || 'unknown' // Log IP for verification console.log(`[LOGIN ATTEMPT] IP: ${clientIp}, Headers:`, { 'x-forwarded-for': xForwardedFor, 'x-real-ip': xRealIp, 'cf-connecting-ip': cfConnectingIp, 'getRequestIP': getRequestIP(event) }) const body = await readBody(event) const { username, password } = body if (!username || !password) { throw createError({ statusCode: 400, message: 'Username and password are required' }) } const user = getUserByUsername(username.toLowerCase()) if (!user) { // Check rate limit ONLY on failed attempt if (!checkRateLimit(clientIp, 'login', 5, 15)) { console.log(`[LOGIN BLOCKED] Rate limited - Username not found: ${username.toLowerCase()}, IP: ${clientIp}`) throw createError({ statusCode: 429, message: 'Too many login attempts. Please try again in 15 minutes.' }) } console.log(`[LOGIN FAILED] Username not found: ${username.toLowerCase()}, IP: ${clientIp}`) throw createError({ statusCode: 401, message: 'Invalid credentials' }) } // SECURITY: Check if account is locked due to too many failed attempts if (isAccountLocked(username.toLowerCase())) { console.log(`[LOGIN BLOCKED] Account locked - User: ${username.toLowerCase()}, IP: ${clientIp}`) throw createError({ statusCode: 403, message: 'Account temporarily locked due to too many failed login attempts. Please try again in 30 minutes or contact an administrator.' }) } // Compare the provided password with the hashed password in the database const passwordMatch = await bcrypt.compare(password, user.password) if (!passwordMatch) { // SECURITY: Increment failed login attempts for this account incrementFailedAttempts(username.toLowerCase()) // Check rate limit ONLY on failed attempt if (!checkRateLimit(clientIp, 'login', 5, 15)) { console.log(`[LOGIN BLOCKED] Rate limited - Invalid password for user: ${username.toLowerCase()}, IP: ${clientIp}`) throw createError({ statusCode: 429, message: 'Too many login attempts. Please try again in 15 minutes.' }) } console.log(`[LOGIN FAILED] Invalid password for user: ${username.toLowerCase()}, IP: ${clientIp}`) throw createError({ statusCode: 401, message: 'Invalid credentials' }) } // Generate session token and CSRF token const sessionToken = generateSessionToken() const csrfToken = generateCsrfToken() const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() // 24 hours // Create session with CSRF token createSession(sessionToken, user.username, expiresAt, csrfToken) // Set session cookie setAuthCookie(event, sessionToken) // Set CSRF cookie setCsrfCookie(event, csrfToken) // SECURITY: Reset failed login attempts on successful login resetFailedAttempts(username.toLowerCase()) // Reset rate limit on successful login resetRateLimit(clientIp, 'login') // Log successful login console.log(`[LOGIN SUCCESS] User: ${user.username}, IP: ${clientIp}`) return { success: true, username: user.username } })