- Grant project members temporary database role access through Just-in-Time (JIT) controls in{' '}
- Database Settings .
+
+
+ Grant project members temporary database role access through short-lived tokens, controlled
+ in Database Settings .
-
+
Enabling this preview will:
- Show JIT database access controls in Database Settings
- Allow configuring role grants and member-level JIT rules
+ Show temporary access controls in Database Settings
+ Allow configuring role grants and member-level temporary access rules
+
+ The minimum Postgres version needed for this feature is 17.6.1.081 (or higher).
+
)
}
diff --git a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.constants.ts b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.constants.ts
deleted file mode 100644
index 86bbc9c2706..00000000000
--- a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.constants.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { JitExpiryMode } from './JitDbAccess.types'
-
-export const JIT_EXPIRY_MODE_OPTIONS: Array<{ value: JitExpiryMode; label: string }> = [
- { value: '1h', label: '1 hour' },
- { value: '1d', label: '1 day' },
- { value: '7d', label: '7 days' },
- { value: '30d', label: '30 days' },
- { value: 'custom', label: 'Custom' },
- { value: 'never', label: 'Never' },
-]
-
-export const JIT_MAX_CUSTOM_EXPIRY_YEARS = 1
diff --git a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.types.ts b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.types.ts
index 8a098eff13e..ea5ef8b7519 100644
--- a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.types.ts
+++ b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.types.ts
@@ -12,6 +12,11 @@ export type JitStatusBadge = {
variant: 'default' | 'success' | 'warning'
}
+export type JitDbAccessUnavailableReason =
+ | 'postgres_upgrade_required'
+ | 'manual_migration_required'
+ | 'temporarily_unavailable'
+
export type JitMemberOption = {
id: string
email: string
@@ -23,14 +28,17 @@ export type JitRoleOption = {
label: string
}
+export type JitIpRangeDraft = {
+ value: string
+}
+
export type JitRoleGrantDraft = {
roleId: string
enabled: boolean
expiryMode: JitExpiryMode
hasExpiry: boolean
expiry: string
- hasIpRestriction: boolean
- ipRanges: string
+ ipRanges: JitIpRangeDraft[]
}
export type JitUserRuleDraft = {
diff --git a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.utils.test.ts b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.utils.test.ts
index 5756364e919..38529a0d0bb 100644
--- a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.utils.test.ts
+++ b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.utils.test.ts
@@ -5,10 +5,9 @@ import type { JitUserRuleDraft } from './JitDbAccess.types'
import {
computeStatusFromGrants,
createEmptyGrant,
- getInvalidCidrs,
+ getInvalidIpRangeRows,
getJitMemberOptions,
getRelativeDatetimeByMode,
- parseCommaSeparatedCidrs,
serializeDraftRolesForGrantMutation,
} from './JitDbAccess.utils'
import type { OrganizationMembersData } from '@/data/organizations/organization-members-query'
@@ -31,8 +30,7 @@ describe('jitDbAccess.utils', () => {
enabled: true,
hasExpiry: true,
expiry: dayjs().add(1, 'day').toISOString(),
- hasIpRestriction: true,
- ipRanges: '192.0.2.0/24',
+ ipRanges: [{ value: '192.0.2.0/24' }],
}
const expiredGrant = {
@@ -40,8 +38,7 @@ describe('jitDbAccess.utils', () => {
enabled: true,
hasExpiry: true,
expiry: dayjs().subtract(1, 'day').toISOString(),
- hasIpRestriction: true,
- ipRanges: '203.0.113.0/24',
+ ipRanges: [{ value: '203.0.113.0/24' }],
}
const perpetualGrant = {
@@ -60,17 +57,15 @@ describe('jitDbAccess.utils', () => {
})
})
- it('parses comma-separated CIDR lists and trims whitespace', () => {
- expect(parseCommaSeparatedCidrs('192.0.2.0/24, 2001:db8::/64 , ,203.0.113.4/32')).toEqual([
- '192.0.2.0/24',
- '2001:db8::/64',
- '203.0.113.4/32',
- ])
- })
-
- it('returns invalid CIDRs from comma-separated input', () => {
+ it('returns invalid CIDRs from repeated input rows', () => {
expect(
- getInvalidCidrs('192.0.2.0/24, not-a-cidr, 10.0.0.1/33, 2001:db8::/64, 2001:db8::/129')
+ getInvalidIpRangeRows([
+ { value: '192.0.2.0/24' },
+ { value: 'not-a-cidr' },
+ { value: '10.0.0.1/33' },
+ { value: '2001:db8::/64' },
+ { value: '2001:db8::/129' },
+ ])
).toEqual(['not-a-cidr', '10.0.0.1/33', '2001:db8::/129'])
})
})
@@ -87,8 +82,7 @@ describe('serializeDraftRolesForGrantMutation', () => {
hasExpiry: true,
expiryMode: 'custom',
expiry,
- hasIpRestriction: true,
- ipRanges: '192.0.2.0/24, 2001:db8::/64',
+ ipRanges: [{ value: '192.0.2.0/24' }, { value: ' ' }, { value: '2001:db8::/64' }],
},
{
...createEmptyGrant('supabase_read_only_user'),
diff --git a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.utils.ts b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.utils.ts
index ae6c3653918..234717436ce 100644
--- a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.utils.ts
+++ b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.utils.ts
@@ -3,6 +3,7 @@ import { IPv4CidrRange, IPv6CidrRange } from 'ip-num'
import type {
JitExpiryMode,
+ JitIpRangeDraft,
JitMemberOption,
JitRoleGrantDraft,
JitRoleOption,
@@ -36,13 +37,24 @@ export function createEmptyGrant(roleId: string): JitRoleGrantDraft {
expiryMode: '1h',
hasExpiry: true,
expiry: getRelativeDatetimeByMode('1h'),
- hasIpRestriction: false,
- ipRanges: '',
+ ipRanges: [createEmptyIpRange()],
}
}
+export function createEmptyIpRange(): JitIpRangeDraft {
+ return { value: '' }
+}
+
+function parseIpRangeRows(value: JitIpRangeDraft[]) {
+ return value.map((item) => item.value.trim()).filter((item) => item.length > 0)
+}
+
+function cloneIpRanges(ipRanges: JitIpRangeDraft[]) {
+ return ipRanges.map((ipRange) => ({ ...ipRange }))
+}
+
function cloneGrants(grants: JitRoleGrantDraft[]) {
- return grants.map((grant) => ({ ...grant }))
+ return grants.map((grant) => ({ ...grant, ipRanges: cloneIpRanges(grant.ipRanges) }))
}
export function createDraft(roleIds: string[]): JitUserRuleDraft {
@@ -80,6 +92,7 @@ export function draftFromRule(rule: JitUserRule, baseRoleIds: string[]): JitUser
return {
...nextGrant,
expiryMode: inferExpiryMode(nextGrant),
+ ipRanges: cloneIpRanges(nextGrant.ipRanges),
}
}),
}
@@ -94,7 +107,7 @@ export function computeStatusFromGrants(grants: JitRoleGrantDraft[]): JitStatus
let expiredIp = 0
enabledGrants.forEach((grant) => {
- const hasIp = grant.hasIpRestriction && grant.ipRanges.trim().length > 0
+ const hasIp = parseIpRangeRows(grant.ipRanges).length > 0
if (!grant.hasExpiry || !grant.expiry) {
active += 1
@@ -151,13 +164,6 @@ function toUnixSeconds(datetimeIso: string) {
return value.unix()
}
-export function parseCommaSeparatedCidrs(value: string) {
- return value
- .split(',')
- .map((item) => item.trim())
- .filter((item) => item.length > 0)
-}
-
function isValidCidr(value: string) {
try {
if (value.includes(':')) {
@@ -172,8 +178,8 @@ function isValidCidr(value: string) {
}
}
-export function getInvalidCidrs(value: string) {
- return parseCommaSeparatedCidrs(value).filter((cidr) => !isValidCidr(cidr))
+export function getInvalidIpRangeRows(value: JitIpRangeDraft[]) {
+ return parseIpRangeRows(value).filter((cidr) => !isValidCidr(cidr))
}
function isAssignableJitRole(role: PgRole) {
@@ -264,8 +270,10 @@ export function mapJitMembersToUserRules(
hasExpiry,
expiryMode: hasExpiry ? 'custom' : 'never',
expiry: hasExpiry ? new Date(expiresAt * 1000).toISOString() : '',
- hasIpRestriction: allowedNetworks.length > 0,
- ipRanges: allowedNetworks.join(', '),
+ ipRanges:
+ allowedNetworks.length > 0
+ ? allowedNetworks.map((cidr) => ({ value: cidr }))
+ : [createEmptyIpRange()],
}
})
@@ -295,15 +303,13 @@ export function mapJitMembersToUserRules(
}
export function serializeDraftRolesForGrantMutation(draft: JitUserRuleDraft) {
- const serializeAllowedNetworks = (value: string) => {
- const cidrs = parseCommaSeparatedCidrs(value)
+ const serializeAllowedNetworks = (value: JitIpRangeDraft[]) => {
+ const cidrs = parseIpRangeRows(value)
if (cidrs.length === 0) return undefined
const allowed_cidrs = cidrs.filter((cidr) => !cidr.includes(':')).map((cidr) => ({ cidr }))
const allowed_cidrs_v6 = cidrs.filter((cidr) => cidr.includes(':')).map((cidr) => ({ cidr }))
- if (allowed_cidrs.length === 0 && allowed_cidrs_v6.length === 0) return undefined
-
return {
...(allowed_cidrs.length > 0 ? { allowed_cidrs } : {}),
...(allowed_cidrs_v6.length > 0 ? { allowed_cidrs_v6 } : {}),
diff --git a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx
index 50af7588a35..7d8f95ed2b1 100644
--- a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx
@@ -107,18 +107,18 @@ export const JitDbAccessConfiguration = () => {
const nextEnabled = variables.requestedConfig.state === 'enabled'
if (nextEnabled) {
- toast.success('JIT access enabled')
+ toast.success('Temporary access enabled')
} else {
toast.success(
activeRuleCount > 0
- ? `JIT access disabled. ${activeRuleCount} configured member${activeRuleCount === 1 ? '' : 's'} can no longer request temporary database access.`
- : 'JIT access disabled'
+ ? `Temporary access disabled. ${activeRuleCount} configured member${activeRuleCount === 1 ? '' : 's'} can no longer request temporary database access.`
+ : 'Temporary access disabled'
)
}
},
onError: (error) => {
setEnabled(initialIsEnabled ?? false)
- toast.error(`Failed to update just-in-time (JIT) database access: ${error.message}`)
+ toast.error(`Failed to update temporary access: ${error.message}`)
},
})
@@ -138,18 +138,13 @@ export const JitDbAccessConfiguration = () => {
const isRulesLoading = isLoadingJitMembers || isLoadingProjectMembers
const initialIsEnabled =
- isSuccessConfiguration &&
- !!jitDbAccessConfiguration &&
- 'appliedSuccessfully' in jitDbAccessConfiguration &&
- jitDbAccessConfiguration.appliedSuccessfully &&
- 'state' in jitDbAccessConfiguration &&
- (jitDbAccessConfiguration as { state: string }).state === 'enabled'
-
- const hasAccessToJitDbAccess = !(
- jitDbAccessConfiguration !== undefined &&
- 'isUnavailable' in jitDbAccessConfiguration &&
- jitDbAccessConfiguration.isUnavailable
- )
+ jitDbAccessConfiguration?.state === 'enabled'
+ ? jitDbAccessConfiguration?.appliedSuccessfully
+ : false
+ const isJitDbAccessUnavailable = jitDbAccessConfiguration?.state === 'unavailable'
+ const unavailableReason = isJitDbAccessUnavailable
+ ? jitDbAccessConfiguration.unavailableReason
+ : undefined
const roleOptions = useMemo(() => getAssignableJitRoleOptions(databaseRoles), [databaseRoles])
@@ -214,7 +209,7 @@ export const JitDbAccessConfiguration = () => {
}
const handleJitToggleChange = (checked: boolean) => {
- if (!hasAccessToJitDbAccess || !canUpdateJitDbAccess) return
+ if (isJitDbAccessUnavailable || !canUpdateJitDbAccess) return
if (checked && !enabled) {
if (activeRuleCount > 0) {
@@ -265,6 +260,26 @@ export const JitDbAccessConfiguration = () => {
'appliedSuccessfully' in jitDbAccessConfiguration &&
!jitDbAccessConfiguration.appliedSuccessfully
+ const projectReference = ref ? (
+ <>
+ This project
{ref}
+ >
+ ) : (
+ 'This project'
+ )
+ const unavailableTitle =
+ unavailableReason === 'postgres_upgrade_required'
+ ? 'Postgres upgrade required'
+ : unavailableReason === 'manual_migration_required'
+ ? 'Migration required'
+ : 'Temporary access unavailable'
+ const unavailableDescription =
+ unavailableReason === 'postgres_upgrade_required'
+ ? 'must be upgraded to Postgres 17 or later before temporary access can be enabled.'
+ : unavailableReason === 'manual_migration_required'
+ ? 'must be migrated before temporary access can be enabled. Contact support to migrate this project.'
+ : 'This feature is currently unavailable for this project. Contact support if you need help enabling it.'
+
useEffect(() => {
if (!isLoadingConfiguration && jitDbAccessConfiguration) {
setEnabled(initialIsEnabled ?? false)
@@ -276,43 +291,64 @@ export const JitDbAccessConfiguration = () => {
- Just-in-Time (JIT)
+ Temporary access
-
+
{isErrorJitDbAccessConfiguration && (
)}
- {!isErrorJitDbAccessConfiguration && !hasAccessToJitDbAccess && (
+ {!isErrorJitDbAccessConfiguration && isJitDbAccessUnavailable && (
+ {projectReference} {unavailableDescription}
+ >
+ )
+ }
actions={
- ref ? (
+ unavailableReason === 'postgres_upgrade_required' && ref ? (
Upgrade Postgres
- ) : undefined
+ ) : (
+
+
+ Contact support
+
+
+ )
}
/>
)}
- {!isErrorJitDbAccessConfiguration && hasAccessToJitDbAccess && (
+ {!isErrorJitDbAccessConfiguration && !isJitDbAccessUnavailable && (
{(isLoadingConfiguration || isUpdatingJitDbAccess) && (
@@ -345,14 +381,14 @@ export const JitDbAccessConfiguration = () => {
- The change didn’t apply. Try turning JIT access on or off again, or{' '}
+ The change didn’t apply. Try enabling or disabling temporary access again, or{' '}
@@ -367,13 +403,14 @@ export const JitDbAccessConfiguration = () => {
)}
- {enabled && hasAccessToJitDbAccess && !isUpdatingJitDbAccess && (
+ {enabled && !isJitDbAccessUnavailable && !isUpdatingJitDbAccess && (
<>
{isErrorJitMembers && (
)}
@@ -408,11 +445,11 @@ export const JitDbAccessConfiguration = () => {
- JIT access will activate existing rules
+ This will activate existing rules
- Enabling JIT will allow {activeRuleCount} configured member
+ Enabling temporary access will allow {activeRuleCount} pre-configured member
{activeRuleCount === 1 ? '' : 's'} to request temporary database access
immediately.
@@ -426,7 +463,7 @@ export const JitDbAccessConfiguration = () => {
disabled={isUpdatingJitDbAccess}
onClick={handleConfirmEnableJit}
>
- Enable JIT access
+ Enable temporary access
diff --git a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessDeleteDialog.tsx b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessDeleteDialog.tsx
index 6f04c91e698..92215a4175f 100644
--- a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessDeleteDialog.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessDeleteDialog.tsx
@@ -31,15 +31,15 @@ export function JitDbAccessDeleteDialog({
!open && !isDeleting && onClose()}>
- Delete JIT access rule
+ Delete temporary access rule
- Remove the JIT access rule for{' '}
+ Remove the temporary access rule for{' '}
{userDisplayName} ?
- This revokes any assigned database roles for this member and removes their JIT
+ This revokes any assigned database roles for this member and removes their temporary
access configuration.
diff --git a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRoleGrantFields.tsx b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRoleGrantFields.tsx
index 28580e649ca..ef91e457d4e 100644
--- a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRoleGrantFields.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRoleGrantFields.tsx
@@ -1,7 +1,8 @@
import dayjs from 'dayjs'
+import type { Control } from 'react-hook-form'
import {
Checkbox,
- Input_Shadcn_,
+ cn,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectItem_Shadcn_,
@@ -11,27 +12,43 @@ import {
} from 'ui'
import { TimestampInfo } from 'ui-patterns'
import { Admonition } from 'ui-patterns/admonition'
+import { SingleValueFieldArray } from 'ui-patterns/form/SingleValueFieldArray/SingleValueFieldArray'
-import { JIT_EXPIRY_MODE_OPTIONS, JIT_MAX_CUSTOM_EXPIRY_YEARS } from './JitDbAccess.constants'
-import type { JitRoleGrantDraft, JitRoleOption } from './JitDbAccess.types'
-import { getRelativeDatetimeByMode } from './JitDbAccess.utils'
+import type { JitRoleGrantDraft, JitRoleOption, JitUserRuleDraft } from './JitDbAccess.types'
+import { createEmptyIpRange, getRelativeDatetimeByMode } from './JitDbAccess.utils'
import { DatePicker } from '@/components/ui/DatePicker'
import { InlineLink } from '@/components/ui/InlineLink'
import { DOCS_URL } from '@/lib/constants'
+const EXPIRY_MODE_OPTIONS: Array<{ value: JitRoleGrantDraft['expiryMode']; label: string }> = [
+ { value: '1h', label: '1 hour' },
+ { value: '1d', label: '1 day' },
+ { value: '7d', label: '7 days' },
+ { value: '30d', label: '30 days' },
+ { value: 'custom', label: 'Custom' },
+ { value: 'never', label: 'Never' },
+]
+
+const MAX_CUSTOM_EXPIRY_YEARS = 1
+
interface JitDbAccessRoleGrantFieldsProps {
+ control: Control
+ grantIndex: number
role: JitRoleOption
grant: JitRoleGrantDraft
onChange: (next: JitRoleGrantDraft) => void
}
export function JitDbAccessRoleGrantFields({
+ control,
+ grantIndex,
role,
grant,
onChange,
}: JitDbAccessRoleGrantFieldsProps) {
const isSuperuserRole = role.id === 'postgres'
const isReadOnlyRole = role.id === 'supabase_read_only_user'
+ const showRoleAdmonition = isSuperuserRole || isReadOnlyRole
const checkboxId = `jit-role-${role.id}`
return (
@@ -86,6 +103,7 @@ export function JitDbAccessRoleGrantFields({
type="warning"
showIcon={false}
layout="vertical"
+ className="rounded-md mb-2"
title="Grants full database control"
description={
<>
@@ -115,11 +133,11 @@ export function JitDbAccessRoleGrantFields({
with only the permissions required.
>
}
- className="rounded-md"
+ className="rounded-md mb-2"
/>
)}
-
+
Expires in
@@ -158,7 +176,7 @@ export function JitDbAccessRoleGrantFields({
- {JIT_EXPIRY_MODE_OPTIONS.map((option) => (
+ {EXPIRY_MODE_OPTIONS.map((option) => (
{option.label}
@@ -174,7 +192,7 @@ export function JitDbAccessRoleGrantFields({
contentSide="top"
to={grant.expiry || undefined}
minDate={new Date()}
- maxDate={dayjs().add(JIT_MAX_CUSTOM_EXPIRY_YEARS, 'year').toDate()}
+ maxDate={dayjs().add(MAX_CUSTOM_EXPIRY_YEARS, 'year').toDate()}
onChange={(value) => {
const selectedDate = value.to || value.from || ''
onChange({
@@ -216,18 +234,19 @@ export function JitDbAccessRoleGrantFields({
Restricted IP addresses{' '}
(optional)
-
- onChange({
- ...grant,
- hasIpRestriction: event.target.value.trim().length > 0,
- ipRanges: event.target.value,
- })
- }
- placeholder="e.g. 192.168.0.0/24, 203.0.113.4/32"
+
- Comma-separated CIDR ranges
diff --git a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRuleSheet.tsx b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRuleSheet.tsx
index 6efa1f0b9d3..f903b2dca0d 100644
--- a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRuleSheet.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRuleSheet.tsx
@@ -36,7 +36,7 @@ import {
createDraft,
draftFromRule,
getAssignableJitRoleOptions,
- getInvalidCidrs,
+ getInvalidIpRangeRows,
mapJitMembersToUserRules,
serializeDraftRolesForGrantMutation,
} from './JitDbAccess.utils'
@@ -57,14 +57,13 @@ const grantSchema = z.object({
expiryMode: z.custom
(),
hasExpiry: z.boolean(),
expiry: z.string(),
- hasIpRestriction: z.boolean(),
- ipRanges: z.string(),
+ ipRanges: z.array(z.object({ value: z.string() })),
})
function createJitRuleSchema(mode: SheetMode, membersWithRules: Set) {
return z
.object({
- memberId: z.string().min(1, 'Select a member for this JIT access rule.'),
+ memberId: z.string().min(1, 'Select a member for this temporary access rule.'),
grants: z.array(grantSchema),
})
.superRefine((data, ctx) => {
@@ -73,12 +72,12 @@ function createJitRuleSchema(mode: SheetMode, membersWithRules: Set) {
code: z.ZodIssueCode.custom,
path: ['memberId'],
message:
- 'This member already has a JIT access rule. Edit their existing rule from the list.',
+ 'This member already has a temporary access rule. Edit their existing rule from the list.',
})
}
- const enabledGrants = data.grants.filter((g) => g.enabled)
- if (enabledGrants.length === 0) {
+ const enabledGrantCount = data.grants.filter((g) => g.enabled).length
+ if (enabledGrantCount === 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['grants'],
@@ -87,19 +86,22 @@ function createJitRuleSchema(mode: SheetMode, membersWithRules: Set) {
return
}
- for (const grant of enabledGrants) {
- const invalidCidrs = getInvalidCidrs(grant.ipRanges)
- if (invalidCidrs.length > 0) {
- const preview = invalidCidrs.slice(0, 3).join(', ')
- const overflow = invalidCidrs.length > 3
+ data.grants.forEach((grant, grantIndex) => {
+ if (!grant.enabled) return
+
+ const invalidCidrs = new Set(getInvalidIpRangeRows(grant.ipRanges))
+
+ grant.ipRanges.forEach((ipRange, ipRangeIndex) => {
+ const value = ipRange.value.trim()
+ if (value.length === 0 || !invalidCidrs.has(value)) return
+
ctx.addIssue({
code: z.ZodIssueCode.custom,
- path: ['grants'],
- message: `Invalid CIDR range${invalidCidrs.length > 1 ? 's' : ''} for role "${grant.roleId}": ${preview}${overflow ? ', ...' : ''}`,
+ path: ['grants', grantIndex, 'ipRanges', ipRangeIndex, 'value'],
+ message: 'Please enter a valid CIDR range',
})
- break
- }
- }
+ })
+ })
})
}
@@ -225,10 +227,10 @@ export function JitDbAccessRuleSheet({
>
- {mode === 'edit' ? 'Edit JIT access rule' : 'New JIT access rule'}
+ {mode === 'edit' ? 'Edit temporary access rule' : 'New temporary access rule'}
- Configure which database roles a user can request with JIT access.
+ Configure which database roles a user can request with temporary access.
@@ -272,8 +274,8 @@ export function JitDbAccessRuleSheet({
{mode === 'add' && availableMembersForAddCount === 0 && (
- All project members already have JIT access rules. Edit an existing rule
- from the table above.
+ All project members already have temporary access rules. Edit an existing
+ rule from the table above.
)}
@@ -311,6 +313,8 @@ export function JitDbAccessRuleSheet({
{grants.map((grant, index) => (
0 ? 'border-t' : ''}>
updateGrant(grant.roleId, () => next)}
diff --git a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRulesTable.tsx b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRulesTable.tsx
index 8df3cec319f..732be4ad65d 100644
--- a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRulesTable.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRulesTable.tsx
@@ -49,7 +49,7 @@ export function JitDbAccessRulesTable({
const addRuleTooltip = !canUpdate
? 'Additional permissions required'
: allProjectMembersHaveRules
- ? 'All project members already have JIT access rules'
+ ? 'All project members already have temporary access rules'
: undefined
if (isLoading) {
@@ -74,9 +74,9 @@ export function JitDbAccessRulesTable({
-
JIT access rules
+
Temporary access rules
- Configure which members can request temporary database access.
+ Manage member access, allowed roles, and expiry settings.
@@ -107,9 +107,9 @@ export function JitDbAccessRulesTable({
{users.length === 0 ? (
- No JIT access rules
+ No rules yet
- Add your first JIT access rule above
+ Add your first temporary access rule above
diff --git a/apps/studio/data/jit-db-access/jit-db-access-query.ts b/apps/studio/data/jit-db-access/jit-db-access-query.ts
index 7ebc094d7c8..a5ed202a59b 100644
--- a/apps/studio/data/jit-db-access/jit-db-access-query.ts
+++ b/apps/studio/data/jit-db-access/jit-db-access-query.ts
@@ -17,23 +17,7 @@ async function getJitDbAccessConfiguration(
signal,
})
- // jit access might not be available on the project due to
- // postgres version
- if (error) {
- const responseError = error as ResponseError
- const isNotAvailableError =
- responseError.code === 400 && responseError.message?.includes('unavailable')
-
- if (isNotAvailableError) {
- return {
- appliedSuccessfully: false,
- state: 'unavailable' as string,
- isUnavailable: true,
- } as const
- } else {
- handleError(error)
- }
- }
+ if (error) handleError(error)
return data
}
diff --git a/apps/studio/data/jit-db-access/jit-db-access-update-mutation.ts b/apps/studio/data/jit-db-access/jit-db-access-update-mutation.ts
index 9d638c18285..3cd9e2391d2 100644
--- a/apps/studio/data/jit-db-access/jit-db-access-update-mutation.ts
+++ b/apps/studio/data/jit-db-access/jit-db-access-update-mutation.ts
@@ -44,7 +44,7 @@ export const useJitDbAccessUpdateMutation = ({
},
async onError(data, variables, context) {
if (onError === undefined) {
- toast.error(`Failed to update just-in-time (JIT) database access: ${data.message}`)
+ toast.error(`Failed to update temporary access: ${data.message}`)
} else {
onError(data, variables, context)
}
diff --git a/apps/studio/pages/project/[ref]/database/settings.tsx b/apps/studio/pages/project/[ref]/database/settings.tsx
index 64fada053f5..303c9d3539f 100644
--- a/apps/studio/pages/project/[ref]/database/settings.tsx
+++ b/apps/studio/pages/project/[ref]/database/settings.tsx
@@ -51,9 +51,9 @@ const DatabaseSettings: NextPageWithLayout = () => {
+ {jitDbAccessEnabled && }
- {jitDbAccessEnabled && }
{showNewDiskManagementUI ? (
// This form is hidden if Disk and Compute form is enabled, new form is on ./settings/compute-and-disk
diff --git a/packages/api-types/types/api.d.ts b/packages/api-types/types/api.d.ts
index 3163eb5c0db..bf619f41f1c 100644
--- a/packages/api-types/types/api.d.ts
+++ b/packages/api-types/types/api.d.ts
@@ -3144,6 +3144,21 @@ export interface components {
}[]
}[]
}
+ JitStateResponse:
+ | {
+ appliedSuccessfully?: boolean
+ /** @enum {string} */
+ state: 'enabled' | 'disabled'
+ }
+ | {
+ /** @enum {string} */
+ state: 'unavailable'
+ /** @enum {string} */
+ unavailableReason:
+ | 'manual_migration_required'
+ | 'postgres_upgrade_required'
+ | 'temporarily_unavailable'
+ }
LegacyApiKeysResponse: {
enabled: boolean
}
@@ -11197,7 +11212,7 @@ export interface operations {
[name: string]: unknown
}
content: {
- 'application/json': components['schemas']['JitAccessResponse']
+ 'application/json': components['schemas']['JitStateResponse']
}
}
/** @description Unauthorized */
@@ -11251,7 +11266,7 @@ export interface operations {
[name: string]: unknown
}
content: {
- 'application/json': components['schemas']['JitAccessResponse']
+ 'application/json': components['schemas']['JitStateResponse']
}
}
/** @description Unauthorized */