| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- 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 = (
- <a
- href={`/dashboard/org/${org?.slug}/apps`}
- target="_blank"
- rel="noreferrer"
- className="underline"
- >
- OAuth apps
- </a>
- )
- 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 (
- <section>
- <Panel>
- <Panel.Content>
- <div className="flex justify-between">
- <div className="flex flex-col gap-2">
- <p className="text-sm">
- {isLegacyKeysEnabled ? 'Disable legacy API keys' : 'Re-enabling legacy API keys'}
- </p>
- <p className="text-foreground-light text-sm">
- {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.'}
- </p>
- </div>
- <div className="flex items-center">
- <ButtonTooltip
- type="default"
- onClick={
- isLegacyKeysEnabled && (authorizedApps?.length || isAuthorizedAppsError)
- ? () => 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'}
- </ButtonTooltip>
- </div>
- </div>
- </Panel.Content>
- </Panel>
- <ToggleApiKeysModal
- visible={isConfirmOpen}
- onClose={() => setIsConfirmOpen(false)}
- legacyAPIKeysStatusData={legacyAPIKeysStatusData}
- />
- <AlertDialog open={isAppsWarningOpen} onOpenChange={(value) => setIsAppsWarningOpen(value)}>
- <AlertDialogContent>
- <AlertDialogHeader>
- <AlertDialogTitle>{appsWarning.title}</AlertDialogTitle>
- <AlertDialogDescription>{appsWarning.description}</AlertDialogDescription>
- </AlertDialogHeader>
- <AlertDialogFooter>
- <AlertDialogCancel>Cancel</AlertDialogCancel>
- <AlertDialogAction variant="danger" onClick={() => setIsConfirmOpen(true)}>
- Disable API keys
- </AlertDialogAction>
- </AlertDialogFooter>
- </AlertDialogContent>
- </AlertDialog>
- </section>
- )
- }
- 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 (
- <TextConfirmModal
- size="medium"
- visible={visible}
- onCancel={() => 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: (
- <span className="prose text-sm">
- Disabling <code>anon</code> and <code>service_role</code> 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.
- <br />
- <br />
- <span className="text-danger">
- This disables API keys when used in the <code>apikey</code> header. They remain
- valid as a JWT.
- </span>
- </span>
- ),
- }
- : {
- title: 'Publishable and secret keys are preferred',
- description: (
- <span className="prose text-sm">
- Re-enabling <code>anon</code> and <code>service_role</code> 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.
- </span>
- ),
- }
- }
- />
- )
- }
|