mirror of
https://github.com/supabase/supabase.git
synced 2026-06-12 00:01:19 +08:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Refactor / security hardening — continues the analytics SQL provenance-tracking series (PR 8). ## What is the current behavior? - `generateRegexpWhere` (unsafe: interpolates user-controlled filter keys/values without escaping) still exists alongside `generateRegexpWhereSafe` and its tests only cover the old function. - `usePostgrestOverviewMetrics` builds a SQL query string with plain string interpolation and calls the analytics endpoint directly via `get()`. - `edge-functions-last-hour-stats-query` builds a SQL query with `functionIds` escaped via Postgres-only `quoteLiteral` and calls the analytics endpoint directly via `post()`. - `executeAnalyticsSql` has no way to pass a `key` query-string param for network-tool identification. - `rawSql('minute')` / `rawSql('hour')` / `rawSql('day')` and `rawSql(value ? 'true' : 'false')` are used for static strings that could be expressed with the `safeSql` template tag. ## What is the new behavior? - `generateRegexpWhere` is deleted; its tests are replaced with `generateRegexpWhereSafe` coverage including injection-attempt cases (`level OR id IS NOT NULL`, `request.method); DROP TABLE edge_logs; --`) that verify predicates are silently dropped rather than emitted. - `usePostgrestOverviewMetrics` returns `SafeLogSqlFragment` from its SQL builder and routes through `executeAnalyticsSql`. - `edge-functions-last-hour-stats-query` uses `analyticsLiteral` (BigQuery/ClickHouse-correct escaping) instead of `quoteLiteral` (Postgres-only) and routes through `executeAnalyticsSql`. - `executeAnalyticsSql` accepts an optional `key?: string` forwarded as a query-string param on both GET and POST requests; `key: 'last-hour-stats'` is restored on the edge-functions query. - Static `rawSql('...')` calls replaced with `safeSql\`...\`` template literals throughout. ## Additional context <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Bug Fixes - Removed legacy unsafe SQL-filter utility from Reports ## Chores - Enhanced analytics SQL execution infrastructure with improved error handling - Added optional request identification parameter to analytics query execution - Refined SQL filtering mechanisms in reporting features <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46466?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
99 lines
2.8 KiB
TypeScript
99 lines
2.8 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
|
|
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
|
import { executeAnalyticsSql } from '@/data/logs/execute-analytics-sql'
|
|
import { safeSql, type SafeLogSqlFragment } from '@/data/logs/safe-analytics-sql'
|
|
|
|
type PostgrestMetricsVariables = {
|
|
projectRef: string
|
|
startDate: string
|
|
endDate: string
|
|
interval: '1hr' | '1day' | '7day'
|
|
}
|
|
|
|
function getIntervalTrunc(interval: '1hr' | '1day' | '7day'): SafeLogSqlFragment {
|
|
switch (interval) {
|
|
case '1hr':
|
|
return safeSql`minute` // 1-minute buckets for 1 hour
|
|
case '1day':
|
|
return safeSql`hour` // 1-hour buckets for 1 day
|
|
case '7day':
|
|
return safeSql`day` // 1-day buckets for 7 days
|
|
default:
|
|
return safeSql`hour`
|
|
}
|
|
}
|
|
|
|
const POSTGREST_METRICS_SQL = (interval: '1hr' | '1day' | '7day'): SafeLogSqlFragment => {
|
|
const trunc = getIntervalTrunc(interval)
|
|
|
|
return safeSql`
|
|
-- postgrest-overview-metrics
|
|
select
|
|
cast(timestamp_trunc(t.timestamp, ${trunc}) as datetime) as timestamp,
|
|
countif(response.status_code < 300) as ok_count,
|
|
countif(response.status_code >= 300 and response.status_code < 400) as warning_count,
|
|
countif(response.status_code >= 400) as error_count
|
|
FROM edge_logs t
|
|
cross join unnest(metadata) as m
|
|
cross join unnest(m.response) as response
|
|
cross join unnest(m.request) as request
|
|
WHERE
|
|
request.path like '/rest/%'
|
|
GROUP BY
|
|
timestamp
|
|
ORDER BY
|
|
timestamp ASC
|
|
`
|
|
}
|
|
|
|
type MetricsRow = {
|
|
timestamp: string
|
|
ok_count: number
|
|
warning_count: number
|
|
error_count: number
|
|
}
|
|
|
|
async function fetchPostgrestMetrics(
|
|
{ projectRef, startDate, endDate, interval }: PostgrestMetricsVariables,
|
|
signal?: AbortSignal
|
|
) {
|
|
const sql = POSTGREST_METRICS_SQL(interval)
|
|
|
|
const data = await executeAnalyticsSql({
|
|
projectRef,
|
|
endpoint: '/platform/projects/{ref}/analytics/endpoints/logs.all',
|
|
sql,
|
|
iso_timestamp_start: startDate,
|
|
iso_timestamp_end: endDate,
|
|
method: 'get',
|
|
signal,
|
|
})
|
|
|
|
if (data?.error) throw data.error
|
|
|
|
return (data?.result || []) as MetricsRow[]
|
|
}
|
|
|
|
export const usePostgrestOverviewMetrics = (
|
|
{ projectRef, startDate, endDate, interval }: PostgrestMetricsVariables,
|
|
options?: { enabled?: boolean }
|
|
) => {
|
|
return useQuery({
|
|
queryKey: ['postgrest-overview-metrics', projectRef, startDate, endDate, interval],
|
|
queryFn: ({ signal }) =>
|
|
fetchPostgrestMetrics({ projectRef, startDate, endDate, interval }, signal),
|
|
enabled: (options?.enabled ?? true) && Boolean(projectRef),
|
|
staleTime: 1000 * 60,
|
|
})
|
|
}
|
|
|
|
export const transformPostgrestMetrics = (rows: MetricsRow[]): LogsBarChartDatum[] => {
|
|
return rows.map((row) => ({
|
|
timestamp: row.timestamp,
|
|
ok_count: row.ok_count,
|
|
warning_count: row.warning_count,
|
|
error_count: row.error_count,
|
|
}))
|
|
}
|