| 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 |
| const isProd = process.env.NODE_ENV === 'production' |
| |
| 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: '/' |
| }) |
| |
| try { |
| res.cookies.set(`${APP_SESSION_COOKIE}_pub`, token, { |
| httpOnly: false, |
| secure: isProd, |
| sameSite, |
| maxAge: DEFAULT_MAX_AGE_SECONDS, |
| path: '/' |
| }) |
| } catch (e) { |
| |
| } |
| 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<AppSessionPayload | null> { |
| |
| let token = req?.cookies?.get(APP_SESSION_COOKIE)?.value |
|
|
| |
| if (!token) { |
| token = (await cookies()).get(APP_SESSION_COOKIE)?.value |
| } |
|
|
| |
| 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) |
| } |
|
|