mirror of
https://github.com/supabase/supabase.git
synced 2026-06-04 11:51:55 +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 -->
120 lines
3.7 KiB
TypeScript
120 lines
3.7 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
import dayjs from 'dayjs'
|
|
|
|
import { edgeFunctionsKeys } from './keys'
|
|
import { handleError } from '@/data/fetchers'
|
|
import { executeAnalyticsSql } from '@/data/logs/execute-analytics-sql'
|
|
import {
|
|
analyticsLiteral,
|
|
joinSqlFragments,
|
|
safeSql,
|
|
type SafeLogSqlFragment,
|
|
} from '@/data/logs/safe-analytics-sql'
|
|
import type { ResponseError, UseCustomQueryOptions } from '@/types'
|
|
|
|
export type EdgeFunctionsLastHourStatsVariables = { projectRef?: string; functionIds?: string[] }
|
|
|
|
export type EdgeFunctionLastHourStats = {
|
|
functionId: string
|
|
requestsCount: number
|
|
serverErrorCount: number
|
|
errorRate: number
|
|
}
|
|
|
|
export type EdgeFunctionsLastHourStatsResponse = Record<string, EdgeFunctionLastHourStats>
|
|
|
|
function getEdgeFunctionsLastHourStatsSql(functionIds: string[]): SafeLogSqlFragment {
|
|
const functionIdFilter: SafeLogSqlFragment =
|
|
functionIds.length > 0
|
|
? safeSql` and function_id in (${joinSqlFragments(functionIds.map(analyticsLiteral), ', ')})\n`
|
|
: safeSql``
|
|
|
|
return safeSql`
|
|
-- edge-functions-last-hour-stats
|
|
select
|
|
function_id,
|
|
count(distinct id) as requests_count,
|
|
count(distinct case when response.status_code >= 500 then id end) as server_err_count
|
|
from
|
|
function_edge_logs
|
|
cross join unnest(metadata) as m
|
|
cross join unnest(m.response) as response
|
|
where
|
|
function_id is not null
|
|
${functionIdFilter}group by
|
|
function_id
|
|
`
|
|
}
|
|
|
|
export async function getEdgeFunctionsLastHourStats(
|
|
{ projectRef, functionIds = [] }: EdgeFunctionsLastHourStatsVariables,
|
|
signal?: AbortSignal
|
|
) {
|
|
if (!projectRef) throw new Error('projectRef is required')
|
|
if (functionIds.length === 0) return {}
|
|
|
|
const endDate = dayjs().toISOString()
|
|
const startDate = dayjs().subtract(1, 'hour').toISOString()
|
|
|
|
const data = await executeAnalyticsSql({
|
|
projectRef,
|
|
endpoint: '/platform/projects/{ref}/analytics/endpoints/logs.all',
|
|
sql: getEdgeFunctionsLastHourStatsSql(functionIds),
|
|
iso_timestamp_start: startDate,
|
|
iso_timestamp_end: endDate,
|
|
key: 'last-hour-stats',
|
|
signal,
|
|
})
|
|
|
|
if (data?.error) handleError(data.error)
|
|
|
|
const result = (data?.result ?? []) as {
|
|
function_id: string
|
|
requests_count: number | string
|
|
server_err_count: number | string
|
|
}[]
|
|
|
|
return result.reduce<EdgeFunctionsLastHourStatsResponse>((acc, row) => {
|
|
const toSafeNumber = (v: number | string | undefined) => {
|
|
const n = Number(v ?? 0)
|
|
return Number.isFinite(n) ? n : 0
|
|
}
|
|
const safeRequestsCount = toSafeNumber(row.requests_count)
|
|
const safeServerErrorCount = toSafeNumber(row.server_err_count)
|
|
|
|
acc[row.function_id] = {
|
|
functionId: row.function_id,
|
|
requestsCount: safeRequestsCount,
|
|
serverErrorCount: safeServerErrorCount,
|
|
errorRate: safeRequestsCount > 0 ? (safeServerErrorCount / safeRequestsCount) * 100 : 0,
|
|
}
|
|
|
|
return acc
|
|
}, {})
|
|
}
|
|
|
|
export type EdgeFunctionsLastHourStatsData = Awaited<
|
|
ReturnType<typeof getEdgeFunctionsLastHourStats>
|
|
>
|
|
export type EdgeFunctionsLastHourStatsError = ResponseError
|
|
|
|
export const useEdgeFunctionsLastHourStatsQuery = <TData = EdgeFunctionsLastHourStatsData>(
|
|
{ projectRef, functionIds = [] }: EdgeFunctionsLastHourStatsVariables,
|
|
{
|
|
enabled = true,
|
|
...options
|
|
}: UseCustomQueryOptions<
|
|
EdgeFunctionsLastHourStatsData,
|
|
EdgeFunctionsLastHourStatsError,
|
|
TData
|
|
> = {}
|
|
) =>
|
|
useQuery<EdgeFunctionsLastHourStatsData, EdgeFunctionsLastHourStatsError, TData>({
|
|
queryKey: edgeFunctionsKeys.lastHourStats(projectRef, functionIds),
|
|
queryFn: ({ signal }) => getEdgeFunctionsLastHourStats({ projectRef, functionIds }, signal),
|
|
enabled: enabled && typeof projectRef !== 'undefined' && functionIds.length > 0,
|
|
staleTime: 60 * 1000,
|
|
retry: false,
|
|
...options,
|
|
})
|