2019-12-05 02:07:04 +00:00
/ *
2020-04-15 00:46:39 +00:00
Copyright 2019 , 2020 The Matrix . org Foundation C . I . C .
2019-12-05 02:07:04 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
import { IProtection } from "./IProtection" ;
import { Mjolnir } from "../Mjolnir" ;
import { LogLevel , LogService } from "matrix-bot-sdk" ;
import { logMessage } from "../LogProxy" ;
2019-12-10 02:20:47 +00:00
import config from "../config" ;
2019-12-05 02:07:04 +00:00
export const MAX_PER_MINUTE = 10 ; // if this is exceeded, we'll ban the user for spam and redact their messages
const TIMESTAMP_THRESHOLD = 30000 ; // 30s out of phase
export class BasicFlooding implements IProtection {
2019-12-10 02:15:51 +00:00
private lastEvents : { [ roomId : string ] : { [ userId : string ] : { originServerTs : number , eventId : string } [ ] } } = { } ;
private recentlyBanned : string [ ] = [ ] ;
2019-12-05 02:07:04 +00:00
constructor ( ) {
}
public get name ( ) : string {
return 'BasicFloodingProtection' ;
}
public async handleEvent ( mjolnir : Mjolnir , roomId : string , event : any ) : Promise < any > {
if ( ! this . lastEvents [ roomId ] ) this . lastEvents [ roomId ] = { } ;
const forRoom = this . lastEvents [ roomId ] ;
if ( ! forRoom [ event [ 'sender' ] ] ) forRoom [ event [ 'sender' ] ] = [ ] ;
let forUser = forRoom [ event [ 'sender' ] ] ;
if ( ( new Date ( ) ) . getTime ( ) - event [ 'origin_server_ts' ] > TIMESTAMP_THRESHOLD ) {
LogService . warn ( "BasicFlooding" , ` ${ event [ 'event_id' ] } is more than ${ TIMESTAMP_THRESHOLD } ms out of phase - rewriting event time to be 'now' ` ) ;
event [ 'origin_server_ts' ] = ( new Date ( ) ) . getTime ( ) ;
}
forUser . push ( { originServerTs : event [ 'origin_server_ts' ] , eventId : event [ 'event_id' ] } ) ;
// Do some math to see if the user is spamming
let messageCount = 0 ;
for ( const prevEvent of forUser ) {
if ( ( new Date ( ) ) . getTime ( ) - prevEvent . originServerTs > 60000 ) continue ; // not important
messageCount ++ ;
}
if ( messageCount >= MAX_PER_MINUTE ) {
2019-12-10 02:15:51 +00:00
// Prioritize redaction over ban - we can always keep redacting what the user said.
if ( this . recentlyBanned . includes ( event [ 'sender' ] ) ) return ; // already handled (will be redacted)
mjolnir . redactionHandler . addUser ( event [ 'sender' ] ) ;
this . recentlyBanned . push ( event [ 'sender' ] ) ; // flag to reduce spam
2019-12-05 02:07:04 +00:00
// Redact all the things the user said too
2019-12-10 02:20:47 +00:00
if ( ! config . noop ) {
for ( const eventId of forUser . map ( e = > e . eventId ) ) {
await mjolnir . client . redactEvent ( roomId , eventId , "spam" ) ;
}
} else {
2020-04-15 00:46:39 +00:00
await logMessage ( LogLevel . WARN , "BasicFlooding" , ` Tried to redact messages for ${ event [ 'sender' ] } in ${ roomId } but Mjolnir is running in no-op mode ` , roomId ) ;
2019-12-05 02:07:04 +00:00
}
2019-12-10 02:15:51 +00:00
2020-04-15 00:46:39 +00:00
await logMessage ( LogLevel . WARN , "BasicFlooding" , ` Banning ${ event [ 'sender' ] } in ${ roomId } for flooding ( ${ messageCount } messages in the last minute) ` , roomId ) ;
2019-12-10 02:20:47 +00:00
if ( ! config . noop ) {
await mjolnir . client . banUser ( event [ 'sender' ] , roomId , "spam" ) ;
} else {
2020-04-15 00:46:39 +00:00
await logMessage ( LogLevel . WARN , "BasicFlooding" , ` Tried to ban ${ event [ 'sender' ] } in ${ roomId } but Mjolnir is running in no-op mode ` , roomId ) ;
2019-12-10 02:20:47 +00:00
}
2019-12-10 02:15:51 +00:00
// Free up some memory now that we're ready to handle it elsewhere
2019-12-05 02:07:04 +00:00
forUser = forRoom [ event [ 'sender' ] ] = [ ] ; // reset the user's list
}
// Trim the oldest messages off the user's history if it's getting large
2019-12-05 02:28:49 +00:00
if ( forUser . length > MAX_PER_MINUTE * 2 ) {
forUser . splice ( 0 , forUser . length - ( MAX_PER_MINUTE * 2 ) - 1 ) ;
2019-12-05 02:07:04 +00:00
}
}
}