import 'dart:convert'; import 'package:shelf/shelf.dart'; import 'package:shelf_router/shelf_router.dart'; import 'package:waylume_server/wireguard/peers.dart'; import 'package:waylume_server/wireguard/utils.dart'; import 'package:waylume_server/core/utils.dart'; import 'package:waylume_server/services/vpn_session_service.dart'; import 'package:waylume_server/services/bandwidth_service.dart'; import 'package:waylume_server/services/rolling_codes_service.dart'; class PeerRoutes { Router get router { final router = Router(); router.get('/peers', _authMiddleware(_getPeers)); router.post('/peer', _authMiddleware(_createPeer)); router.delete('/peer/', _authMiddleware(_deletePeer)); router.get('/peer//config', _authMiddleware(_getPeerConfig)); router.patch('/peer//speed-limit', _authMiddleware(_setSpeedLimit)); router.patch('/peer//data-cap', _authMiddleware(_setDataCap)); router.get('/bandwidth-stats', _authMiddleware(_getBandwidthStats)); return router; } static const bool kTESTING_MODE = true; /// Authentication middleware for API endpoints Handler _authMiddleware(Handler handler) { return (Request request) async { try { // Skip authentication in testing mode if (kTESTING_MODE) { return await handler(request); } // Check if server is registered if (!RollingCodesService.isRegistered) { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Server not registered - missing operational seed' }), headers: {'Content-Type': 'application/json'} ); } // Get authorization header String? authHeader = request.headers['authorization']; if (authHeader == null) { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Authorization header required' }), headers: {'Content-Type': 'application/json'} ); } // Validate WaylumeAuth format if (!authHeader.startsWith('WaylumeAuth ')) { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Invalid authorization format - expected WaylumeAuth' }), headers: {'Content-Type': 'application/json'} ); } // Extract auth code String authCode = authHeader.substring('WaylumeAuth '.length); // Validate rolling code bool isValid = RollingCodesService.validateOperationalCode(authCode); if (!isValid) { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Invalid authentication code' }), headers: {'Content-Type': 'application/json'} ); } // Authentication successful, proceed to handler return await handler(request); } catch (e) { return Response(500, body: jsonEncode({ 'success': false, 'error': 'Authentication error: $e' }), headers: {'Content-Type': 'application/json'} ); } }; } Future _getPeers(Request request) async { try { final statusParam = request.url.queryParameters['status']?.toUpperCase(); List> peers; if (statusParam == 'DEAD') { peers = await VpnSessionService.getInactivePeers(); } else if (statusParam == 'ALIVE') { final allPeers = await VpnSessionService.getAllLocalPeers(); peers = allPeers.where((peer) => peer['status'] == 'ALIVE').toList(); } else { // No filter or invalid filter - return all peers peers = await VpnSessionService.getAllLocalPeers(); } return Response.ok( jsonEncode({ 'success': true, 'peers': peers, 'total_count': peers.length, 'filter': statusParam, }), headers: {'Content-Type': 'application/json'}, ); } catch (e) { return Response.internalServerError( body: jsonEncode({ 'success': false, 'error': e.toString(), }), headers: {'Content-Type': 'application/json'}, ); } } Future _createPeer(Request request) async { try { final peer = await createPeer(); final geoData = await getGeolocationData(); String serverEndpoint = '${geoData.ip}:51820'; String serverPublicKey = await getServerPublicKey(); final responseData = { 'peer': { 'ip': peer.ip, 'privateKey': peer.privateKey, 'publicKey': peer.publicKey, }, 'server': { 'publicKey': serverPublicKey, 'endpoint': serverEndpoint, }, 'success': true, }; return Response.ok( jsonEncode(responseData), headers: {'Content-Type': 'application/json'}, ); } catch (e) { return Response.internalServerError( body: jsonEncode({ 'success': false, 'error': e.toString(), }), headers: {'Content-Type': 'application/json'}, ); } } Future _deletePeer(Request request) async { try { final publicKey = request.params['publicKey']; if (publicKey == null || publicKey.isEmpty) { return Response.badRequest( body: jsonEncode({ 'success': false, 'error': 'publicKey parameter is required', }), headers: {'Content-Type': 'application/json'}, ); } final success = await deletePeer(publicKey); return Response.ok( jsonEncode({ 'success': success, 'message': success ? 'Peer deleted successfully' : 'Failed to delete peer', }), headers: {'Content-Type': 'application/json'}, ); } catch (e) { return Response.internalServerError( body: jsonEncode({ 'success': false, 'error': e.toString(), }), headers: {'Content-Type': 'application/json'}, ); } } Future _getPeerConfig(Request request) async { try { final publicKey = request.params['publicKey']; if (publicKey == null || publicKey.isEmpty) { return Response.badRequest( body: jsonEncode({ 'success': false, 'error': 'publicKey parameter is required', }), headers: {'Content-Type': 'application/json'}, ); } return Response.notFound( jsonEncode({ 'success': false, 'error': 'Config retrieval not implemented - peer info not stored', }), headers: {'Content-Type': 'application/json'}, ); } catch (e) { return Response.internalServerError( body: jsonEncode({ 'success': false, 'error': e.toString(), }), headers: {'Content-Type': 'application/json'}, ); } } Future _setSpeedLimit(Request request) async { try { final publicKey = request.params['publicKey']; final body = await request.readAsString(); final data = jsonDecode(body) as Map; final bytesPerSecond = data['bytesPerSecond'] as int?; if (publicKey == null || publicKey.isEmpty) { return Response.badRequest( body: jsonEncode({ 'success': false, 'error': 'publicKey parameter is required', }), headers: {'Content-Type': 'application/json'}, ); } if (bytesPerSecond == null) { return Response.badRequest( body: jsonEncode({ 'success': false, 'error': 'bytesPerSecond parameter is required', }), headers: {'Content-Type': 'application/json'}, ); } await setSpeedLimit(publicKey, bytesPerSecond); return Response.ok( jsonEncode({ 'success': true, 'message': 'Speed limit set successfully', }), headers: {'Content-Type': 'application/json'}, ); } catch (e) { return Response.internalServerError( body: jsonEncode({ 'success': false, 'error': e.toString(), }), headers: {'Content-Type': 'application/json'}, ); } } Future _setDataCap(Request request) async { try { final publicKey = request.params['publicKey']; final body = await request.readAsString(); final data = jsonDecode(body) as Map; final quotaBytes = data['quotaBytes'] as int?; if (publicKey == null || publicKey.isEmpty) { return Response.badRequest( body: jsonEncode({ 'success': false, 'error': 'publicKey parameter is required', }), headers: {'Content-Type': 'application/json'}, ); } if (quotaBytes == null) { return Response.badRequest( body: jsonEncode({ 'success': false, 'error': 'quotaBytes parameter is required', }), headers: {'Content-Type': 'application/json'}, ); } await setDataCap(publicKey, quotaBytes); return Response.ok( jsonEncode({ 'success': true, 'message': 'Data cap set successfully', }), headers: {'Content-Type': 'application/json'}, ); } catch (e) { return Response.internalServerError( body: jsonEncode({ 'success': false, 'error': e.toString(), }), headers: {'Content-Type': 'application/json'}, ); } } Future _getBandwidthStats(Request request) async { try { final collectorId = request.url.queryParameters['collector_id']; final bandwidthData = await BandwidthService.getBandwidthStats(collectorId); return Response.ok( jsonEncode({ 'success': true, 'data': bandwidthData, }), headers: {'Content-Type': 'application/json'}, ); } catch (e) { return Response.internalServerError( body: jsonEncode({ 'success': false, 'error': e.toString(), }), headers: {'Content-Type': 'application/json'}, ); } } }