This commit is contained in:
Terry Sutton committed 2024-05-21 22:29:33 -02:30
1 parent 0878275c7d
commit 182f7fd299
5 files changed
+33 -257

No files matched your search

@@ -28,6 +28,7 @@ interface LinterDataGridProps {
selectedLint: Lint | null
setSelectedLint: (value: Lint | null) => void
currentTab: LINTER_LEVELS
handleLintClick: (lint: Lint | null) => void
}
const LinterDataGrid = ({
@@ -36,6 +37,7 @@ const LinterDataGrid = ({
selectedLint,
setSelectedLint,
currentTab,
handleLintClick,
}: LinterDataGridProps) => {
const gridRef = useRef<DataGridHandle>(null)
const { ref } = useParams()
@@ -147,7 +149,8 @@ const LinterDataGrid = ({
{...props}
onClick={() => {
if (typeof idx === 'number' && idx >= 0) {
setSelectedLint(props.row)
// setSelectedLint(props.row)
handleLintClick(props.row)
gridRef.current?.scrollToCell({ idx: 0, rowIdx: idx })
}
}}
@@ -173,7 +176,7 @@ const LinterDataGrid = ({
className="absolute top-3 right-3 px-1"
icon={<X size={14} />}
onClick={() => {
setSelectedLint(null)
handleLintClick(null)
}}
/>
@@ -12,7 +12,7 @@ import {
TableEditor,
} from 'icons'
import { ICON_SIZE, ICON_STROKE_WIDTH } from './NavigationBar'
import { Settings, FileText, List } from 'lucide-react'
import { Settings, FileText, List, Lightbulb } from 'lucide-react'
export const generateToolRoutes = (ref?: string, project?: Project): Route[] => {
const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP
@@ -103,6 +103,16 @@ export const generateOtherRoutes = (ref?: string, project?: Project): Route[] =>
const buildingUrl = `/project/${ref}/building`
return [
...(IS_PLATFORM
? [
{
key: 'advisors',
label: 'Advisors',
icon: <Lightbulb size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/advisors/security`),
},
]
: []),
...(IS_PLATFORM
? [
{
+17
View File
@@ -39,6 +39,7 @@ index_ as (
indisvalid
)
select
md5(fk.schema_name || fk.table_name || fk.fkey_name) AS id,
'unindexed_foreign_keys' as name,
'Unindexed foreign keys' as title,
'INFO' as level,
@@ -53,6 +54,7 @@ select
) as detail,
'https://supabase.com/docs/guides/database/database-linter?lint=0001_unindexed_foreign_keys' as remediation,
jsonb_build_object(
'id', md5(fk.schema_name || fk.table_name || fk.fkey_name),
'schema', fk.schema_name,
'name', fk.table_name,
'type', 'table',
@@ -81,6 +83,7 @@ order by
union all
(
select
md5(n.nspname || c.relname) AS id,
'auth_users_exposed' as name,
'Exposed Auth Users' as title,
'ERROR' as level,
@@ -198,6 +201,7 @@ with policies as (
and pa.polname = pb.policyname
)
select
md5(schema_name || table_name || policy_name) AS id,
'auth_rls_initplan' as name,
'Auth RLS Initialization Plan' as title,
'WARN' as level,
@@ -262,6 +266,7 @@ where
union all
(
select
md5(pgns.nspname || pgc.relname) AS id,
'no_primary_key' as name,
'No Primary Key' as title,
'INFO' as level,
@@ -308,6 +313,7 @@ having
union all
(
select
md5(psui.schemaname || psui.relname || psui.indexrelname) AS id,
'unused_index' as name,
'Unused Index' as title,
'INFO' as level,
@@ -351,6 +357,7 @@ where
union all
(
select
md5(n.nspname || c.relname || r.rolname || act.cmd) AS id,
'multiple_permissive_policies' as name,
'Multiple Permissive Policies' as title,
'WARN' as level,
@@ -424,6 +431,7 @@ having
union all
(
select
md5(n.nspname || c.relname) AS id,
'policy_exists_rls_disabled' as name,
'Policy Exists RLS Disabled' as title,
'ERROR' as level,
@@ -470,6 +478,7 @@ group by
union all
(
select
md5(n.nspname || c.relname),
'rls_enabled_no_policy' as name,
'RLS Enabled No Policy' as title,
'INFO' as level,
@@ -516,6 +525,7 @@ group by
union all
(
select
md5(n.nspname || c.relname || replace(pi.indexdef, pi.indexname, '')) AS id,
'duplicate_index' as name,
'Duplicate Index' as title,
'WARN' as level,
@@ -571,6 +581,7 @@ having
union all
(
select
md5(n.nspname || c.relname) AS id,
'security_definer_view' as name,
'Security Definer View' as title,
'ERROR' as level,
@@ -623,6 +634,7 @@ where
union all
(
select
md5(n.nspname || p.proname || md5(p.prosrc)) AS id,
'function_search_path_mutable' as name,
'Function Search Path Mutable' as title,
'WARN' as level,
@@ -663,6 +675,7 @@ where
union all
(
select
md5(n.nspname || c.relname) AS id,
'rls_disabled_in_public' as name,
'RLS Disabled in Public' as title,
'ERROR' as level,
@@ -704,6 +717,7 @@ where
union all
(
select
md5('public' || pe.extname) AS id,
'extension_in_public' as name,
'Extension in Public' as title,
'WARN' as level,
@@ -756,6 +770,7 @@ with policies as (
and pa.polname = pb.policyname
)
select
md5(schema_name || table_name || policy_name) AS id,
'rls_references_user_metadata' as name,
'RLS references user metadata' as title,
'ERROR' as level,
@@ -793,6 +808,7 @@ where
union all
(
select
md5(n.nspname || c.relname) AS id,
'materialized_view_in_api' as name,
'Materialized View in API' as title,
'WARN' as level,
@@ -857,6 +873,7 @@ export const LINT_TYPES = [
export type LINT_TYPES = (typeof LINT_TYPES)[number]
export type Lint = {
id: string
name: LINT_TYPES
level: 'ERROR' | 'WARN' | 'INFO'
facing: string
@@ -1,93 +0,0 @@
import { useParams } from 'common'
import { lintInfoMap } from 'components/interfaces/Linter/Linter.utils'
import { DatabaseLayout } from 'components/layouts'
import { FormHeader } from 'components/ui/Forms'
import { Lint, useProjectLintsQuery } from 'data/lint/lint-query'
import { useSelectedProject } from 'hooks'
import { useState } from 'react'
import type { NextPageWithLayout } from 'types'
import { LoadingLine } from 'ui'
import LintPageTabs from 'components/interfaces/Linter/LintPageTabs'
import { LINTER_LEVELS } from 'components/interfaces/Linter/Linter.constants'
import LinterDataGrid from 'components/interfaces/Linter/LinterDataGrid'
import LinterFilters from 'components/interfaces/Linter/LinterFilters'
import LinterPageFooter from 'components/interfaces/Linter/LinterPageFooter'
const ProjectLints: NextPageWithLayout = () => {
const project = useSelectedProject()
const { preset } = useParams()
// need to maintain a list of filters for each tab
const [filters, setFilters] = useState<{ level: LINTER_LEVELS; filters: string[] }[]>([
{ level: LINTER_LEVELS.ERROR, filters: [] },
{ level: LINTER_LEVELS.WARN, filters: [] },
{ level: LINTER_LEVELS.INFO, filters: [] },
])
const [currentTab, setCurrentTab] = useState<LINTER_LEVELS>(
(preset as LINTER_LEVELS) ?? LINTER_LEVELS.ERROR
)
const [selectedLint, setSelectedLint] = useState<Lint | null>(null)
const { data, isLoading, isRefetching, refetch } = useProjectLintsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const activeLints = data?.filter((x) => x.categories.includes('PERFORMANCE')) || []
const currentTabFilters = (filters.find((filter) => filter.level === currentTab)?.filters ||
[]) as string[]
const filteredLints = activeLints
.filter((x) => x.level === currentTab)
.filter((x) => (currentTabFilters.length > 0 ? currentTabFilters.includes(x.name) : x))
const filterOptions = lintInfoMap
// only show filters for lint types which are present in the results and not ignored
.filter((item) =>
activeLints.some((lint) => lint.name === item.name && lint.level === currentTab)
)
.map((type) => ({
name: type.title,
value: type.name,
}))
return (
<div className="h-full flex flex-col">
<FormHeader
className="py-4 px-6 !mb-0"
title="Performance Advisor"
docsUrl="https://supabase.com/docs/guides/database/database-linter"
/>
<LintPageTabs
activeLints={activeLints}
isLoading={isLoading}
currentTab={currentTab}
setCurrentTab={setCurrentTab}
setSelectedLint={setSelectedLint}
/>
<LinterFilters
filterOptions={filterOptions}
activeLints={activeLints}
currentTab={currentTab}
filters={filters}
setFilters={setFilters}
/>
<LoadingLine loading={isRefetching} />
<LinterDataGrid
filteredLints={filteredLints}
currentTab={currentTab}
selectedLint={selectedLint}
setSelectedLint={setSelectedLint}
isLoading={isLoading}
/>
<LinterPageFooter isLoading={isLoading} isRefetching={isRefetching} refetch={refetch} />
</div>
)
}
ProjectLints.getLayout = (page) => <DatabaseLayout title="Linter">{page}</DatabaseLayout>
export default ProjectLints
@@ -1,161 +0,0 @@
import { useState } from 'react'
import { useParams } from 'common'
import LintPageTabs from 'components/interfaces/Linter/LintPageTabs'
import { LINTER_LEVELS } from 'components/interfaces/Linter/Linter.constants'
import { lintInfoMap } from 'components/interfaces/Linter/Linter.utils'
import LinterDataGrid from 'components/interfaces/Linter/LinterDataGrid'
import LinterFilters from 'components/interfaces/Linter/LinterFilters'
import LinterPageFooter from 'components/interfaces/Linter/LinterPageFooter'
import { DatabaseLayout } from 'components/layouts'
import { FormHeader } from 'components/ui/Forms'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { Lint, useProjectLintsQuery } from 'data/lint/lint-query'
import { useSelectedProject } from 'hooks'
import type { NextPageWithLayout } from 'types'
import { LoadingLine } from 'ui'
const ProjectLints: NextPageWithLayout = () => {
const project = useSelectedProject()
const { preset } = useParams()
// need to maintain a list of filters for each tab
const [filters, setFilters] = useState<{ level: LINTER_LEVELS; filters: string[] }[]>([
{ level: LINTER_LEVELS.ERROR, filters: [] },
{ level: LINTER_LEVELS.WARN, filters: [] },
{ level: LINTER_LEVELS.INFO, filters: [] },
])
const [currentTab, setCurrentTab] = useState<LINTER_LEVELS>(
(preset as LINTER_LEVELS) ?? LINTER_LEVELS.ERROR
)
const [selectedLint, setSelectedLint] = useState<Lint | null>(null)
const {
data,
isLoading: areLintsLoading,
isRefetching,
refetch: refetchLintsQuery,
} = useProjectLintsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const {
data: authConfig,
isLoading: isAuthConfigLoading,
refetch: refetchAuthConfigQuery,
} = useAuthConfigQuery({
projectRef: project?.ref,
})
const isLoading = areLintsLoading || isAuthConfigLoading
const refetch = () => {
refetchLintsQuery()
refetchAuthConfigQuery()
}
let clientLints: Lint[] = []
// [Alaister]: checking this client side for speed, but should be moved into the query if possible
if (authConfig?.EXTERNAL_EMAIL_ENABLED) {
if (authConfig.MAILER_OTP_EXP > 3600) {
clientLints.push({
name: 'auth_otp_long_expiry',
level: 'WARN',
facing: 'EXTERNAL',
categories: ['SECURITY'],
description: 'OTP expiry exceeds recommended threshold',
detail:
'We have detected that you have enabled the email provider with the OTP expiry set to more than an hour. It is recommended to set this value to less than an hour.',
cache_key: 'auth_otp_long_expiry',
remediation: 'https://supabase.com/docs/guides/platform/going-into-prod#security',
metadata: {
type: 'auth',
entity: 'Auth',
},
})
}
if (authConfig.EXTERNAL_PHONE_ENABLED && authConfig.SMS_OTP_LENGTH < 6) {
clientLints.push({
name: 'auth_otp_short_length',
level: 'WARN',
facing: 'EXTERNAL',
categories: ['SECURITY'],
description: 'OTP length is less than recommended threshold',
detail: 'We have detected that you have set the OTP length to less than 6 characters',
cache_key: 'auth_otp_short_length',
remediation: 'https://supabase.com/docs/guides/platform/going-into-prod#security',
metadata: {
type: 'auth',
entity: 'Auth',
},
})
}
}
const activeLints = [...(data ?? []), ...clientLints]?.filter((x) =>
x.categories.includes('SECURITY')
)
const currentTabFilters = (filters.find((filter) => filter.level === currentTab)?.filters ||
[]) as string[]
const filteredLints = activeLints
.filter((x) => x.level === currentTab)
.filter((x) => (currentTabFilters.length > 0 ? currentTabFilters.includes(x.name) : x))
const filterOptions = lintInfoMap
// only show filters for lint types which are present in the results and not ignored
.filter((item) =>
activeLints.some((lint) => lint.name === item.name && lint.level === currentTab)
)
.map((type) => ({
name: type.title,
value: type.name,
}))
return (
<div className="h-full flex flex-col">
<FormHeader
className="py-4 px-6 !mb-0"
title="Security Advisor"
docsUrl="https://supabase.com/docs/guides/database/database-linter"
/>
<LintPageTabs
activeLints={activeLints}
isLoading={isLoading}
currentTab={currentTab}
setCurrentTab={setCurrentTab}
setSelectedLint={setSelectedLint}
/>
<LinterFilters
filterOptions={filterOptions}
activeLints={activeLints}
currentTab={currentTab}
filters={filters}
setFilters={setFilters}
/>
<LoadingLine loading={isRefetching} />
<LinterDataGrid
filteredLints={filteredLints}
currentTab={currentTab}
selectedLint={selectedLint}
setSelectedLint={setSelectedLint}
isLoading={isLoading}
/>
<LinterPageFooter
hideDbInspectCTA
isLoading={isLoading}
isRefetching={isRefetching}
refetch={refetch}
/>
</div>
)
}
ProjectLints.getLayout = (page) => <DatabaseLayout title="Linter">{page}</DatabaseLayout>
export default ProjectLints