🔒 Add rate limiter on email signin endpoint
This commit is contained in:
@ -49,6 +49,8 @@
|
||||
"@uiw/codemirror-theme-github": "^4.20.2",
|
||||
"@uiw/codemirror-theme-tokyo-night": "^4.20.2",
|
||||
"@uiw/react-codemirror": "^4.20.2",
|
||||
"@upstash/ratelimit": "^0.4.3",
|
||||
"@upstash/redis": "^1.21.0",
|
||||
"@use-gesture/react": "^10.2.27",
|
||||
"aws-sdk": "2.1384.0",
|
||||
"browser-image-compression": "2.0.2",
|
||||
|
@ -72,17 +72,24 @@ export const SignInForm = ({
|
||||
e.preventDefault()
|
||||
if (isMagicLinkSent) return
|
||||
setAuthLoading(true)
|
||||
const response = await signIn('email', {
|
||||
email: emailValue,
|
||||
redirect: false,
|
||||
})
|
||||
if (response?.error) {
|
||||
showToast({
|
||||
title: scopedT('signinErrorToast.title'),
|
||||
description: scopedT('signinErrorToast.description'),
|
||||
try {
|
||||
const response = await signIn('email', {
|
||||
email: emailValue,
|
||||
redirect: false,
|
||||
})
|
||||
if (response?.error) {
|
||||
showToast({
|
||||
title: scopedT('signinErrorToast.title'),
|
||||
description: scopedT('signinErrorToast.description'),
|
||||
})
|
||||
} else {
|
||||
setIsMagicLinkSent(true)
|
||||
}
|
||||
} catch {
|
||||
showToast({
|
||||
status: 'info',
|
||||
description: scopedT('signinErrorToast.tooManyRequests'),
|
||||
})
|
||||
} else {
|
||||
setIsMagicLinkSent(true)
|
||||
}
|
||||
setAuthLoading(false)
|
||||
}
|
||||
|
@ -85,6 +85,8 @@ export default {
|
||||
'auth.error.unknown': 'Ein Fehler ist aufgetreten. Bitte versuche es erneut.',
|
||||
'auth.signinErrorToast.title': 'Nicht autorisiert',
|
||||
'auth.signinErrorToast.description': 'Anmeldungen sind deaktiviert.',
|
||||
'auth.signinErrorToast.tooManyRequests':
|
||||
'Zu viele Anfragen. Versuche es später erneut.',
|
||||
'auth.noProvider.preLink': 'Du musst',
|
||||
'auth.noProvider.link':
|
||||
'mindestens einen Authentifizierungsanbieter konfigurieren (E-Mail, Google, GitHub, Facebook oder Azure AD).',
|
||||
@ -111,12 +113,10 @@ export default {
|
||||
'billing.upgradeLimitLabel':
|
||||
'Um {type} hinzuzufügen, musst du deinen Tarif aktualisieren',
|
||||
'billing.currentSubscription.heading': 'Abonnement',
|
||||
'billing.currentSubscription.subheading':
|
||||
'Aktuelles Workspace-Abonnement:',
|
||||
'billing.currentSubscription.subheading': 'Aktuelles Workspace-Abonnement:',
|
||||
'billing.currentSubscription.cancelLink': 'Mein Abonnement kündigen',
|
||||
'billing.invoices.heading': 'Rechnungen',
|
||||
'billing.invoices.empty':
|
||||
'Keine Rechnungen für diesen Workspace gefunden.',
|
||||
'billing.invoices.empty': 'Keine Rechnungen für diesen Workspace gefunden.',
|
||||
'billing.invoices.paidAt': 'Bezahlt am',
|
||||
'billing.invoices.subtotal': 'Zwischensumme',
|
||||
'billing.preCheckoutModal.companyInput.label': 'Firmenname:',
|
||||
|
@ -82,6 +82,8 @@ export default {
|
||||
'auth.error.unknown': 'An error occurred. Please try again.',
|
||||
'auth.signinErrorToast.title': 'Unauthorized',
|
||||
'auth.signinErrorToast.description': 'Sign ups are disabled.',
|
||||
'auth.signinErrorToast.tooManyRequests':
|
||||
'Too many requests. Try again later.',
|
||||
'auth.noProvider.preLink': 'You need to',
|
||||
'auth.noProvider.link':
|
||||
'configure at least one auth provider (Email, Google, GitHub, Facebook or Azure AD).',
|
||||
|
@ -83,6 +83,7 @@ export default {
|
||||
'auth.error.unknown': 'Une erreur est survenue. Essaye à nouveau.',
|
||||
'auth.signinErrorToast.title': 'Non autorisé',
|
||||
'auth.signinErrorToast.description': 'Les inscriptions sont désactivées.',
|
||||
'auth.signinErrorToast.tooManyRequests': 'Trop de tentatives de connexion.',
|
||||
'auth.noProvider.preLink': 'Tu as besoin de',
|
||||
'auth.noProvider.link':
|
||||
"configurer au moins un fournisseur d'authentification (E-mail, Google, GitHub, Facebook ou Azure AD).",
|
||||
|
@ -84,6 +84,8 @@ export default {
|
||||
'auth.error.unknown': 'Ocorreu um erro. Tente novamente.',
|
||||
'auth.signinErrorToast.title': 'Não autorizado',
|
||||
'auth.signinErrorToast.description': 'As inscrições estão desativadas.',
|
||||
'auth.signinErrorToast.tooManyRequests':
|
||||
'Muitas tentativas. Tente novamente mais tarde.',
|
||||
'auth.noProvider.preLink': 'Você precisa',
|
||||
'auth.noProvider.link':
|
||||
'configurar pelo menos um provedor de autenticação (E-mail, Google, GitHub, Facebook ou Azure AD).',
|
||||
|
@ -14,9 +14,23 @@ import { env, getAtPath, isDefined, isNotEmpty } from '@typebot.io/lib'
|
||||
import { mockedUser } from '@/features/auth/mockedUser'
|
||||
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
|
||||
import { sendVerificationRequest } from '@/features/auth/helpers/sendVerificationRequest'
|
||||
import { Ratelimit } from '@upstash/ratelimit'
|
||||
import { Redis } from '@upstash/redis/nodejs'
|
||||
|
||||
const providers: Provider[] = []
|
||||
|
||||
let rateLimit: Ratelimit | undefined
|
||||
|
||||
if (
|
||||
process.env.UPSTASH_REDIS_REST_URL &&
|
||||
process.env.UPSTASH_REDIS_REST_TOKEN
|
||||
) {
|
||||
rateLimit = new Ratelimit({
|
||||
redis: Redis.fromEnv(),
|
||||
limiter: Ratelimit.slidingWindow(1, '60 s'),
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
isNotEmpty(process.env.GITHUB_CLIENT_ID) &&
|
||||
isNotEmpty(process.env.GITHUB_CLIENT_SECRET)
|
||||
@ -174,6 +188,24 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (isMockingSession) return res.send({ user: mockedUser })
|
||||
const requestIsFromCompanyFirewall = req.method === 'HEAD'
|
||||
if (requestIsFromCompanyFirewall) return res.status(200).end()
|
||||
|
||||
if (
|
||||
rateLimit &&
|
||||
req.url === '/api/auth/signin/email' &&
|
||||
req.method === 'POST'
|
||||
) {
|
||||
let ip = req.headers['x-real-ip'] as string | undefined
|
||||
if (!ip) {
|
||||
const forwardedFor = req.headers['x-forwarded-for']
|
||||
if (Array.isArray(forwardedFor)) {
|
||||
ip = forwardedFor.at(0)
|
||||
} else {
|
||||
ip = forwardedFor?.split(',').at(0) ?? 'Unknown'
|
||||
}
|
||||
}
|
||||
const { success } = await rateLimit.limit(ip as string)
|
||||
if (!success) return res.status(429).json({ error: 'Too many requests' })
|
||||
}
|
||||
return await NextAuth(req, res, authOptions)
|
||||
}
|
||||
|
||||
|
44
pnpm-lock.yaml
generated
44
pnpm-lock.yaml
generated
@ -127,6 +127,12 @@ importers:
|
||||
'@uiw/react-codemirror':
|
||||
specifier: ^4.20.2
|
||||
version: 4.20.2(@babel/runtime@7.22.3)(@codemirror/autocomplete@6.7.1)(@codemirror/language@6.7.0)(@codemirror/lint@6.2.1)(@codemirror/search@6.4.0)(@codemirror/state@6.2.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.12.0)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@upstash/ratelimit':
|
||||
specifier: ^0.4.3
|
||||
version: 0.4.3
|
||||
'@upstash/redis':
|
||||
specifier: ^1.21.0
|
||||
version: 1.21.0
|
||||
'@use-gesture/react':
|
||||
specifier: ^10.2.27
|
||||
version: 10.2.27(react@18.2.0)
|
||||
@ -8970,6 +8976,31 @@ packages:
|
||||
- '@codemirror/search'
|
||||
dev: false
|
||||
|
||||
/@upstash/core-analytics@0.0.6:
|
||||
resolution: {integrity: sha512-cpPSR0XJAJs4Ddz9nq3tINlPS5aLfWVCqhhtHnXt4p7qr5+/Znlt1Es736poB/9rnl1hAHrOsOvVj46NEXcVqA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
dependencies:
|
||||
'@upstash/redis': 1.21.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@upstash/ratelimit@0.4.3:
|
||||
resolution: {integrity: sha512-Dsp9Mw09Flg28JRklKgFiCXqr3bqv8bbG0kgpUYoHjcgPPolFFyaYOj/I2HExvYLZiogl77NUavBoNvMOK0zUQ==}
|
||||
dependencies:
|
||||
'@upstash/core-analytics': 0.0.6
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@upstash/redis@1.21.0:
|
||||
resolution: {integrity: sha512-c6M+cl0LOgGK/7Gp6ooMkIZ1IDAJs8zFR+REPkoSkAq38o7CWFX5FYwYEqGZ6wJpUGBuEOr/7hTmippXGgL25A==}
|
||||
dependencies:
|
||||
isomorphic-fetch: 3.0.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@use-gesture/core@10.2.27:
|
||||
resolution: {integrity: sha512-V4XV7hn9GAD2MYu8yBBVi5iuWBsAMfjPRMsEVzoTNGYH72tf0kFP+OKqGKc8YJFQIJx6yj+AOqxmEHOmx2/MEA==}
|
||||
dev: false
|
||||
@ -14405,6 +14436,15 @@ packages:
|
||||
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
/isomorphic-fetch@3.0.0:
|
||||
resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==}
|
||||
dependencies:
|
||||
node-fetch: 2.6.11
|
||||
whatwg-fetch: 3.6.2
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/istanbul-lib-coverage@3.2.0:
|
||||
resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==}
|
||||
engines: {node: '>=8'}
|
||||
@ -21232,6 +21272,10 @@ packages:
|
||||
iconv-lite: 0.6.3
|
||||
dev: true
|
||||
|
||||
/whatwg-fetch@3.6.2:
|
||||
resolution: {integrity: sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==}
|
||||
dev: false
|
||||
|
||||
/whatwg-mimetype@3.0.0:
|
||||
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
Reference in New Issue
Block a user