238 lines
5.2 KiB
TypeScript
238 lines
5.2 KiB
TypeScript
|
|
import * as plugins from '../../plugins.js';
|
||
|
|
import { CachedDocument, TTL } from '../classes.cached.document.js';
|
||
|
|
import { CacheDb } from '../classes.cachedb.js';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Helper to get the smartdata database instance
|
||
|
|
*/
|
||
|
|
const getDb = () => CacheDb.getInstance().getDb();
|
||
|
|
|
||
|
|
/**
|
||
|
|
* IP reputation result data
|
||
|
|
*/
|
||
|
|
export interface IIPReputationData {
|
||
|
|
score: number;
|
||
|
|
isSpam: boolean;
|
||
|
|
isProxy: boolean;
|
||
|
|
isTor: boolean;
|
||
|
|
isVPN: boolean;
|
||
|
|
country?: string;
|
||
|
|
asn?: string;
|
||
|
|
org?: string;
|
||
|
|
blacklists?: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* CachedIPReputation - Stores IP reputation lookup results
|
||
|
|
*
|
||
|
|
* Caches the results of IP reputation checks to avoid repeated
|
||
|
|
* external API calls. Default TTL is 24 hours.
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.Collection(() => getDb())
|
||
|
|
export class CachedIPReputation extends CachedDocument<CachedIPReputation> {
|
||
|
|
/**
|
||
|
|
* IP address (unique identifier)
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.unI()
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public ipAddress: string;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reputation score (0-100, higher = better)
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public score: number;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Whether the IP is flagged as spam source
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public isSpam: boolean;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Whether the IP is a known proxy
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public isProxy: boolean;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Whether the IP is a Tor exit node
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public isTor: boolean;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Whether the IP is a VPN endpoint
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public isVPN: boolean;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Country code (ISO 3166-1 alpha-2)
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public country: string;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Autonomous System Number
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public asn: string;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Organization name
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public org: string;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* List of blacklists the IP appears on
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public blacklists: string[];
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Number of times this IP has been checked
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public checkCount: number = 0;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Number of connections from this IP
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public connectionCount: number = 0;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Number of emails received from this IP
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public emailCount: number = 0;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Number of spam emails from this IP
|
||
|
|
*/
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public spamCount: number = 0;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
super();
|
||
|
|
this.setTTL(TTL.HOURS_24); // Default 24-hour TTL
|
||
|
|
this.blacklists = [];
|
||
|
|
this.score = 50; // Default neutral score
|
||
|
|
this.isSpam = false;
|
||
|
|
this.isProxy = false;
|
||
|
|
this.isTor = false;
|
||
|
|
this.isVPN = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create from reputation data
|
||
|
|
*/
|
||
|
|
public static fromReputationData(ipAddress: string, data: IIPReputationData): CachedIPReputation {
|
||
|
|
const cached = new CachedIPReputation();
|
||
|
|
cached.ipAddress = ipAddress;
|
||
|
|
cached.score = data.score;
|
||
|
|
cached.isSpam = data.isSpam;
|
||
|
|
cached.isProxy = data.isProxy;
|
||
|
|
cached.isTor = data.isTor;
|
||
|
|
cached.isVPN = data.isVPN;
|
||
|
|
cached.country = data.country || '';
|
||
|
|
cached.asn = data.asn || '';
|
||
|
|
cached.org = data.org || '';
|
||
|
|
cached.blacklists = data.blacklists || [];
|
||
|
|
cached.checkCount = 1;
|
||
|
|
return cached;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Convert to reputation data object
|
||
|
|
*/
|
||
|
|
public toReputationData(): IIPReputationData {
|
||
|
|
this.touch();
|
||
|
|
return {
|
||
|
|
score: this.score,
|
||
|
|
isSpam: this.isSpam,
|
||
|
|
isProxy: this.isProxy,
|
||
|
|
isTor: this.isTor,
|
||
|
|
isVPN: this.isVPN,
|
||
|
|
country: this.country,
|
||
|
|
asn: this.asn,
|
||
|
|
org: this.org,
|
||
|
|
blacklists: this.blacklists,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find by IP address
|
||
|
|
*/
|
||
|
|
public static async findByIP(ipAddress: string): Promise<CachedIPReputation | null> {
|
||
|
|
return await CachedIPReputation.getInstance({
|
||
|
|
ipAddress,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find all IPs flagged as spam
|
||
|
|
*/
|
||
|
|
public static async findSpamIPs(): Promise<CachedIPReputation[]> {
|
||
|
|
return await CachedIPReputation.getInstances({
|
||
|
|
isSpam: true,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find IPs with score below threshold
|
||
|
|
*/
|
||
|
|
public static async findLowScoreIPs(threshold: number): Promise<CachedIPReputation[]> {
|
||
|
|
return await CachedIPReputation.getInstances({
|
||
|
|
score: { $lt: threshold },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Record a connection from this IP
|
||
|
|
*/
|
||
|
|
public recordConnection(): void {
|
||
|
|
this.connectionCount++;
|
||
|
|
this.touch();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Record an email from this IP
|
||
|
|
*/
|
||
|
|
public recordEmail(isSpam: boolean = false): void {
|
||
|
|
this.emailCount++;
|
||
|
|
if (isSpam) {
|
||
|
|
this.spamCount++;
|
||
|
|
}
|
||
|
|
this.touch();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update the reputation data
|
||
|
|
*/
|
||
|
|
public updateReputation(data: IIPReputationData): void {
|
||
|
|
this.score = data.score;
|
||
|
|
this.isSpam = data.isSpam;
|
||
|
|
this.isProxy = data.isProxy;
|
||
|
|
this.isTor = data.isTor;
|
||
|
|
this.isVPN = data.isVPN;
|
||
|
|
this.country = data.country || this.country;
|
||
|
|
this.asn = data.asn || this.asn;
|
||
|
|
this.org = data.org || this.org;
|
||
|
|
this.blacklists = data.blacklists || this.blacklists;
|
||
|
|
this.checkCount++;
|
||
|
|
this.touch();
|
||
|
|
// Refresh TTL on update
|
||
|
|
this.setTTL(TTL.HOURS_24);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if this IP should be blocked
|
||
|
|
*/
|
||
|
|
public shouldBlock(): boolean {
|
||
|
|
return this.isSpam || this.score < 20 || this.blacklists.length > 2;
|
||
|
|
}
|
||
|
|
}
|