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