mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Initial build of the URL mode interstitial. Has complete mock data to be able to view different states (this will be stripped out in the end). A starting point for us to use as an intercept. This is linked with @barryroodt ticket in the MCP project. https://github.com/supabase/mcp/pull/412 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added an authenticated MCP Secrets page for securely storing API secrets requested by AI tools. - Added provider-aware guidance for OpenAI, Anthropic, Resend, and Stripe keys, including validation warnings and dashboard links. - Added clear success, cancellation, expiration, error, and wrong-account states with account switching. - Added loading placeholders, secret visibility controls, overwrite warnings, and accessibility announcements. - **Bug Fixes** - Improved interstitial animations to respect reduced-motion preferences. - Preserved return destinations and related parameters during sign-in flows. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
122 lines
4.1 KiB
TypeScript
122 lines
4.1 KiB
TypeScript
import { useIsLoggedIn, useIsMFAEnabled, useParams } from 'common'
|
|
import { useRouter } from 'next/router'
|
|
import { PropsWithChildren, useEffect } from 'react'
|
|
import { toast } from 'sonner'
|
|
|
|
import { MCP_SECRETS_ROUTE } from '@/components/interfaces/MCP/Secrets/McpSecrets.constants'
|
|
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
|
|
import { useProjectDetailQuery } from '@/data/projects/project-detail-query'
|
|
import { useDashboardHistory } from '@/hooks/misc/useDashboardHistory'
|
|
import { useLastVisitedOrganization } from '@/hooks/misc/useLastVisitedOrganization'
|
|
import { useLatest } from '@/hooks/misc/useLatest'
|
|
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
|
import { IS_PLATFORM } from '@/lib/constants'
|
|
|
|
// Ideally these could all be within a _middleware when we use Next 12
|
|
export const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
|
|
const router = useRouter()
|
|
const { ref, slug, id } = useParams()
|
|
const { data: organization } = useSelectedOrganizationQuery()
|
|
|
|
const isLoggedIn = useIsLoggedIn()
|
|
const isUserMFAEnabled = useIsMFAEnabled()
|
|
|
|
const { setLastVisitedSnippet, setLastVisitedTable } = useDashboardHistory()
|
|
const { lastVisitedOrganization, setLastVisitedOrganization } = useLastVisitedOrganization()
|
|
|
|
const DEFAULT_HOME = IS_PLATFORM
|
|
? !!lastVisitedOrganization
|
|
? `/org/${lastVisitedOrganization}`
|
|
: '/organizations'
|
|
: '/project/default'
|
|
|
|
/**
|
|
* Array of urls/routes that should be ignored
|
|
*/
|
|
const excemptUrls: string[] = [
|
|
// project creation route, allows the page to self determine it's own route, it will redirect to the first org
|
|
// or prompt the user to create an organaization
|
|
// this is used by database.dev, usually as /new/new-project
|
|
'/new/[slug]',
|
|
'/join',
|
|
MCP_SECRETS_ROUTE,
|
|
]
|
|
|
|
/**
|
|
* Map through all the urls that are excluded
|
|
* from route validation check
|
|
*
|
|
* @returns a boolean
|
|
*/
|
|
function isExceptUrl() {
|
|
return excemptUrls.includes(router?.pathname)
|
|
}
|
|
|
|
const { isError: isErrorProject, error: projectError } = useProjectDetailQuery({ ref })
|
|
|
|
const { data: organizations, isSuccess: orgsInitialized } = useOrganizationsQuery({
|
|
enabled: isLoggedIn,
|
|
})
|
|
const organizationsRef = useLatest(organizations)
|
|
|
|
useEffect(() => {
|
|
// check if current route is excempted from route validation check
|
|
if (isExceptUrl() || !isLoggedIn) return
|
|
|
|
if (orgsInitialized && slug) {
|
|
// Check validity of organization that user is trying to access
|
|
const organizations = organizationsRef.current ?? []
|
|
const isValidOrg = organizations.some((org) => org.slug === slug)
|
|
|
|
if (!isValidOrg) {
|
|
toast.error('You do not have access to this organization')
|
|
router.push(DEFAULT_HOME)
|
|
return
|
|
}
|
|
}
|
|
}, [orgsInitialized])
|
|
|
|
useEffect(() => {
|
|
// check if current route is excempted from route validation check
|
|
if (isExceptUrl() || !isLoggedIn) return
|
|
|
|
// A successful request to project details will validate access to both project and branches
|
|
if (!!ref && isErrorProject) {
|
|
// 404 means the project no longer exists (e.g. was deleted), not an access error
|
|
if (projectError?.code !== 404) {
|
|
toast.error('You do not have access to this project')
|
|
}
|
|
router.push(DEFAULT_HOME)
|
|
return
|
|
}
|
|
}, [isErrorProject])
|
|
|
|
useEffect(() => {
|
|
if (ref !== undefined && id !== undefined) {
|
|
if (router.pathname.endsWith('/sql/[id]') && id !== 'new') {
|
|
setLastVisitedSnippet(id)
|
|
} else if (router.pathname.endsWith('/editor/[id]')) {
|
|
setLastVisitedTable(id)
|
|
}
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [ref, id])
|
|
|
|
useEffect(() => {
|
|
if (organization) {
|
|
setLastVisitedOrganization(organization.slug)
|
|
|
|
if (
|
|
organization.organization_requires_mfa &&
|
|
!isUserMFAEnabled &&
|
|
router.pathname !== '/org/[slug]'
|
|
) {
|
|
router.push(`/org/${organization.slug}`)
|
|
}
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [organization])
|
|
|
|
return <>{children}</>
|
|
}
|