bypass captcha keys

This commit is contained in:
localhost 2026-04-29 11:36:21 +02:00
parent 2a833c12b7
commit 2fd502039f
4 changed files with 40 additions and 27 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -11,6 +11,7 @@ services:
- /mnt/hdd/preservetube-videos:/usr/src/preservetube/backend/videos - /mnt/hdd/preservetube-videos:/usr/src/preservetube/backend/videos
- ./.env:/usr/src/preservetube/backend/.env - ./.env:/usr/src/preservetube/backend/.env
- ./s3.json:/usr/src/preservetube/backend/s3.json - ./s3.json:/usr/src/preservetube/backend/s3.json
- ./keys.yaml:/usr/src/preservetube/backend/keys.yaml
networks: networks:
public: public:

View File

@ -17,6 +17,7 @@
"@elysiajs/static": "^1.4.0", "@elysiajs/static": "^1.4.0",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/html-minifier-next": "^2.1.0", "@types/html-minifier-next": "^2.1.0",
"@types/js-yaml": "^4.0.9",
"@types/pg": "^8.11.10", "@types/pg": "^8.11.10",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"age-encryption": "^0.2.4", "age-encryption": "^0.2.4",
@ -26,6 +27,7 @@
"html-minifier-next": "^2.1.4", "html-minifier-next": "^2.1.4",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"isomorphic-dompurify": "^2.18.0", "isomorphic-dompurify": "^2.18.0",
"js-yaml": "^4.1.1",
"kysely": "^0.27.4", "kysely": "^0.27.4",
"pg": "^8.13.1", "pg": "^8.13.1",
"readable-to-ms": "^1.0.3", "readable-to-ms": "^1.0.3",

View File

@ -1,5 +1,6 @@
import { Elysia, t } from 'elysia'; import { Elysia, t } from 'elysia';
import * as fs from 'node:fs' import * as fs from 'node:fs'
import yaml from 'js-yaml'
import { db } from '@/utils/database' import { db } from '@/utils/database'
import { validateVideo, validateChannel } from '@/utils/regex' import { validateVideo, validateChannel } from '@/utils/regex'
@ -20,6 +21,7 @@ const saveKey = (videoId: string) => `save:${videoId}`
const DEFAULT_STORAGE_LIMIT_MESSAGE = 'Daily storage limit reached. Is this an urgent archive? Please email me: admin@preservetube.com' const DEFAULT_STORAGE_LIMIT_MESSAGE = 'Daily storage limit reached. Is this an urgent archive? Please email me: admin@preservetube.com'
const NEW_VISITOR_STORAGE_LIMIT_MESSAGE = 'You are a new visitor, so your storage limit is lower for the first few hours. Please come back later. </br>Is this an urgent archive? Please email me: admin@preservetube.com' const NEW_VISITOR_STORAGE_LIMIT_MESSAGE = 'You are a new visitor, so your storage limit is lower for the first few hours. Please come back later. </br>Is this an urgent archive? Please email me: admin@preservetube.com'
const bKeys = yaml.load(await Bun.file('keys.yaml').text()) as { keys: string[] }
const checkMbLimit = async (subjects: string[], mb?: number): Promise<{ isLimited: boolean, isNewVisitorLimited: boolean }> => { const checkMbLimit = async (subjects: string[], mb?: number): Promise<{ isLimited: boolean, isNewVisitorLimited: boolean }> => {
const keys = subjects.map(subject => `save-mb:${Bun.hash(subject).toString()}`) const keys = subjects.map(subject => `save-mb:${Bun.hash(subject).toString()}`)
@ -114,7 +116,8 @@ const getRateLimitKey = (ip: string): string => {
app.ws('/save', { app.ws('/save', {
query: t.Object({ query: t.Object({
url: t.String(), url: t.String(),
rlid: t.Optional(t.String()) rlid: t.Optional(t.String()),
bKey: t.Optional(t.String())
}), }),
body: t.String(), body: t.String(),
open: async (ws) => { open: async (ws) => {
@ -140,6 +143,7 @@ app.ws('/save', {
ws.send(`DONE - ${process.env.FRONTEND}/watch?v=${videoId}`) ws.send(`DONE - ${process.env.FRONTEND}/watch?v=${videoId}`)
ws.close() ws.close()
} else { } else {
if (!(ws.data.query.bKey && bKeys.keys.includes(ws.data.query.bKey))) {
const subjects = getRateLimitSubjects( const subjects = getRateLimitSubjects(
getRateLimitKey(ws.data.headers['cf-connecting-ip'] || '0.0.0.0'), getRateLimitKey(ws.data.headers['cf-connecting-ip'] || '0.0.0.0'),
ws.data.query.rlid ws.data.query.rlid
@ -150,6 +154,8 @@ app.ws('/save', {
} }
console.log(`saving (${subjects.map(subject => Bun.hash(subject).toString()).join(',')}) - ${ws.data.path} - ${JSON.stringify(ws.data.query)}`) console.log(`saving (${subjects.map(subject => Bun.hash(subject).toString()).join(',')}) - ${ws.data.path} - ${JSON.stringify(ws.data.query)}`)
}
ws.send('DATA - This process is automatic. Your video will start archiving shortly.') ws.send('DATA - This process is automatic. Your video will start archiving shortly.')
ws.send('CAPTCHA - Solving a cryptographic challenge before downloading.') ws.send('CAPTCHA - Solving a cryptographic challenge before downloading.')
videoIds[ws.id] = videoId videoIds[ws.id] = videoId
@ -164,6 +170,7 @@ app.ws('/save', {
if (await redis.get(saveKey(videoId)) !== 'downloading') { if (await redis.get(saveKey(videoId)) !== 'downloading') {
await redis.set(saveKey(videoId), 'downloading', 'EX', 300) await redis.set(saveKey(videoId), 'downloading', 'EX', 300)
if (!(ws.data.query.bKey && bKeys.keys.includes(ws.data.query.bKey))) {
const captchaCheck = await checkCaptcha(message, ws.data.headers['cf-connecting-ip'] || '0.0.0.0') const captchaCheck = await checkCaptcha(message, ws.data.headers['cf-connecting-ip'] || '0.0.0.0')
if (!captchaCheck.success) { if (!captchaCheck.success) {
await cleanup(ws, videoId); await cleanup(ws, videoId);
@ -172,6 +179,7 @@ app.ws('/save', {
} else { } else {
ws.send('DATA - Captcha validated. Starting download...'); ws.send('DATA - Captcha validated. Starting download...');
} }
}
const data = await getVideo(videoId) const data = await getVideo(videoId)
if (data.error) { if (data.error) {
@ -194,6 +202,7 @@ app.ws('/save', {
return sendError(ws, downloadResult.message); return sendError(ws, downloadResult.message);
} }
if (!(ws.data.query.bKey && bKeys.keys.includes(ws.data.query.bKey))) {
const mbsUsed = Math.ceil(downloadResult.size / (1024 * 1024)) const mbsUsed = Math.ceil(downloadResult.size / (1024 * 1024))
const subjects = getRateLimitSubjects( const subjects = getRateLimitSubjects(
getRateLimitKey(ws.data.headers['cf-connecting-ip'] || '0.0.0.0'), getRateLimitKey(ws.data.headers['cf-connecting-ip'] || '0.0.0.0'),
@ -206,6 +215,7 @@ app.ws('/save', {
await cleanup(ws, videoId); await cleanup(ws, videoId);
return sendError(ws, limitStatus.isNewVisitorLimited ? NEW_VISITOR_STORAGE_LIMIT_MESSAGE : DEFAULT_STORAGE_LIMIT_MESSAGE); return sendError(ws, limitStatus.isNewVisitorLimited ? NEW_VISITOR_STORAGE_LIMIT_MESSAGE : DEFAULT_STORAGE_LIMIT_MESSAGE);
} }
}
const uploadSuccess = await handleUpload(ws, videoId); const uploadSuccess = await handleUpload(ws, videoId);
if (!uploadSuccess) await redis.del(saveKey(videoId)); if (!uploadSuccess) await redis.del(saveKey(videoId));