@@ -4,6 +4,7 @@ import fs from 'fs';
44import Fuse from 'fuse.js' ;
55import crypto from 'crypto' ;
66import AdminForth , { AdminForthConfig } from '../index.js' ;
7+ import { RateLimiterMemory , RateLimiterAbstract } from "rate-limiter-flexible" ;
78// @ts -ignore-next-line
89
910
@@ -381,90 +382,50 @@ export function md5hash(str:string) {
381382}
382383
383384export class RateLimiter {
384- static counterData = { } ;
385-
386- /**
387- * Very dirty version of ratelimiter for demo purposes (should not be considered as production ready)
388- * Will be used as RateLimiter.checkRateLimit('key', '5/24h', clientIp)
389- * Stores counter in this class, in RAM, resets limits on app restart.
390- * Also it creates setTimeout for every call, so is not optimal for high load.
391- * @param key - key to store rate limit for
392- * @param limit - limit in format '5/24h' - 5 requests per 24 hours
393- * @param clientIp
394- */
395- static checkRateLimit ( key : string , limit : string , clientIp : string ) {
396-
397- if ( ! limit ) {
398- throw new Error ( 'Rate limit is not set' ) ;
399- }
385+ // constructor, accepts string like 10/10m, or 20/10s, or 30/1d
400386
401- if ( ! key ) {
402- throw new Error ( 'Rate limit key is not set' ) ;
403- }
404387
405- if ( ! clientIp ) {
406- throw new Error ( 'Client IP is not set' ) ;
407- }
388+ rateLimiter : RateLimiterAbstract ;
408389
409- if ( ! limit . includes ( '/' ) ) {
410- throw new Error ( 'Rate limit should be in format count/period, like 5/24h' ) ;
411- }
412390
413- // parse limit
414- const [ count , period ] = limit . split ( '/' ) ;
415- const [ preiodAmount , periodType ] = / ( \d + ) ( \w + ) / . exec ( period ) . slice ( 1 ) ;
416- const preiodAmountNumber = parseInt ( preiodAmount ) ;
417-
418- // get current time
419- const whenClear = new Date ( ) ;
420- if ( periodType === 'h' ) {
421- whenClear . setHours ( whenClear . getHours ( ) + preiodAmountNumber ) ;
422- } else if ( periodType === 'd' ) {
423- whenClear . setDate ( whenClear . getDate ( ) + preiodAmountNumber ) ;
424- } else if ( periodType === 'm' ) {
425- whenClear . setMinutes ( whenClear . getMinutes ( ) + preiodAmountNumber ) ;
426- } else if ( periodType === 'y' ) {
427- whenClear . setFullYear ( whenClear . getFullYear ( ) + preiodAmountNumber ) ;
428- } else if ( periodType === 's' ) {
429- whenClear . setSeconds ( whenClear . getSeconds ( ) + preiodAmountNumber ) ;
430- } else {
431- throw new Error ( `Unsupported period type for rate limiting: ${ periodType } ` ) ;
391+ durStringToSeconds ( rate : string ) : number {
392+ if ( ! rate ) {
393+ throw new Error ( 'Rate duration is required' ) ;
432394 }
433395
434-
435- // get current counter
436- const counter = this . counterData [ key ] && this . counterData [ key ] [ clientIp ] || 0 ;
437- if ( counter >= count ) {
438- return { error : true } ;
396+
397+ const period = rate . slice ( - 1 ) ;
398+ const duration = parseInt ( rate . slice ( 0 , - 1 ) ) ;
399+ if ( period === 's' ) {
400+ return duration ;
401+ } else if ( period === 'm' ) {
402+ return duration * 60 ;
403+ } else if ( period === 'h' ) {
404+ return duration * 60 * 60 ;
405+ } else if ( period === 'd' ) {
406+ return duration * 60 * 60 * 24 ;
439407 }
440- RateLimiter . incrementCounter ( key , clientIp ) ;
441- setTimeout ( ( ) => {
442- RateLimiter . decrementCounter ( key , clientIp ) ;
443- } , whenClear . getTime ( ) - Date . now ( ) ) ;
408+ throw new Error ( `Invalid rate duration period: ${ period } ` ) ;
409+ }
444410
445- return { error : false } ;
446411
412+ constructor ( rate : string ) {
413+ const [ points , duration ] = rate . split ( '/' ) ;
414+ const durationSeconds = this . durStringToSeconds ( duration ) ;
415+ const opts = {
416+ points : parseInt ( points ) ,
417+ duration : durationSeconds , // Per second
418+ } ;
419+ this . rateLimiter = new RateLimiterMemory ( opts ) ;
447420 }
448421
449- static incrementCounter ( key : string , ip : string ) {
450- if ( ! RateLimiter . counterData [ key ] ) {
451- RateLimiter . counterData [ key ] = { } ;
452- }
453- if ( ! RateLimiter . counterData [ key ] [ ip ] ) {
454- RateLimiter . counterData [ key ] [ ip ] = 0 ;
455- }
456- RateLimiter . counterData [ key ] [ ip ] ++ ;
457- }
458422
459- static decrementCounter ( key : string , ip : string ) {
460- if ( ! RateLimiter . counterData [ key ] ) {
461- RateLimiter . counterData [ key ] = { } ;
462- }
463- if ( ! RateLimiter . counterData [ key ] [ ip ] ) {
464- RateLimiter . counterData [ key ] [ ip ] = 0 ;
465- }
466- if ( RateLimiter . counterData [ key ] [ ip ] > 0 ) {
467- RateLimiter . counterData [ key ] [ ip ] -- ;
423+ async consume ( key : string ) {
424+ try {
425+ await this . rateLimiter . consume ( key ) ;
426+ return true ;
427+ } catch ( rejRes ) {
428+ return false ;
468429 }
469430 }
470431
0 commit comments