Enhance verification system with multi-provider support and alt-account detection
- Added support for multiple captcha providers including Google reCAPTCHA (v2 and v3), hCaptcha, and Cloudflare Turnstile. - Introduced new fields in the verification configuration for selecting captcha providers and enabling alt-account detection. - Implemented logic to handle verification attempts and flag potential alt accounts based on IP and invite code analysis. - Updated environment configuration to include necessary keys for captcha providers. - Enhanced the user interface to allow selection of captcha providers in the verification setup. - Improved backend handling of verification records to store additional data related to captcha provider usage.
This commit is contained in:
62
apps/bot/src/modules/verification/alt-detection.ts
Normal file
62
apps/bot/src/modules/verification/alt-detection.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
export type AltSignalMatch = {
|
||||
reason: 'ip' | 'invite_cluster';
|
||||
matchedUserId: string | null;
|
||||
};
|
||||
|
||||
export type AltDetectionInput = {
|
||||
userId: string;
|
||||
accountAgeDays: number;
|
||||
inviteCode: string | null;
|
||||
ipHash: string | null;
|
||||
/** Prior verification records in the same guild (excluding current user). */
|
||||
priorRecords: Array<{
|
||||
userId: string;
|
||||
inviteCode: string | null;
|
||||
ipHash: string | null;
|
||||
createdAt: Date;
|
||||
flaggedAsAlt: boolean;
|
||||
}>;
|
||||
altIpWindowDays: number;
|
||||
altInviteWindowHours: number;
|
||||
altMaxAccountsPerInvite: number;
|
||||
/** Only flag invite clusters when the current account is this young or younger. */
|
||||
youngAccountMaxDays?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Detects likely alt accounts from Discord + captcha signals.
|
||||
* Does not use browser fingerprinting beyond salted IP / UA hashes from the captcha page.
|
||||
*/
|
||||
export function detectAltAccount(input: AltDetectionInput): AltSignalMatch | null {
|
||||
const youngMax = input.youngAccountMaxDays ?? 7;
|
||||
const now = Date.now();
|
||||
|
||||
if (input.ipHash) {
|
||||
const ipWindowMs = input.altIpWindowDays * 24 * 60 * 60 * 1000;
|
||||
const ipMatch = input.priorRecords.find(
|
||||
(record) =>
|
||||
record.ipHash === input.ipHash &&
|
||||
now - record.createdAt.getTime() <= ipWindowMs
|
||||
);
|
||||
if (ipMatch) {
|
||||
return { reason: 'ip', matchedUserId: ipMatch.userId };
|
||||
}
|
||||
}
|
||||
|
||||
if (input.inviteCode && input.accountAgeDays <= youngMax) {
|
||||
const inviteWindowMs = input.altInviteWindowHours * 60 * 60 * 1000;
|
||||
const recentSameInvite = input.priorRecords.filter(
|
||||
(record) =>
|
||||
record.inviteCode === input.inviteCode &&
|
||||
now - record.createdAt.getTime() <= inviteWindowMs
|
||||
);
|
||||
if (recentSameInvite.length >= input.altMaxAccountsPerInvite) {
|
||||
return {
|
||||
reason: 'invite_cluster',
|
||||
matchedUserId: recentSameInvite[0]?.userId ?? null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user