Download src/lib/appSession.ts from HonzaH/Letschat: direct link, hf CLI and curl.
- Browser
- Download file 3.19 kB
-
https://huggingface.co/spaces/HonzaH/Letschat/resolve/main/src/lib/appSession.ts
- Command line
-
hf download hf://spaces/HonzaH/Letschat/src/lib/appSession.ts
-
curl -L -o appSession.ts https://huggingface.co/spaces/HonzaH/Letschat/resolve/main/src/lib/appSession.ts
3.19 kB
| 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<AppSessionPayload | null> { | |
| // 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) | |
| } | |