ProjectNeedsSecuring.utils.ts72 lines · main
| 1 | import { type ProjectSecurityTable } from './ProjectNeedsSecuring.types' |
| 2 | import { parseDbSchemaString } from '@/data/config/project-postgrest-config-query' |
| 3 | |
| 4 | const DEFAULT_EXPOSED_SCHEMA = 'public' |
| 5 | |
| 6 | export const getTableKey = ({ schema, name }: { schema: string; name: string }) => |
| 7 | `${schema}.${name}` |
| 8 | |
| 9 | export const getTablePoliciesHref = ( |
| 10 | projectRef: string | undefined, |
| 11 | schema: string | undefined, |
| 12 | name: string | undefined |
| 13 | ): string => { |
| 14 | return `/project/${projectRef ?? ''}/auth/policies?schema=${encodeURIComponent( |
| 15 | schema ?? '' |
| 16 | )}&search=${encodeURIComponent(name ?? '')}` |
| 17 | } |
| 18 | |
| 19 | export const getExposedSchemas = (dbSchema: string | null | undefined) => { |
| 20 | const schemas = dbSchema ? parseDbSchemaString(dbSchema) : [] |
| 21 | return schemas.length > 0 ? schemas : [DEFAULT_EXPOSED_SCHEMA] |
| 22 | } |
| 23 | |
| 24 | export const formatRlsDescription = (count: number) => { |
| 25 | const isSingular = count === 1 |
| 26 | const noun = isSingular ? 'table' : 'tables' |
| 27 | const verb = isSingular ? 'has' : 'have' |
| 28 | const pronoun = isSingular ? 'its' : 'their' |
| 29 | |
| 30 | return `${count} ${noun} ${verb} RLS disabled which means anyone can access ${pronoun} data via the Data API.` |
| 31 | } |
| 32 | |
| 33 | export const buildSecurityPromptMarkdown = (issueCount: number, tables: ProjectSecurityTable[]) => { |
| 34 | const header = [ |
| 35 | '## Project security review', |
| 36 | '', |
| 37 | formatRlsDescription(issueCount), |
| 38 | '', |
| 39 | '### Tables', |
| 40 | '', |
| 41 | '| Table | Schema | Accessible via Data API | RLS |', |
| 42 | '| --- | --- | --- | --- |', |
| 43 | ] |
| 44 | |
| 45 | const rows = tables.map( |
| 46 | (table) => |
| 47 | `| ${table.name} | ${table.schema} | ${table.dataApiAccessible ? 'Yes' : 'No'} | ${table.rlsEnabled ? 'Enabled' : 'Disabled'} |` |
| 48 | ) |
| 49 | |
| 50 | const footer = [ |
| 51 | '', |
| 52 | '### Next step', |
| 53 | '', |
| 54 | 'Help me enable RLS on these tables and suggest the minimum policies I should create.', |
| 55 | ] |
| 56 | |
| 57 | return [...header, ...rows, ...footer].join('\n') |
| 58 | } |
| 59 | |
| 60 | export const sortTables = (tables: ProjectSecurityTable[]) => { |
| 61 | return [...tables].sort((a, b) => { |
| 62 | const aPriority = a.hasRlsIssue ? 0 : a.rlsEnabled ? 2 : 1 |
| 63 | const bPriority = b.hasRlsIssue ? 0 : b.rlsEnabled ? 2 : 1 |
| 64 | |
| 65 | if (aPriority !== bPriority) return aPriority - bPriority |
| 66 | |
| 67 | const schemaComparison = a.schema.localeCompare(b.schema) |
| 68 | if (schemaComparison !== 0) return schemaComparison |
| 69 | |
| 70 | return a.name.localeCompare(b.name) |
| 71 | }) |
| 72 | } |