Files
supabase/apps/studio/components/layouts/ProjectNeedsSecuring/ProjectNeedsSecuring.utils.ts
Saxon Fletcher 3b756e4d9f Chore/project secure (#45108)
<img width="2652" height="830" alt="image"
src="https://github.com/user-attachments/assets/3c3921e7-c255-4e59-a9c3-c5f97da87788"
/>

Adds a full screen alert behind a feature flag `projectNeedsSecuring`
that prompts for fixing RLS issues.

Adjusts a few other small styles to add more prominence to critical
advisor issues.

To test:

- Enable the flag
- Make sure you have a table with RLS disabled
- Open project home and note the fade in of full page review
- Click "copy prompt" or "fix" and note the prompt
- Click skip to home and refresh the page, note it doesn't appear
anymore


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Project-level security gate on project home with AI assistant prompts,
table details, per-project dismissible notice, and a new telemetry event
for CTA interactions.

* **Improvements**
* Stronger visual treatment for critical advisor items and advisor CTA
when critical issues exist.
* Assistant dropdown supports a copy-prompt callback; added
local-storage key and utilities/types to support project security
workflows.

* **Tests**
  * Added tests covering gate behavior, navigation, and dismissal logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-04-29 04:08:09 +00:00

63 lines
2.0 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 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)
})
}