← back to Handbag Auth Nextjs

src/app/api/auth/login/route.ts

177 lines

/**
 * Login API Route
 * Handles user authentication with secure password verification
 */

import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { generateToken, verifyPassword, checkLoginRateLimit, getSecureCookieOptions } from '@/lib/auth'
import { validateInput } from '@/lib/validation'
import { addSecurityHeaders } from '@/lib/securityHeaders'
import { securityLogger, SecurityEventType } from '@/lib/security-logger'
import { getClientIp } from '@/lib/rateLimit'

// Login schema
const loginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8).max(100),
})

// Temporary in-memory user store (replace with database in production)
const users = new Map([
  ['admin@handbag.com', {
    id: '1',
    email: 'admin@handbag.com',
    // Password: Admin123!@# (hashed)
    password: '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5L2iNSp8E7HuO',
    role: 'admin' as const,
  }],
  ['user@handbag.com', {
    id: '2',
    email: 'user@handbag.com',
    // Password: User123!@# (hashed)
    password: '$2a$12$hBU7hVVVpFLCDDvA7PoWnuPLcW4g8Vh3N9KwVqPCJcLLUfXL6efGi',
    role: 'user' as const,
  }],
])

export async function POST(request: NextRequest) {
  const clientIp = getClientIp(request)

  try {
    // Parse and validate input
    const body = await request.json()
    const validation = validateInput(loginSchema, body)

    if (!validation.success) {
      securityLogger.log({
        type: SecurityEventType.INVALID_INPUT,
        ip: clientIp,
        endpoint: '/api/auth/login',
        method: 'POST',
        details: { errors: validation.errors.format() },
        severity: 'low',
      })

      return NextResponse.json(
        { success: false, error: 'Invalid credentials' },
        { status: 400 }
      )
    }

    const { email, password } = validation.data

    // Check rate limiting for login attempts
    if (!checkLoginRateLimit(clientIp)) {
      securityLogger.log({
        type: SecurityEventType.RATE_LIMIT_EXCEEDED,
        ip: clientIp,
        endpoint: '/api/auth/login',
        method: 'POST',
        details: { email },
        severity: 'medium',
      })

      const response = NextResponse.json(
        { success: false, error: 'Too many login attempts. Please try again later.' },
        { status: 429 }
      )
      addSecurityHeaders(response)
      return response
    }

    // Find user
    const user = users.get(email)

    if (!user) {
      securityLogger.log({
        type: SecurityEventType.AUTH_FAILURE,
        ip: clientIp,
        endpoint: '/api/auth/login',
        method: 'POST',
        details: { email, reason: 'user_not_found' },
        severity: 'medium',
      })

      // Don't reveal whether user exists
      return NextResponse.json(
        { success: false, error: 'Invalid credentials' },
        { status: 401 }
      )
    }

    // Verify password
    const isValid = await verifyPassword(password, user.password)

    if (!isValid) {
      securityLogger.log({
        type: SecurityEventType.AUTH_FAILURE,
        ip: clientIp,
        userId: user.id,
        endpoint: '/api/auth/login',
        method: 'POST',
        details: { email, reason: 'invalid_password' },
        severity: 'medium',
      })

      return NextResponse.json(
        { success: false, error: 'Invalid credentials' },
        { status: 401 }
      )
    }

    // Generate JWT token
    const token = generateToken({
      userId: user.id,
      email: user.email,
      role: user.role as any,
    })

    // Log successful auth
    securityLogger.log({
      type: SecurityEventType.AUTH_SUCCESS,
      ip: clientIp,
      userId: user.id,
      endpoint: '/api/auth/login',
      method: 'POST',
      details: { email },
      severity: 'low',
    })

    // Create response with token
    const response = NextResponse.json({
      success: true,
      token,
      user: {
        id: user.id,
        email: user.email,
        role: user.role,
      },
    })

    // Set secure HTTP-only cookie
    response.cookies.set('auth-token', token, getSecureCookieOptions())

    addSecurityHeaders(response)
    return response

  } catch (error) {
    console.error('Login error:', error)

    securityLogger.log({
      type: SecurityEventType.SUSPICIOUS_ACTIVITY,
      ip: clientIp,
      endpoint: '/api/auth/login',
      method: 'POST',
      details: { error: error instanceof Error ? error.message : 'Unknown error' },
      severity: 'high',
    })

    const response = NextResponse.json(
      { success: false, error: 'Authentication failed' },
      { status: 500 }
    )
    addSecurityHeaders(response)
    return response
  }
}