Phase 1: token schemas and anon RLS check script
tokens.ts validates link tokens (32 hex chars) before any DB query. check:rls proves the anon key cannot read or insert on any table, and fails loudly if the migration has not been applied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
// Proves the anon key has no table access (CLAUDE.md rule 2).
|
||||
// Run with: npm run check:rls
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
const TABLES = ["events", "teams", "games", "criteria", "judges", "scores", "adjustments"];
|
||||
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||
if (!url || !anonKey || !serviceKey) {
|
||||
console.error("Missing Supabase env vars. Fill in .env.local first.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const options = { auth: { persistSession: false, autoRefreshToken: false } };
|
||||
const anon = createClient(url, anonKey, options);
|
||||
const service = createClient(url, serviceKey, options);
|
||||
|
||||
let failed = false;
|
||||
const fail = (message) => {
|
||||
failed = true;
|
||||
console.error(` FAIL ${message}`);
|
||||
};
|
||||
|
||||
for (const table of TABLES) {
|
||||
console.log(table);
|
||||
|
||||
// Not a HEAD request: those report no error when the table is missing.
|
||||
const real = await service.from(table).select("id", { count: "exact" }).limit(1);
|
||||
if (real.error) {
|
||||
fail(`table not reachable with the service key (${real.error.code}). Has the migration been applied?`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const read = await anon.from(table).select("id").limit(1);
|
||||
if (read.data?.length) fail("anon can read rows");
|
||||
else if (real.count === 0) console.log(" ok anon reads nothing (table is empty, so this is a weak check)");
|
||||
else console.log(` ok anon reads nothing (${real.count} rows exist)`);
|
||||
|
||||
const write = await anon.from(table).insert({}).select("id");
|
||||
if (!write.error) {
|
||||
fail("anon inserted a row");
|
||||
await service.from(table).delete().in("id", write.data.map((row) => row.id));
|
||||
} else {
|
||||
console.log(` ok anon insert denied (${write.error.code})`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(failed ? "\nRLS check FAILED" : "\nRLS check passed: anon has no table access.");
|
||||
process.exit(failed ? 1 : 0);
|
||||
Reference in New Issue
Block a user