UNPKG

better-auth-abuse-detection

Version:

AI-powered abuse detection plugin for Better Auth - Detect and prevent account takeover attempts

121 lines (120 loc) 3.88 kB
// src/index.ts function abuseDetection(options = {}) { const { strategies = { credentialStuffing: { enabled: true, threshold: 5, windowMinutes: 10 }, velocityCheck: { enabled: true, maxSignIns: 10, windowMinutes: 5 }, impossibleTravel: { enabled: true, speedKmh: 1e3 }, deviceAnomaly: { enabled: true }, behavioralAnalysis: { enabled: false } }, riskScoring = { enabled: true, blockThreshold: 0.9, challengeThreshold: 0.7 }, actions = { block: { duration: 3600 }, challenge: { types: ["captcha"] }, notify: { user: true, admin: false } } } = options; const calculateRiskScore = (factors) => { let score = 0; const weights = riskScoring.factors || {}; for (const [factor, value] of Object.entries(factors)) { score += value * (weights[factor] || 0.1); } return Math.min(score, 1); }; const detectImpossibleTravel = (lat1, lon1, lat2, lon2, timeDiffHours) => { const R = 6371; const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const distance = R * c; const speed = distance / timeDiffHours; return speed > (strategies.impossibleTravel?.speedKmh || 1e3); }; return { id: "abuse-detection", schema: { threatEvent: { modelName: "threatEvent", fields: { id: { type: "string" }, userId: { type: "string", references: { model: "user", field: "id", onDelete: "cascade" } }, type: { type: "string" }, // credential_stuffing, impossible_travel, etc. severity: { type: "string" }, // low, medium, high, critical riskScore: { type: "number" }, ipAddress: { type: "string" }, userAgent: { type: "string" }, location: { type: "string" }, // JSON with lat, lon, country, city deviceFingerprint: { type: "string" }, action: { type: "string" }, // blocked, challenged, allowed metadata: { type: "string" }, // JSON with additional data detectedAt: { type: "date", defaultValue: /* @__PURE__ */ new Date() } } }, deviceTrust: { modelName: "deviceTrust", fields: { id: { type: "string" }, userId: { type: "string", references: { model: "user", field: "id", onDelete: "cascade" } }, fingerprint: { type: "string" }, trustScore: { type: "number" }, lastSeen: { type: "date" }, firstSeen: { type: "date", defaultValue: /* @__PURE__ */ new Date() }, metadata: { type: "string" } // JSON with device info } }, blockedEntity: { modelName: "blockedEntity", fields: { id: { type: "string" }, type: { type: "string" }, // ip, email, fingerprint value: { type: "string" }, reason: { type: "string" }, expiresAt: { type: "date" }, createdAt: { type: "date", defaultValue: /* @__PURE__ */ new Date() } } } }, // Hooks to analyze authentication attempts hooks: { before: [ { matcher: (ctx) => ctx.path === "/sign-in" || ctx.path === "/sign-up", handler: async (ctx) => { } } ], after: [ { matcher: (ctx) => ctx.path === "/sign-in", handler: async (ctx) => { } } ] } }; } var src_default = abuseDetection; export { abuseDetection, src_default as default };