library-ts/node/users/requirements/security/NotTooManyRequests.ts

77 lines
2.1 KiB
TypeScript
Raw Permalink Normal View History

2025-11-10 17:41:48 +00:00
import { FastifyReply, FastifyRequest } from "fastify";
import { RequestRequirement } from "../RequestRequirement";
import { UserManagementServer } from "../../UserManagementServer";
import { MapList } from "../../../../browser/tools/MapList";
import { DateHelper } from "../../../../browser/date/DateHelper";
import { Duration } from "../../../../browser/date/Duration";
import { DateMath } from "../../../../browser/date/DateMath";
2025-11-11 21:46:18 +00:00
import { Message } from "../../../../browser/messages/Message";
2025-11-10 17:41:48 +00:00
export class NotTooManyRequests extends RequestRequirement
{
_trackedIPs = new Map<string,number[]>();
_maxRequests = 50;
_duration = Duration.fromMinutes( 2 );
_watchList = new Set<string>();
_blockList = new Map<string,number>()
_blockDuration = Duration.fromHours( 4 );
2025-11-11 21:46:18 +00:00
async handle( request:FastifyRequest, reply:FastifyReply ):Promise<Message[]>
2025-11-10 17:41:48 +00:00
{
if ( this._blockList.has( request.ip ) )
{
let blockTime = this._blockList.get( request.ip );
if ( ! DateMath.isExpired( new Date( blockTime + this._blockDuration ) ) )
{
2025-11-15 18:58:30 +00:00
return this.sendError( "User blocked" );
2025-11-10 17:41:48 +00:00
}
}
let valid = this.updateIP( request.ip );
2025-11-11 21:46:18 +00:00
if ( ! valid )
{
2025-11-15 18:58:30 +00:00
return this.sendError( "Too many requests" );
2025-11-11 21:46:18 +00:00
}
2025-11-15 18:58:30 +00:00
return this.giveOK();
2025-11-10 17:41:48 +00:00
}
async updateIP( ip:string ):Promise<boolean>
{
MapList.add( this._trackedIPs, ip, DateHelper.nowMS() );
MapList.shiftToSize( this._trackedIPs, ip, this._maxRequests );
let lastRelevantDate = DateMath.addSeconds( DateHelper.now(), -this._duration );
let timeStamps = this._trackedIPs.get( ip );
let filteredStamps = timeStamps.filter( ts => DateMath.isAfter( lastRelevantDate, new Date( ts ) ) );
if ( filteredStamps.length < this._maxRequests )
{
this._trackedIPs.set( ip, filteredStamps );
return Promise.resolve( true );
}
this._trackedIPs.delete( ip );
if ( this._watchList.has( ip ) )
{
this._blockList.set( ip, DateHelper.nowMS() );
this._watchList.delete( ip );
}
else
{
this._watchList.add( ip );
}
return Promise.resolve( false );
}
}