← 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
}
}