/** * Tor Integration Service * Provides Tor network connectivity for anonymous plant sharing */ import { SocksProxyAgent } from 'socks-proxy-agent'; export interface TorConfig { enabled: boolean; socksHost: string; socksPort: number; controlPort: number; hiddenServiceDir?: string; } export class TorService { private config: TorConfig; private proxyAgent: any; constructor(config?: Partial) { this.config = { enabled: process.env.TOR_ENABLED === 'true', socksHost: process.env.TOR_SOCKS_HOST || '127.0.0.1', socksPort: parseInt(process.env.TOR_SOCKS_PORT || '9050'), controlPort: parseInt(process.env.TOR_CONTROL_PORT || '9051'), hiddenServiceDir: process.env.TOR_HIDDEN_SERVICE_DIR, ...config, }; if (this.config.enabled) { this.initializeProxy(); } } /** * Initialize SOCKS proxy for Tor */ private initializeProxy(): void { const proxyUrl = `socks5://${this.config.socksHost}:${this.config.socksPort}`; this.proxyAgent = new SocksProxyAgent(proxyUrl); } /** * Check if Tor is available and running */ async isAvailable(): Promise { if (!this.config.enabled) return false; try { // Try to fetch check.torproject.org through Tor const response = await fetch('https://check.torproject.org/api/ip', { // @ts-ignore agent: this.proxyAgent, signal: AbortSignal.timeout(5000), }); const data = await response.json(); return data.IsTor === true; } catch (error) { console.error('Tor availability check failed:', error); return false; } } /** * Fetch data through Tor network */ async fetchThroughTor(url: string, options: RequestInit = {}): Promise { if (!this.config.enabled) { throw new Error('Tor is not enabled'); } return fetch(url, { ...options, // @ts-ignore agent: this.proxyAgent, }); } /** * Get current Tor circuit info */ async getCircuitInfo(): Promise<{ country?: string; ip?: string }> { try { const response = await this.fetchThroughTor('https://check.torproject.org/api/ip'); return await response.json(); } catch (error) { return {}; } } /** * Request new Tor identity (new circuit) */ async requestNewIdentity(): Promise { // This would require Tor control port access // For now, just return true if Tor is enabled return this.config.enabled; } /** * Get onion service hostname if available */ getOnionAddress(): string | null { if (!this.config.hiddenServiceDir) return null; try { const fs = require('fs'); const path = require('path'); const hostnamePath = path.join(this.config.hiddenServiceDir, 'hostname'); if (fs.existsSync(hostnamePath)) { return fs.readFileSync(hostnamePath, 'utf-8').trim(); } } catch (error) { console.error('Error reading onion address:', error); } return null; } /** * Generate QR code for onion address */ async generateOnionQRCode(): Promise { const onionAddress = this.getOnionAddress(); if (!onionAddress) return null; // In production, you'd use a QR code library // For now, return the address return `http://${onionAddress}`; } /** * Check if request came through Tor */ isRequestFromTor(headers: any): boolean { // Check various indicators that request came through Tor return ( headers['x-tor-connection'] === 'true' || headers['x-forwarded-proto'] === 'tor' || false ); } } // Singleton instance let torService: TorService | null = null; export function getTorService(): TorService { if (!torService) { torService = new TorService(); } return torService; } /** * Middleware to detect and mark Tor connections */ export function torDetectionMiddleware(req: any, res: any, next: any) { const torService = getTorService(); // Mark request as coming from Tor if detected req.isTorConnection = torService.isRequestFromTor(req.headers); // Add Tor status to response headers if (req.isTorConnection) { res.setHeader('X-Tor-Enabled', 'true'); } next(); }