APIKeys.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import { PermissionAction } from '@supabase/shared-types/out/constants'
  2. import { JwtSecretUpdateStatus } from '@supabase/shared-types/out/events'
  3. import { useParams } from 'common'
  4. import { Loader } from 'lucide-react'
  5. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'ui'
  6. import { Admonition, GenericSkeletonLoader } from 'ui-patterns'
  7. import { Input } from 'ui-patterns/DataInputs/Input'
  8. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  9. import { ConnectionIcon } from '@/components/interfaces/Connect/ConnectionIcon'
  10. import { ConnectButton } from '@/components/interfaces/ConnectButton/ConnectButton'
  11. import { AlertError } from '@/components/ui/AlertError'
  12. import { InlineLink } from '@/components/ui/InlineLink'
  13. import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
  14. import { useLegacyAPIKeysStatusQuery } from '@/data/api-keys/legacy-api-keys-status-query'
  15. import { useJwtSecretUpdatingStatusQuery } from '@/data/config/jwt-secret-updating-status-query'
  16. import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
  17. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  18. export const APIKeys = () => {
  19. const { ref: projectRef } = useParams()
  20. const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
  21. const {
  22. data: settings,
  23. error: projectSettingsError,
  24. isError: isProjectSettingsError,
  25. isPending: isProjectSettingsLoading,
  26. } = useProjectSettingsV2Query({ projectRef })
  27. const { data: legacyAPIKeysStatusData, isPending: isLoadingAPIKeysStatus } =
  28. useLegacyAPIKeysStatusQuery({ projectRef }, { enabled: canReadAPIKeys })
  29. const {
  30. data: apiKeys,
  31. error: errorAPIKeys,
  32. isError: isErrorAPIKeys,
  33. isPending: isLoadingAPIKeys,
  34. } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
  35. const { anonKey, serviceKey, publishableKey, secretKey } = getKeys(apiKeys)
  36. const hasNewAPIKeys = !!publishableKey && !!secretKey
  37. const isLegacyKeysEnabled = legacyAPIKeysStatusData?.enabled ?? false
  38. const isApiKeysEmpty = !hasNewAPIKeys && !anonKey && !serviceKey
  39. const {
  40. data,
  41. error: jwtSecretUpdateError,
  42. isError: isJwtSecretUpdateStatusError,
  43. isPending: isJwtSecretUpdateStatusLoading,
  44. } = useJwtSecretUpdatingStatusQuery(
  45. { projectRef },
  46. { enabled: !isProjectSettingsLoading && isApiKeysEmpty }
  47. )
  48. const isLoading = isLoadingAPIKeys || isLoadingAPIKeysStatus || isProjectSettingsLoading
  49. const isError = isErrorAPIKeys || isProjectSettingsError || isJwtSecretUpdateStatusError
  50. // Only show JWT loading state if the query is actually enabled
  51. const showJwtLoading =
  52. isJwtSecretUpdateStatusLoading && !isProjectSettingsLoading && isApiKeysEmpty
  53. const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus
  54. const isNotUpdatingJwtSecret =
  55. jwtSecretUpdateStatus === undefined || jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updated
  56. const protocol = settings?.app_config?.protocol ?? 'https'
  57. const endpoint = settings?.app_config?.endpoint
  58. const apiUrl = `${protocol}://${endpoint ?? '-'}`
  59. return (
  60. <Card>
  61. <CardHeader>
  62. <CardTitle className="text-sm font-medium">Project API</CardTitle>
  63. <CardDescription>
  64. Your API is secured behind an API gateway which requires an API Key for every request.
  65. <br />
  66. You can use the parameters below to use Briven client libraries.
  67. </CardDescription>
  68. </CardHeader>
  69. {isLoading ? (
  70. <CardContent>
  71. <GenericSkeletonLoader />
  72. </CardContent>
  73. ) : isError ? (
  74. <AlertError
  75. className="rounded-none border-0"
  76. subject={
  77. isErrorAPIKeys
  78. ? 'Failed to retrieve API Keys'
  79. : isProjectSettingsError
  80. ? 'Failed to retrieve project settings'
  81. : isJwtSecretUpdateStatusError
  82. ? 'Failed to update JWT secret'
  83. : ''
  84. }
  85. error={errorAPIKeys ?? projectSettingsError ?? jwtSecretUpdateError}
  86. />
  87. ) : showJwtLoading ? (
  88. <CardContent>
  89. <div className="flex items-center justify-center py-4 space-x-2">
  90. <Loader className="animate-spin" size={16} strokeWidth={1.5} />
  91. <p className="text-sm text-foreground-light">JWT secret is being updated</p>
  92. </div>
  93. </CardContent>
  94. ) : !isLegacyKeysEnabled && !hasNewAPIKeys ? (
  95. <Admonition
  96. type="default"
  97. className="border-0 rounded-none"
  98. title="Project has no API keys created yet"
  99. description={
  100. <>
  101. Create a set of API keys from your{' '}
  102. <InlineLink href={`/project/${projectRef}/settings/api-keys`}>
  103. project settings
  104. </InlineLink>{' '}
  105. to connect to your project
  106. </>
  107. }
  108. />
  109. ) : (
  110. <>
  111. <CardContent>
  112. <FormItemLayout
  113. isReactForm={false}
  114. layout="horizontal"
  115. label="Project URL"
  116. description="A RESTful endpoint for querying and managing your database."
  117. >
  118. <Input readOnly copy className="input-mono" value={apiUrl} />
  119. </FormItemLayout>
  120. </CardContent>
  121. <CardContent>
  122. <FormItemLayout
  123. isReactForm={false}
  124. layout="horizontal"
  125. label={
  126. hasNewAPIKeys ? (
  127. 'Publishable API Key'
  128. ) : (
  129. <div className="space-y-2">
  130. <p className="text-sm">API Key</p>
  131. <div className="flex items-center space-x-1 -ml-1">
  132. <code className="text-code-inline">{anonKey?.name}</code>
  133. <code className="text-code-inline">public</code>
  134. </div>
  135. </div>
  136. )
  137. }
  138. description={
  139. <p>
  140. This key is safe to use in a browser if you have enabled Row Level Security (RLS)
  141. for your tables and configured policies. You may also use the{' '}
  142. {hasNewAPIKeys ? 'secret' : 'service'} key which can be found{' '}
  143. <InlineLink
  144. href={`/project/${projectRef}/settings/api-keys${!hasNewAPIKeys ? '/legacy' : ''}`}
  145. >
  146. here
  147. </InlineLink>{' '}
  148. to bypass RLS.
  149. </p>
  150. }
  151. >
  152. <Input
  153. readOnly
  154. className="input-mono"
  155. copy={canReadAPIKeys && isNotUpdatingJwtSecret}
  156. reveal={anonKey?.name !== 'anon' && canReadAPIKeys && isNotUpdatingJwtSecret}
  157. value={
  158. !canReadAPIKeys
  159. ? 'You need additional permissions to view API keys'
  160. : jwtSecretUpdateStatus === JwtSecretUpdateStatus.Failed
  161. ? 'JWT secret update failed, new API key may have issues'
  162. : jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updating
  163. ? 'Updating JWT secret...'
  164. : (publishableKey?.api_key ?? anonKey?.api_key)
  165. }
  166. />
  167. </FormItemLayout>
  168. </CardContent>
  169. <CardContent className="relative overflow-hidden">
  170. <div
  171. className="absolute inset-0 rounded-md -mt-px"
  172. style={{
  173. backgroundImage: `
  174. 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%),
  175. linear-gradient(to right, hsl(var(--border-default)/0.33) 1px, transparent 1px),
  176. linear-gradient(to bottom, hsl(var(--border-default)/0.33) 1px, transparent 1px)
  177. `,
  178. backgroundSize: '100% 100%, 15px 15px, 15px 15px',
  179. backgroundPosition: '0 0, 0 0, 0 0',
  180. }}
  181. />
  182. <div className="relative mt-6 mb-3">
  183. <div className="flex gap-x-3.5 relative ml-0.5 mb-4 opacity-80">
  184. <ConnectionIcon icon="nextjs" size={26} />
  185. <ConnectionIcon icon="react" size={26} />
  186. <ConnectionIcon icon="svelte" size={22} />
  187. <ConnectionIcon icon="flutter" size={23} />
  188. <ConnectionIcon icon="prisma" size={22} />
  189. </div>
  190. <p className="mb-1">Choose your preferred framework</p>
  191. <p className="text-sm text-foreground-light mb-4 md:mr-20 text-balance">
  192. Connect to your project from a variety of frameworks, ORMs, an MCP server, or even
  193. directly via connection string.
  194. </p>
  195. <ConnectButton />
  196. </div>
  197. </CardContent>
  198. </>
  199. )}
  200. </Card>
  201. )
  202. }