import { NextResponse, type NextRequest } from "next/server"; import { loginToMinnect, LoginError } from "@/lib/minnect"; import { createSession, getSession } from "@/lib/session-store"; import { SESSION_COOKIE, sessionCookieOptions } from "@/lib/cookie"; // This route holds the session on the server. The browser talks only to this // app; Minnect tokens never leave the server (ADR-001). // // POST signs in, GET says who the person is. There is no sign-out: the session ends // when the server restarts or the cookie is cleared, and Minnect's revoke endpoint // is never called (the refresh token is shared across the person's devices). // The reusable token-refresh logic lives in session-refresh.ts for the feed // route (a later task) to call before it uses a token. export const runtime = "nodejs"; function badRequest(message: string) { return NextResponse.json({ error: message }, { status: 400 }); } /** * POST /api/session * Body: { email, password } * On success: sets the opaque session cookie, returns { name, email, pictureUrl }. * On failure: no session, no cookie, one generic message. */ export async function POST(req: NextRequest) { let body: unknown; try { body = await req.json(); } catch { body = null; } const record = body && typeof body === "object" ? (body as Record) : {}; const email = typeof record.email === "string" ? record.email : ""; const password = typeof record.password === "string" ? record.password : ""; if (!email || !password) { return badRequest("Email and password are required."); } try { const { person, tokens } = await loginToMinnect(email, password); const sessionId = createSession({ profileAccessToken: tokens.profileAccessToken, profileAccessTokenExpiresAt: tokens.profileAccessTokenExpiresAt, profileRefreshToken: tokens.profileRefreshToken, feedAccessToken: tokens.feedAccessToken, feedAccessTokenExpiresAt: tokens.feedAccessTokenExpiresAt, person, createdAt: Date.now(), }); const res = NextResponse.json({ name: person.name, email: person.email, pictureUrl: person.pictureUrl, }); res.cookies.set(SESSION_COOKIE, sessionId, sessionCookieOptions()); return res; } catch (err) { if (err instanceof LoginError) { return NextResponse.json({ error: err.publicMessage }, { status: err.status }); } // Never echo an unexpected error to the browser. return NextResponse.json({ error: "Could not sign you in." }, { status: 500 }); } } /** * GET /api/session * Returns { name, email, pictureUrl } for the signed-in person, or 401 * { error: "sign_in" } when the session is missing or dead. */ export async function GET(req: NextRequest) { const sessionId = req.cookies.get(SESSION_COOKIE)?.value; if (!sessionId) { return NextResponse.json({ error: "sign_in" }, { status: 401 }); } const session = getSession(sessionId); if (!session) { return NextResponse.json({ error: "sign_in" }, { status: 401 }); } return NextResponse.json({ name: session.person.name, email: session.person.email, pictureUrl: session.person.pictureUrl, feedUserId: session.person.feedUserId ?? null, }); }