import { NextResponse } from 'next/server' import { authenticateUser, createSession } from '@/lib/auth' import { logAuditEvent, needsFirstTimeSetup } from '@/lib/db' import { getMcSessionCookieName, getMcSessionCookieOptions, isRequestSecure } from '@/lib/session-cookie' import { loginLimiter } from '@/lib/rate-limit' import { logger } from '@/lib/logger' export async function POST(request: Request) { try { const rateCheck = loginLimiter(request) if (rateCheck) return rateCheck const { username, password } = await request.json() if (!username || !password) { return NextResponse.json({ error: 'Username and password are required' }, { status: 400 }) } const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' const userAgent = request.headers.get('user-agent') || undefined const user = authenticateUser(username, password) if (!user) { logAuditEvent({ action: 'login_failed', actor: username, ip_address: ipAddress, user_agent: userAgent }) // When no users exist at all, give actionable feedback instead of "Invalid credentials" if (needsFirstTimeSetup()) { return NextResponse.json( { error: 'No admin account has been created yet', code: 'NO_USERS', hint: 'Visit /setup to create your admin account', }, { status: 401 } ) } return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) } const { token, expiresAt } = createSession(user.id, ipAddress, userAgent, user.workspace_id) logAuditEvent({ action: 'login', actor: user.username, actor_id: user.id, ip_address: ipAddress, user_agent: userAgent }) const response = NextResponse.json({ user: { id: user.id, username: user.username, display_name: user.display_name, role: user.role, provider: user.provider || 'local', email: user.email || null, avatar_url: user.avatar_url || null, workspace_id: user.workspace_id ?? 1, tenant_id: user.tenant_id ?? 1, }, }) const isSecureRequest = isRequestSecure(request) const cookieName = getMcSessionCookieName(isSecureRequest) response.cookies.set(cookieName, token, { ...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000), isSecureRequest }), }) return response } catch (error) { logger.error({ err: error }, 'Login error') return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }