mirror of
https://github.com/supabase/supabase.git
synced 2026-09-07 02:20:52 +08:00
## Context Previous PR was [here](https://github.com/supabase/supabase/pull/45143) but it got stale with lots of conflicts so figured it'll be easier redo it off the latest master Moves policies page from Auth to Database under an Access Control section along with Roles. This moves all existing files, applies redirects, and updates urls to point to the new route <img width="274" height="412" alt="image" src="https://github.com/user-attachments/assets/7952c185-64ae-4355-ba36-45397efe1787" /> <img width="453" height="471" alt="image" src="https://github.com/user-attachments/assets/04b3dcb3-48a5-4049-9893-d01109fb46a9" /> ## To test - [ ] Verify that policies now live under Database correctly <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a quick navigation shortcut to open **Database > Policies (RLS)**. * **Bug Fixes** * Updated Policies and RLS-related links across the product to open the **Database policies** area (menus, command palette, context actions, alerts, and link-outs). * Added a permanent redirect from the old **auth policies** URL to the new **database policies** URL. * **Documentation** * Updated RLS Dashboard and security checklist instructions to reference **Database > Policies**. * **Tests** * Adjusted automated tests to validate the new Policies route. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
140 lines
4.1 KiB
TypeScript
140 lines
4.1 KiB
TypeScript
import { SupaTable } from '@/components/grid/types'
|
|
import { Lint } from '@/data/lint/lint-query'
|
|
|
|
export const getEntityLintDetails = (
|
|
entityName: string,
|
|
lintName: string,
|
|
lintLevels: ('ERROR' | 'WARN' | 'INFO')[],
|
|
lints: Lint[],
|
|
schema: string
|
|
): { hasLint: boolean; count: number; matchingLint: Lint | null } => {
|
|
const matchingLint =
|
|
lints?.find(
|
|
(lint) =>
|
|
lint?.metadata?.name === entityName &&
|
|
lint?.metadata?.schema === schema &&
|
|
lint?.name === lintName &&
|
|
lintLevels.includes(lint?.level)
|
|
) || null
|
|
|
|
return {
|
|
hasLint: matchingLint !== null,
|
|
count: matchingLint ? 1 : 0,
|
|
matchingLint,
|
|
}
|
|
}
|
|
|
|
export const getTablePoliciesUrl = (
|
|
projectRef: string | undefined,
|
|
schema: string | undefined,
|
|
name: string | undefined
|
|
): string => {
|
|
return `/project/${projectRef ?? ''}/database/policies?search=${encodeURIComponent(
|
|
name ?? ''
|
|
)}&schema=${encodeURIComponent(schema ?? '')}`
|
|
}
|
|
|
|
export const formatTableRowsToSQL = (table: SupaTable, rows: any[]) => {
|
|
if (rows.length === 0) return ''
|
|
|
|
const columns = table.columns.map((col) => `"${col.name}"`).join(', ')
|
|
|
|
const valuesSets = rows
|
|
.map((row) => {
|
|
const filteredRow = { ...row }
|
|
if ('idx' in filteredRow) delete filteredRow.idx
|
|
|
|
const values = Object.entries(filteredRow).map(([key, val]) => {
|
|
const { dataType, format } = table.columns.find((col) => col.name === key) ?? {}
|
|
|
|
// We only check for NULL, array and JSON types, everything else we stringify
|
|
// given that Postgres can implicitly cast the right type based on the column type
|
|
// For string types, we need to deal with escaping single quotes
|
|
const stringFormats = ['text', 'varchar']
|
|
|
|
if (val === null) {
|
|
return 'null'
|
|
} else if (dataType === 'ARRAY') {
|
|
const array = Array.isArray(val) ? val : JSON.parse(val as string)
|
|
return `${formatArrayForSql(array as unknown[])}`
|
|
} else if (format?.includes('json')) {
|
|
return `${JSON.stringify(val).replace(/\\"/g, '"').replace(/'/g, "''").replace('"', "'").replace(/.$/, "'")}`
|
|
} else if (
|
|
typeof format === 'string' &&
|
|
typeof val === 'string' &&
|
|
stringFormats.includes(format)
|
|
) {
|
|
return `'${val.replaceAll("'", "''")}'`
|
|
} else if (typeof val === 'number' || typeof val === 'boolean') {
|
|
return `${val}`
|
|
} else if (typeof val === 'string') {
|
|
return `'${val.replaceAll("'", "''")}'`
|
|
} else {
|
|
return `'${val}'`
|
|
}
|
|
})
|
|
|
|
return `(${values.join(', ')})`
|
|
})
|
|
.join(', ')
|
|
|
|
return `INSERT INTO "${table.schema}"."${table.name}" (${columns}) VALUES ${valuesSets};`
|
|
}
|
|
|
|
/**
|
|
* Generate a random tag for dollar-quoting of SQL strings
|
|
*
|
|
* @return A random tag in the format `$tag$`
|
|
*/
|
|
const generateRandomTag = (): `$${string}$` => {
|
|
const inner = Math.random().toString(36).substring(2, 15)
|
|
// Ensure the tag starts with a character not a digit to avoid conflicts with
|
|
// Postgres parameter syntax
|
|
return `$x${inner}$`
|
|
}
|
|
|
|
/**
|
|
* Wrap a string in dollar-quote tags, ensuring the tag does not appear in the string
|
|
*
|
|
* @throws Error if unable to generate a unique dollar-quote tag after multiple attempts
|
|
*/
|
|
const safeDollarQuote = (str: string): string => {
|
|
let tag = generateRandomTag()
|
|
|
|
let attempts = 0
|
|
const maxAttempts = 100
|
|
while (str.includes(tag)) {
|
|
if (attempts >= maxAttempts) {
|
|
throw new Error('Unable to generate a unique dollar-quote tag after multiple attempts.')
|
|
}
|
|
|
|
attempts++
|
|
tag = generateRandomTag()
|
|
}
|
|
return `${tag}${str}${tag}`
|
|
}
|
|
|
|
const formatArrayForSql = (arr: unknown[]): string => {
|
|
let result = 'ARRAY['
|
|
|
|
arr.forEach((item, index) => {
|
|
if (Array.isArray(item)) {
|
|
result += formatArrayForSql(item)
|
|
} else if (typeof item === 'string') {
|
|
result += `'${item.replaceAll("'", "''")}'`
|
|
} else if (!!item && typeof item === 'object') {
|
|
result += `${safeDollarQuote(JSON.stringify(item))}::json`
|
|
} else {
|
|
result += `${item}`
|
|
}
|
|
|
|
if (index < arr.length - 1) {
|
|
result += ','
|
|
}
|
|
})
|
|
|
|
result += ']'
|
|
|
|
return result
|
|
}
|