mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 18:35:07 +03:00
drop expired temporary access roles when they elapse
This commit is contained in:
1 parent
23c121aa53
commit
2ffbd4258d
3 files changed
+78
-6
No files matched your search
@@ -2,6 +2,7 @@ import { describe, expect, test } from 'vitest'
|
||||
|
||||
import {
|
||||
getActiveTemporaryAccessRoles,
|
||||
getNextTemporaryAccessExpirySeconds,
|
||||
isTemporaryAccessRoleExpired,
|
||||
} from './jit-db-access-roles.utils'
|
||||
|
||||
@@ -33,3 +34,28 @@ describe('getActiveTemporaryAccessRoles', () => {
|
||||
).toEqual(['postgres', 'analytics'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getNextTemporaryAccessExpirySeconds', () => {
|
||||
test('returns the soonest future expiry and ignores missing or past ones', () => {
|
||||
expect(getNextTemporaryAccessExpirySeconds(undefined, 1_000)).toBeUndefined()
|
||||
expect(
|
||||
getNextTemporaryAccessExpirySeconds(
|
||||
{
|
||||
user_roles: [
|
||||
{ role: 'postgres', expires_at: 500 },
|
||||
{ role: 'analytics' },
|
||||
{ role: 'postgres', expires_at: 2_000 },
|
||||
{ role: 'supabase_read_only_user', expires_at: 1_500 },
|
||||
],
|
||||
},
|
||||
1_000
|
||||
)
|
||||
).toBe(1_500)
|
||||
expect(
|
||||
getNextTemporaryAccessExpirySeconds(
|
||||
{ user_roles: [{ role: 'postgres', expires_at: 1_701_000_000_000 }] },
|
||||
1_700_000_000
|
||||
)
|
||||
).toBe(1_701_000_000)
|
||||
})
|
||||
})
|
||||
@@ -5,13 +5,32 @@ export type TemporaryAccessRoleGrant = {
|
||||
|
||||
const MILLISECONDS_THRESHOLD = 1e12
|
||||
|
||||
function toExpirySeconds(expiresAt: number) {
|
||||
return expiresAt > MILLISECONDS_THRESHOLD ? expiresAt / 1000 : expiresAt
|
||||
}
|
||||
|
||||
export function isTemporaryAccessRoleExpired(
|
||||
expiresAt: number | undefined,
|
||||
nowSeconds: number
|
||||
): boolean {
|
||||
if (!expiresAt) return false
|
||||
const expiresSeconds = expiresAt > MILLISECONDS_THRESHOLD ? expiresAt / 1000 : expiresAt
|
||||
return expiresSeconds <= nowSeconds
|
||||
return toExpirySeconds(expiresAt) <= nowSeconds
|
||||
}
|
||||
|
||||
export function getNextTemporaryAccessExpirySeconds(
|
||||
grants: { user_roles?: TemporaryAccessRoleGrant[] } | undefined,
|
||||
nowSeconds: number
|
||||
): number | undefined {
|
||||
if (!grants?.user_roles?.length) return
|
||||
|
||||
let next: number | undefined
|
||||
for (const grant of grants.user_roles) {
|
||||
if (!grant.role || grant.expires_at == null) continue
|
||||
const expiresSeconds = toExpirySeconds(grant.expires_at)
|
||||
if (expiresSeconds <= nowSeconds) continue
|
||||
if (next == null || expiresSeconds < next) next = expiresSeconds
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
export function getActiveTemporaryAccessRoles(
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { useIsJitDbAccessEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
import { getActiveTemporaryAccessRoles } from '@/data/jit-db-access/jit-db-access-roles.utils'
|
||||
import {
|
||||
getActiveTemporaryAccessRoles,
|
||||
getNextTemporaryAccessExpirySeconds,
|
||||
} from '@/data/jit-db-access/jit-db-access-roles.utils'
|
||||
import { useJitDbAccessSelfQuery } from '@/data/jit-db-access/jit-db-access-self-query'
|
||||
|
||||
function readNowSeconds() {
|
||||
return Math.floor(Date.now() / 1000)
|
||||
}
|
||||
|
||||
export function useActiveTemporaryAccessRoles(
|
||||
projectRef: string | undefined,
|
||||
{ enabled = true }: { enabled?: boolean } = {}
|
||||
@@ -13,11 +20,31 @@ export function useActiveTemporaryAccessRoles(
|
||||
{ projectRef },
|
||||
{ enabled: enabled && isJitEnabled }
|
||||
)
|
||||
const [nowSeconds, setNowSeconds] = useState(readNowSeconds)
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !isJitEnabled || isError) return
|
||||
|
||||
const now = readNowSeconds()
|
||||
if (now !== nowSeconds) {
|
||||
setNowSeconds(now)
|
||||
return
|
||||
}
|
||||
|
||||
const nextExpiry = getNextTemporaryAccessExpirySeconds(data, nowSeconds)
|
||||
if (nextExpiry == null) return
|
||||
|
||||
const timeout = window.setTimeout(
|
||||
() => setNowSeconds(readNowSeconds()),
|
||||
Math.max(0, nextExpiry * 1000 - Date.now())
|
||||
)
|
||||
return () => window.clearTimeout(timeout)
|
||||
}, [data, enabled, isError, isJitEnabled, nowSeconds])
|
||||
|
||||
const activeRoles = useMemo(() => {
|
||||
if (!isJitEnabled || isError) return []
|
||||
return getActiveTemporaryAccessRoles(data, Math.floor(Date.now() / 1000))
|
||||
}, [data, isError, isJitEnabled])
|
||||
return getActiveTemporaryAccessRoles(data, nowSeconds)
|
||||
}, [data, isError, isJitEnabled, nowSeconds])
|
||||
|
||||
return {
|
||||
activeRoles,
|
||||
|
||||
Reference in new issue
Block a user