CreateSecretAPIKeyDialog.tsx170 lines · main
1import { zodResolver } from '@hookform/resolvers/zod'
2import { useParams } from 'common'
3import { Plus, ShieldCheck } from 'lucide-react'
4import { parseAsString, useQueryState } from 'nuqs'
5import { useForm, type SubmitHandler } from 'react-hook-form'
6import { toast } from 'sonner'
7import {
8 Alert,
9 AlertDescription,
10 AlertTitle,
11 Button,
12 Dialog,
13 DialogContent,
14 DialogDescription,
15 DialogFooter,
16 DialogHeader,
17 DialogSection,
18 DialogSectionSeparator,
19 DialogTitle,
20 DialogTrigger,
21 Form,
22 FormControl,
23 FormField,
24 Input,
25} from 'ui'
26import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
27import * as z from 'zod'
28
29import { useAPIKeyCreateMutation } from '@/data/api-keys/api-key-create-mutation'
30
31const NAME_SCHEMA = z
32 .string()
33 .min(4, 'Name must be at least 4 characters')
34 .max(64, "Name can't be more than 64 characters long")
35 .regex(/^[a-z0-9_]+$/, 'Name can only contain lowercased letters, digits and underscore')
36 .refine((val: string) => !val.match(/^[0-9].+$/), 'Name must not start with a digit')
37 .refine(
38 (val: string) => val !== 'anon' && val !== 'service_role',
39 'Using "anon" or "service_role" for API key name is not possible'
40 )
41
42const FORM_ID = 'create-secret-api-key'
43const SCHEMA = z.object({
44 name: NAME_SCHEMA,
45 description: z.string().max(256, "Description shouldn't be too long").trim(),
46})
47
48export const CreateSecretAPIKeyDialog = () => {
49 const { ref: projectRef } = useParams()
50 const [visible, setVisible] = useQueryState('new', parseAsString)
51
52 const onOpenChange = (value: boolean) => {
53 if (value) setVisible('secret')
54 else setVisible('')
55 }
56
57 const defaultValues = { name: '', description: '' }
58 const form = useForm<z.infer<typeof SCHEMA>>({
59 resolver: zodResolver(SCHEMA as any),
60 defaultValues,
61 })
62
63 const { mutate: createAPIKey, isPending: isCreatingAPIKey } = useAPIKeyCreateMutation()
64
65 const onSubmit: SubmitHandler<z.infer<typeof SCHEMA>> = async (values) => {
66 createAPIKey(
67 {
68 projectRef,
69 type: 'secret',
70 name: values.name,
71 description: values.description,
72 },
73 {
74 onSuccess: (data) => {
75 toast.success(`Your secret API key ${data.prefix}... is ready.`)
76 form.reset(defaultValues)
77 onOpenChange(false)
78 },
79 }
80 )
81 }
82
83 return (
84 <Dialog open={visible === 'secret'} onOpenChange={onOpenChange}>
85 <DialogTrigger asChild>
86 <Button type="default" className="mt-2" icon={<Plus />}>
87 New secret key
88 </Button>
89 </DialogTrigger>
90 <DialogContent>
91 <DialogHeader>
92 <DialogTitle>Create new secret API key</DialogTitle>
93 <DialogDescription className="grid gap-y-2">
94 <p>
95 Secret API keys allow elevated access to your project's data, bypassing Row-Level
96 security.
97 </p>
98 </DialogDescription>
99 </DialogHeader>
100 <DialogSectionSeparator />
101 <DialogSection className="flex flex-col gap-4">
102 <Form {...form}>
103 <form
104 className="flex flex-col gap-4"
105 id={FORM_ID}
106 onSubmit={form.handleSubmit(onSubmit)}
107 >
108 <FormField
109 key="name"
110 name="name"
111 control={form.control}
112 render={({ field }) => (
113 <FormItemLayout
114 label="Name"
115 description="A short, unique name of lowercased letters, digits and underscore"
116 >
117 <FormControl>
118 <Input {...field} placeholder="Example: my_super_secret_key_123" />
119 </FormControl>
120 </FormItemLayout>
121 )}
122 />
123 <FormField
124 key="description"
125 name="description"
126 control={form.control}
127 render={({ field }) => (
128 <FormItemLayout label="Description" labelOptional="Optional">
129 <FormControl>
130 <Input
131 {...field}
132 placeholder="Short notes on how or where this key will be used"
133 />
134 </FormControl>
135 </FormItemLayout>
136 )}
137 />
138 </form>
139 </Form>
140 <Alert variant="warning">
141 <ShieldCheck />
142 <AlertTitle>Securing your API key</AlertTitle>
143 <AlertDescription className="">
144 <ul className="list-disc">
145 <li>Keep this key secret.</li>
146 <li>Do not use on the web, in mobile or desktop apps.</li>
147 <li>Don't post it publicly or commit in source control.</li>
148 <li>
149 This key provides elevated access to your data, bypassing Row-Level Security.
150 </li>
151 <li>
152 If it leaks or is revealed, swap it with a new secret API key and then delete it.
153 </li>
154 <li>
155 If used in a browser, it will always return HTTP 401 Unauthorized. Delete
156 immediately.
157 </li>
158 </ul>
159 </AlertDescription>
160 </Alert>
161 </DialogSection>
162 <DialogFooter>
163 <Button form={FORM_ID} htmlType="submit" loading={isCreatingAPIKey}>
164 Create API key
165 </Button>
166 </DialogFooter>
167 </DialogContent>
168 </Dialog>
169 )
170}