@kanadi/core
Version:
Multi-Layer CAPTCHA Framework with customizable validators and challenge bundles
78 lines (68 loc) • 2.1 kB
text/typescript
import { Injectable } from "@nestjs/common";
import {
BanRule,
BanDecision,
type IBanRule,
type BanContext,
type BanCheckResult,
} from "../";
import { ValidatorSessionRepository } from "../../models/validator-session.repository";
()
({
id: "failed_challenges",
name: "Failed Challenges Detection",
priority: 7,
enabled: true,
category: "pattern",
description: "Detects repeated challenge failures indicating automated attacks",
})
export class FailedChallengesRule implements IBanRule {
private validatorRepo = new ValidatorSessionRepository();
async check(context: BanContext): Promise<BanCheckResult> {
const days = 1;
const suspiciousActivity = await this.validatorRepo.findSuspiciousActivity(days);
const sessionFailures = suspiciousActivity.filter(
(session) => session.session_id === context.sessionId,
);
const threshold = this.getThreshold(context);
if (sessionFailures.length >= threshold) {
const failureRate = sessionFailures.length;
const severity = failureRate > 10 ? BanDecision.BAN : BanDecision.WARN;
return {
decision: severity,
ruleId: "failed_challenges",
ruleName: "Failed Challenges Detection",
confidence: 0.85,
reason: `${failureRate} failed/suspicious challenges in the last ${days} day(s)`,
evidence: {
failureCount: failureRate,
timeWindow: days,
threshold,
recentFailures: sessionFailures.slice(0, 5).map((s) => ({
validatorId: s.validator_id,
result: s.result,
createdAt: s.created_at,
})),
},
expiresAt: new Date(Date.now() + 15 * 60 * 1000),
shouldCache: true,
};
}
return {
decision: BanDecision.ALLOW,
ruleId: "failed_challenges",
ruleName: "Failed Challenges Detection",
confidence: 1.0,
reason: "No unusual failure pattern detected",
evidence: {},
shouldCache: false,
};
}
canExecute(context: BanContext): boolean {
return !!context.sessionId;
}
getThreshold(context: BanContext): number {
const trustScore = context.metadata.trustScore || 50;
return trustScore < 50 ? 5 : 8;
}
}