UNPKG

@kanadi/core

Version:

Multi-Layer CAPTCHA Framework with customizable validators and challenge bundles

78 lines (68 loc) 2.1 kB
import { Injectable } from "@nestjs/common"; import { BanRule, BanDecision, type IBanRule, type BanContext, type BanCheckResult, } from "../"; import { ValidatorSessionRepository } from "../../models/validator-session.repository"; @Injectable() @BanRule({ 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; } }