Skip to content

Commit bd7b46e

Browse files
ralyodioclaude
andcommitted
Fix: monetize page must show only sites you own
The projects RLS also permits org-wide and project-member reads, so the /ads/slots list was showing teammates' sites. Monetization is owner-only (payouts go to the slot owner), so scope the project list to owner_id, and harden createSlot to require ownership rather than trusting the broad RLS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c35388d commit bd7b46e

2 files changed

Lines changed: 11 additions & 3 deletions

File tree

‎app/(app)/ads/slots/page.tsx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,13 @@ export default async function SlotsPage() {
4848
const payoutsBySlot = new Map<string, Payout[]>();
4949
if (user) {
5050
const [{ data: p }, { data: s }, { data: ledger }, { data: payouts }] = await Promise.all([
51-
supabase.from("projects").select("id, name, url").order("created_at", { ascending: false }),
51+
// Monetization is owner-only: you earn from a slot, so only list projects
52+
// you OWN — not org/member-shared ones the broad RLS would also return.
53+
supabase
54+
.from("projects")
55+
.select("id, name, url")
56+
.eq("owner_id", user.id)
57+
.order("created_at", { ascending: false }),
5258
supabase.from("ad_slots").select("id, project_id, status, payout_address, payout_currency"),
5359
supabase.from("ad_ledger").select("slot_id, amount_cents").eq("kind", "publisher_accrual"),
5460
supabase

‎app/actions/ads.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,15 @@ export async function createSlot(input: {
215215
} = await supabase.auth.getUser();
216216
if (!user) return { ok: false, error: "Not authenticated." };
217217

218-
// Confirm the project belongs to the user (RLS also enforces this).
218+
// Monetization is owner-only — the broad projects RLS also allows org/member
219+
// reads, so scope explicitly to owner_id (payouts go to the slot owner).
219220
const { data: project } = await supabase
220221
.from("projects")
221222
.select("id, organization_id")
222223
.eq("id", input.projectId)
224+
.eq("owner_id", user.id)
223225
.maybeSingle();
224-
if (!project) return { ok: false, error: "Project not found." };
226+
if (!project) return { ok: false, error: "You can only monetize a site you own." };
225227

226228
const payload: Record<string, unknown> = {
227229
project_id: project.id,

0 commit comments

Comments
 (0)