355 lines
10 KiB
Dart
355 lines
10 KiB
Dart
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/<publicKey>', _authMiddleware(_deletePeer));
|
|
router.get('/peer/<publicKey>/config', _authMiddleware(_getPeerConfig));
|
|
router.patch('/peer/<publicKey>/speed-limit', _authMiddleware(_setSpeedLimit));
|
|
router.patch('/peer/<publicKey>/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<Response> _getPeers(Request request) async {
|
|
try {
|
|
final statusParam = request.url.queryParameters['status']?.toUpperCase();
|
|
|
|
List<Map<String, dynamic>> 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<Response> _createPeer(Request request) async {
|
|
try {
|
|
final peer = await createPeer();
|
|
final geoData = await getGeolocationData();
|
|
String serverEndpoint = '${geoData.ip}:51820';
|
|
String serverPublicKey = await getServerPublicKey();
|
|
|
|
final responseData = <String, dynamic>{
|
|
'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<Response> _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<Response> _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<Response> _setSpeedLimit(Request request) async {
|
|
try {
|
|
final publicKey = request.params['publicKey'];
|
|
final body = await request.readAsString();
|
|
final data = jsonDecode(body) as Map<String, dynamic>;
|
|
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<Response> _setDataCap(Request request) async {
|
|
try {
|
|
final publicKey = request.params['publicKey'];
|
|
final body = await request.readAsString();
|
|
final data = jsonDecode(body) as Map<String, dynamic>;
|
|
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<Response> _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'},
|
|
);
|
|
}
|
|
}
|
|
} |