| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- import {
- Box,
- Clock,
- Eye,
- Lock,
- LockIcon,
- Ruler,
- Scaling,
- Table2,
- TextSearch,
- Unlock,
- User,
- } from 'lucide-react'
- import Link from 'next/link'
- import { Badge, Button } from 'ui'
- import { asGraphqlExposureLint, GraphqlExposureLintCTA } from './GraphqlExposureLintCTA'
- import { LINTER_LEVELS, LintInfo } from '@/components/interfaces/Linter/Linter.constants'
- import { Lint, LINT_TYPES } from '@/data/lint/lint-query'
- import { DOCS_URL } from '@/lib/constants'
- export const lintInfoMap: LintInfo[] = [
- {
- name: 'unindexed_foreign_keys',
- title: 'Unindexed foreign keys',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/database/indexes?schema=${encodeURIComponent(metadata?.schema ?? '')}`,
- linkText: 'Create an index',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0001_unindexed_foreign_keys`,
- category: 'performance',
- },
- {
- name: 'auth_users_exposed',
- title: 'Exposed Auth Users',
- icon: <Lock className="text-foreground-muted" size={15} strokeWidth={1.5} />,
- link: ({ projectRef }) => `/project/${projectRef}/editor`,
- linkText: 'View table',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0002_auth_users_exposed`,
- category: 'security',
- },
- {
- name: 'auth_rls_initplan',
- title: 'Auth RLS Initialization Plan',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/policies`,
- linkText: 'View policies',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0003_auth_rls_initplan`,
- category: 'performance',
- },
- {
- name: 'no_primary_key',
- title: 'No Primary Key',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/editor`,
- linkText: 'View table',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0004_no_primary_key`,
- category: 'performance',
- },
- {
- name: 'unused_index',
- title: 'Unused Index',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/database/indexes?schema=${encodeURIComponent(metadata?.schema ?? '')}&table=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View index',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0005_unused_index`,
- category: 'performance',
- },
- {
- name: 'multiple_permissive_policies',
- title: 'Multiple Permissive Policies',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/auth/policies?schema=${encodeURIComponent(metadata?.schema ?? '')}&search=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View policies',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0006_multiple_permissive_policies`,
- category: 'performance',
- },
- {
- name: 'policy_exists_rls_disabled',
- title: 'Policy Exists RLS Disabled',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/auth/policies?schema=${encodeURIComponent(metadata?.schema ?? '')}&search=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View policies',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0007_policy_exists_rls_disabled`,
- category: 'security',
- },
- {
- name: 'rls_enabled_no_policy',
- title: 'RLS Enabled No Policy',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/auth/policies?schema=${encodeURIComponent(metadata?.schema ?? '')}&search=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View table',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0008_rls_enabled_no_policy`,
- category: 'security',
- },
- {
- name: 'duplicate_index',
- title: 'Duplicate Index',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/database/indexes?schema=${encodeURIComponent(metadata?.schema ?? '')}&table=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View index',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0009_duplicate_index`,
- category: 'performance',
- },
- {
- name: 'security_definer_view',
- title: 'Security Definer View',
- icon: <Eye className="text-foreground-muted" size={15} strokeWidth={1.5} />,
- link: () =>
- `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0010_security_definer_view`,
- linkText: 'View docs',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0010_security_definer_view`,
- category: 'security',
- },
- {
- name: 'function_search_path_mutable',
- title: 'Function Search Path Mutable',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/database/functions?schema=${encodeURIComponent(metadata?.schema ?? '')}&search=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View functions',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0011_function_search_path_mutable`,
- category: 'security',
- },
- {
- name: 'auth_allow_anonymous_sign_ins',
- title: 'Anonymous Sign-Ins Allowed',
- icon: <User className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/providers`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0012_auth_allow_anonymous_sign_ins`,
- category: 'security',
- },
- {
- name: 'rls_disabled_in_public',
- title: 'RLS Disabled in Public',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/auth/policies?schema=${encodeURIComponent(metadata?.schema ?? '')}&search=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View policies',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0013_rls_disabled_in_public`,
- category: 'security',
- },
- {
- name: 'extension_in_public',
- title: 'Extension in Public',
- icon: <Unlock className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/database/extensions?filter=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View extension',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0014_extension_in_public`,
- category: 'security',
- },
- {
- name: 'auth_otp_long_expiry',
- title: 'Auth OTP Long Expiry',
- icon: <Clock className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/providers`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/platform/going-into-prod#security`,
- category: 'security',
- },
- {
- name: 'auth_otp_short_length',
- title: 'Auth OTP Short Length',
- icon: <Ruler className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/providers`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/platform/going-into-prod#security`,
- category: 'security',
- },
- {
- name: 'auth_db_connections_absolute',
- title: 'Auth Absolute Connection Management Strategy',
- icon: <Scaling className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/performance`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/platform/going-into-prod`,
- category: 'performance',
- },
- {
- name: 'rls_references_user_metadata',
- title: 'RLS references user metadata',
- icon: <User className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/policies`,
- linkText: 'View policies',
- docsLink: `${DOCS_URL}/guides/database/database-linter?queryGroups=lint&lint=0015_rls_references_user_metadata`,
- category: 'security',
- },
- {
- name: 'materialized_view_in_api',
- title: 'Materialized View in API',
- icon: <Eye className="text-foreground-muted" size={15} strokeWidth={1.5} />,
- link: () => `${DOCS_URL}/guides/database/database-advisors?lint=0016_materialized_view_in_api`,
- linkText: 'View docs',
- docsLink: `${DOCS_URL}/guides/database/database-advisors?lint=0016_materialized_view_in_api`,
- category: 'security',
- },
- {
- name: 'foreign_table_in_api',
- title: 'Foreign Table in API',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1.5} />,
- link: () => `${DOCS_URL}/guides/database/database-linter?lint=0017_foreign_table_in_api`,
- linkText: 'View docs',
- docsLink: `${DOCS_URL}/guides/database/database-linter?lint=0017_foreign_table_in_api`,
- category: 'security',
- },
- {
- name: 'unsupported_reg_types',
- title: 'Unsupported reg types',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1.5} />,
- link: () =>
- `${DOCS_URL}/guides/database/database-advisors?lint=0018_unsupported_reg_types&queryGroups=lint`,
- linkText: 'View docs',
- docsLink: `${DOCS_URL}/guides/database/database-advisors?lint=0018_unsupported_reg_types&queryGroups=lint`,
- category: 'security',
- },
- {
- name: 'ssl_not_enforced',
- title: 'SSL not enforced',
- icon: <Ruler className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/database/settings`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/platform/ssl-enforcement`,
- category: 'security',
- },
- {
- name: 'network_restrictions_not_set',
- title: 'No network restrictions',
- icon: <Ruler className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/database/settings`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/platform/network-restrictions`,
- category: 'security',
- },
- {
- name: 'password_requirements_min_length',
- title: 'Minimum password length not set or inadequate',
- icon: <Ruler className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/providers?provider=Email`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/platform/going-into-prod#security`,
- category: 'security',
- },
- {
- name: 'pitr_not_enabled',
- title: 'PITR not enabled',
- icon: <Ruler className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/database/backups/pitr`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/platform/backups#point-in-time-recovery`,
- category: 'security',
- },
- {
- name: 'auth_leaked_password_protection',
- title: 'Leaked Password Protection Disabled',
- icon: <LockIcon className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/providers?provider=Email`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/auth/password-security#password-strength-and-leaked-password-protection`,
- category: 'security',
- },
- {
- name: 'auth_insufficient_mfa_options',
- title: 'Insufficient MFA Options',
- icon: <LockIcon className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/mfa`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/auth/auth-mfa`,
- category: 'security',
- },
- {
- name: 'auth_password_policy_missing',
- title: 'Password Policy Missing',
- icon: <LockIcon className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/providers?provider=Email`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/auth/password-security`,
- category: 'security',
- },
- {
- name: 'leaked_service_key',
- title: 'Leaked Service Key Detected',
- icon: <LockIcon className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/settings/api-keys`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/api/api-keys#the-servicerole-key`,
- category: 'security',
- },
- {
- name: 'no_backup_admin',
- title: 'No Backup Admin Detected',
- icon: <LockIcon className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/auth/mfa`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/auth/auth-mfa`,
- category: 'security',
- },
- {
- name: 'vulnerable_postgres_version',
- title: 'Postgres version has security patches available',
- icon: <LockIcon className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef }) => `/project/${projectRef}/settings/infrastructure`,
- linkText: 'View settings',
- docsLink: `${DOCS_URL}/guides/platform/upgrading`,
- category: 'security',
- },
- {
- name: 'sensitive_columns_exposed',
- title: 'Sensitive Columns Exposed',
- icon: <Eye className="text-foreground-muted" size={15} strokeWidth={1.5} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/editor?schema=${encodeURIComponent(metadata?.schema ?? '')}&table=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View table',
- docsLink: `${DOCS_URL}/guides/database/database-linter?lint=0023_sensitive_columns_exposed`,
- category: 'security',
- },
- {
- name: 'rls_policy_always_true',
- title: 'RLS Policy Always True',
- icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/auth/policies?schema=${encodeURIComponent(metadata?.schema ?? '')}&search=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View policies',
- docsLink: `${DOCS_URL}/guides/database/database-linter?lint=0024_permissive_rls_policy`,
- category: 'security',
- },
- {
- name: 'public_bucket_allows_listing',
- title: 'Public Bucket Allows Listing',
- icon: <Box className="text-foreground-muted" size={15} strokeWidth={1.5} />,
- link: ({ projectRef, metadata }) => {
- const bucketId = (metadata as Record<string, string | undefined> | undefined)?.bucket_id
- return `/project/${projectRef}/storage/files/buckets/${encodeURIComponent(bucketId ?? metadata?.name ?? '')}`
- },
- linkText: 'View bucket',
- docsLink: `${DOCS_URL}/guides/database/database-linter?lint=0025_public_bucket_allows_listing`,
- category: 'security',
- },
- {
- name: 'pg_graphql_anon_table_exposed',
- title: 'Public Can See Object in GraphQL Schema',
- icon: <Eye className="text-foreground-muted" size={15} strokeWidth={1.5} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/editor?schema=${encodeURIComponent(metadata?.schema ?? '')}&table=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View object',
- docsLink: `${DOCS_URL}/guides/database/database-linter?lint=0026_pg_graphql_anon_table_exposed`,
- category: 'security',
- },
- {
- name: 'pg_graphql_authenticated_table_exposed',
- title: 'Signed-In Users Can See Object in GraphQL Schema',
- icon: <Eye className="text-foreground-muted" size={15} strokeWidth={1.5} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/editor?schema=${encodeURIComponent(metadata?.schema ?? '')}&table=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View object',
- docsLink: `${DOCS_URL}/guides/database/database-linter?lint=0027_pg_graphql_authenticated_table_exposed`,
- category: 'security',
- },
- {
- name: 'anon_security_definer_function_executable',
- title: 'Public Can Execute SECURITY DEFINER Function',
- icon: <LockIcon className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/database/functions?schema=${encodeURIComponent(metadata?.schema ?? '')}&search=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View function',
- docsLink: `${DOCS_URL}/guides/database/database-linter?lint=0028_anon_security_definer_function_executable`,
- category: 'security',
- },
- {
- name: 'authenticated_security_definer_function_executable',
- title: 'Signed-In Users Can Execute SECURITY DEFINER Function',
- icon: <LockIcon className="text-foreground-muted" size={15} strokeWidth={1} />,
- link: ({ projectRef, metadata }) =>
- `/project/${projectRef}/database/functions?schema=${encodeURIComponent(metadata?.schema ?? '')}&search=${encodeURIComponent(metadata?.name ?? '')}`,
- linkText: 'View function',
- docsLink: `${DOCS_URL}/guides/database/database-linter?lint=0029_authenticated_security_definer_function_executable`,
- category: 'security',
- },
- ]
- export const LintCTA = ({
- title,
- projectRef,
- metadata,
- onAfterAction,
- }: {
- title: LINT_TYPES
- projectRef: string
- metadata: Lint['metadata']
- onAfterAction?: () => void
- }) => {
- const lintInfo = lintInfoMap.find((item) => item.name === title)
- if (!lintInfo) {
- return null
- }
- const graphqlExposureLintName = asGraphqlExposureLint(title)
- if (graphqlExposureLintName) {
- return (
- <GraphqlExposureLintCTA
- lintName={graphqlExposureLintName}
- projectRef={projectRef}
- metadata={metadata}
- onAfterAction={onAfterAction}
- />
- )
- }
- const link = lintInfo.link({ projectRef, metadata })
- const linkText = lintInfo.linkText
- return (
- <Button asChild type="default">
- <Link href={link} rel="noreferrer" className="no-underline">
- {linkText}
- </Link>
- </Button>
- )
- }
- export const EntityTypeIcon = ({ type }: { type: string | undefined }) => {
- switch (type) {
- case 'table':
- return <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />
- case 'view':
- return <Eye className="text-foreground-muted" size={15} strokeWidth={1.5} />
- case 'auth':
- return <Lock className="text-foreground-muted" size={15} strokeWidth={1.5} />
- default:
- return <Box className="text-foreground-muted" size={15} strokeWidth={1.5} />
- }
- }
- export const LintEntity = ({ metadata }: { metadata: Lint['metadata'] }) => {
- return getLintEntityString(metadata)
- }
- export const LintCategoryBadge = ({ category }: { category: string }) => {
- return (
- <Badge variant={category === 'SECURITY' ? 'destructive' : 'warning'}>
- {category.toLowerCase()}
- </Badge>
- )
- }
- export const NoIssuesFound = ({ level }: { level: string }) => {
- const noun = level === LINTER_LEVELS.ERROR ? 'errors' : 'warnings'
- return (
- <div className="absolute top-28 px-6 flex flex-col items-center justify-center w-full gap-y-2">
- <TextSearch className="text-foreground-muted" strokeWidth={1} />
- <div className="text-center">
- <p className="text-foreground">No {noun} detected</p>
- <p className="text-foreground-light">
- Congrats! There are no {noun} detected for this database
- </p>
- </div>
- </div>
- )
- }
- export const createLintSummaryPrompt = (lint: Lint) => {
- const title = lintInfoMap.find((item) => item.name === lint.name)?.title ?? lint.title
- const entity = getLintEntityString(lint.metadata) || 'N/A'
- const schema = lint.metadata?.schema ?? 'N/A'
- const issue = lint.detail ? lint.detail.replace(/\\`/g, '`') : 'N/A'
- const description = lint.description ? lint.description.replace(/\\`/g, '`') : 'N/A'
- return `Summarize the issue and suggest fixes for the following lint item:
- Title: ${title}
- Entity: ${entity}
- Schema: ${schema}
- Issue Details: ${issue}
- Description: ${description}`
- }
- export const getLintEntityString = (metadata: Lint['metadata']) => {
- if (!metadata) {
- return undefined
- }
- if (metadata.entity) {
- return metadata.entity
- }
- if (metadata.schema && metadata.name) {
- const extendedMetadata = metadata as typeof metadata & { arguments?: string }
- const args =
- typeof extendedMetadata.arguments === 'string' ? extendedMetadata.arguments : undefined
- return `${metadata.schema}.${metadata.name}${args !== undefined ? `(${args})` : ''}`
- }
- return undefined
- }
|