first commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
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<string | null> {
|
||||
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<boolean> {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user