fix(save): ban selected asns and expire synthient blocks
This commit is contained in:
parent
c1dc678629
commit
d11bf1f817
|
|
@ -13,6 +13,7 @@ import redis from '@/utils/redis';
|
|||
import { parseSlop } from '@/utils/slop';
|
||||
import { checkIpRanges } from '@/utils/ranges';
|
||||
import { getRateLimitState, getRateLimitSubjects } from '@/utils/rate-limit';
|
||||
import { isSynthientBlocked } from '@/utils/synthient'
|
||||
|
||||
const app = new Elysia()
|
||||
const videoIds: Record<string, string> = {}
|
||||
|
|
@ -176,9 +177,12 @@ app.ws('/save', {
|
|||
await cleanup(ws, videoId);
|
||||
console.log(`captcha failed for ${videoId} - ${JSON.stringify(captchaCheck)}`)
|
||||
return sendError(ws, 'Captcha validation failed.');
|
||||
} else {
|
||||
ws.send('DATA - Captcha validated. Starting download...');
|
||||
}
|
||||
if (await isSynthientBlocked(ws.data.headers['cf-connecting-ip'] || '')) {
|
||||
await cleanup(ws, videoId)
|
||||
return sendError(ws, 'Your network is flagged as malicious.')
|
||||
}
|
||||
ws.send('DATA - Captcha validated. Starting download...');
|
||||
}
|
||||
|
||||
const data = await getVideo(videoId)
|
||||
|
|
@ -272,9 +276,12 @@ app.ws('/savechannel', {
|
|||
await cleanup(ws, channelId);
|
||||
console.log(`captcha failed for ${channelId} - ${JSON.stringify(captchaCheck)}`)
|
||||
return sendError(ws, 'Captcha validation failed.');
|
||||
} else {
|
||||
ws.send('DATA - Captcha validated. Starting download...');
|
||||
}
|
||||
if (await isSynthientBlocked(ws.data.headers['cf-connecting-ip'] || '')) {
|
||||
await cleanup(ws, channelId)
|
||||
return sendError(ws, 'Your network is flagged as malicious.')
|
||||
}
|
||||
ws.send('DATA - Captcha validated. Starting download...');
|
||||
|
||||
videoIds[ws.id] = `downloading-${channelId}`;
|
||||
const [videos, channelData] = await Promise.all([getChannelVideos(channelId), getChannel(channelId)]);
|
||||
|
|
@ -284,6 +291,10 @@ app.ws('/savechannel', {
|
|||
}
|
||||
|
||||
for (const video of videos.slice(0, 5)) {
|
||||
if ((await checkIpRanges(ws.data.headers['cf-connecting-ip'] || '')).blocked) {
|
||||
await cleanup(ws, channelId)
|
||||
return sendError(ws, 'Your network is flagged as malicious.')
|
||||
}
|
||||
if (!video || (await redis.get(saveKey(video.videoId))) || (await redis.get(`blacklist:${video.videoId}`))) continue;
|
||||
|
||||
const already = await db.selectFrom('videos')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { readdir, stat } from 'node:fs/promises'
|
||||
import * as path from 'node:path'
|
||||
import redis from '@/utils/redis'
|
||||
|
||||
export interface BlockedIpResult {
|
||||
blocked: boolean
|
||||
|
|
@ -26,6 +27,10 @@ const asnBanList: number[] = [
|
|||
|
||||
14618, // aws
|
||||
|
||||
62327,
|
||||
8075, // microsoft
|
||||
210644, // aeza group
|
||||
|
||||
// expressvpn
|
||||
206092,
|
||||
137409,
|
||||
|
|
@ -397,7 +402,14 @@ export async function checkIpRanges(ip: string): Promise<BlockedIpResult> {
|
|||
return { blocked: false, list: null, range: null }
|
||||
}
|
||||
|
||||
if (await redis.exists(`synthient:blocked-ip:${ip}`)) {
|
||||
return { blocked: true, list: 'synthient', range: null }
|
||||
}
|
||||
|
||||
const asnMatch = await resolveIpAsn(parsedIp)
|
||||
if (asnMatch.range && await redis.exists(`synthient:blocked-range:${asnMatch.range}`)) {
|
||||
return { blocked: true, list: 'synthient', range: asnMatch.range }
|
||||
}
|
||||
const entries = await readdir(BLOCKED_DIR, { withFileTypes: true })
|
||||
|
||||
const files = entries
|
||||
|
|
@ -444,6 +456,11 @@ export async function checkIpRanges(ip: string): Promise<BlockedIpResult> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getIpNetworkRange(ip: string): Promise<string | null> {
|
||||
const parsedIp = parseIp(ip)
|
||||
return parsedIp ? (await resolveIpAsn(parsedIp)).range : null
|
||||
}
|
||||
|
||||
const networkRefreshTimer = setInterval(() => {
|
||||
void refreshNetworksCache(true)
|
||||
}, NETWORKS_REFRESH_INTERVAL_MS)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import { isIP } from 'node:net'
|
||||
import redis from '@/utils/redis'
|
||||
import { checkIpRanges, getIpNetworkRange } from '@/utils/ranges'
|
||||
|
||||
const responseKey = (ip: string) => `synthient:response:${ip}`
|
||||
const banTtlSeconds = 30 * 24 * 60 * 60
|
||||
|
||||
function getBlockDecision(body: string) {
|
||||
const result = JSON.parse(body) as {
|
||||
intelligence?: {
|
||||
categories?: string[]
|
||||
providers?: { provider?: string, type?: string }[]
|
||||
risk_score?: number
|
||||
}
|
||||
}
|
||||
if (!result.intelligence || !Array.isArray(result.intelligence.categories) || !Array.isArray(result.intelligence.providers)) {
|
||||
throw new Error('Invalid IP lookup response')
|
||||
}
|
||||
|
||||
if (result.intelligence.categories.includes('RESIDENTIAL_PROXY')) {
|
||||
return {
|
||||
blocked: typeof result.intelligence.risk_score === 'number' && result.intelligence.risk_score > 90,
|
||||
blockRange: false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
blocked: result.intelligence.categories.includes('FREE_VPN') || result.intelligence.providers.some(provider =>
|
||||
provider.type === 'FREE_VPN' ||
|
||||
provider.provider === 'NORDVPN' ||
|
||||
provider.provider === 'PROTONVPN'),
|
||||
blockRange: true
|
||||
}
|
||||
}
|
||||
|
||||
export async function isSynthientBlocked(ip: string): Promise<boolean> {
|
||||
if (!isIP(ip)) return false
|
||||
if ((await checkIpRanges(ip)).blocked) return true
|
||||
|
||||
try {
|
||||
const cached = await redis.get(responseKey(ip))
|
||||
if (cached) return getBlockDecision(cached).blocked
|
||||
if (!process.env.SYNTHIENT_API_KEY) return false
|
||||
|
||||
const response = await fetch(`https://api.synthient.com/api/v4/lookup/ip/${encodeURIComponent(ip)}`, {
|
||||
headers: { 'x-api-key': process.env.SYNTHIENT_API_KEY },
|
||||
signal: AbortSignal.timeout(5000)
|
||||
})
|
||||
const body = await response.text()
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
||||
const decision = getBlockDecision(body)
|
||||
if (!decision.blocked) {
|
||||
await redis.set(responseKey(ip), body, 'EX', 24 * 60 * 60)
|
||||
return false
|
||||
}
|
||||
|
||||
const range = decision.blockRange ? await getIpNetworkRange(ip) : null
|
||||
const transaction = redis.multi()
|
||||
transaction.set(responseKey(ip), body, 'EX', banTtlSeconds)
|
||||
transaction.set(`synthient:blocked-ip:${ip}`, '1', 'EX', banTtlSeconds)
|
||||
if (range) transaction.set(`synthient:blocked-range:${range}`, '1', 'EX', banTtlSeconds)
|
||||
await transaction.exec()
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue