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.
156 lines
4.9 KiB
TypeScript
156 lines
4.9 KiB
TypeScript
/**
|
|
* API Route: Register an anonymous plant
|
|
* POST /api/plants/register-anonymous
|
|
*
|
|
* This endpoint allows for privacy-preserving plant registration
|
|
*/
|
|
|
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
import { getBlockchain, saveBlockchain } from '../../../lib/blockchain/manager';
|
|
import { PlantData } from '../../../lib/blockchain/types';
|
|
import {
|
|
generateAnonymousId,
|
|
generateWalletAddress,
|
|
obfuscateLocation,
|
|
generateAnonymousPlantName,
|
|
createAnonymousContact,
|
|
PrivacySettings,
|
|
} from '../../../lib/privacy/anonymity';
|
|
import { getTorService } from '../../../lib/services/tor';
|
|
|
|
interface AnonymousPlantRequest {
|
|
commonName: string;
|
|
scientificName?: string;
|
|
species?: string;
|
|
genus?: string;
|
|
family?: string;
|
|
location: {
|
|
latitude: number;
|
|
longitude: number;
|
|
};
|
|
privacySettings: PrivacySettings;
|
|
pseudonym?: string;
|
|
encryptionKey?: string;
|
|
}
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
if (req.method !== 'POST') {
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|
|
|
|
try {
|
|
const requestData: AnonymousPlantRequest = req.body;
|
|
|
|
// Validate required fields
|
|
if (!requestData.commonName || !requestData.location || !requestData.privacySettings) {
|
|
return res.status(400).json({
|
|
error: 'Missing required fields: commonName, location, privacySettings',
|
|
});
|
|
}
|
|
|
|
const { location, privacySettings } = requestData;
|
|
|
|
// Check if request came through Tor
|
|
const torService = getTorService();
|
|
const isTorConnection = torService.isRequestFromTor(req.headers);
|
|
|
|
// Generate anonymous identifiers
|
|
const anonymousUserId = generateAnonymousId();
|
|
const walletAddress = generateWalletAddress();
|
|
const plantId = `plant-${generateAnonymousId()}`;
|
|
|
|
// Obfuscate location based on privacy settings
|
|
const fuzzyLocation = obfuscateLocation(
|
|
location.latitude,
|
|
location.longitude,
|
|
privacySettings.locationPrivacy
|
|
);
|
|
|
|
// Determine display name based on privacy settings
|
|
let displayName: string;
|
|
if (privacySettings.identityPrivacy === 'anonymous') {
|
|
displayName = 'Anonymous Grower';
|
|
} else if (privacySettings.identityPrivacy === 'pseudonym' && requestData.pseudonym) {
|
|
displayName = requestData.pseudonym;
|
|
} else {
|
|
displayName = `Grower-${anonymousUserId.substring(0, 8)}`;
|
|
}
|
|
|
|
// Create anonymous contact
|
|
const anonymousEmail = createAnonymousContact(
|
|
anonymousUserId,
|
|
requestData.encryptionKey || 'default-key'
|
|
);
|
|
|
|
// Build plant data with privacy protections
|
|
const plantData: PlantData = {
|
|
id: plantId,
|
|
commonName: privacySettings.sharePlantDetails
|
|
? requestData.commonName
|
|
: generateAnonymousPlantName(requestData.commonName, 0),
|
|
scientificName: privacySettings.sharePlantDetails ? requestData.scientificName : undefined,
|
|
species: privacySettings.sharePlantDetails ? requestData.species : undefined,
|
|
genus: privacySettings.sharePlantDetails ? requestData.genus : undefined,
|
|
family: privacySettings.sharePlantDetails ? requestData.family : undefined,
|
|
propagationType: 'original',
|
|
generation: 0,
|
|
plantedDate: new Date().toISOString(),
|
|
status: 'growing',
|
|
location: {
|
|
latitude: fuzzyLocation.latitude,
|
|
longitude: fuzzyLocation.longitude,
|
|
address: fuzzyLocation.displayName,
|
|
city: privacySettings.locationPrivacy !== 'hidden' ? 'Privacy Protected' : undefined,
|
|
country: privacySettings.locationPrivacy === 'country' ? 'Anonymous Region' : undefined,
|
|
},
|
|
owner: {
|
|
id: anonymousUserId,
|
|
name: displayName,
|
|
email: anonymousEmail,
|
|
walletAddress: walletAddress,
|
|
},
|
|
childPlants: [],
|
|
registeredAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
notes: privacySettings.anonymousMode
|
|
? 'Registered anonymously via privacy mode'
|
|
: undefined,
|
|
};
|
|
|
|
const blockchain = getBlockchain();
|
|
|
|
// Register the plant
|
|
const block = blockchain.registerPlant(plantData);
|
|
|
|
// Save blockchain
|
|
saveBlockchain();
|
|
|
|
// Prepare response with privacy info
|
|
res.status(201).json({
|
|
success: true,
|
|
plant: block.plant,
|
|
privacy: {
|
|
anonymousId: anonymousUserId,
|
|
walletAddress: walletAddress,
|
|
locationAccuracy: fuzzyLocation.accuracy,
|
|
torConnection: isTorConnection,
|
|
privacyLevel: privacySettings.locationPrivacy,
|
|
},
|
|
block: {
|
|
index: block.index,
|
|
hash: block.hash,
|
|
timestamp: block.timestamp,
|
|
},
|
|
message: 'Plant registered anonymously',
|
|
warning: !isTorConnection
|
|
? 'For maximum privacy, consider accessing through Tor network'
|
|
: null,
|
|
});
|
|
} catch (error: any) {
|
|
console.error('Error registering anonymous plant:', error);
|
|
res.status(500).json({ error: error.message || 'Internal server error' });
|
|
}
|
|
}
|