Merge pull request #14090 from overleaf/bg-best-reduce-rate-limiter-on-confirmation-email-resend

reduce rate limiter on confirmation emails

GitOrigin-RevId: 87743dd9dac483a68ff82f1185ae1156d60b0575
This commit is contained in:
Brian Gough 2023-08-30 09:36:51 +01:00 committed by Copybot
parent 462b7a2256
commit bf04275478
5 changed files with 56 additions and 12 deletions

View file

@ -187,7 +187,7 @@ const rateLimiters = {
duration: 60,
}),
resendConfirmation: new RateLimiter('resend-confirmation', {
points: 10,
points: 1,
duration: 60,
}),
sendChatMessage: new RateLimiter('send-chat-message', {

View file

@ -4,7 +4,10 @@ import { Button } from 'react-bootstrap'
import Icon from '../../../../../../shared/components/icon'
import getMeta from '../../../../../../utils/meta'
import useAsync from '../../../../../../shared/hooks/use-async'
import { postJSON } from '../../../../../../infrastructure/fetch-json'
import {
FetchError,
postJSON,
} from '../../../../../../infrastructure/fetch-json'
import { UserEmailData } from '../../../../../../../../types/user-email'
import { ExposedSettings } from '../../../../../../../../types/exposed-settings'
import { Institution } from '../../../../../../../../types/institution'
@ -21,7 +24,7 @@ function ReconfirmAffiliation({
}: ReconfirmAffiliationProps) {
const { t } = useTranslation()
const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
const { isLoading, isError, isSuccess, runAsync } = useAsync()
const { error, isLoading, isError, isSuccess, runAsync } = useAsync()
const [hasSent, setHasSent] = useState(false)
const [isPending, setIsPending] = useState(false)
const ssoEnabled = institution.ssoEnabled
@ -48,6 +51,9 @@ function ReconfirmAffiliation({
}
}
const rateLimited =
error && error instanceof FetchError && error.response?.status === 429
if (hasSent) {
return (
<div className="w-100">
@ -73,7 +79,11 @@ function ReconfirmAffiliation({
{isError && (
<>
<br />
<div>{t('generic_something_went_wrong')}</div>
<div>
{rateLimited
? t('too_many_requests')
: t('generic_something_went_wrong')}
</div>
</>
)}
</div>
@ -116,7 +126,11 @@ function ReconfirmAffiliation({
{isError && (
<>
<br />
<div>{t('generic_something_went_wrong')}</div>
<div>
{rateLimited
? t('too_many_requests')
: t('generic_something_went_wrong')}
</div>
</>
)}
</div>

View file

@ -1,6 +1,6 @@
import { useState, useEffect, useLayoutEffect } from 'react'
import useAsync from '../../../../../shared/hooks/use-async'
import { postJSON } from '../../../../../infrastructure/fetch-json'
import { FetchError, postJSON } from '../../../../../infrastructure/fetch-json'
import { Trans, useTranslation } from 'react-i18next'
import { Institution } from '../../../../../../../types/institution'
import { Button } from 'react-bootstrap'
@ -24,7 +24,7 @@ function ReconfirmationInfoPrompt({
}: ReconfirmationInfoPromptProps) {
const { t } = useTranslation()
const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
const { isLoading, isError, isSuccess, runAsync } = useAsync()
const { error, isLoading, isError, isSuccess, runAsync } = useAsync()
const { state, setLoading: setUserEmailsContextLoading } =
useUserEmailsContext()
const [isPending, setIsPending] = useState(false)
@ -59,6 +59,9 @@ function ReconfirmationInfoPrompt({
}
}
const rateLimited =
isError && error instanceof FetchError && error.response?.status === 429
if (hasSent) {
return (
<div>
@ -87,7 +90,11 @@ function ReconfirmationInfoPrompt({
)}
<br />
{isError && (
<div className="text-danger">{t('generic_something_went_wrong')}</div>
<div className="text-danger">
{rateLimited
? t('too_many_requests')
: t('generic_something_went_wrong')}
</div>
)}
</div>
)
@ -117,7 +124,11 @@ function ReconfirmationInfoPrompt({
</Button>
<br />
{isError && (
<div className="text-danger">{t('generic_something_went_wrong')}</div>
<div className="text-danger">
{rateLimited
? t('too_many_requests')
: t('generic_something_went_wrong')}
</div>
)}
</div>
</>

View file

@ -2,7 +2,7 @@ import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import Icon from '../../../../shared/components/icon'
import { Button } from 'react-bootstrap'
import { postJSON } from '../../../../infrastructure/fetch-json'
import { FetchError, postJSON } from '../../../../infrastructure/fetch-json'
import useAsync from '../../../../shared/hooks/use-async'
import { UserEmailData } from '../../../../../../types/user-email'
import { useUserEmailsContext } from '../../context/user-email-context'
@ -15,7 +15,7 @@ function ResendConfirmationEmailButton({
email,
}: ResendConfirmationEmailButtonProps) {
const { t } = useTranslation()
const { isLoading, isError, runAsync } = useAsync()
const { error, isLoading, isError, runAsync } = useAsync()
const { state, setLoading: setUserEmailsContextLoading } =
useUserEmailsContext()
@ -42,6 +42,9 @@ function ResendConfirmationEmailButton({
)
}
const rateLimited =
error && error instanceof FetchError && error.response?.status === 429
return (
<>
<Button
@ -54,7 +57,11 @@ function ResendConfirmationEmailButton({
</Button>
<br />
{isError && (
<div className="text-danger">{t('generic_something_went_wrong')}</div>
<div className="text-danger">
{rateLimited
? t('too_many_requests')
: t('generic_something_went_wrong')}
</div>
)}
</>
)

View file

@ -13,6 +13,16 @@ const {
UserAuditLogEntry,
} = require('../../../../app/src/models/UserAuditLogEntry')
// Import the rate limiter so we can clear it between tests
const {
RateLimiter,
} = require('../../../../app/src/infrastructure/RateLimiter')
const rateLimiters = {
resendConfirmation: new RateLimiter('resend-confirmation'),
}
let globalUserNum = Settings.test.counterInit
class UserHelper {
@ -438,6 +448,8 @@ class UserHelper {
}
async confirmEmail(userId, email) {
// clear ratelimiting on resend confirmation endpoint
await rateLimiters.resendConfirmation.delete(userId)
// UserHelper.createUser does not create a confirmation token
let response = await this.fetch('/user/emails/resend_confirmation', {
method: 'POST',