diff --git a/apps/docs/content/guides/api/securing-your-api.mdx b/apps/docs/content/guides/api/securing-your-api.mdx index 894b2e8179b56..477e7c0755767 100644 --- a/apps/docs/content/guides/api/securing-your-api.mdx +++ b/apps/docs/content/guides/api/securing-your-api.mdx @@ -68,6 +68,12 @@ alter default privileges for role postgres in schema public revoke execute on functions from public; ``` + + +These default privileges pose no direct security risk. They appear in [pg_default_acl](https://www.postgresql.org/docs/current/catalog-pg-default-acl.html), granted by [supabase_admin](/docs/guides/database/postgres/roles#supabaseadmin) to `anon`, `authenticated`, and `service_role`. The default privileges are intentional and part of Supabase's standard permission model. The `supabase_admin` role is an internal management role that can't authenticate through the Data API. + + + ## Use a dedicated API schema If you want an extra boundary around your Data API, lock down the `public` schema and expose a dedicated schema, such as `api`, instead. You can control access with grants in any schema, but this can make the surface easier to reason about: objects in `api` represent your Data API, while internal tables and helper functions stay in schemas that are not exposed. See [Using Custom Schemas](/docs/guides/api/using-custom-schemas) for setup steps. diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/ViewTokenSheet.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/ViewTokenSheet.tsx index e3a6a51075c7a..e6f16ba25c08d 100644 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/ViewTokenSheet.tsx +++ b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/ViewTokenSheet.tsx @@ -22,6 +22,7 @@ import { formatAccessText, getRealAccess } from '../AccessToken.utils' import { useOrgAndProjectData } from '../hooks/useOrgAndProjectData' import { DocsButton } from '@/components/ui/DocsButton' import { useScopedAccessTokenQuery } from '@/data/scoped-access-tokens/scoped-access-token-query' +import { DOCS_URL } from '@/lib/constants' interface ViewTokenSheetProps { visible: boolean @@ -110,7 +111,7 @@ export function ViewTokenSheet({ visible, tokenId, onClose }: ViewTokenSheetProp

View access for {token?.name}

- +
diff --git a/apps/studio/components/interfaces/Auth/AuthProvidersForm/index.tsx b/apps/studio/components/interfaces/Auth/AuthProvidersForm/index.tsx index 9d801843c987e..4950b329c37bf 100644 --- a/apps/studio/components/interfaces/Auth/AuthProvidersForm/index.tsx +++ b/apps/studio/components/interfaces/Auth/AuthProvidersForm/index.tsx @@ -18,6 +18,7 @@ import { AlertError } from '@/components/ui/AlertError' import { ResourceList } from '@/components/ui/Resource/ResourceList' import { HorizontalShimmerWithIcon } from '@/components/ui/Shimmers' import { useAuthConfigQuery } from '@/data/auth/auth-config-query' +import { DOCS_URL } from '@/lib/constants' export const AuthProvidersForm = () => { const { ref: projectRef } = useParams() @@ -59,7 +60,7 @@ export const AuthProvidersForm = () => { hour.

@@ -89,17 +90,17 @@ export const AuthProvidersForm = () => { : provider let isActive = false if (providerSchema.title === 'SAML 2.0') { - isActive = authConfig && (authConfig as any)['SAML_ENABLED'] + isActive = authConfig && authConfig['SAML_ENABLED'] } else if (providerSchema.title === 'LinkedIn (OIDC)') { - isActive = authConfig && (authConfig as any)['EXTERNAL_LINKEDIN_OIDC_ENABLED'] + isActive = authConfig && authConfig['EXTERNAL_LINKEDIN_OIDC_ENABLED'] } else if (providerSchema.title === 'Slack (OIDC)') { - isActive = authConfig && (authConfig as any)['EXTERNAL_SLACK_OIDC_ENABLED'] + isActive = authConfig && authConfig['EXTERNAL_SLACK_OIDC_ENABLED'] } else if (providerSchema.title.includes('Web3')) { - isActive = authConfig && (authConfig as any)['EXTERNAL_WEB3_SOLANA_ENABLED'] + isActive = authConfig && authConfig['EXTERNAL_WEB3_SOLANA_ENABLED'] } else if (providerSchema.title.includes('X / Twitter (OAuth 2.0)')) { isActive = authConfig && (authConfig as any)['EXTERNAL_X_ENABLED'] } else if (providerSchema.title === 'Twitter (Deprecated)') { - isActive = authConfig && (authConfig as any)['EXTERNAL_TWITTER_ENABLED'] + isActive = authConfig && authConfig['EXTERNAL_TWITTER_ENABLED'] } else { isActive = authConfig && diff --git a/apps/studio/components/interfaces/ConnectSheet/ConnectStepsSection.tsx b/apps/studio/components/interfaces/ConnectSheet/ConnectStepsSection.tsx index aa472c7d04c21..947e24b4c75c1 100644 --- a/apps/studio/components/interfaces/ConnectSheet/ConnectStepsSection.tsx +++ b/apps/studio/components/interfaces/ConnectSheet/ConnectStepsSection.tsx @@ -286,10 +286,7 @@ export function ConnectStepsSection({ steps, state, projectKeys }: ConnectStepsS title="MCP for self-hosted Supabase requires extra setup" description="The configuration below points at the hosted Supabase MCP server. To use MCP against your self-hosted instance, follow the self-hosted MCP guide." actions={[ - , + , ]} /> )} diff --git a/apps/studio/components/interfaces/ConnectSheet/content/steps/direct-connection/content.tsx b/apps/studio/components/interfaces/ConnectSheet/content/steps/direct-connection/content.tsx index 64687afd8423e..6c1be4ba4b97d 100644 --- a/apps/studio/components/interfaces/ConnectSheet/content/steps/direct-connection/content.tsx +++ b/apps/studio/components/interfaces/ConnectSheet/content/steps/direct-connection/content.tsx @@ -28,12 +28,14 @@ import { } from '@/components/interfaces/ConnectSheet/ConnectionString.utils' import { PasswordEncodingNote } from '@/components/interfaces/ConnectSheet/PasswordEncodingNote' import { ResetDbPasswordDialog } from '@/components/interfaces/Settings/Database/DatabaseSettings/ResetDbPasswordDialog' +import { InlineLink } from '@/components/ui/InlineLink' import { usePgbouncerConfigQuery } from '@/data/database/pgbouncer-config-query' import { useSupavisorConfigurationQuery } from '@/data/database/supavisor-configuration-query' import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query' import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' import { useIsHighAvailability } from '@/hooks/misc/useSelectedProject' +import { DOCS_URL } from '@/lib/constants' import { pluckObjectFields } from '@/lib/helpers' import { useTrack } from '@/lib/telemetry/track' @@ -275,14 +277,11 @@ function DirectConnectionContent({ state, deploymentMode }: StepContentProps) { {showSelfHostedDirectNotice && (

Manually{' '} - configurable - {' '} + {' '} for self-hosted Supabase.

)} diff --git a/apps/studio/components/interfaces/ErrorHandling/README.md b/apps/studio/components/interfaces/ErrorHandling/README.md index dda0f17e2abe4..41b1067dbabd0 100644 --- a/apps/studio/components/interfaces/ErrorHandling/README.md +++ b/apps/studio/components/interfaces/ErrorHandling/README.md @@ -73,6 +73,7 @@ import { FixWithAITroubleshootingSection, TroubleshootingGuideSection, } from '../TroubleshootingSections' +import { DOCS_URL } from '@/lib/constants' const ERROR_TYPE = 'your-error' const BUILD_PROMPT = () => `Describe the issue for the AI assistant.` @@ -89,7 +90,7 @@ export function YourErrorTroubleshooting() { { return summarizeErrorMessage(group.message) } -const TROUBLESHOOTING_DOCS_BASE = 'https://supabase.com/docs/guides/troubleshooting' +const TROUBLESHOOTING_DOCS_BASE = `${DOCS_URL}/guides/troubleshooting` export const buildTroubleshootingDocsUrl = ({ statusCode }: { statusCode?: string }): string => { const numericStatusCode = Number(statusCode) diff --git a/apps/studio/components/interfaces/Integrations/GraphQL/constants.ts b/apps/studio/components/interfaces/Integrations/GraphQL/constants.ts index e5e4ef531d712..96224e2be00fc 100644 --- a/apps/studio/components/interfaces/Integrations/GraphQL/constants.ts +++ b/apps/studio/components/interfaces/Integrations/GraphQL/constants.ts @@ -1,3 +1,5 @@ +import { DOCS_URL } from '@/lib/constants' + export const PG_GRAPHQL_EXTENSION_NAME = 'pg_graphql' /** @@ -7,4 +9,4 @@ export const PG_GRAPHQL_EXTENSION_NAME = 'pg_graphql' */ export const DEFAULT_INTROSPECTION_SCHEMA = 'public' -export const PG_GRAPHQL_CONFIG_DOCS_URL = 'https://supabase.com/docs/guides/graphql#supabase-studio' +export const PG_GRAPHQL_CONFIG_DOCS_URL = `${DOCS_URL}/guides/graphql#supabase-studio` diff --git a/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx b/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx index fc1a8981af105..f6fe9c81b13db 100644 --- a/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx +++ b/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx @@ -72,6 +72,7 @@ import { useJwtSecretUpdatingStatusQuery } from '@/data/config/jwt-secret-updati import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-config-query' import { useLegacyJWTSigningKeyQuery } from '@/data/jwt-signing-keys/legacy-jwt-signing-key-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' +import { DOCS_URL } from '@/lib/constants' import { uuidv4 } from '@/lib/helpers' const MAX_JWT_EXP = 604800 @@ -504,9 +505,9 @@ export const JWTSettings = () => { {disableLegacyJwtSecretRotation ? (
+ } + footer={ + + } + > + <> + {projectCreationDisabled ? ( + + ) : ( +
+ + + {canCreateProject && ( + <> + {canConfigureGitHubOnCreate && ( + + + Ideal for agent-first workflows: update your schema in code, push it + to GitHub, and Supabase deploys the changes automatically.{' '} + + Learn more + + + } + disabled={isCreatingNewProject} + repositories={githubRepos} + gitHubAuthorization={gitHubAuthorization} + hasPartialResponseDueToSSO={hasPartialResponseDueToSSO} + isLoading={isLoadingRepositoryOptions} + refetch={refetchRepositoryOptions} + onConnectClick={() => track('project_creation_github_connect_clicked')} + /> + + )} + + + {canChooseInstanceSize && } + + + + + + + + {showInternalOnlyConfiguration && } + + {showAdvancedConfig && !!availableOrioleVersion && ( + + )} + + {shouldShowFreeProjectInfo ? ( + + You can have up to 2 free projects across all organizations.{' '} + + Create a free organization + {' '} + to use them. +

+ } + /> + ) : null} + + )} + + {freePlanWithExceedingLimits ? ( + isAdmin && + slug && ( + + ) + ) : hasOutstandingInvoices ? ( + + +

+ Please resolve all outstanding invoices first before creating a new + project +

+ +
+ +
+
+ } + /> + + ) : null} + + )} + + + + setIsComputeCostsConfirmationModalVisible(false)} + onConfirm={async () => { + const values = form.getValues() + await onSubmit(values) + setIsComputeCostsConfirmationModalVisible(false) + }} + > +
+

+ Launching a project on compute size "{instanceLabel(instanceSize)}" increases your + monthly costs by ${additionalMonthlySpend}, independent of how actively you use it. By + clicking "I understand", you agree to the additional costs.{' '} + + Compute Costs + {' '} + are non-refundable. +

+
+
+ + + ) +} diff --git a/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.tsx b/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.tsx index 759d36847e5e0..1876bc1035e4b 100644 --- a/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.tsx +++ b/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.tsx @@ -1,11 +1,13 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { toast } from 'sonner' -import { TextArea } from 'ui' +import { Card, CardContent, cn, TextArea } from 'ui' +import { CollapsibleCardSection } from 'ui-patterns/CollapsibleCardSection' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { CANCELLATION_REASONS } from '@/components/interfaces/Billing/Billing.constants' import { LogicalBackupCliInstructions } from '@/components/layouts/ProjectLayout/LogicalBackupCliInstructions' +import { InlineLink } from '@/components/ui/InlineLink' import { TextConfirmModal } from '@/components/ui/TextConfirmModalWrapper' import { useSendDowngradeFeedbackMutation } from '@/data/feedback/exit-survey-send' import type { OrgProject } from '@/data/projects/org-projects-infinite-query' @@ -14,6 +16,7 @@ import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-q import { useLastVisitedOrganization } from '@/hooks/misc/useLastVisitedOrganization' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { DOCS_URL } from '@/lib/constants' import type { Organization } from '@/types' export const DeleteProjectModal = ({ @@ -125,14 +128,9 @@ export const DeleteProjectModal = ({ <> {!isFree && 'All project data will be lost, and cannot be undone. '} Read the{' '} - + documentation - {' '} + {' '} for prerequisites, implications, and recovery information. ), @@ -151,7 +149,19 @@ export const DeleteProjectModal = ({ }} >
- + + div>button]:tracking-normal', + '[&>div>button]:text-foreground [&>div>button]:hover:text-foreground', + '[&>div>button]:data-open:text-foreground [&>div>button]:text-sm' + )} + > + + + + + {/* [Joshen] This is basically ExitSurvey.tsx, ideally we have one shared component but the one @@ -181,6 +191,7 @@ export const DeleteProjectModal = ({ ].join(' ')} >