localgreenchain/lib/services/tor.ts
Claude ccea9535d4
Add Tor integration and privacy features for anonymous plant sharing
Implements comprehensive privacy and anonymity features including Tor
hidden service support, location obfuscation, and anonymous registration.

Privacy Features:
- Anonymous plant registration with zero personal information
- Location privacy levels: exact, fuzzy, city, country, hidden
- Pseudonymous identities and wallet addresses
- Privacy settings component with real-time Tor status
- Encrypted anonymous contact generation

Tor Integration:
- SOCKS proxy support for Tor connections
- Hidden service (.onion) configuration
- Tor connection detection and status API
- Docker Compose setup for easy Tor deployment
- Automatic privacy warnings when not using Tor

Location Obfuscation:
- Fuzzy location: ±1-5km random offset
- City level: ~10km grid
- Country level: ~100km grid
- Hidden: complete location privacy
- Haversine-based distance calculations preserved

Anonymous Registration:
- /plants/register-anonymous endpoint
- Privacy-first UI with Tor status banner
- Anonymous IDs and wallet addresses
- Optional pseudonym support
- Encryption key support for enhanced security

Infrastructure:
- Tor service integration (lib/services/tor.ts)
- Privacy utilities (lib/privacy/anonymity.ts)
- PrivacySettings React component
- Tor status API endpoint
- Docker and docker-compose configurations
- Example Tor configuration (torrc.example)

Documentation:
- Comprehensive TOR_SETUP.md guide
- Installation instructions for Linux/macOS/Windows
- Privacy best practices
- Troubleshooting guide
- Security considerations
- Updated README with Tor features

Dependencies:
- Added socks-proxy-agent for Tor proxy support

This enables:
- Privacy-conscious growers to share anonymously
- Protection of exact home locations
- Censorship-resistant plant sharing
- Community building without identity disclosure
- Compliance with privacy regulations

All privacy features are optional and configurable.
Users can choose their desired privacy level.
2025-11-16 12:32:59 +00:00

172 lines
4.2 KiB
TypeScript

/**
* 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<TorConfig>) {
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<boolean> {
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<Response> {
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<boolean> {
// 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<string | null> {
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();
}