| | | 1 | | import type { ToastInput } from "~/lib/toasts"; |
| | | 2 | | import { redis } from "./redis.server"; |
| | | 3 | | |
| | | 4 | | export interface SessionUser { |
| | | 5 | | sub: string; |
| | | 6 | | name?: string; |
| | | 7 | | preferred_username?: string; |
| | | 8 | | email?: string; |
| | | 9 | | groups?: string[] | null; |
| | | 10 | | } |
| | | 11 | | |
| | | 12 | | export interface Session { |
| | | 13 | | accessToken: string; |
| | | 14 | | refreshToken: string; |
| | | 15 | | expiresAt: number; // Unix seconds |
| | | 16 | | idToken: string; |
| | | 17 | | user: SessionUser; |
| | | 18 | | flashes?: ToastInput[]; |
| | | 19 | | } |
| | | 20 | | |
| | 2 | 21 | | const COOKIE = "clutterstock_sid"; |
| | 2 | 22 | | const TTL = 7 * 24 * 60 * 60; // 7 days |
| | | 23 | | |
| | 0 | 24 | | export function getSid(request: Request): string | undefined { |
| | 0 | 25 | | const cookie = request.headers.get("cookie") ?? ""; |
| | 0 | 26 | | return /(?:^|;\s*)clutterstock_sid=([^;]+)/.exec(cookie)?.[1]; |
| | | 27 | | } |
| | | 28 | | |
| | 0 | 29 | | export async function getSession( |
| | | 30 | | request: Request, |
| | | 31 | | ): Promise<{ sid: string; data: Session } | null> { |
| | 0 | 32 | | const sid = getSid(request); |
| | 0 | 33 | | if (!sid) return null; |
| | 0 | 34 | | const raw = await redis().get(`session:${sid}`); |
| | 0 | 35 | | if (!raw) return null; |
| | 0 | 36 | | return { sid, data: JSON.parse(raw) as Session }; |
| | | 37 | | } |
| | | 38 | | |
| | 0 | 39 | | export async function createSession(data: Session): Promise<string> { |
| | 0 | 40 | | const sid = crypto.randomUUID(); |
| | 0 | 41 | | await redis().set(`session:${sid}`, JSON.stringify(data), { EX: TTL }); |
| | 0 | 42 | | return sid; |
| | | 43 | | } |
| | | 44 | | |
| | 0 | 45 | | export async function updateSession(sid: string, data: Session): Promise<void> { |
| | 0 | 46 | | const ttl = await redis().ttl(`session:${sid}`); |
| | 0 | 47 | | await redis().set(`session:${sid}`, JSON.stringify(data), { EX: ttl > 0 ? ttl : TTL }); |
| | | 48 | | } |
| | | 49 | | |
| | 0 | 50 | | export async function destroySession(sid: string): Promise<void> { |
| | 0 | 51 | | await redis().del(`session:${sid}`); |
| | | 52 | | } |
| | | 53 | | |
| | 0 | 54 | | export function sessionCookie(sid: string): string { |
| | 0 | 55 | | return `${COOKIE}=${sid}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${TTL}`; |
| | | 56 | | } |
| | | 57 | | |
| | 0 | 58 | | export function clearCookie(): string { |
| | 0 | 59 | | return `${COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`; |
| | | 60 | | } |