feat: Add more lint rules (#22594)

* Refactor the functions page to keep its state in the URL.

* Derive the filter options from currently active lints.

* Update the lint query sql.

* Update the urls to the docs.

* Fix the paddings on the filterPopover.

* Add handling for the other types of lints.

* Comment out the ignore lint functionality.

* Fix a misspell.

* unbest

* Bring back the CTA button.

* Update the SQL code from the splinter repo.

* Add handling for the duplicate index lint rule.

---------

Co-authored-by: Terry Sutton <saltcod@gmail.com>
This commit is contained in:
Ivan VasilovandTerry Sutton authored and GitHub committed 2024-04-11 11:50:17 +02:00
1 parent 9ec2bcce9d
commit db2fb81ec8
6 files changed
+482 -99

No files matched your search

@@ -2,9 +2,10 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import type { PostgresFunction } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { noop, partition } from 'lodash'
import { useState } from 'react'
import { useRouter } from 'next/router'
import { Button, IconSearch, Input } from 'ui'
import { useParams } from 'common'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
import Table from 'components/to-be-cleaned/Table'
@@ -15,6 +16,7 @@ import { useDatabaseFunctionsQuery } from 'data/database-functions/database-func
import { useSchemasQuery } from 'data/database/schemas-query'
import { useCheckPermissions } from 'hooks'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import { useEffect } from 'react'
import ProtectedSchemaWarning from '../../ProtectedSchemaWarning'
import FunctionList from './FunctionList'
@@ -30,8 +32,33 @@ const FunctionsList = ({
deleteFunction = noop,
}: FunctionsListProps) => {
const { project } = useProjectContext()
const [selectedSchema, setSelectedSchema] = useState<string>('public')
const [filterString, setFilterString] = useState<string>('')
const router = useRouter()
const { schema, search } = useParams()
const selectedSchema = schema ?? 'public'
const filterString = search ?? ''
const setSelectedSchema = (s: string) => {
const url = new URL(document.URL)
url.searchParams.delete('search')
url.searchParams.set('schema', s)
router.push(url)
}
const setFilterString = (str: string) => {
const url = new URL(document.URL)
if (str === '') {
url.searchParams.delete('search')
} else {
url.searchParams.set('search', str)
}
router.push(url)
}
// update the url to point to public schema
useEffect(() => {
if (schema !== selectedSchema) {
setSelectedSchema(selectedSchema)
}
}, [])
const canCreateFunctions = useCheckPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
@@ -45,8 +72,8 @@ const FunctionsList = ({
const [protectedSchemas] = partition(schemas ?? [], (schema) =>
EXCLUDED_SCHEMAS.includes(schema?.name ?? '')
)
const schema = schemas?.find((schema) => schema.name === selectedSchema)
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
const foundSchema = schemas?.find((schema) => schema.name === selectedSchema)
const isLocked = protectedSchemas.some((s) => s.id === foundSchema?.id)
const {
data: functions,
@@ -20,6 +20,18 @@ export function getHumanReadableTitle(title: LINT_TYPES) {
return 'Unused Index'
case 'multiple_permissive_policies':
return 'Multiple Permissive Policies'
case 'function_search_path_mutable':
return 'Function Search Path Mutable'
case 'rls_enabled_no_policy':
return 'RLS Enabled No Policy'
case 'policy_exists_rls_disabled':
return 'Policy Exists RLS Disabled'
case 'rls_disabled_in_public':
return 'RLS Disabled in Public'
case 'security_definer_view':
return 'Security Definer View'
case 'duplicate_index':
return 'Duplicate Index'
default:
assertUnreachable(title)
throw new Error('This case should never be reached')
@@ -85,6 +97,18 @@ export const LintCTA = ({
</Link>
</Button>
)
case 'duplicate_index':
return (
<Button asChild type="default">
<Link
href={`/project/${projectRef}/database/indexes?schema=${metadata?.schema}&table=${metadata?.name}`}
target="_blank"
rel="noreferrer"
>
View index
</Link>
</Button>
)
case 'multiple_permissive_policies':
return (
<Button asChild type="default">
@@ -97,6 +121,58 @@ export const LintCTA = ({
</Link>
</Button>
)
case 'function_search_path_mutable':
return (
<Button asChild type="default">
<Link
href={`/project/${projectRef}/database/functions?schema=${metadata?.schema}&search=${metadata?.name}`}
target="_blank"
rel="noreferrer"
>
View functions
</Link>
</Button>
)
case 'rls_enabled_no_policy':
return (
<Button asChild type="default">
<Link
href={`/project/${projectRef}/auth/policies?schema=${metadata?.schema}&search=${metadata?.name}`}
target="_blank"
rel="noreferrer"
>
View policies
</Link>
</Button>
)
case 'policy_exists_rls_disabled':
return (
<Button asChild type="default">
<Link
href={`/project/${projectRef}/auth/policies?schema=${metadata?.schema}&search=${metadata?.name}`}
target="_blank"
rel="noreferrer"
>
View policies
</Link>
</Button>
)
case 'rls_disabled_in_public':
return (
<Button asChild type="default">
<Link
href={`/project/${projectRef}/auth/policies?schema=${metadata?.schema}&search=${metadata?.name}`}
target="_blank"
rel="noreferrer"
>
View policies
</Link>
</Button>
)
case 'security_definer_view':
// we don't have a good place to send the user to check out a view
return <></>
default:
assertUnreachable(title)
return <></>
@@ -1,19 +1,9 @@
import { Eye, EyeIcon, EyeOff, HelpCircle, Table2 } from 'lucide-react'
import { useState } from 'react'
import { Eye, HelpCircle, Table2 } from 'lucide-react'
import { useParams } from 'common'
import Table from 'components/to-be-cleaned/Table'
import { LINT_TYPES, Lint } from 'data/lint/lint-query'
import { useLocalStorageQuery } from 'hooks'
import { LOCAL_STORAGE_KEYS } from 'lib/constants'
import {
Badge,
Button,
Modal,
TooltipContent_Shadcn_,
TooltipTrigger_Shadcn_,
Tooltip_Shadcn_,
} from 'ui'
import { Badge, TooltipContent_Shadcn_, TooltipTrigger_Shadcn_, Tooltip_Shadcn_ } from 'ui'
import { Markdown } from '../Markdown'
import { LintCTA, getHumanReadableTitle } from './ReportLints.utils'
@@ -23,33 +13,35 @@ type ReportLintsTableRowProps = {
const ReportLintsTableRow = ({ lint }: ReportLintsTableRowProps) => {
const { ref } = useParams()
const [selectedLint, setSelectedLint] = useState<Lint | null>(null)
// TODO: Comment out functionality for ignoring lints because it relied on local storage. Will revisit later.
// const [selectedLint, setSelectedLint] = useState<Lint | null>(null)
const [lintIgnoreList, setLintIgnoreList] = useLocalStorageQuery<string[]>(
LOCAL_STORAGE_KEYS.PROJECT_LINT_IGNORE_LIST,
[]
)
const isIgnored = lintIgnoreList.includes(lint.cache_key)
// const [lintIgnoreList, setLintIgnoreList] = useLocalStorageQuery<string[]>(
// LOCAL_STORAGE_KEYS.PROJECT_LINT_IGNORE_LIST,
// []
// )
// const isIgnored = lintIgnoreList.includes(lint.cache_key)
// if the lint type can't be handled (there's no CTA text defined), don't render it
if (!LINT_TYPES.includes(lint.name)) {
console.log('Unhandled lint type:', lint.name)
return null
}
const toggleLintIgnore = () => {
let currentIgnoreList = []
const cacheKey = lint.cache_key
// const toggleLintIgnore = () => {
// let currentIgnoreList = []
// const cacheKey = lint.cache_key
// Check if the cacheKey exists in the array and ignore or unignore it
const index = lintIgnoreList.indexOf(cacheKey)
if (index !== -1) {
currentIgnoreList = lintIgnoreList.filter((l) => l !== cacheKey)
} else {
currentIgnoreList = lintIgnoreList.concat(cacheKey)
}
setLintIgnoreList(currentIgnoreList)
setSelectedLint(null)
}
// // Check if the cacheKey exists in the array and ignore or unignore it
// const index = lintIgnoreList.indexOf(cacheKey)
// if (index !== -1) {
// currentIgnoreList = lintIgnoreList.filter((l) => l !== cacheKey)
// } else {
// currentIgnoreList = lintIgnoreList.concat(cacheKey)
// }
// setLintIgnoreList(currentIgnoreList)
// setSelectedLint(null)
// }
return (
<>
@@ -109,7 +101,7 @@ const ReportLintsTableRow = ({ lint }: ReportLintsTableRowProps) => {
)}
{lint.remediation && (
<p className="text-foreground-light max-w-full leading-relaxed">
You can read more about this lint rule and the best ways to remedy it{' '}
You can read more about this lint rule and ways to remedy it{' '}
<a
className="underline text-foreground-light transition-all hover:text-foreground hover:decoration-brand"
href={lint.remediation}
@@ -125,7 +117,7 @@ const ReportLintsTableRow = ({ lint }: ReportLintsTableRowProps) => {
<Table.td>
<div className="flex items-center justify-end gap-x-2">
<LintCTA title={lint.name} projectRef={ref!} metadata={lint.metadata} />
<Tooltip_Shadcn_>
{/* <Tooltip_Shadcn_>
<TooltipTrigger_Shadcn_ asChild>
<Button
type="text"
@@ -141,11 +133,11 @@ const ReportLintsTableRow = ({ lint }: ReportLintsTableRowProps) => {
<TooltipContent_Shadcn_ side="bottom">
{isIgnored ? 'Unignore problem' : 'Ignore problem'}
</TooltipContent_Shadcn_>
</Tooltip_Shadcn_>
</Tooltip_Shadcn_> */}
</div>
</Table.td>
</Table.tr>
<Modal
{/* <Modal
size="small"
alignFooter="right"
visible={selectedLint !== null}
@@ -162,7 +154,7 @@ const ReportLintsTableRow = ({ lint }: ReportLintsTableRowProps) => {
</p>
</Modal.Content>
</div>
</Modal>
</Modal> */}
</>
)
}
+2 -1
View File
@@ -40,9 +40,10 @@ export const FilterPopover = ({
header={<div className="prose text-xs">Select {name.toLowerCase()}</div>}
overlay={
<>
<div className="space-y-4 px-3 py-3 min-w-[170px]">
<div className="space-y-4 min-w-[170px]">
<ScrollArea className={options.length > 7 ? 'h-[205px]' : ''}>
<Checkbox.Group
className="px-3 py-3"
id="projects"
onChange={(event) => {
const value = event.target.value
+316 -26
View File
@@ -10,10 +10,10 @@ with foreign_keys as (
ct.conname as fkey_name,
ct.conkey col_attnums
from
pg_constraint ct
join pg_class cl -- fkey owning table
pg_catalog.pg_constraint ct
join pg_catalog.pg_class cl -- fkey owning table
on ct.conrelid = cl.oid
left join pg_depend d
left join pg_catalog.pg_depend d
on d.objid = cl.oid
and d.deptype = 'e'
where
@@ -29,7 +29,7 @@ index_ as (
indexrelid::regclass as index_,
string_to_array(indkey::text, ' ')::smallint[] as col_attnums
from
pg_index
pg_catalog.pg_index
where
indisvalid
)
@@ -42,7 +42,6 @@ select
'Table \`%s.%s\` has a foreign key \`%s\` without a covering index. This can lead to suboptimal query performance.',
fk.schema_,
fk.table_,
fk.table_,
fk.fkey_name
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0001_unindexed_foreign_keys' as remediation,
@@ -53,7 +52,7 @@ select
'fkey_name', fk.fkey_name,
'fkey_columns', fk.col_attnums
) as metadata,
format('0001_unindexed_foreign_keys_%s_%s_%s', fk.schema_, fk.table_, fk.fkey_name) as cache_key
format('unindexed_foreign_keys_%s_%s_%s', fk.schema_, fk.table_, fk.fkey_name) as cache_key
from
foreign_keys fk
left join index_ idx
@@ -61,6 +60,9 @@ from
and fk.col_attnums = idx.col_attnums
where
idx.index_ is null
and fk.schema_::text not in (
'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault'
)
order by
fk.table_,
fk.fkey_name)
@@ -84,17 +86,25 @@ select
) as metadata,
format('auth_users_exposed_%s_%s', 'public', c.relname) as cache_key
from
pg_depend d
join pg_rewrite r
-- Identify the oid for auth.users
pg_catalog.pg_class auth_users_pg_class
join pg_catalog.pg_namespace auth_users_pg_namespace
on auth_users_pg_class.relnamespace = auth_users_pg_namespace.oid
and auth_users_pg_class.relname = 'users'
and auth_users_pg_namespace.nspname = 'auth'
-- Depends on auth.users
join pg_catalog.pg_depend d
on d.refobjid = auth_users_pg_class.oid
join pg_catalog.pg_rewrite r
on r.oid = d.objid
join pg_class c
join pg_catalog.pg_class c
on c.oid = r.ev_class
join pg_namespace n
join pg_catalog.pg_namespace n
on n.oid = c.relnamespace
join pg_catalog.pg_class pg_class_auth_users
on d.refobjid = pg_class_auth_users.oid
where
d.refobjid = 'auth.users'::regclass
and d.deptype = 'n'
and c.relkind in ('v', 'm') -- v for view, m for materialized view
d.deptype = 'n'
and n.nspname = 'public'
and (
pg_catalog.has_table_privilege('anon', c.oid, 'SELECT')
@@ -102,6 +112,43 @@ where
)
-- Exclude self
and c.relname <> '0002_auth_users_exposed'
-- There are 3 insecure configurations
and
(
-- Materialized views don't support RLS so this is insecure by default
(c.relkind in ('m')) -- m for materialized view
or
-- Standard View, accessible to anon or authenticated that is security_definer
(
c.relkind = 'v' -- v for view
-- Exclude security invoker views
and not (
lower(coalesce(c.reloptions::text,'{}'))::text[]
&& array[
'security_invoker=1',
'security_invoker=true',
'security_invoker=yes',
'security_invoker=on'
]
)
)
or
-- Standard View, security invoker, but no RLS enabled on auth.users
(
c.relkind in ('v') -- v for view
-- is security invoker
and (
lower(coalesce(c.reloptions::text,'{}'))::text[]
&& array[
'security_invoker=1',
'security_invoker=true',
'security_invoker=yes',
'security_invoker=on'
]
)
and not pg_class_auth_users.relrowsecurity
)
)
group by
c.relname, c.oid)
union all
@@ -151,12 +198,12 @@ with policies as (
qual,
with_check
from
pg_policy pa
join pg_class pc
pg_catalog.pg_policy pa
join pg_catalog.pg_class pc
on pa.polrelid = pc.oid
join pg_namespace nsp
join pg_catalog.pg_namespace nsp
on pc.relnamespace = nsp.oid
join pg_policies pb
join pg_catalog.pg_policies pb
on pc.relname = pb.tablename
and nsp.nspname = pb.schemaname
and pa.polname = pb.policyname
@@ -182,6 +229,9 @@ from
policies
where
is_rls_active
and schema_::text not in (
'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault'
)
and (
(
-- Example: auth.uid()
@@ -210,7 +260,7 @@ select
pgc.relname
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0004_no_primary_key' as remediation,
jsonb_build_object(
jsonb_build_object(
'schema', pgns.nspname,
'name', pgc.relname,
'type', 'table'
@@ -221,15 +271,15 @@ select
pgc.relname
) as cache_key
from
pg_class pgc
join pg_namespace pgns
pg_catalog.pg_class pgc
join pg_catalog.pg_namespace pgns
on pgns.oid = pgc.relnamespace
left join pg_index pgi
left join pg_catalog.pg_index pgi
on pgi.indrelid = pgc.oid
where
pgc.relkind = 'r' -- regular tables
and pgns.nspname not in (
'pg_catalog', 'information_schema', 'auth', 'storage', 'vault', 'pgsodium'
'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault'
)
group by
pgc.oid,
@@ -272,7 +322,7 @@ where
and not pi.indisunique
and not pi.indisprimary
and psui.schemaname not in (
'pg_catalog', 'information_schema', 'auth', 'storage', 'vault', 'pgsodium'
'pg_catalog', 'information_schema', 'auth', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault'
))
union all
(
@@ -326,7 +376,7 @@ from
where
c.relkind = 'r' -- regular tables
and n.nspname not in (
'pg_catalog', 'information_schema', 'auth', 'storage', 'vault', 'pgsodium'
'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault'
)
and r.rolname not like 'pg_%'
and r.rolname not like 'supabase%admin'
@@ -337,16 +387,256 @@ group by
r.rolname,
act.cmd
having
count(1) > 1)`.trim()
count(1) > 1)
union all
(
select
'policy_exists_rls_disabled' as name,
'INFO' as level,
'EXTERNAL' as facing,
'Detects cases where row level security (RLS) policies have been created, but RLS has not been enabled for the underlying table.' as description,
format(
'Table \`%s.%s\` has RLS policies but RLS is not enabled on the table. Policies include %s.',
n.nspname,
c.relname,
array_agg(p.polname order by p.polname)
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0007_policy_exists_rls_disabled' as remediation,
jsonb_build_object(
'schema', n.nspname,
'name', c.relname,
'type', 'table'
) as metadata,
format(
'policy_exists_rls_disabled_%s_%s',
n.nspname,
c.relname
) as cache_key
from
pg_catalog.pg_policy p
join pg_catalog.pg_class c
on p.polrelid = c.oid
join pg_catalog.pg_namespace n
on c.relnamespace = n.oid
where
c.relkind = 'r' -- regular tables
and n.nspname not in (
'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault'
)
-- RLS is disabled
and not c.relrowsecurity
group by
n.nspname,
c.relname)
union all
(
select
'rls_enabled_no_policy' as name,
'INFO' as level,
'EXTERNAL' as facing,
'Detects cases where row level security (RLS) has been enabled on a table but no RLS policies have been created.' as description,
format(
'Table \`%s.%s\` has RLS enabled, but no policies exist',
n.nspname,
c.relname
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0008_rls_enabled_no_policy' as remediation,
jsonb_build_object(
'schema', n.nspname,
'name', c.relname,
'type', 'table'
) as metadata,
format(
'rls_enabled_no_policy_%s_%s',
n.nspname,
c.relname
) as cache_key
from
pg_catalog.pg_class c
left join pg_catalog.pg_policy p
on p.polrelid = c.oid
join pg_catalog.pg_namespace n
on c.relnamespace = n.oid
where
c.relkind = 'r' -- regular tables
and n.nspname not in (
'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault'
)
-- RLS is enabled
and c.relrowsecurity
and p.polname is null
group by
n.nspname,
c.relname)
union all
(
select
'duplicate_index' as name,
'WARN' as level,
'EXTERNAL' as facing,
'Detects cases where two ore more identical indexes exist.' as description,
format(
'Table \`%s.%s\` has identical indexes %s. Drop all except one of them',
n.nspname,
c.relname,
array_agg(pi.indexname order by pi.indexname)
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0009_duplicate_index' as remediation,
jsonb_build_object(
'schema', n.nspname,
'name', c.relname,
'type', case
when c.relkind = 'r' then 'table'
when c.relkind = 'm' then 'materialized view'
else 'ERROR'
end,
'indexes', array_agg(pi.indexname order by pi.indexname)
) as metadata,
format(
'duplicate_index_%s_%s_%s',
n.nspname,
c.relname,
array_agg(pi.indexname order by pi.indexname)
) as cache_key
from
pg_catalog.pg_indexes pi
join pg_catalog.pg_namespace n
on n.nspname = pi.schemaname
join pg_catalog.pg_class c
on pi.tablename = c.relname
and n.oid = c.relnamespace
where
c.relkind in ('r', 'm') -- tables and materialized views
and n.nspname not in (
'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault'
)
group by
n.nspname,
c.relkind,
c.relname,
replace(pi.indexdef, pi.indexname, '')
having
count(*) > 1)
union all
(
select
'security_definer_view' as name,
'WARN' as level,
'EXTERNAL' as facing,
'Detects views that are SECURITY DEFINER meaning that they ignore row level security (RLS) policies.' as description,
format(
'View \`%s.%s\` is SECURITY DEFINER',
n.nspname,
c.relname
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0010_security_definer_view' as remediation,
jsonb_build_object(
'schema', n.nspname,
'name', c.relname,
'type', 'view'
) as metadata,
format(
'security_definer_view_%s_%s',
n.nspname,
c.relname
) as cache_key
from
pg_catalog.pg_class c
join pg_catalog.pg_namespace n
on n.oid = c.relnamespace
where
c.relkind = 'v'
and n.nspname = 'public'
and not (
lower(coalesce(c.reloptions::text,'{}'))::text[]
&& array[
'security_invoker=1',
'security_invoker=true',
'security_invoker=yes',
'security_invoker=on'
]
))
union all
(
select
'function_search_path_mutable' as name,
'WARN' as level,
'EXTERNAL' as facing,
'Detects functions with a mutable search_path parameter which could fail to execute successfully for some roles.' as description,
format(
'Function \`%s.%s\` has a role mutable search_path',
n.nspname,
p.proname
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0011_function_search_path_mutable' as remediation,
jsonb_build_object(
'schema', n.nspname,
'name', p.proname,
'type', 'function'
) as metadata,
format(
'function_search_path_mutable_%s_%s_%s',
n.nspname,
p.proname,
md5(p.prosrc) -- required when function is polymorphic
) as cache_key
from
pg_catalog.pg_proc p
join pg_catalog.pg_namespace n
on p.pronamespace = n.oid
where
n.nspname not in (
'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault'
)
-- Search path not set to ''
and not coalesce(p.proconfig, '{}') && array['search_path=""'])
union all
(
select
'rls_disabled_in_public' as name,
'ERROR' as level,
'EXTERNAL' as facing,
'Detects cases where row level security (RLS) has not been enabled on a table in the \`public\` schema.' as description,
format(
'Table \`%s.%s\` is public, but RLS has not been enabled.',
n.nspname,
c.relname
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0013_rls_disabled_in_public' as remediation,
jsonb_build_object(
'schema', n.nspname,
'name', c.relname,
'type', 'table'
) as metadata,
format(
'rls_disabled_in_public_%s_%s',
n.nspname,
c.relname
) as cache_key
from
pg_catalog.pg_class c
join pg_catalog.pg_namespace n
on c.relnamespace = n.oid
where
c.relkind = 'r' -- regular tables
and n.nspname = 'public'
-- RLS is disabled
and not c.relrowsecurity)`.trim()
// Array of all lint rules we handle right now.
export const LINT_TYPES = [
'unindexed_foreign_keys',
'auth_users_exposed',
'auth_rls_initplan',
'no_primary_key',
'unused_index',
'multiple_permissive_policies',
'auth_rls_initplan',
'policy_exists_rls_disabled',
'rls_enabled_no_policy',
'duplicate_index',
'security_definer_view',
'function_search_path_mutable',
'rls_disabled_in_public',
] as const
export type LINT_TYPES = (typeof LINT_TYPES)[number]
@@ -1,25 +1,18 @@
import { partition, sortBy } from 'lodash'
import { sortBy } from 'lodash'
import { Check, ExternalLink, Loader } from 'lucide-react'
import { useMemo, useState } from 'react'
import { AccordionTrigger } from '@ui/components/shadcn/ui/accordion'
import { getHumanReadableTitle } from 'components/interfaces/Reports/ReportLints.utils'
import ReportLintsTableRow from 'components/interfaces/Reports/ReportLintsTableRow'
import { DatabaseLayout } from 'components/layouts'
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import Table from 'components/to-be-cleaned/Table'
import { FormHeader } from 'components/ui/Forms'
import { useProjectLintsQuery } from 'data/lint/lint-query'
import { useLocalStorageQuery, useSelectedProject } from 'hooks'
import { LOCAL_STORAGE_KEYS } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
import {
AccordionContent_Shadcn_,
AccordionItem_Shadcn_,
Accordion_Shadcn_,
Button,
LoadingLine,
} from 'ui'
import { FilterPopover } from 'components/ui/FilterPopover'
import { FormHeader } from 'components/ui/Forms'
import { LINT_TYPES, useProjectLintsQuery } from 'data/lint/lint-query'
import { useSelectedProject } from 'hooks'
import type { NextPageWithLayout } from 'types'
import { Button, LoadingLine } from 'ui'
const ProjectLints: NextPageWithLayout = () => {
const project = useSelectedProject()
@@ -27,10 +20,10 @@ const ProjectLints: NextPageWithLayout = () => {
levels: [] as string[],
types: [] as string[],
})
const [lintIgnoreList] = useLocalStorageQuery<string[]>(
LOCAL_STORAGE_KEYS.PROJECT_LINT_IGNORE_LIST,
[]
)
// const [lintIgnoreList] = useLocalStorageQuery<string[]>(
// LOCAL_STORAGE_KEYS.PROJECT_LINT_IGNORE_LIST,
// []
// )
const { data, isLoading, isRefetching, refetch } = useProjectLintsQuery({
projectRef: project?.ref,
@@ -45,9 +38,10 @@ const ProjectLints: NextPageWithLayout = () => {
return 3
})
const [ignoredLints, activeLints] = partition(lints, (lint) =>
lintIgnoreList.includes(lint.cache_key)
)
const activeLints = lints
// const [ignoredLints, activeLints] = partition(lints, (lint) =>
// lintIgnoreList.includes(lint.cache_key)
// )
const filteredLints = useMemo(() => {
return activeLints
.filter((x) => (filters.levels.length > 0 ? filters.levels.includes(x.level) : x))
@@ -57,6 +51,16 @@ const ProjectLints: NextPageWithLayout = () => {
const warnLintsCount = activeLints.filter((x) => x.level === 'WARN').length
const errorLintsCount = activeLints.filter((x) => x.level === 'ERROR').length
const filterOptions = useMemo(() => {
// only show filters for lint types which are present in the results and not ignored
return LINT_TYPES.filter((type) => activeLints.some((lint) => lint.name === type)).map(
(type) => ({
name: getHumanReadableTitle(type),
value: type,
})
)
}, [activeLints])
return (
<ScaffoldContainer>
<ScaffoldSection>
@@ -86,14 +90,7 @@ const ProjectLints: NextPageWithLayout = () => {
/>
<FilterPopover
name="Type"
options={[
{ name: 'Unindexed foreign keys', value: 'unindexed_foreign_keys' },
{ name: 'Auth users exposed', value: 'auth_users_exposed' },
{ name: 'No primary key', value: 'no_primary_key' },
{ name: 'Unused index', value: 'unused_index' },
{ name: 'Multiple permissive policies', value: 'multiple_permissive_policies' },
{ name: 'Auth RLS Initialization Plan', value: 'auth_rls_initplan' },
]}
options={filterOptions}
labelKey="name"
valueKey="value"
activeOptions={filters.types}
@@ -179,7 +176,7 @@ const ProjectLints: NextPageWithLayout = () => {
/>
</div>
{ignoredLints.length > 0 && (
{/* {ignoredLints.length > 0 && (
<div className="col-span-12 flex flex-col text-sm max-w-none gap-8 py-4">
<Accordion_Shadcn_ type="single" collapsible>
<AccordionItem_Shadcn_ value="1" className="border-none">
@@ -219,7 +216,7 @@ const ProjectLints: NextPageWithLayout = () => {
</AccordionItem_Shadcn_>
</Accordion_Shadcn_>
</div>
)}
)} */}
</ScaffoldSection>
</ScaffoldContainer>
)