Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions apps/studio/components/grid/components/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useTableRowsCountQuery } from 'data/table-rows/table-rows-count-query'
import { fetchAllTableRows, useTableRowsQuery } from 'data/table-rows/table-rows-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { RoleImpersonationState } from 'lib/role-impersonation'
Expand Down Expand Up @@ -84,8 +84,10 @@ const DefaultHeader = () => {

const snap = useTableEditorTableStateSnapshot()
const tableEditorSnap = useTableEditorStateSnapshot()
const canCreateColumns = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns')

const { can: canCreateColumns } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'columns'
)
const { mutate: sendEvent } = useSendEventMutation()

const onAddRow =
Expand Down
28 changes: 20 additions & 8 deletions apps/studio/components/interfaces/APIKeys/APIKeyDeleteDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Trash2 } from 'lucide-react'
import { useState } from 'react'
import { toast } from 'sonner'
import { Trash2 } from 'lucide-react'

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common/hooks'
import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip'
import { useAPIKeyDeleteMutation } from 'data/api-keys/api-key-delete-mutation'
import { APIKeysData } from 'data/api-keys/api-keys-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { DropdownMenuItem } from 'ui'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal'

interface APIKeyDeleteDialogProps {
Expand All @@ -19,7 +19,10 @@ export const APIKeyDeleteDialog = ({ apiKey, lastSeen }: APIKeyDeleteDialogProps
const { ref: projectRef } = useParams()
const [isOpen, setIsOpen] = useState(false)

const canDeleteAPIKeys = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, '*')
const { can: canDeleteAPIKeys } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'*'
)

const { mutate: deleteAPIKey, isLoading: isDeletingAPIKey } = useAPIKeyDeleteMutation({
onSuccess: () => {
Expand All @@ -35,17 +38,26 @@ export const APIKeyDeleteDialog = ({ apiKey, lastSeen }: APIKeyDeleteDialogProps

return (
<>
<DropdownMenuItem
className="flex gap-2 !pointer-events-auto"
<DropdownMenuItemTooltip
className="flex gap-2"
onClick={async (e) => {
if (canDeleteAPIKeys) {
e.preventDefault()
setIsOpen(true)
}
}}
disabled={!canDeleteAPIKeys}
tooltip={{
content: {
side: 'left',
text: !canDeleteAPIKeys
? 'You need additional permissions to delete API keys'
: undefined,
},
}}
>
<Trash2 className="size-4 text-destructive" strokeWidth={1.5} /> Delete API key
</DropdownMenuItem>
</DropdownMenuItemTooltip>
<TextConfirmModal
visible={isOpen}
onCancel={() => setIsOpen(false)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,10 @@ export const ApiKeysCreateCallout = () => {
* Feedback banner for users who have API keys and the feature is rolled out to them
*/
export const ApiKeysFeedbackBanner = () => {
const { hasApiKeys, isInRollout } = useApiKeysVisibility()
const { hasApiKeys } = useApiKeysVisibility()

// Don't show anything if not in rollout or if keys don't exist
if (!isInRollout || !hasApiKeys) {
return null
}
if (!hasApiKeys) return null

return (
<FeatureBanner
Expand Down
34 changes: 17 additions & 17 deletions apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useParams } from 'common'
import CopyButton from 'components/ui/CopyButton'
import { FormHeader } from 'components/ui/Forms/FormHeader'
import { useAPIKeysQuery } from 'data/api-keys/api-keys-query'
import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { cn, EyeOffIcon, Input_Shadcn_, Skeleton, WarningIcon } from 'ui'

// to add in later with follow up PR
Expand All @@ -27,8 +27,10 @@ export const PublishableAPIKeys = () => {
[apiKeysData]
)

const isPermissionsLoading = !usePermissionsLoaded()
const canReadAPIKeys = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, '*')
const { can: canReadAPIKeys, isLoading: isPermissionsLoading } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'*'
)

// The default publisahble key will always be the first one
const apiKey = publishableApiKeys[0]
Expand All @@ -46,12 +48,12 @@ export const PublishableAPIKeys = () => {
<div className="flex items-center gap-2">
<ApiKeyInput />
<CopyButton
disabled={isPermissionsLoading || isLoadingApiKeys || !canReadAPIKeys}
type="default"
text={apiKey?.api_key}
iconOnly
size={'tiny'}
size="tiny"
type="default"
className="px-2 rounded-full"
disabled={isPermissionsLoading || isLoadingApiKeys || !canReadAPIKeys}
text={apiKey?.api_key}
/>
</div>
</div>
Expand All @@ -65,31 +67,29 @@ export const PublishableAPIKeys = () => {
</div>
)}
</div>
<div className="flex flex-col gap-2 max-w-64">
{/* <Separator /> */}
{/* @mildtomato - To add in later with follow up PR */}
{/* <ShowPublicJWTsDialogComposer /> */}
</div>

{/* <CreatePublishableAPIKeyModal /> */}
</div>
</div>
)
}

function ApiKeyInput() {
const ApiKeyInput = () => {
const { ref: projectRef } = useParams()

const {
data: apiKeysData,
isLoading: isApiKeysLoading,
error,
} = useAPIKeysQuery({ projectRef, reveal: false })

const publishableApiKeys = useMemo(
() => apiKeysData?.filter(({ type }) => type === 'publishable') ?? [],
[apiKeysData]
)
const isPermissionsLoading = !usePermissionsLoaded()
const canReadAPIKeys = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, '*')

const { can: canReadAPIKeys, isLoading: isPermissionsLoading } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'*'
)
// The default publisahble key will always be the first one
const apiKey = publishableApiKeys[0]

Expand Down
16 changes: 6 additions & 10 deletions apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import relativeTime from 'dayjs/plugin/relativeTime'

import { PermissionAction } from '@supabase/shared-types/out/constants'
import dayjs from 'dayjs'
import { useMemo, useRef } from 'react'

import { useParams } from 'common'
import { FormHeader } from 'components/ui/Forms/FormHeader'
import { APIKeysData, useAPIKeysQuery } from 'data/api-keys/api-keys-query'
import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
import useLogsQuery from 'hooks/analytics/useLogsQuery'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { Card, CardContent, EyeOffIcon, Skeleton, WarningIcon, cn } from 'ui'
import {
Table,
Expand All @@ -22,9 +19,6 @@ import {
import { APIKeyRow } from './APIKeyRow'
import CreateSecretAPIKeyDialog from './CreateSecretAPIKeyDialog'

dayjs.extend(duration)
dayjs.extend(relativeTime)

interface LastSeenData {
[hash: string]: { timestamp: string }
}
Expand Down Expand Up @@ -62,8 +56,10 @@ export const SecretAPIKeys = () => {
error,
} = useAPIKeysQuery({ projectRef, reveal: false })

const isLoadingPermissions = !usePermissionsLoaded()
const canReadAPIKeys = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, '*')
const { can: canReadAPIKeys, isLoading: isLoadingPermissions } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'*'
)

const lastSeen = useLastSeen(projectRef!)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useMemo } from 'react'

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { useAPIKeysQuery } from 'data/api-keys/api-keys-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useFlag } from 'hooks/ui/useFlag'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'

interface ApiKeysVisibilityState {
isInRollout: boolean
hasApiKeys: boolean
isLoading: boolean
canReadAPIKeys: boolean
Expand All @@ -21,8 +19,7 @@ interface ApiKeysVisibilityState {
*/
export function useApiKeysVisibility(): ApiKeysVisibilityState {
const { ref: projectRef } = useParams()
const canReadAPIKeys = useCheckPermissions(PermissionAction.READ, 'api_keys')
const isBasicApiKeysEnabled = useFlag('basicApiKeys')
const { can: canReadAPIKeys } = useAsyncCheckProjectPermissions(PermissionAction.READ, 'api_keys')

const { data: apiKeysData, isLoading } = useAPIKeysQuery({
projectRef,
Expand All @@ -39,13 +36,12 @@ export function useApiKeysVisibility(): ApiKeysVisibilityState {
const hasApiKeys = publishableApiKeys.length > 0

// Can initialize API keys when in rollout, has permissions, not loading, and no API keys yet
const canInitApiKeys = isBasicApiKeysEnabled && canReadAPIKeys && !isLoading && !hasApiKeys
const canInitApiKeys = canReadAPIKeys && !isLoading && !hasApiKeys

// Disable UI for publishable keys and secrets keys if flag is not enabled OR no API keys created yet
const shouldDisableUI = !isBasicApiKeysEnabled || !hasApiKeys
const shouldDisableUI = !hasApiKeys

return {
isInRollout: isBasicApiKeysEnabled,
hasApiKeys,
isLoading,
canReadAPIKeys,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import NoPermission from 'components/ui/NoPermission'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM } from 'lib/constants'
import {
Expand Down Expand Up @@ -43,18 +43,19 @@ const FormSchema = z.object({
export const AdvancedAuthSettingsForm = () => {
const { ref: projectRef } = useParams()
const { data: organization } = useSelectedOrganizationQuery()
const canReadConfig = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue')
const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
const { can: canReadConfig } = useAsyncCheckProjectPermissions(
PermissionAction.READ,
'custom_config_gotrue'
)
const { can: canUpdateConfig } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'custom_config_gotrue'
)

const [isUpdatingRequestDurationForm, setIsUpdatingRequestDurationForm] = useState(false)
const [isUpdatingDatabaseForm, setIsUpdatingDatabaseForm] = useState(false)

const {
data: authConfig,
error: authConfigError,
isLoading,
isError,
} = useAuthConfigQuery({ projectRef })
const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef })

const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import { ProviderForm } from './ProviderForm'
export const AuthProvidersForm = () => {
const { ref: projectRef } = useParams()
const {
isLoading,
data: authConfig,
error: authConfigError,
isLoading,
isError,
data: authConfig,
isSuccess,
} = useAuthConfigQuery({ projectRef })

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Check } from 'lucide-react'
import { useQueryState } from 'nuqs'
import { useEffect, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import { toast } from 'sonner'
Expand All @@ -13,31 +14,31 @@ import type { components } from 'data/api'
import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { BASE_PATH } from 'lib/constants'
import { useQueryState } from 'nuqs'
import { Button, Form, Input, Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle } from 'ui'
import { Admonition } from 'ui-patterns'
import { NO_REQUIRED_CHARACTERS } from '../Auth.constants'
import { AuthAlert } from './AuthAlert'
import type { Provider } from './AuthProvidersForm.types'
import FormField from './FormField'

export interface ProviderFormProps {
interface ProviderFormProps {
config: components['schemas']['GoTrueConfigResponse']
provider: Provider
isActive: boolean
}

const doubleNegativeKeys = ['SMS_AUTOCONFIRM']

export const ProviderForm = ({ config, provider, isActive }: ProviderFormProps) => {
const { ref: projectRef } = useParams()
const [urlProvider, setUrlProvider] = useQueryState('provider', { defaultValue: '' })

const [open, setOpen] = useState(false)
const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation()

const doubleNegativeKeys = ['SMS_AUTOCONFIRM']
const canUpdateConfig: boolean = useCheckPermissions(
const { can: canUpdateConfig } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'custom_config_gotrue'
)
Expand Down
Loading
Loading