@kanadi/core
Version:
Multi-Layer CAPTCHA Framework with customizable validators and challenge bundles
72 lines (64 loc) • 1.79 kB
text/typescript
import { Injectable } from "@nestjs/common";
import {
BanRule,
BanDecision,
type IBanRule,
type BanContext,
type BanCheckResult,
} from "../";
import { BundleSessionRepository } from "../../models/bundle-session.repository";
()
({
id: "volume_attack",
name: "Volume Attack Detection",
priority: 10,
enabled: true,
category: "volume",
description: "Detects high-volume request patterns from single sources",
})
export class VolumeAttackRule implements IBanRule {
private bundleRepo = new BundleSessionRepository();
async check(context: BanContext): Promise<BanCheckResult> {
const threshold = this.getThreshold(context);
const timeWindow = 1;
const attacks = await this.bundleRepo.detectVolumeAttack(
timeWindow,
threshold,
);
const attackingIp = attacks.find((a) => a.ip === context.ip);
if (attackingIp) {
return {
decision: BanDecision.BAN,
ruleId: "volume_attack",
ruleName: "Volume Attack Detection",
confidence: 0.95,
reason: `${attackingIp.count} requests in ${timeWindow} minute(s), threshold: ${threshold}`,
evidence: {
requestCount: attackingIp.count,
timeWindow,
threshold,
firstRequest: attackingIp.first,
lastRequest: attackingIp.last,
},
expiresAt: new Date(Date.now() + 60 * 60 * 1000),
shouldCache: true,
};
}
return {
decision: BanDecision.ALLOW,
ruleId: "volume_attack",
ruleName: "Volume Attack Detection",
confidence: 1.0,
reason: "Request rate within normal limits",
evidence: {},
shouldCache: false,
};
}
canExecute(context: BanContext): boolean {
return !!context.ip;
}
getThreshold(context: BanContext): number {
const trustScore = context.metadata.trustScore || 50;
return trustScore < 50 ? 10 : 20;
}
}