Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions supabase/functions/proxy-health/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
// Ideal para chamar via cron a cada 5 minutos com evaluate=1.

import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.49.1'
import { getCorsHeaders } from '../_shared/validation.ts'

const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
function buildJsonHeaders(req: Request): Record<string, string> {
return {
...getCorsHeaders(req),
'Content-Type': 'application/json',
Comment on lines 12 to +18

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change switches from Access-Control-Allow-Origin: * (previous inline corsHeaders) to origin-validated getCorsHeaders(req) (allowlist + fallback origin) and also adds default Cache-Control: no-store/security headers. That’s a behavioral change for CORS/caching, so the PR description’s claim that functional behavior is “otherwise unchanged” is no longer accurate; please update the description/release notes (or explicitly confirm the new CORS policy is intended for these endpoints).

Copilot uses AI. Check for mistakes.
}
}

// Limiares de alerta — ajustáveis via env vars.
Expand Down Expand Up @@ -167,8 +170,10 @@ export function evaluateAlerts(m: ComputedMetrics): AlertCandidate[] {
}

Deno.serve(async (req) => {
const headers = buildJsonHeaders(req)

if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
return new Response('ok', { headers })
Comment on lines 172 to +176

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildJsonHeaders(req) always sets Content-Type: application/json, but the OPTIONS preflight response body is the plain string 'ok'. This makes the OPTIONS response content-type inaccurate and may confuse clients/tools. Consider using handleCors(req) (which returns an empty body) or returning null/empty body (e.g., 204) and/or omitting Content-Type for OPTIONS responses.

Copilot uses AI. Check for mistakes.
}

const url = new URL(req.url)
Expand All @@ -192,7 +197,7 @@ Deno.serve(async (req) => {

if (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 500, headers,
})
}

Expand Down Expand Up @@ -259,6 +264,6 @@ Deno.serve(async (req) => {
alert_candidates: candidates,
fired_alerts: firedAlerts,
}, null, 2), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
headers,
})
})
27 changes: 15 additions & 12 deletions supabase/functions/proxy-metrics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@
// Output: text/plain; version=0.0.4 (Prometheus exposition format).

import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.45.0'
import { getCorsHeaders } from '../_shared/validation.ts'

const SUPABASE_URL = Deno.env.get('SUPABASE_URL')!
const SERVICE_ROLE_KEY = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
const SCRAPE_TOKEN = Deno.env.get('PROXY_METRICS_TOKEN') ?? ''

const PROM_HEADERS = {
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
'Cache-Control': 'no-store',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, content-type',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
function buildPromHeaders(req: Request): Record<string, string> {
return {
...getCorsHeaders(req),
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
'Cache-Control': 'no-store',
}
Comment on lines 17 to +29

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change replaces Access-Control-Allow-Origin: * with origin-validated getCorsHeaders(req) (allowlist + fallback origin) and adds default Cache-Control: no-store/security headers from _shared/validation.ts. That’s a functional change in CORS/caching behavior for proxy-metrics, so please reflect it in the PR description (or explicitly confirm this endpoint is intended to follow the stricter CORS allowlist now).

Copilot uses AI. Check for mistakes.
}

type MetricRow = {
Expand Down Expand Up @@ -194,24 +195,26 @@ function buildExposition(rows: MetricRow[], windowKey: WindowKey, generatedAtMs:
}

Deno.serve(async (req) => {
const headers = buildPromHeaders(req)

if (req.method === 'OPTIONS') {
return new Response('ok', { headers: PROM_HEADERS })
return new Response('ok', { headers })
}
if (req.method !== 'GET') {
return new Response('Method not allowed\n', { status: 405, headers: PROM_HEADERS })
return new Response('Method not allowed\n', { status: 405, headers })
}

// Auth — fail-closed if no token configured.
if (!SCRAPE_TOKEN) {
return new Response(
'# proxy-metrics: PROXY_METRICS_TOKEN secret is not configured.\n',
{ status: 503, headers: PROM_HEADERS },
{ status: 503, headers },
)
}
const auth = req.headers.get('Authorization') ?? ''
const provided = auth.startsWith('Bearer ') ? auth.slice(7) : auth
if (provided !== SCRAPE_TOKEN) {
return new Response('# unauthorized\n', { status: 401, headers: PROM_HEADERS })
return new Response('# unauthorized\n', { status: 401, headers })
}

const url = new URL(req.url)
Expand All @@ -236,7 +239,7 @@ Deno.serve(async (req) => {
if (error) {
return new Response(
`# error fetching proxy_metrics: ${error.message.replace(/\n/g, ' ')}\n`,
{ status: 500, headers: PROM_HEADERS },
{ status: 500, headers },
)
}
if (!data || data.length === 0) break
Expand All @@ -245,5 +248,5 @@ Deno.serve(async (req) => {
}

const body = buildExposition(rows, windowKey, Date.now())
return new Response(body, { status: 200, headers: PROM_HEADERS })
return new Response(body, { status: 200, headers })
})
Loading