mirror of
https://github.com/supabase/supabase.git
synced 2026-07-19 22:07:13 -04:00
1de298ff31
## 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 -->
97 lines
3.5 KiB
TypeScript
97 lines
3.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import type { ProjectSecurityTable } from './ProjectNeedsSecuring.types'
|
|
import {
|
|
buildSecurityPromptMarkdown,
|
|
formatRlsDescription,
|
|
getTableKey,
|
|
getTablePoliciesHref,
|
|
sortTables,
|
|
} from './ProjectNeedsSecuring.utils'
|
|
|
|
const table = (overrides: Partial<ProjectSecurityTable>): ProjectSecurityTable => ({
|
|
id: 1,
|
|
name: 't',
|
|
schema: 'public',
|
|
rlsEnabled: false,
|
|
hasRlsIssue: false,
|
|
dataApiAccessible: false,
|
|
...overrides,
|
|
})
|
|
|
|
describe('ProjectNeedsSecuring.utils: getTableKey', () => {
|
|
it('joins schema and name with a dot', () => {
|
|
expect(getTableKey({ schema: 'public', name: 'users' })).toBe('public.users')
|
|
})
|
|
})
|
|
|
|
describe('ProjectNeedsSecuring.utils: formatRlsDescription', () => {
|
|
it('returns the singular form when count is 1', () => {
|
|
expect(formatRlsDescription(1)).toContain('1 table has RLS disabled')
|
|
expect(formatRlsDescription(1)).toContain('its data')
|
|
})
|
|
|
|
it('returns the plural form for any other count', () => {
|
|
expect(formatRlsDescription(3)).toContain('3 tables have RLS disabled')
|
|
expect(formatRlsDescription(3)).toContain('their data')
|
|
})
|
|
})
|
|
|
|
describe('ProjectNeedsSecuring.utils: sortTables', () => {
|
|
it('puts tables with active RLS issues first', () => {
|
|
const tables = [
|
|
table({ id: 1, name: 'a', hasRlsIssue: false, rlsEnabled: true }),
|
|
table({ id: 2, name: 'b', hasRlsIssue: true, rlsEnabled: false }),
|
|
table({ id: 3, name: 'c', hasRlsIssue: false, rlsEnabled: false }),
|
|
]
|
|
const sorted = sortTables(tables)
|
|
expect(sorted.map((t) => t.name)).toEqual(['b', 'c', 'a'])
|
|
})
|
|
})
|
|
|
|
describe('ProjectNeedsSecuring.utils: buildSecurityPromptMarkdown', () => {
|
|
it('builds a markdown report with a header row and one row per table', () => {
|
|
const markdown = buildSecurityPromptMarkdown(1, [
|
|
table({ name: 'invoices', schema: 'public', dataApiAccessible: true, rlsEnabled: false }),
|
|
])
|
|
expect(markdown).toContain('## Project security review')
|
|
expect(markdown).toContain('1 table has RLS disabled')
|
|
expect(markdown).toContain('| invoices | public | Yes | Disabled |')
|
|
})
|
|
})
|
|
|
|
describe('ProjectNeedsSecuring.utils: getTablePoliciesHref', () => {
|
|
it('builds the policies href with plain values', () => {
|
|
expect(getTablePoliciesHref('abc', 'public', 'invoices')).toBe(
|
|
'/project/abc/database/policies?schema=public&search=invoices'
|
|
)
|
|
})
|
|
|
|
it('preserves special characters in the table name', () => {
|
|
const href = getTablePoliciesHref('abc', 'public', 'user_data&secret=1')
|
|
const parsed = new URL(href, 'http://example.com')
|
|
expect(parsed.searchParams.get('search')).toBe('user_data&secret=1')
|
|
expect(parsed.searchParams.get('schema')).toBe('public')
|
|
})
|
|
|
|
it('preserves special characters in the schema', () => {
|
|
const href = getTablePoliciesHref('abc', 'my schema+x', 'users')
|
|
const parsed = new URL(href, 'http://example.com')
|
|
expect(parsed.searchParams.get('schema')).toBe('my schema+x')
|
|
expect(parsed.searchParams.get('search')).toBe('users')
|
|
})
|
|
|
|
it('encodes both values together', () => {
|
|
const href = getTablePoliciesHref('abc', 'a&b=c', 'd e+f')
|
|
const parsed = new URL(href, 'http://example.com')
|
|
expect(parsed.searchParams.get('schema')).toBe('a&b=c')
|
|
expect(parsed.searchParams.get('search')).toBe('d e+f')
|
|
})
|
|
|
|
it('falls back to empty strings for undefined inputs', () => {
|
|
expect(getTablePoliciesHref(undefined, undefined, undefined)).toBe(
|
|
'/project//database/policies?schema=&search='
|
|
)
|
|
})
|
|
})
|