feat: Add a banner for clock skew in studio (#32866)

* Add a ClockSkewBanner on top of studio.

* Fix the color of the RestrictionBanner.

* Add an API endpoint which return the current UTC time on the server.

* Fix the glob pattern for docs in turbo.json.

* Force docs to build.

* Revert unneeded changes.

* Add feature flag for clock skew banner.

* Add a comment.
This commit is contained in:
Ivan Vasilov
2025-01-21 17:45:06 +01:00
committed by GitHub
parent 3f5e586995
commit b5437c630e
5 changed files with 78 additions and 3 deletions

View File

@@ -2,6 +2,7 @@ import { useMonaco } from '@monaco-editor/react'
import { useTheme } from 'next-themes'
import { PropsWithChildren, useMemo } from 'react'
import { ClockSkewBanner } from 'components/layouts/AppLayout/ClockSkewBanner'
import IncidentBanner from 'components/layouts/AppLayout/IncidentBanner'
import { NoticeBanner } from 'components/layouts/AppLayout/NoticeBanner'
import { RestrictionBanner } from 'components/layouts/AppLayout/RestrictionBanner'
@@ -16,6 +17,7 @@ const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => {
const ongoingIncident = useFlag('ongoingIncident')
const showNoticeBanner = useFlag('showNoticeBanner')
const clockSkewBanner = useFlag('clockSkewBanner')
// Define the supabase theme for Monaco before anything is rendered. Using useEffect would sometime load the theme
// after the editor was loaded, so it looked off. useMemo will always be run before rendering
@@ -32,6 +34,7 @@ const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => {
{ongoingIncident && <IncidentBanner />}
{showNoticeBanner && <NoticeBanner />}
{profile !== undefined && <RestrictionBanner />}
{clockSkewBanner && <ClockSkewBanner />}
</div>
{children}
</div>

View File

@@ -0,0 +1,58 @@
import { BASE_PATH } from 'lib/constants'
import { useCallback, useEffect, useState } from 'react'
import { Button } from 'ui'
// Show the banner if the clock skew is greater than 2 minutes
const CLOCK_SKEW_THRESHOLD = 2 * 60 * 1000
// check every 5 minutes
const CLOCK_SKEW_CHECK_INTERVAL = 30 * 60 * 1000
const isClockSkewed = async () => {
try {
const response = await fetch(`${BASE_PATH}/api/get-utc-time`)
const data = await response.json()
// The received time is in UTC timezone, add Z at the end to make JS understand that
const serverTime = new Date(data.utcTime).getTime()
const clientTime = new Date().getTime()
const clockSkew = Math.abs(clientTime - serverTime)
return clockSkew > CLOCK_SKEW_THRESHOLD
} catch {
return false
}
}
export const ClockSkewBanner = () => {
const [clockSkew, setClockSkew] = useState(false)
const checkClockSkew = useCallback(async () => {
const value = await isClockSkewed()
setClockSkew(value)
}, [])
useEffect(() => {
// check for clock skew every CLOCK_SKEW_CHECK_INTERVAL
checkClockSkew()
const interval = setInterval(checkClockSkew, CLOCK_SKEW_CHECK_INTERVAL)
return () => clearInterval(interval)
}, [checkClockSkew])
if (!clockSkew) return null
return (
<div
style={{ height: '44px' }}
className="flex items-center justify-center gap-x-4 bg-destructive-400 py-3 transition text-foreground box-border border-b border-default"
>
<p className="text-sm">
Your computer's clock appears to be inaccurate. This can cause issues with certain features.
</p>
<Button asChild type="default">
<a href="https://supabase.com/docs" target="_blank">
More information
</a>
</Button>
</div>
)
}

View File

@@ -3,7 +3,7 @@ import Link from 'next/link'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { AlertTitle_Shadcn_, Alert_Shadcn_, Button, CriticalIcon, WarningIcon } from 'ui'
import { AlertTitle_Shadcn_, Alert_Shadcn_, Button, CriticalIcon, WarningIcon, cn } from 'ui'
/**
* Shown on projects in organization which are above their qouta
@@ -18,7 +18,10 @@ export const RestrictionBanner = () => {
return (
<Alert_Shadcn_
variant={currentOrg.restriction_status === 'restricted' ? 'destructive' : 'warning'}
className="rounded-none border-l-0 border-r-0 h-[44px] p-0 flex items-center justify-center"
className={cn(
'rounded-none border-0 h-[44px] p-0 flex items-center justify-center',
currentOrg.restriction_status === 'restricted' ? 'bg-destructive-400' : 'bg-warning-500'
)}
>
<AlertTitle_Shadcn_ className="flex items-center gap-x-4">
{currentOrg.restriction_status === 'restricted' ? <CriticalIcon /> : <WarningIcon />}

View File

@@ -17,10 +17,10 @@ const HOSTED_SUPPORTED_API_URLS = [
'/ai/sql/cron',
'/ai/docs',
'/get-ip-address',
'/get-utc-time',
]
export function middleware(request: NextRequest) {
const url = request.url
if (IS_PLATFORM && !HOSTED_SUPPORTED_API_URLS.some((url) => request.url.endsWith(url))) {
return Response.json(
{ success: false, message: 'Endpoint not supported on hosted' },

View File

@@ -0,0 +1,11 @@
import { NextApiRequest, NextApiResponse } from 'next'
// Returns the current UTC time in ISO format. Used to check if the client and server time are skewed. Clock skew causes
// issues with JWT verification.
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const utcTime = new Date().toISOString()
return res.status(200).json({ utcTime })
}
export default handler