Skip to content

Commit

Permalink
feat(core): Rate limit forgot password endpoint (#7604)
Browse files Browse the repository at this point in the history
Github issue / Community forum post (link here to close automatically):

---------

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <netroy@users.noreply.github.com>
  • Loading branch information
RicardoE105 and netroy authored Nov 3, 2023
1 parent acec9ba commit 5790e25
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 12 deletions.
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/AbstractServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
};
12 changes: 10 additions & 2 deletions packages/cli/src/controllers/passwordReset.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(
Expand Down
1 change: 1 addition & 0 deletions packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "You’ve 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",
Expand Down
20 changes: 13 additions & 7 deletions packages/editor-ui/src/views/ForgotMyPasswordView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand Down
20 changes: 17 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5790e25

Please sign in to comment.