Files
honeyDueWeb/src/app/api/auth/login/route.ts
T
Trey t 7884ebbfd4 feat: Phase 4-5 — demo mode, polish, deploy, and bug fixes
Add demo mode with mock data provider, Docker deployment, Playwright
tests, PostHog analytics, error boundaries, and SEO metadata. Fix
residences API response unwrapping, kanban drag-and-drop with optimistic
updates, trailing slash proxy redirects, and column name mismatches with
Go API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:37:41 -06:00

77 lines
2.3 KiB
TypeScript

import { cookies } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';
// ---------------------------------------------------------------------------
// POST /api/auth/login
// ---------------------------------------------------------------------------
// Special route handler for login. On success, sets the auth token in an
// httpOnly cookie so it is never exposed to client-side JavaScript.
// ---------------------------------------------------------------------------
const API_BASE_URL =
process.env.API_URL ||
process.env.NEXT_PUBLIC_API_URL ||
'https://casera.treytartt.com/api';
const COOKIE_NAME = 'casera-token';
const COOKIE_MAX_AGE = 60 * 60 * 24 * 30; // 30 days
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const upstream = await fetch(`${API_BASE_URL}/auth/login/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Timezone':
request.headers.get('x-timezone') ||
Intl.DateTimeFormat().resolvedOptions().timeZone,
},
cache: 'no-store',
body: JSON.stringify(body),
});
const data = await upstream.json().catch(() => null);
if (!upstream.ok) {
return NextResponse.json(
data || { error: 'Login failed' },
{ status: upstream.status },
);
}
// Extract token from Go API response
// The Go API returns { token: "...", user: { ... } }
const token: string | undefined = data?.token;
if (!token) {
return NextResponse.json(
{ error: 'No token in response' },
{ status: 500 },
);
}
// Set httpOnly cookie
const cookieStore = await cookies();
cookieStore.set(COOKIE_NAME, token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
path: '/',
maxAge: COOKIE_MAX_AGE,
});
// Return the full response (including user data) to the client,
// but strip the raw token since it is now in the cookie.
const { token: _stripped, ...safeData } = data;
return NextResponse.json(safeData, { status: 200 });
} catch (error) {
console.error('[auth/login] Error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 },
);
}
}