RateLimits.tsx 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. import { zodResolver } from '@hookform/resolvers/zod'
  2. import { PermissionAction } from '@supabase/shared-types/out/constants'
  3. import { useParams } from 'common'
  4. import Link from 'next/link'
  5. import { useEffect } from 'react'
  6. import { useForm } from 'react-hook-form'
  7. import { toast } from 'sonner'
  8. import {
  9. Button,
  10. Card,
  11. CardContent,
  12. CardFooter,
  13. Form,
  14. FormControl,
  15. FormField,
  16. FormInputGroupInput,
  17. InputGroup,
  18. InputGroupAddon,
  19. InputGroupText,
  20. Switch,
  21. Tooltip,
  22. TooltipContent,
  23. TooltipTrigger,
  24. } from 'ui'
  25. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  26. import {
  27. PageSection,
  28. PageSectionContent,
  29. PageSectionDescription,
  30. PageSectionMeta,
  31. PageSectionSummary,
  32. PageSectionTitle,
  33. } from 'ui-patterns/PageSection'
  34. import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
  35. import * as z from 'zod'
  36. import { isSmtpEnabled } from '../SmtpForm/SmtpForm.utils'
  37. import AlertError from '@/components/ui/AlertError'
  38. import { InlineLink } from '@/components/ui/InlineLink'
  39. import NoPermission from '@/components/ui/NoPermission'
  40. import { useAuthConfigQuery } from '@/data/auth/auth-config-query'
  41. import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation'
  42. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  43. import { DOCS_URL } from '@/lib/constants'
  44. export const RateLimits = () => {
  45. const { ref: projectRef } = useParams()
  46. const { can: canUpdateConfig } = useAsyncCheckPermissions(
  47. PermissionAction.UPDATE,
  48. 'custom_config_gotrue'
  49. )
  50. const { can: canReadConfig } = useAsyncCheckPermissions(
  51. PermissionAction.READ,
  52. 'custom_config_gotrue'
  53. )
  54. const {
  55. data: authConfig,
  56. error,
  57. isPending: isLoading,
  58. isError,
  59. isSuccess,
  60. } = useAuthConfigQuery({ projectRef })
  61. const { mutate: updateAuthConfig, isPending: isUpdatingConfig } = useAuthConfigUpdateMutation({
  62. onSuccess: () => {
  63. toast.success('Rate limits successfully updated')
  64. },
  65. onError: (error) => {
  66. toast.error(`Failed to update rate limits: ${error.message}`)
  67. },
  68. })
  69. const canUpdateEmailLimit = authConfig?.EXTERNAL_EMAIL_ENABLED && isSmtpEnabled(authConfig)
  70. const canUpdateSMSRateLimit = authConfig?.EXTERNAL_PHONE_ENABLED
  71. const canUpdateAnonymousUsersRateLimit = authConfig?.EXTERNAL_ANONYMOUS_USERS_ENABLED
  72. const canUpdateWeb3RateLimit = authConfig?.EXTERNAL_WEB3_SOLANA_ENABLED
  73. const IPForwardingFormSchema = z.object({
  74. SECURITY_SB_FORWARDED_FOR_ENABLED: z.coerce.boolean(),
  75. })
  76. const ipForwardingForm = useForm<z.infer<typeof IPForwardingFormSchema>>({
  77. resolver: zodResolver(IPForwardingFormSchema as any),
  78. defaultValues: {
  79. SECURITY_SB_FORWARDED_FOR_ENABLED: false,
  80. },
  81. })
  82. const onSubmitIPForwardingForm = (data: z.infer<typeof IPForwardingFormSchema>) => {
  83. if (!projectRef) return console.error('Project ref is required')
  84. const payload: Partial<z.infer<typeof IPForwardingFormSchema>> = {}
  85. if (data.SECURITY_SB_FORWARDED_FOR_ENABLED !== authConfig?.SECURITY_SB_FORWARDED_FOR_ENABLED) {
  86. payload.SECURITY_SB_FORWARDED_FOR_ENABLED = data.SECURITY_SB_FORWARDED_FOR_ENABLED
  87. }
  88. updateAuthConfig(
  89. { projectRef, config: payload },
  90. { onSuccess: () => ipForwardingForm.reset(data) }
  91. )
  92. }
  93. const RateLimitFormSchema = z.object({
  94. RATE_LIMIT_TOKEN_REFRESH: z.coerce
  95. .number()
  96. .min(0, 'Must be not be lower than 0')
  97. .max(32767, 'Must not be more than 32,767 an 5 minutes'),
  98. RATE_LIMIT_VERIFY: z.coerce
  99. .number()
  100. .min(0, 'Must be not be lower than 0')
  101. .max(32767, 'Must not be more than 32,767 an 5 minutes'),
  102. RATE_LIMIT_EMAIL_SENT: z.coerce
  103. .number()
  104. .min(0, 'Must be not be lower than 0')
  105. .max(32767, 'Must not be more than 32,767 an hour'),
  106. RATE_LIMIT_SMS_SENT: z.coerce
  107. .number()
  108. .min(0, 'Must be not be lower than 0')
  109. .max(32767, 'Must not be more than 32,767 an hour'),
  110. RATE_LIMIT_ANONYMOUS_USERS: z.coerce
  111. .number()
  112. .min(0, 'Must be not be lower than 0')
  113. .max(32767, 'Must not be more than 32,767 an hour'),
  114. RATE_LIMIT_OTP: z.coerce
  115. .number()
  116. .min(0, 'Must be not be lower than 0')
  117. .max(32767, 'Must not be more than 32,767 an hour'),
  118. RATE_LIMIT_WEB3: z.coerce
  119. .number()
  120. .min(0, 'Must be not be lower than 0')
  121. .max(32767, 'Must not be more than 32,767 an hour'),
  122. })
  123. const rateLimitForm = useForm<z.infer<typeof RateLimitFormSchema>>({
  124. resolver: zodResolver(RateLimitFormSchema as any),
  125. defaultValues: {
  126. RATE_LIMIT_TOKEN_REFRESH: 0,
  127. RATE_LIMIT_VERIFY: 0,
  128. RATE_LIMIT_EMAIL_SENT: 0,
  129. RATE_LIMIT_SMS_SENT: 0,
  130. RATE_LIMIT_ANONYMOUS_USERS: 0,
  131. RATE_LIMIT_OTP: 0,
  132. RATE_LIMIT_WEB3: 0,
  133. },
  134. })
  135. const onSubmitRateLimitForm = (data: z.infer<typeof RateLimitFormSchema>) => {
  136. if (!projectRef) return console.error('Project ref is required')
  137. const payload: Partial<z.infer<typeof RateLimitFormSchema>> = {}
  138. const params = [
  139. 'RATE_LIMIT_TOKEN_REFRESH',
  140. 'RATE_LIMIT_VERIFY',
  141. 'RATE_LIMIT_EMAIL_SENT',
  142. 'RATE_LIMIT_SMS_SENT',
  143. 'RATE_LIMIT_ANONYMOUS_USERS',
  144. 'RATE_LIMIT_OTP',
  145. 'RATE_LIMIT_WEB3',
  146. ] as (keyof typeof payload)[]
  147. params.forEach((param) => {
  148. if (data[param] !== authConfig?.[param]) payload[param] = data[param]
  149. })
  150. updateAuthConfig(
  151. { projectRef, config: payload },
  152. { onSuccess: () => rateLimitForm.reset(data) }
  153. )
  154. }
  155. useEffect(() => {
  156. if (isSuccess) {
  157. rateLimitForm.reset({
  158. RATE_LIMIT_TOKEN_REFRESH: authConfig.RATE_LIMIT_TOKEN_REFRESH,
  159. RATE_LIMIT_VERIFY: authConfig.RATE_LIMIT_VERIFY,
  160. RATE_LIMIT_EMAIL_SENT: authConfig.RATE_LIMIT_EMAIL_SENT,
  161. RATE_LIMIT_SMS_SENT: authConfig.RATE_LIMIT_SMS_SENT,
  162. RATE_LIMIT_ANONYMOUS_USERS: authConfig.RATE_LIMIT_ANONYMOUS_USERS,
  163. RATE_LIMIT_OTP: authConfig.RATE_LIMIT_OTP,
  164. RATE_LIMIT_WEB3: authConfig.RATE_LIMIT_WEB3 ?? 0,
  165. })
  166. ipForwardingForm.reset({
  167. SECURITY_SB_FORWARDED_FOR_ENABLED: authConfig.SECURITY_SB_FORWARDED_FOR_ENABLED,
  168. })
  169. }
  170. // eslint-disable-next-line react-hooks/exhaustive-deps
  171. }, [isSuccess])
  172. if (isError) {
  173. return (
  174. <PageSection>
  175. <PageSectionContent>
  176. <AlertError error={error} subject="Failed to retrieve auth configuration" />
  177. </PageSectionContent>
  178. </PageSection>
  179. )
  180. }
  181. if (!canReadConfig) {
  182. return (
  183. <PageSection>
  184. <PageSectionContent>
  185. <NoPermission resourceText="view auth configuration settings" />
  186. </PageSectionContent>
  187. </PageSection>
  188. )
  189. }
  190. if (isLoading) {
  191. return (
  192. <PageSection>
  193. <PageSectionContent>
  194. <GenericSkeletonLoader />
  195. </PageSectionContent>
  196. </PageSection>
  197. )
  198. }
  199. return (
  200. <>
  201. <PageSection>
  202. <PageSectionContent>
  203. <Form {...rateLimitForm}>
  204. <form onSubmit={rateLimitForm.handleSubmit(onSubmitRateLimitForm)}>
  205. <Card>
  206. <CardContent>
  207. <FormField
  208. control={rateLimitForm.control}
  209. name="RATE_LIMIT_EMAIL_SENT"
  210. render={({ field }) => (
  211. <FormItemLayout
  212. layout="flex-row-reverse"
  213. label="Rate limit for sending emails"
  214. description="Number of emails that can be sent per hour from your project"
  215. >
  216. <Tooltip>
  217. <TooltipTrigger asChild>
  218. <FormControl>
  219. <InputGroup>
  220. <FormInputGroupInput
  221. type="number"
  222. min={0}
  223. {...field}
  224. disabled={!canUpdateConfig || !canUpdateEmailLimit}
  225. />
  226. <InputGroupAddon align="inline-end">
  227. <InputGroupText>emails/h</InputGroupText>
  228. </InputGroupAddon>
  229. </InputGroup>
  230. </FormControl>
  231. </TooltipTrigger>
  232. {!canUpdateConfig || !canUpdateEmailLimit ? (
  233. <TooltipContent side="left" className="w-80 p-4">
  234. {!authConfig.EXTERNAL_EMAIL_ENABLED ? (
  235. <>
  236. <p className="font-medium">
  237. Email-based logins are not enabled for your project
  238. </p>
  239. <p className="mt-1">
  240. Enable email-based logins to update this rate limit
  241. </p>
  242. <div className="mt-3">
  243. <Button asChild type="default" size="tiny">
  244. <Link href={`/project/${projectRef}/auth/providers`}>
  245. View auth providers
  246. </Link>
  247. </Button>
  248. </div>
  249. </>
  250. ) : (
  251. <>
  252. <p className="font-medium">
  253. Custom SMTP provider is required to update this configuration
  254. </p>
  255. <p className="mt-1">
  256. The built-in email service has a fixed rate limit. You will need
  257. to set up your own custom SMTP provider to update your email
  258. rate limit
  259. </p>
  260. <div className="mt-3">
  261. <Button asChild type="default" size="tiny">
  262. <Link href={`/project/${projectRef}/auth/smtp`}>
  263. View SMTP settings
  264. </Link>
  265. </Button>
  266. </div>
  267. </>
  268. )}
  269. </TooltipContent>
  270. ) : null}
  271. </Tooltip>
  272. </FormItemLayout>
  273. )}
  274. />
  275. </CardContent>
  276. <CardContent>
  277. <FormField
  278. control={rateLimitForm.control}
  279. name="RATE_LIMIT_SMS_SENT"
  280. render={({ field }) => (
  281. <FormItemLayout
  282. layout="flex-row-reverse"
  283. label="Rate limit for sending SMS messages"
  284. description="Number of SMS messages that can be sent per hour from your project"
  285. >
  286. <Tooltip>
  287. <TooltipTrigger asChild>
  288. <FormControl>
  289. <InputGroup>
  290. <FormInputGroupInput
  291. type="number"
  292. min={0}
  293. {...field}
  294. disabled={!canUpdateConfig || !canUpdateSMSRateLimit}
  295. />
  296. <InputGroupAddon align="inline-end">
  297. <InputGroupText>sms/h</InputGroupText>
  298. </InputGroupAddon>
  299. </InputGroup>
  300. </FormControl>
  301. </TooltipTrigger>
  302. {!canUpdateConfig || !canUpdateSMSRateLimit ? (
  303. <TooltipContent side="left" className="w-80 p-4">
  304. <p className="font-medium">
  305. Phone-based logins are not enabled for your project
  306. </p>
  307. <p className="mt-1">
  308. Enable phone-based logins to update this rate limit
  309. </p>
  310. <div className="mt-3">
  311. <Button asChild type="default" size="tiny">
  312. <Link href={`/project/${projectRef}/auth/providers`}>
  313. View auth providers
  314. </Link>
  315. </Button>
  316. </div>
  317. </TooltipContent>
  318. ) : null}
  319. </Tooltip>
  320. </FormItemLayout>
  321. )}
  322. />
  323. </CardContent>
  324. <CardContent>
  325. <FormField
  326. control={rateLimitForm.control}
  327. name="RATE_LIMIT_TOKEN_REFRESH"
  328. render={({ field }) => (
  329. <FormItemLayout
  330. layout="flex-row-reverse"
  331. label="Rate limit for token refreshes"
  332. description="Number of sessions that can be refreshed in a 5 minute interval per IP address"
  333. >
  334. <Tooltip>
  335. <TooltipTrigger asChild>
  336. <FormControl>
  337. <InputGroup>
  338. <FormInputGroupInput
  339. type="number"
  340. min={0}
  341. {...field}
  342. disabled={!canUpdateConfig}
  343. />
  344. <InputGroupAddon align="inline-end">
  345. <InputGroupText>requests/5 min</InputGroupText>
  346. </InputGroupAddon>
  347. </InputGroup>
  348. </FormControl>
  349. </TooltipTrigger>
  350. {!canUpdateConfig && (
  351. <TooltipContent side="left" className="w-80 p-4">
  352. <p className="font-medium">
  353. You don't have permission to update this setting
  354. </p>
  355. <p className="mt-1">
  356. You need additional permissions to update auth configuration
  357. settings
  358. </p>
  359. </TooltipContent>
  360. )}
  361. </Tooltip>
  362. {rateLimitForm.watch('RATE_LIMIT_TOKEN_REFRESH') > 0 && (
  363. <p className="text-foreground-lighter text-sm mt-2">
  364. {rateLimitForm.watch('RATE_LIMIT_TOKEN_REFRESH') * 12} requests per hour
  365. </p>
  366. )}
  367. </FormItemLayout>
  368. )}
  369. />
  370. </CardContent>
  371. <CardContent>
  372. <FormField
  373. control={rateLimitForm.control}
  374. name="RATE_LIMIT_VERIFY"
  375. render={({ field }) => (
  376. <FormItemLayout
  377. layout="flex-row-reverse"
  378. label="Rate limit for token verifications"
  379. description="Number of OTP/Magic link verifications that can be made in a 5 minute interval per IP address"
  380. >
  381. <Tooltip>
  382. <TooltipTrigger asChild>
  383. <FormControl>
  384. <InputGroup>
  385. <FormInputGroupInput
  386. type="number"
  387. min={0}
  388. {...field}
  389. disabled={!canUpdateConfig}
  390. />
  391. <InputGroupAddon align="inline-end">
  392. <InputGroupText>requests/5 min</InputGroupText>
  393. </InputGroupAddon>
  394. </InputGroup>
  395. </FormControl>
  396. </TooltipTrigger>
  397. {!canUpdateConfig && (
  398. <TooltipContent side="left" className="w-80 p-4">
  399. <p className="font-medium">
  400. You don't have permission to update this setting
  401. </p>
  402. <p className="mt-1">
  403. You need additional permissions to update auth configuration
  404. settings
  405. </p>
  406. </TooltipContent>
  407. )}
  408. </Tooltip>
  409. {rateLimitForm.watch('RATE_LIMIT_VERIFY') > 0 && (
  410. <p className="text-foreground-lighter text-sm mt-2">
  411. {rateLimitForm.watch('RATE_LIMIT_VERIFY') * 12} requests per hour
  412. </p>
  413. )}
  414. </FormItemLayout>
  415. )}
  416. />
  417. </CardContent>
  418. <CardContent>
  419. <FormField
  420. control={rateLimitForm.control}
  421. name="RATE_LIMIT_ANONYMOUS_USERS"
  422. render={({ field }) => (
  423. <FormItemLayout
  424. layout="flex-row-reverse"
  425. label="Rate limit for anonymous users"
  426. description="Number of anonymous sign-ins that can be made per hour per IP address"
  427. >
  428. <Tooltip>
  429. <TooltipTrigger asChild>
  430. <FormControl>
  431. <InputGroup>
  432. <FormInputGroupInput
  433. type="number"
  434. min={0}
  435. {...field}
  436. disabled={!canUpdateConfig || !canUpdateAnonymousUsersRateLimit}
  437. />
  438. <InputGroupAddon align="inline-end">
  439. <InputGroupText>requests/h</InputGroupText>
  440. </InputGroupAddon>
  441. </InputGroup>
  442. </FormControl>
  443. </TooltipTrigger>
  444. {!canUpdateConfig || !canUpdateAnonymousUsersRateLimit ? (
  445. <TooltipContent side="left" className="w-80 p-4">
  446. <p className="font-medium">
  447. Anonymous sign-ins are not enabled for your project. Enable them to
  448. control this rate limit.
  449. </p>
  450. <div className="mt-3">
  451. <Button asChild type="default" size="tiny">
  452. <Link href={`/project/${projectRef}/auth/providers`}>
  453. View auth settings
  454. </Link>
  455. </Button>
  456. </div>
  457. </TooltipContent>
  458. ) : null}
  459. </Tooltip>
  460. </FormItemLayout>
  461. )}
  462. />
  463. </CardContent>
  464. <CardContent>
  465. <FormField
  466. control={rateLimitForm.control}
  467. name="RATE_LIMIT_OTP"
  468. render={({ field }) => (
  469. <FormItemLayout
  470. layout="flex-row-reverse"
  471. label="Rate limit for sign-ups and sign-ins"
  472. description="Number of sign-up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users)"
  473. >
  474. <Tooltip>
  475. <TooltipTrigger asChild>
  476. <FormControl>
  477. <InputGroup>
  478. <FormInputGroupInput
  479. type="number"
  480. min={0}
  481. {...field}
  482. disabled={!canUpdateConfig}
  483. />
  484. <InputGroupAddon align="inline-end">
  485. <InputGroupText>requests/5 min</InputGroupText>
  486. </InputGroupAddon>
  487. </InputGroup>
  488. </FormControl>
  489. </TooltipTrigger>
  490. {!canUpdateConfig && (
  491. <TooltipContent side="left" className="w-80 p-4">
  492. <p className="font-medium">
  493. You don't have permission to update this setting
  494. </p>
  495. <p className="mt-1">
  496. You need additional permissions to update auth configuration
  497. settings
  498. </p>
  499. </TooltipContent>
  500. )}
  501. </Tooltip>
  502. {rateLimitForm.watch('RATE_LIMIT_OTP') > 0 && (
  503. <p className="text-foreground-lighter text-sm mt-2">
  504. {rateLimitForm.watch('RATE_LIMIT_OTP') * 12} requests per hour
  505. </p>
  506. )}
  507. </FormItemLayout>
  508. )}
  509. />
  510. </CardContent>
  511. <CardContent>
  512. <FormField
  513. control={rateLimitForm.control}
  514. name="RATE_LIMIT_WEB3"
  515. render={({ field }) => (
  516. <FormItemLayout
  517. layout="flex-row-reverse"
  518. label="Rate limit for Web3 sign-ups and sign-ins"
  519. description="Number of Web3 sign-up or sign-in requests that can be made per IP address in 5 minutes"
  520. >
  521. <Tooltip>
  522. <TooltipTrigger asChild>
  523. <FormControl>
  524. <InputGroup>
  525. <FormInputGroupInput
  526. type="number"
  527. min={0}
  528. {...field}
  529. disabled={!canUpdateConfig || !canUpdateWeb3RateLimit}
  530. />
  531. <InputGroupAddon align="inline-end">
  532. <InputGroupText>requests/5 min</InputGroupText>
  533. </InputGroupAddon>
  534. </InputGroup>
  535. </FormControl>
  536. </TooltipTrigger>
  537. {!canUpdateConfig || !canUpdateWeb3RateLimit ? (
  538. <TooltipContent side="left" className="w-80 p-4">
  539. <p className="font-medium">
  540. Web3 auth provider is not enabled for this project. Enable it to
  541. control this rate limit.
  542. </p>
  543. <div className="mt-3">
  544. <Button asChild type="default" size="tiny">
  545. <Link href={`/project/${projectRef}/auth/providers`}>
  546. View Auth provider settings
  547. </Link>
  548. </Button>
  549. </div>
  550. </TooltipContent>
  551. ) : null}
  552. </Tooltip>
  553. </FormItemLayout>
  554. )}
  555. />
  556. </CardContent>
  557. <CardFooter className="justify-end space-x-2">
  558. {rateLimitForm.formState.isDirty && (
  559. <Button type="default" onClick={() => rateLimitForm.reset()}>
  560. Cancel
  561. </Button>
  562. )}
  563. <Button
  564. type="primary"
  565. htmlType="submit"
  566. disabled={
  567. !canUpdateConfig || isUpdatingConfig || !rateLimitForm.formState.isDirty
  568. }
  569. loading={isUpdatingConfig}
  570. >
  571. Save changes
  572. </Button>
  573. </CardFooter>
  574. </Card>
  575. </form>
  576. </Form>
  577. </PageSectionContent>
  578. </PageSection>
  579. <PageSection>
  580. <PageSectionMeta>
  581. <PageSectionSummary>
  582. <PageSectionTitle>IP Address Forwarding</PageSectionTitle>
  583. <PageSectionDescription>
  584. Control how Auth determines source IP address for rate limiting.
  585. </PageSectionDescription>
  586. </PageSectionSummary>
  587. </PageSectionMeta>
  588. <PageSectionContent>
  589. <Form {...ipForwardingForm}>
  590. <form onSubmit={ipForwardingForm.handleSubmit(onSubmitIPForwardingForm)}>
  591. <Card>
  592. <CardContent>
  593. <FormField
  594. control={ipForwardingForm.control}
  595. name="SECURITY_SB_FORWARDED_FOR_ENABLED"
  596. render={({ field }) => (
  597. <FormItemLayout
  598. layout="flex-row-reverse"
  599. label="Enable IP address forwarding"
  600. description=<>
  601. Clients can forward end-user IP addresses to Auth for rate limiting when
  602. using secret API keys.{' '}
  603. <InlineLink
  604. href={`${DOCS_URL}/guides/auth/rate-limits#ip-address-forwarding`}
  605. >
  606. Learn more
  607. </InlineLink>
  608. </>
  609. >
  610. <FormControl>
  611. <Switch
  612. checked={field.value}
  613. onCheckedChange={(value) => field.onChange(value)}
  614. disabled={!canUpdateConfig}
  615. />
  616. </FormControl>
  617. </FormItemLayout>
  618. )}
  619. />
  620. </CardContent>
  621. <CardFooter className="justify-end space-x-2">
  622. {ipForwardingForm.formState.isDirty && (
  623. <Button type="default" onClick={() => ipForwardingForm.reset()}>
  624. Cancel
  625. </Button>
  626. )}
  627. <Button
  628. type="primary"
  629. htmlType="submit"
  630. disabled={
  631. !canUpdateConfig || isUpdatingConfig || !ipForwardingForm.formState.isDirty
  632. }
  633. loading={isUpdatingConfig}
  634. >
  635. Save changes
  636. </Button>
  637. </CardFooter>
  638. </Card>
  639. </form>
  640. </Form>
  641. </PageSectionContent>
  642. </PageSection>
  643. </>
  644. )
  645. }