sessions.ts 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. import { getSupabaseAdminClient } from "@/lib/supabase/admin";
  2. import { ABSOLUTE_SESSION_CAP_SECONDS } from "@/lib/auth/jwt";
  3. export async function createSession(userId: string): Promise<{ id: string; iat_original: number }> {
  4. const admin = getSupabaseAdminClient();
  5. const { data, error } = await admin
  6. .from("user_sessions")
  7. .insert({ user_id: userId })
  8. .select("id, iat_original")
  9. .single();
  10. if (error || !data) throw new Error("Failed to create session");
  11. return {
  12. id: data.id,
  13. iat_original: Math.floor(new Date(data.iat_original).getTime() / 1000),
  14. };
  15. }
  16. /**
  17. * Returns true iff the session row exists, is not revoked, and the
  18. * absolute-cap (30 days from `iat_original`) has not elapsed.
  19. *
  20. * Side-effect (fire-and-forget): updates `last_seen_at`.
  21. */
  22. export async function isSessionLive(sessionId: string, iatOriginal: number): Promise<boolean> {
  23. const admin = getSupabaseAdminClient();
  24. const { data } = await admin
  25. .from("user_sessions")
  26. .select("revoked_at")
  27. .eq("id", sessionId)
  28. .maybeSingle();
  29. if (!data || data.revoked_at) return false;
  30. if (Math.floor(Date.now() / 1000) - iatOriginal > ABSOLUTE_SESSION_CAP_SECONDS) {
  31. return false;
  32. }
  33. void admin
  34. .from("user_sessions")
  35. .update({ last_seen_at: new Date().toISOString() })
  36. .eq("id", sessionId);
  37. return true;
  38. }
  39. export async function revokeSession(sessionId: string): Promise<void> {
  40. const admin = getSupabaseAdminClient();
  41. await admin
  42. .from("user_sessions")
  43. .update({ revoked_at: new Date().toISOString() })
  44. .eq("id", sessionId);
  45. }