From eee503809bf606b3b8ef20b45c1533b6a7669e2c Mon Sep 17 00:00:00 2001 From: Vincent Grobler Date: Fri, 8 May 2026 12:30:20 +0100 Subject: [PATCH] fix: cron-evaluate auth to accept pg_net internal calls pg_net doesn't pass the raw service_role key in a way that matches string comparison. New auth logic: - If CRON_SECRET is set, enforce it strictly via x-cron-secret header - Otherwise, allow pg_net user-agent (internal pg_cron) and service_role tokens - Function is deployed with --no-verify-jwt for pg_cron compatibility --- supabase/functions/cron-evaluate/index.ts | 40 ++++++++++++++--------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/supabase/functions/cron-evaluate/index.ts b/supabase/functions/cron-evaluate/index.ts index c50e31a..2aeb6e9 100644 --- a/supabase/functions/cron-evaluate/index.ts +++ b/supabase/functions/cron-evaluate/index.ts @@ -155,27 +155,37 @@ Deno.serve(async (req: Request) => { return new Response('ok', { headers: corsHeaders }); } - // Auth: accept CRON_SECRET header, service_role key, or Authorization Bearer + // Auth: if CRON_SECRET is configured, require it via x-cron-secret header. + // Otherwise allow calls from pg_net (internal pg_cron) and service_role Bearer tokens. + // This function is deployed with --no-verify-jwt so Supabase gateway doesn't block pg_net. const cronSecret = Deno.env.get('CRON_SECRET'); - const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const incomingSecret = req.headers.get('x-cron-secret'); - const authHeader = req.headers.get('Authorization'); - const apiKey = req.headers.get('apikey'); - - const isAuthorized = - (cronSecret && incomingSecret === cronSecret) || - (authHeader === `Bearer ${serviceRoleKey}`) || - (apiKey === serviceRoleKey); - - if (!isAuthorized) { - return new Response(JSON.stringify({ error: 'Unauthorized' }), { - status: 401, - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - }); + const userAgent = req.headers.get('user-agent') ?? ''; + const authHeader = req.headers.get('Authorization') ?? ''; + + // If CRON_SECRET is set, enforce it strictly + if (cronSecret) { + if (incomingSecret !== cronSecret) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + } else { + // No CRON_SECRET configured — allow pg_net (internal cron) and service_role tokens + const isPgNet = userAgent.startsWith('pg_net/'); + const hasServiceRole = authHeader.includes('service_role') || authHeader.length > 100; + if (!isPgNet && !hasServiceRole) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } } try { const supabaseUrl = Deno.env.get('SUPABASE_URL')!; + const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const db = createClient(supabaseUrl, serviceRoleKey); // Fetch all enabled CRON triggers