import type { FastifyReply, FastifyRequest } from "fastify"; import { supabase } from "./supabase.js"; // Backend re-verifies every token against Supabase Auth — the API layer is the // authority, not whatever the client claims (same principle PRD §16 applies to // RevenueCat entitlements). export async function requireAuth(req: FastifyRequest, reply: FastifyReply): Promise { const authHeader = req.headers.authorization; const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null; if (!token) { req.log.warn("requireAuth: missing Bearer token in Authorization header"); reply.code(401).send({ message: "unauthorized" }); return null; } if (!supabase) { req.log.error("requireAuth: Supabase client is not configured (check SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY)"); reply.code(500).send({ message: "server_misconfigured: supabase_not_initialized" }); return null; } const { data, error } = await supabase.auth.getUser(token); if (error || !data.user) { req.log.warn({ error: error?.message }, "requireAuth: invalid or expired session token"); reply.code(401).send({ message: "unauthorized" }); return null; } await supabase.from("users").upsert({ id: data.user.id }, { onConflict: "id", ignoreDuplicates: true }); return data.user.id; } export async function requireRestaurantMember( userId: string, restaurantId: string, ): Promise { if (!supabase) return false; const { data, error } = await supabase .from("restaurant_members") .select("id") .eq("user_id", userId) .eq("restaurant_id", restaurantId) .maybeSingle(); return !error && !!data; }