mirror of
https://github.com/supabase/supabase.git
synced 2026-06-23 03:08:54 +08:00
Closes #45847. ## Summary `ProjectNeedsSecuringView.tsx` built the `View policies` href on the first-time security gate by interpolating `table.schema` and `table.name` directly into the URL. A table or schema containing `&`, `=`, `+`, or `#` corrupted the destination and routed the user to the wrong policies filter on what is meant to be a guided onboarding flow. Extracts the URL into `getTablePoliciesHref` in `ProjectNeedsSecuring.utils.ts` with `encodeURIComponent` wraps, and replaces the inline interpolation. Same pattern as #45385. ## Test plan Added `ProjectNeedsSecuring.utils.test.ts` covering `getTablePoliciesHref` (plain values, special chars in name, special chars in schema, both, undefined inputs) and pulling in the previously-untested `getTableKey`, `formatRlsDescription`, `sortTables`, and `buildSecurityPromptMarkdown` utilities. Ten tests total. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Added comprehensive test coverage for security utilities, including URL construction, formatting, sorting, and markdown report generation. * **Refactor** * Extracted URL building logic into a centralized utility function for improved consistency and maintainability. [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45849) <!-- end of auto-generated comment: release notes by coderabbit.ai -->
73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
import { type ProjectSecurityTable } from './ProjectNeedsSecuring.types'
|
|
import { parseDbSchemaString } from '@/data/config/project-postgrest-config-query'
|
|
|
|
const DEFAULT_EXPOSED_SCHEMA = 'public'
|
|
|
|
export const getTableKey = ({ schema, name }: { schema: string; name: string }) =>
|
|
`${schema}.${name}`
|
|
|
|
export const getTablePoliciesHref = (
|
|
projectRef: string | undefined,
|
|
schema: string | undefined,
|
|
name: string | undefined
|
|
): string => {
|
|
return `/project/${projectRef ?? ''}/auth/policies?schema=${encodeURIComponent(
|
|
schema ?? ''
|
|
)}&search=${encodeURIComponent(name ?? '')}`
|
|
}
|
|
|
|
export const getExposedSchemas = (dbSchema: string | null | undefined) => {
|
|
const schemas = dbSchema ? parseDbSchemaString(dbSchema) : []
|
|
return schemas.length > 0 ? schemas : [DEFAULT_EXPOSED_SCHEMA]
|
|
}
|
|
|
|
export const formatRlsDescription = (count: number) => {
|
|
const isSingular = count === 1
|
|
const noun = isSingular ? 'table' : 'tables'
|
|
const verb = isSingular ? 'has' : 'have'
|
|
const pronoun = isSingular ? 'its' : 'their'
|
|
|
|
return `${count} ${noun} ${verb} RLS disabled which means anyone can access ${pronoun} data via the Data API.`
|
|
}
|
|
|
|
export const buildSecurityPromptMarkdown = (issueCount: number, tables: ProjectSecurityTable[]) => {
|
|
const header = [
|
|
'## Project security review',
|
|
'',
|
|
formatRlsDescription(issueCount),
|
|
'',
|
|
'### Tables',
|
|
'',
|
|
'| Table | Schema | Accessible via Data API | RLS |',
|
|
'| --- | --- | --- | --- |',
|
|
]
|
|
|
|
const rows = tables.map(
|
|
(table) =>
|
|
`| ${table.name} | ${table.schema} | ${table.dataApiAccessible ? 'Yes' : 'No'} | ${table.rlsEnabled ? 'Enabled' : 'Disabled'} |`
|
|
)
|
|
|
|
const footer = [
|
|
'',
|
|
'### Next step',
|
|
'',
|
|
'Help me enable RLS on these tables and suggest the minimum policies I should create.',
|
|
]
|
|
|
|
return [...header, ...rows, ...footer].join('\n')
|
|
}
|
|
|
|
export const sortTables = (tables: ProjectSecurityTable[]) => {
|
|
return [...tables].sort((a, b) => {
|
|
const aPriority = a.hasRlsIssue ? 0 : a.rlsEnabled ? 2 : 1
|
|
const bPriority = b.hasRlsIssue ? 0 : b.rlsEnabled ? 2 : 1
|
|
|
|
if (aPriority !== bPriority) return aPriority - bPriority
|
|
|
|
const schemaComparison = a.schema.localeCompare(b.schema)
|
|
if (schemaComparison !== 0) return schemaComparison
|
|
|
|
return a.name.localeCompare(b.name)
|
|
})
|
|
}
|