| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- import { PermissionAction } from '@supabase/shared-types/out/constants'
- import { JwtSecretUpdateStatus } from '@supabase/shared-types/out/events'
- import { useParams } from 'common'
- import { Loader } from 'lucide-react'
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'ui'
- import { Admonition, GenericSkeletonLoader } from 'ui-patterns'
- import { Input } from 'ui-patterns/DataInputs/Input'
- import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
- import { ConnectionIcon } from '@/components/interfaces/Connect/ConnectionIcon'
- import { ConnectButton } from '@/components/interfaces/ConnectButton/ConnectButton'
- import { AlertError } from '@/components/ui/AlertError'
- import { InlineLink } from '@/components/ui/InlineLink'
- import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
- import { useLegacyAPIKeysStatusQuery } from '@/data/api-keys/legacy-api-keys-status-query'
- import { useJwtSecretUpdatingStatusQuery } from '@/data/config/jwt-secret-updating-status-query'
- import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
- import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
- export const APIKeys = () => {
- const { ref: projectRef } = useParams()
- const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const {
- data: settings,
- error: projectSettingsError,
- isError: isProjectSettingsError,
- isPending: isProjectSettingsLoading,
- } = useProjectSettingsV2Query({ projectRef })
- const { data: legacyAPIKeysStatusData, isPending: isLoadingAPIKeysStatus } =
- useLegacyAPIKeysStatusQuery({ projectRef }, { enabled: canReadAPIKeys })
- const {
- data: apiKeys,
- error: errorAPIKeys,
- isError: isErrorAPIKeys,
- isPending: isLoadingAPIKeys,
- } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
- const { anonKey, serviceKey, publishableKey, secretKey } = getKeys(apiKeys)
- const hasNewAPIKeys = !!publishableKey && !!secretKey
- const isLegacyKeysEnabled = legacyAPIKeysStatusData?.enabled ?? false
- const isApiKeysEmpty = !hasNewAPIKeys && !anonKey && !serviceKey
- const {
- data,
- error: jwtSecretUpdateError,
- isError: isJwtSecretUpdateStatusError,
- isPending: isJwtSecretUpdateStatusLoading,
- } = useJwtSecretUpdatingStatusQuery(
- { projectRef },
- { enabled: !isProjectSettingsLoading && isApiKeysEmpty }
- )
- const isLoading = isLoadingAPIKeys || isLoadingAPIKeysStatus || isProjectSettingsLoading
- const isError = isErrorAPIKeys || isProjectSettingsError || isJwtSecretUpdateStatusError
- // Only show JWT loading state if the query is actually enabled
- const showJwtLoading =
- isJwtSecretUpdateStatusLoading && !isProjectSettingsLoading && isApiKeysEmpty
- const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus
- const isNotUpdatingJwtSecret =
- jwtSecretUpdateStatus === undefined || jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updated
- const protocol = settings?.app_config?.protocol ?? 'https'
- const endpoint = settings?.app_config?.endpoint
- const apiUrl = `${protocol}://${endpoint ?? '-'}`
- return (
- <Card>
- <CardHeader>
- <CardTitle className="text-sm font-medium">Project API</CardTitle>
- <CardDescription>
- Your API is secured behind an API gateway which requires an API Key for every request.
- <br />
- You can use the parameters below to use Briven client libraries.
- </CardDescription>
- </CardHeader>
- {isLoading ? (
- <CardContent>
- <GenericSkeletonLoader />
- </CardContent>
- ) : isError ? (
- <AlertError
- className="rounded-none border-0"
- subject={
- isErrorAPIKeys
- ? 'Failed to retrieve API Keys'
- : isProjectSettingsError
- ? 'Failed to retrieve project settings'
- : isJwtSecretUpdateStatusError
- ? 'Failed to update JWT secret'
- : ''
- }
- error={errorAPIKeys ?? projectSettingsError ?? jwtSecretUpdateError}
- />
- ) : showJwtLoading ? (
- <CardContent>
- <div className="flex items-center justify-center py-4 space-x-2">
- <Loader className="animate-spin" size={16} strokeWidth={1.5} />
- <p className="text-sm text-foreground-light">JWT secret is being updated</p>
- </div>
- </CardContent>
- ) : !isLegacyKeysEnabled && !hasNewAPIKeys ? (
- <Admonition
- type="default"
- className="border-0 rounded-none"
- title="Project has no API keys created yet"
- description={
- <>
- Create a set of API keys from your{' '}
- <InlineLink href={`/project/${projectRef}/settings/api-keys`}>
- project settings
- </InlineLink>{' '}
- to connect to your project
- </>
- }
- />
- ) : (
- <>
- <CardContent>
- <FormItemLayout
- isReactForm={false}
- layout="horizontal"
- label="Project URL"
- description="A RESTful endpoint for querying and managing your database."
- >
- <Input readOnly copy className="input-mono" value={apiUrl} />
- </FormItemLayout>
- </CardContent>
- <CardContent>
- <FormItemLayout
- isReactForm={false}
- layout="horizontal"
- label={
- hasNewAPIKeys ? (
- 'Publishable API Key'
- ) : (
- <div className="space-y-2">
- <p className="text-sm">API Key</p>
- <div className="flex items-center space-x-1 -ml-1">
- <code className="text-code-inline">{anonKey?.name}</code>
- <code className="text-code-inline">public</code>
- </div>
- </div>
- )
- }
- description={
- <p>
- This key is safe to use in a browser if you have enabled Row Level Security (RLS)
- for your tables and configured policies. You may also use the{' '}
- {hasNewAPIKeys ? 'secret' : 'service'} key which can be found{' '}
- <InlineLink
- href={`/project/${projectRef}/settings/api-keys${!hasNewAPIKeys ? '/legacy' : ''}`}
- >
- here
- </InlineLink>{' '}
- to bypass RLS.
- </p>
- }
- >
- <Input
- readOnly
- className="input-mono"
- copy={canReadAPIKeys && isNotUpdatingJwtSecret}
- reveal={anonKey?.name !== 'anon' && canReadAPIKeys && isNotUpdatingJwtSecret}
- value={
- !canReadAPIKeys
- ? 'You need additional permissions to view API keys'
- : jwtSecretUpdateStatus === JwtSecretUpdateStatus.Failed
- ? 'JWT secret update failed, new API key may have issues'
- : jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updating
- ? 'Updating JWT secret...'
- : (publishableKey?.api_key ?? anonKey?.api_key)
- }
- />
- </FormItemLayout>
- </CardContent>
- <CardContent className="relative overflow-hidden">
- <div
- className="absolute inset-0 rounded-md -mt-px"
- style={{
- backgroundImage: `
- linear-gradient(to top, hsl(var(--background-surface-100)/1) 0%, hsl(var(--background-surface-100)/1) 30%, hsl(var(--background-surface-75)/0) 100%),
- linear-gradient(to right, hsl(var(--border-default)/0.33) 1px, transparent 1px),
- linear-gradient(to bottom, hsl(var(--border-default)/0.33) 1px, transparent 1px)
- `,
- backgroundSize: '100% 100%, 15px 15px, 15px 15px',
- backgroundPosition: '0 0, 0 0, 0 0',
- }}
- />
- <div className="relative mt-6 mb-3">
- <div className="flex gap-x-3.5 relative ml-0.5 mb-4 opacity-80">
- <ConnectionIcon icon="nextjs" size={26} />
- <ConnectionIcon icon="react" size={26} />
- <ConnectionIcon icon="svelte" size={22} />
- <ConnectionIcon icon="flutter" size={23} />
- <ConnectionIcon icon="prisma" size={22} />
- </div>
- <p className="mb-1">Choose your preferred framework</p>
- <p className="text-sm text-foreground-light mb-4 md:mr-20 text-balance">
- Connect to your project from a variety of frameworks, ORMs, an MCP server, or even
- directly via connection string.
- </p>
- <ConnectButton />
- </div>
- </CardContent>
- </>
- )}
- </Card>
- )
- }
|