import { cookies, headers } from 'next/headers' import { NextResponse, NextRequest } from 'next/server' import { createHmac } from 'crypto' import { isHuggingFaceSpace } from 'lib/supabase/publicEnv' const APP_SESSION_COOKIE = 'letschat_session' const DEFAULT_MAX_AGE_SECONDS = 30 * 24 * 60 * 60 // 30 days const isProd = process.env.NODE_ENV === 'production' // Prefer broadest compatibility in production (embeds/iframe) -> None; dev -> Lax. const sameSite: 'lax' | 'none' = isProd || process.env.FORCE_SAMESITE_NONE === '1' || isHuggingFaceSpace ? 'none' : 'lax' type AppSessionPayload = { userId: string email: string exp: number } const secret = process.env.APP_SECRET || process.env.SUPABASE_SERVICE_ROLE_KEY || 'dev-secret' function sign(payload: AppSessionPayload) { const data = Buffer.from(JSON.stringify(payload)).toString('base64url') const sig = createHmac('sha256', secret).update(data).digest('base64url') return `${data}.${sig}` } function verify(token: string): AppSessionPayload | null { const [data, sig] = token.split('.') if (!data || !sig) return null const expected = createHmac('sha256', secret).update(data).digest('base64url') if (expected !== sig) return null try { const payload = JSON.parse(Buffer.from(data, 'base64url').toString()) as AppSessionPayload if (payload.exp && Date.now() > payload.exp) return null return payload } catch { return null } } export async function setAppSession(res: NextResponse, userId: string, email: string) { const exp = Date.now() + DEFAULT_MAX_AGE_SECONDS * 1000 const token = sign({ userId, email, exp }) res.cookies.set(APP_SESSION_COOKIE, token, { httpOnly: true, secure: isProd, sameSite, maxAge: DEFAULT_MAX_AGE_SECONDS, path: '/' }) // Also set a non-httpOnly cookie so client JS / proxies can read it if necessary try { res.cookies.set(`${APP_SESSION_COOKIE}_pub`, token, { httpOnly: false, secure: isProd, sameSite, maxAge: DEFAULT_MAX_AGE_SECONDS, path: '/' }) } catch (e) { // ignore in environments that disallow multiple Set-Cookie settings } return token } export async function clearAppSession(res: NextResponse) { res.cookies.set(APP_SESSION_COOKIE, '', { httpOnly: true, secure: isProd, sameSite, maxAge: 0, path: '/' }) } export async function getAppSession(req?: NextRequest): Promise { // Try req cookies first (server handler may pass NextRequest) let token = req?.cookies?.get(APP_SESSION_COOKIE)?.value // Fallback to Next.js cookies() helper if (!token) { token = (await cookies()).get(APP_SESSION_COOKIE)?.value } // As a last resort, try to parse Cookie header string directly (some proxies/platforms) if (!token && req?.headers?.get) { const cookieHeader = req.headers.get('cookie') || '' if (cookieHeader) { const match = cookieHeader.split(';').map((s) => s.trim()).find((c) => c.startsWith(`${APP_SESSION_COOKIE}=`)) if (match) token = match.split('=')[1] } } console.log('[getAppSession] token present=', !!token) if (!token) return null return verify(token) }