feat(core): Rate limit forgot password endpoint (#7604)

Github issue / Community forum post (link here to close automatically):

---------

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <netroy@users.noreply.github.com>
This commit is contained in:
Ricardo Espinoza 2023-11-03 13:44:12 -04:00 committed by GitHub
parent acec9bad71
commit 5790e251b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 52 additions and 12 deletions

View file

@ -129,6 +129,7 @@
"express-handlebars": "^7.0.2",
"express-openapi-validator": "^4.13.6",
"express-prom-bundle": "^6.6.0",
"express-rate-limit": "^7.1.3",
"fast-glob": "^3.2.5",
"flatted": "^3.2.4",
"formidable": "^3.5.0",

View file

@ -55,6 +55,9 @@ export abstract class AbstractServer {
this.app = express();
this.app.disable('x-powered-by');
const proxyHops = config.getEnv('proxy_hops');
if (proxyHops > 0) this.app.set('trust proxy', proxyHops);
this.protocol = config.getEnv('protocol');
this.sslKey = config.getEnv('ssl_key');
this.sslCert = config.getEnv('ssl_cert');

View file

@ -1344,4 +1344,11 @@ export const schema = {
env: 'N8N_LEADER_SELECTION_CHECK_INTERVAL',
},
},
proxy_hops: {
format: Number,
default: 0,
env: 'N8N_PROXY_HOPS',
doc: 'Number of reverse-proxies n8n is running behind',
},
};

View file

@ -24,12 +24,18 @@ import { isSamlCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
import { UserService } from '@/services/user.service';
import { License } from '@/License';
import { Container } from 'typedi';
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
import { RESPONSE_ERROR_MESSAGES, inTest } from '@/constants';
import { TokenExpiredError } from 'jsonwebtoken';
import type { JwtPayload } from '@/services/jwt.service';
import { JwtService } from '@/services/jwt.service';
import { MfaService } from '@/Mfa/mfa.service';
import { Logger } from '@/Logger';
import { rateLimit } from 'express-rate-limit';
const throttle = rateLimit({
windowMs: 5 * 60 * 1000, // 5 minutes
limit: 5, // Limit each IP to 5 requests per `window` (here, per 5 minutes).
});
@RestController()
export class PasswordResetController {
@ -46,7 +52,9 @@ export class PasswordResetController {
/**
* Send a password reset email.
*/
@Post('/forgot-password')
@Post('/forgot-password', {
middlewares: !inTest ? [throttle] : [],
})
async forgotPassword(req: PasswordResetRequest.Email) {
if (!this.mailer.isEmailSetUp) {
this.logger.debug(

View file

@ -660,6 +660,7 @@
"forgotPassword.sendingEmailError": "Problem sending email",
"forgotPassword.ldapUserPasswordResetUnavailable": "Please contact your LDAP administrator to reset your password",
"forgotPassword.smtpErrorContactAdministrator": "Please contact your administrator (problem with your SMTP setup)",
"forgotPassword.tooManyRequests": "Youve reached the password reset limit. Please try again in a few minutes.",
"forms.resourceFiltersDropdown.filters": "Filters",
"forms.resourceFiltersDropdown.ownedBy": "Owned by",
"forms.resourceFiltersDropdown.sharedWith": "Shared with",

View file

@ -88,14 +88,20 @@ export default defineComponent({
});
} catch (error) {
let message = this.$locale.baseText('forgotPassword.smtpErrorContactAdministrator');
if (error.httpStatusCode === 422) {
message = this.$locale.baseText(error.message);
if (error.isAxiosError) {
const { status } = error.response;
if (status === 429) {
message = this.$locale.baseText('forgotPassword.tooManyRequests');
} else if (error.httpStatusCode === 422) {
message = this.$locale.baseText(error.message);
}
this.showMessage({
type: 'error',
title: this.$locale.baseText('forgotPassword.sendingEmailError'),
message,
});
}
this.showMessage({
type: 'error',
title: this.$locale.baseText('forgotPassword.sendingEmailError'),
message,
});
}
this.loading = false;
},

View file

@ -284,6 +284,9 @@ importers:
express-prom-bundle:
specifier: ^6.6.0
version: 6.6.0(prom-client@13.2.0)
express-rate-limit:
specifier: ^7.1.3
version: 7.1.3(express@4.18.2)
fast-glob:
specifier: ^3.2.5
version: 3.2.12
@ -9641,7 +9644,7 @@ packages:
/axios@0.21.4:
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
dependencies:
follow-redirects: 1.15.2(debug@4.3.4)
follow-redirects: 1.15.2(debug@3.2.7)
transitivePeerDependencies:
- debug
dev: false
@ -9670,11 +9673,12 @@ packages:
form-data: 4.0.0
transitivePeerDependencies:
- debug
dev: true
/axios@1.4.0:
resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==}
dependencies:
follow-redirects: 1.15.2(debug@4.3.4)
follow-redirects: 1.15.2(debug@3.2.7)
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
@ -12772,6 +12776,15 @@ packages:
url-value-parser: 2.2.0
dev: false
/express-rate-limit@7.1.3(express@4.18.2):
resolution: {integrity: sha512-BDes6WeNYSGRRGQU8QDNwUnwqaBro28HN/TTweM3RlxXRHDld8RLoH7tbfCxAc0hamQyn6aL0KrfR45+ZxknYg==}
engines: {node: '>= 16'}
peerDependencies:
express: 4 || 5 || ^5.0.0-beta.1
dependencies:
express: 4.18.2
dev: false
/express@4.18.2:
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
engines: {node: '>= 0.10.0'}
@ -13236,6 +13249,7 @@ packages:
optional: true
dependencies:
debug: 4.3.4(supports-color@8.1.1)
dev: true
/for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@ -18476,7 +18490,7 @@ packages:
resolution: {integrity: sha512-aXYe/D+28kF63W8Cz53t09ypEORz+ULeDCahdAqhVrRm2scbOXFbtnn0GGhvMpYe45grepLKuwui9KxrZ2ZuMw==}
engines: {node: '>=14.17.0'}
dependencies:
axios: 0.27.2(debug@4.3.4)
axios: 0.27.2(debug@3.2.7)
transitivePeerDependencies:
- debug
dev: false