import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { useState } from 'react' import { toast } from 'sonner' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from 'ui' import Panel from '../Panel' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { TextConfirmModal } from '@/components/ui/TextConfirmModalWrapper' import { useToggleLegacyAPIKeysMutation } from '@/data/api-keys/legacy-api-key-toggle-mutation' import { useLegacyAPIKeysStatusQuery } from '@/data/api-keys/legacy-api-keys-status-query' import { useLegacyJWTSigningKeyQuery } from '@/data/jwt-signing-keys/legacy-jwt-signing-key-query' import { useAuthorizedAppsQuery } from '@/data/oauth/authorized-apps-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' export const ToggleLegacyApiKeysPanel = () => { const { ref: projectRef } = useParams() const { data: org } = useSelectedOrganizationQuery() const [isConfirmOpen, setIsConfirmOpen] = useState(false) const [isAppsWarningOpen, setIsAppsWarningOpen] = useState(false) const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*') const { can: canUpdateAPIKeys, isSuccess: isPermissionsSuccess } = useAsyncCheckPermissions( PermissionAction.SECRETS_WRITE, '*' ) const { data: legacyAPIKeysStatusData, isSuccess: isLegacyAPIKeysStatusSuccess } = useLegacyAPIKeysStatusQuery({ projectRef }, { enabled: canReadAPIKeys }) const { data: legacyJWTSecret } = useLegacyJWTSigningKeyQuery( { projectRef }, { enabled: canReadAPIKeys } ) const { data: authorizedApps = [], isError: isAuthorizedAppsError } = useAuthorizedAppsQuery({ slug: org?.slug, }) const { enabled: isLegacyKeysEnabled } = legacyAPIKeysStatusData || {} const oauthAppsLink = ( OAuth apps ) const appsWarning = isAuthorizedAppsError ? { title: 'Check your OAuth apps before continuing', description: ( <> Disabling legacy API keys can break apps that integrate with Briven. Before continuing, check your organization's {oauthAppsLink} to ensure none of them depend on the legacy API keys. ), } : { title: 'Apps using Briven may break', description: ( <> Your project uses apps that integrate with Briven. Disabling the legacy API keys is a brand new feature and the apps you're using may not have added support for this yet. It can cause them to stop functioning. Check your {oauthAppsLink} before continuing. ), } if (!(isLegacyAPIKeysStatusSuccess && isPermissionsSuccess)) { return null } return (

{isLegacyKeysEnabled ? 'Disable legacy API keys' : 'Re-enabling legacy API keys'}

{isLegacyKeysEnabled ? 'Make sure you are no longer using your legacy API keys before proceeding.' : 'We recommend you use the new API keys whenever possible, but re-enabling is an option.'}

setIsAppsWarningOpen(true) : () => setIsConfirmOpen(true) } disabled={ !canUpdateAPIKeys || (!isLegacyKeysEnabled && legacyJWTSecret?.status === 'revoked') } tooltip={{ content: { side: 'bottom', text: !canUpdateAPIKeys ? 'You need additional permissions to enable or disable JWT-based API keys' : !isLegacyKeysEnabled && legacyJWTSecret?.status === 'revoked' ? 'The legacy JWT secret is revoked. Re-enabling is not possible until it is at least moved to previously used.' : undefined, }, }} > {legacyAPIKeysStatusData.enabled ? 'Disable JWT-based API keys' : 'Re-enable JWT-based API keys'}
setIsConfirmOpen(false)} legacyAPIKeysStatusData={legacyAPIKeysStatusData} /> setIsAppsWarningOpen(value)}> {appsWarning.title} {appsWarning.description} Cancel setIsConfirmOpen(true)}> Disable API keys
) } const ToggleApiKeysModal = ({ visible, onClose, legacyAPIKeysStatusData, }: { visible: boolean onClose: () => void legacyAPIKeysStatusData: { enabled: boolean } }) => { const { ref: projectRef } = useParams() const { enabled: isLegacyKeysEnabled } = legacyAPIKeysStatusData || {} const { mutate: toggleLegacyAPIKey, isPending: isTogglingLegacyAPIKey } = useToggleLegacyAPIKeysMutation() const onToggleLegacyAPIKeysEnabled = () => { const enabled = !legacyAPIKeysStatusData?.enabled toggleLegacyAPIKey( { projectRef, enabled }, { onSuccess: () => { toast.success( enabled ? 'Your anon and service_role keys have been re-enabled!' : 'Your anon and service_role keys have been disabled!' ) onClose() }, } ) } return ( onClose()} onConfirm={onToggleLegacyAPIKeysEnabled} title={isLegacyKeysEnabled ? 'Disable JWT-based keys' : 'Re-enable JWT-based keys'} confirmString={isLegacyKeysEnabled ? 'disable' : 're-enable'} confirmLabel={`Confirm to ${isLegacyKeysEnabled ? 'disable' : 're-enable'} anon and service_role`} confirmPlaceholder={isLegacyKeysEnabled ? 'disable' : 're-enable'} loading={isTogglingLegacyAPIKey} variant={isLegacyKeysEnabled ? 'destructive' : 'default'} alert={ isLegacyKeysEnabled ? { title: 'Ensure legacy keys are no longer in use before disabling', description: ( Disabling anon and service_role keys while they are in use will cause downtime for your application. Ensure they are no longer in use before proceeding. If you have not created a publishable and at least one secret API key, some dashboard functionality may become unavailable.

This disables API keys when used in the apikey header. They remain valid as a JWT.
), } : { title: 'Publishable and secret keys are preferred', description: ( Re-enabling anon and service_role keys may be appropriate in certain cases, but using a publishable and secret key is more secure. We recommend against re-enabling legacy API keys. ), } } /> ) }