1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Server: Add request rate limter on session and login end points

This commit is contained in:
Laurent Cozic 2021-08-15 00:31:27 +01:00
parent 2c79ce25fa
commit 543413d64b
7 changed files with 47 additions and 0 deletions

View File

@ -31,6 +31,7 @@
"pg": "^8.5.1",
"pretty-bytes": "^5.6.0",
"query-string": "^6.8.3",
"rate-limiter-flexible": "^2.2.4",
"raw-body": "^2.4.1",
"sqlite3": "^4.1.0",
"stripe": "^8.150.0",
@ -8802,6 +8803,11 @@
"node": ">= 0.6"
}
},
"node_modules/rate-limiter-flexible": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.2.4.tgz",
"integrity": "sha512-8u4k5b1afuBcfydX0L0l3J2PNjgcuo3zua8plhvIisyDqOBldrCwfSFut/Fj00LAB1nxJYVM9jeszr2rZyDhQw=="
},
"node_modules/raw-body": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz",
@ -17770,6 +17776,11 @@
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"dev": true
},
"rate-limiter-flexible": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.2.4.tgz",
"integrity": "sha512-8u4k5b1afuBcfydX0L0l3J2PNjgcuo3zua8plhvIisyDqOBldrCwfSFut/Fj00LAB1nxJYVM9jeszr2rZyDhQw=="
},
"raw-body": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz",

View File

@ -43,6 +43,7 @@
"pg": "^8.5.1",
"pretty-bytes": "^5.6.0",
"query-string": "^6.8.3",
"rate-limiter-flexible": "^2.2.4",
"raw-body": "^2.4.1",
"sqlite3": "^4.1.0",
"stripe": "^8.150.0",

View File

@ -38,6 +38,8 @@ export default async function(ctx: AppContext) {
const responseFormat = routeResponseFormat(ctx);
if (error.retryAfterMs) ctx.set('Retry-After', Math.ceil(error.retryAfterMs / 1000).toString());
if (error.code === 'invalidOrigin') {
ctx.response.body = error.message;
} else if (responseFormat === RouteResponseFormat.Html) {

View File

@ -5,12 +5,15 @@ import { ErrorForbidden } from '../../utils/errors';
import { AppContext } from '../../utils/types';
import { bodyFields } from '../../utils/requestUtils';
import { User } from '../../db';
import limiterLoginBruteForce from '../../utils/request/limiterLoginBruteForce';
const router = new Router(RouteType.Api);
router.public = true;
router.post('api/sessions', async (_path: SubPath, ctx: AppContext) => {
await limiterLoginBruteForce(ctx.ip);
const fields: User = await bodyFields(ctx.req);
const user = await ctx.joplin.models.user().login(fields.email, fields.password);
if (!user) throw new ErrorForbidden('Invalid username or password');

View File

@ -6,6 +6,7 @@ import { formParse } from '../../utils/requestUtils';
import config from '../../config';
import defaultView from '../../utils/defaultView';
import { View } from '../../services/MustacheService';
import limiterLoginBruteForce from '../../utils/request/limiterLoginBruteForce';
function makeView(error: any = null): View {
const view = defaultView('login', 'Login');
@ -25,6 +26,8 @@ router.get('login', async (_path: SubPath, _ctx: AppContext) => {
});
router.post('login', async (_path: SubPath, ctx: AppContext) => {
await limiterLoginBruteForce(ctx.ip);
try {
const body = await formParse(ctx.req);

View File

@ -97,6 +97,17 @@ export class ErrorPayloadTooLarge extends ApiError {
}
}
export class ErrorTooManyRequests extends ApiError {
public static httpCode: number = 429;
public retryAfterMs: number = 0;
public constructor(message: string = null, retryAfterMs: number = 0) {
super(message === null ? 'Too Many Requests' : message, ErrorTooManyRequests.httpCode);
this.retryAfterMs = retryAfterMs;
Object.setPrototypeOf(this, ErrorTooManyRequests.prototype);
}
}
export function errorToString(error: Error): string {
const msg: string[] = [];
msg.push(error.message ? error.message : 'Unknown error');

View File

@ -0,0 +1,16 @@
import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible';
import { ErrorTooManyRequests } from '../errors';
const limiterSlowBruteByIP = new RateLimiterMemory({
points: 3, // Up to 3 request per IP
duration: 30, // Per 30 seconds
});
export default async function(ip: string) {
try {
await limiterSlowBruteByIP.consume(ip);
} catch (error) {
const result = error as RateLimiterRes;
throw new ErrorTooManyRequests(`Too many login attempts. Please try again in ${Math.ceil(result.msBeforeNext / 1000)} seconds.`, result.msBeforeNext);
}
}