mirror of
https://github.com/supabase/supabase.git
synced 2026-05-08 09:50:33 -04:00
4a0bb36ca8
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
123 lines
4.1 KiB
TypeScript
123 lines
4.1 KiB
TypeScript
import { LOCAL_STORAGE_KEYS, useIsLoggedIn, useIsMFAEnabled, useParams } from 'common'
|
|
import { useRouter } from 'next/router'
|
|
import { PropsWithChildren, useEffect } from 'react'
|
|
import { toast } from 'sonner'
|
|
|
|
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
|
|
import { useProjectDetailQuery } from '@/data/projects/project-detail-query'
|
|
import { useDashboardHistory } from '@/hooks/misc/useDashboardHistory'
|
|
import useLatest from '@/hooks/misc/useLatest'
|
|
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
|
|
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] = useLocalStorageQuery(
|
|
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
|
|
''
|
|
)
|
|
|
|
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',
|
|
]
|
|
|
|
/**
|
|
* 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}?error=org_not_found&org=${slug}`)
|
|
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}</>
|
|
}
|