Add Waylume Manifesto and update API routes for peer management

This commit is contained in:
ImBenji
2025-08-17 21:56:59 +01:00
parent 573744a22d
commit 8b63de7db2
6 changed files with 574 additions and 253 deletions

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager"> <component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />

286
MANIFESTO.md Normal file
View File

@@ -0,0 +1,286 @@
# The Waylume Manifesto
## Project Overview
Waylume is a VPN service consisting of three main components: a Flutter client application, Supabase backend with edge functions, and self-managing WireGuard server nodes. This manifesto documents the current architecture and implementation patterns based on the existing codebase.
## System Architecture
### Current Component Structure
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ Flutter Client │◄──►│ Supabase Cloud │◄──►│ Waylume Servers │
│ App │ │ (Backend) │ │ (Nodes) │
│ │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ User Device │ │ PostgreSQL + │ │ WireGuard │
│ (iOS/Android/ │ │ Edge Functions │ │ Interface │
│ Desktop) │ │ + Stripe │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
## Component Breakdown
### Frontend: Flutter Client Application
**Repository**: Waylume VPN
**Technology Stack**: Flutter 3.9+, Provider state management, Go Router, ShadCN UI
**Purpose**: Cross-platform VPN client with subscription management
**Current Features**:
- WireGuard VPN integration
- Interactive map-based server selection (Mapbox)
- Authentication system (email/password)
- Stripe-powered subscription management with trial support
- Payment history and account management
- Real-time connection status monitoring
- Bottom sheet interface with connection controls
**File Structure**:
```
lib/
├── pages/ # Application screens (home, login, register, account, payment, plans, stripe_portal)
├── providers/ # State management (vpn_connection, subscription)
├── widgets/ # UI components (server_map, connection_status, home_bottom_sheet, stripe components)
├── backend/ # API utilities
├── themes/ # UI theming
└── utils/ # Helper functions
```
### Backend: Supabase Edge Functions
**Repository**: Waylume Supabase Edge Functions
**Technology**: Deno with TypeScript, PostgreSQL, Stripe integration
**Purpose**: Business logic layer between client and server nodes
**Implemented Edge Functions**:
1. **`vpn-nodes-list`** (GET, no auth)
- Returns available VPN servers from `waylume_servers` table
- Filters for servers with valid coordinates
- Provides geolocation data (country, city, coordinates)
2. **`connection-request`** (POST, Bearer token or user_id)
- Creates VPN sessions in database
- Calls Waylume Server API to generate WireGuard peer
- Returns peer configuration to client
3. **`serve-payment`** (POST, no auth required)
- Creates Stripe checkout sessions
- Handles both one-time and subscription billing
- Manages trial period eligibility
4. **`stripe-webhook`** (POST, signature verification)
- Processes Stripe webhook events
- Handles: checkout completion, subscription renewals, cancellations, updates
- Maintains Stripe-database synchronization
5. **`query-transactions`** (GET, Bearer token)
- Multi-endpoint: `/payments`, `/subscriptions`, `/overview`
- Provides transaction history with filtering and pagination
- Supports date filtering, sorting, and totals
6. **`cancel-subscription`** (POST, Bearer token)
- Validates user ownership
- Schedules cancellation through Stripe API
7. **`serve-portal`** (POST, Bearer token)
- Creates Stripe customer portal sessions
- Allows subscription and payment method management
**Database Schemas**:
- **Public**: `waylume_servers`, `vpn_sessions`
- **Commerce**: `products`, `payments`, `subscriptions`
### Infrastructure: Waylume Server Nodes
**Repository**: Waylume Server
**Technology**: Dart server application with Docker deployment
**Purpose**: Self-managing WireGuard VPN nodes
**Current Implementation**:
- **WireGuard Management**: Interface initialization (`wg0`), peer creation/deletion
- **Traffic Control**: HTB-based speed limiting, iptables quota enforcement
- **Peer Isolation**: iptables rules preventing peer-to-peer communication
- **Server Registration**: Automatic registration with Supabase backend
- **Health Monitoring**: Continuous heartbeat system
- **IP Management**: Automatic assignment from 10.0.0.0/24 range
**API Endpoints** (Internal use only - called by Supabase Edge Functions):
- `POST /api/peers` - Create peer with generated keys and IP
- `POST /api/peers/delete` - Remove peer and cleanup
- `POST /api/peers/speed-limit` - Set bandwidth limits (bytes/second)
- `POST /api/peers/data-cap` - Set data usage quotas (bytes)
- `POST /api/peers/config` - Retrieve peer config (returns 404 - not implemented)
**Network Configuration**:
- WireGuard Interface: `wg0` on `10.0.0.1/24`
- Peer IP Range: `10.0.0.2` - `10.255.255.254`
- WireGuard Port: `51820/udp`
- API Port: `3000/tcp`
## Data Flow Patterns
### Server Discovery Flow
```
Flutter App ──► vpn-nodes-list edge function ──► waylume_servers table ──► Server list with geolocation
```
### VPN Connection Flow
```
User selects server ──► connection-request edge function ──► Server API POST /api/peers ──► WireGuard peer config ──► Client connects
vpn_sessions table updated
```
### Payment Flow
```
Plan selection ──► serve-payment ──► Stripe checkout ──► stripe-webhook ──► Database update ──► Subscription active
```
### Server Health Flow
```
Waylume Server ──► Heartbeat service ──► waylume_servers.last_heartbeat ──► Server availability status
```
## Technology Stack Rationale
### Flutter Frontend
- **Cross-platform**: Single codebase for iOS, Android, Desktop
- **WireGuard Plugin**: Native WireGuard integration available
- **UI Framework**: ShadCN UI for consistent design
- **State Management**: Provider pattern for connection and subscription state
- **Maps**: Flutter Map with Mapbox for server selection
### Supabase Backend
- **PostgreSQL**: Full SQL database with Row Level Security
- **Edge Functions**: Deno runtime for serverless business logic
- **Authentication**: Built-in JWT-based auth system
- **Real-time**: WebSocket subscriptions (not currently used but available)
- **Global**: Edge network for reduced latency
### Dart Server Infrastructure
- **Language Consistency**: Same language as Flutter client
- **Process Control**: Direct execution of WireGuard and iptables commands
- **Docker Deployment**: Containerized with network capabilities
- **Self-Management**: Autonomous registration and health reporting
### WireGuard Protocol
- **Performance**: Minimal overhead and high throughput
- **Security**: Modern cryptographic protocols
- **Mobile-Friendly**: Battery efficient and handles network changes
- **Simple Configuration**: Easy peer management
## Current Security Model
### API Security
- **Internal APIs**: Waylume Server APIs are internal-only (no authentication by design)
- **Edge Functions**: Protected by Supabase JWT tokens where required
- **Stripe Integration**: Webhook signature verification
- **Database**: Row Level Security policies
### VPN Security
- **Peer Isolation**: iptables rules prevent peer-to-peer communication
- **Traffic Control**: Per-peer bandwidth and data limits
- **Key Management**: Client-side key generation, server manages peer configs
- **Network Segmentation**: Dedicated IP ranges for VPN traffic
### Payment Security
- **Stripe Integration**: PCI-compliant payment processing
- **Webhook Verification**: Cryptographic signature validation
- **User Validation**: Subscription ownership verification for cancellations
## Development Standards
### Code Organization
```
waylume-client/ # Flutter application
├── lib/pages/ # Application screens
├── lib/providers/ # State management
├── lib/widgets/ # Reusable UI components
├── lib/backend/ # API utilities
└── lib/themes/ # UI theming
waylume-server/ # VPN node server
├── lib/config/ # Supabase configuration
├── lib/services/ # Core services
├── lib/web/ # HTTP routes
├── lib/wireguard/ # WireGuard functionality
└── lib/main.dart # Entry point
waylume-supabase/ # Edge functions
├── functions/vpn-nodes-list/
├── functions/connection-request/
├── functions/serve-payment/
├── functions/stripe-webhook/
├── functions/query-transactions/
├── functions/cancel-subscription/
└── functions/serve-portal/
```
### Naming Conventions
- **Files**: snake_case (Dart standard)
- **Database Tables**: snake_case with descriptive prefixes
- **Environment Variables**: SCREAMING_SNAKE_CASE
- **API Endpoints**: kebab-case paths
### Environment Configuration
**Waylume Server**:
```env
SUPABASE_URL=project_url
SUPABASE_KEY=anon_key
SERVER_ID=unique_identifier
EXTERNAL_PORT=3000
```
**Supabase Functions**:
```env
SUPABASE_URL=project_url
SUPABASE_SERVICE_ROLE_KEY=service_key
STRIPE_SECRET_KEY=stripe_key
STRIPE_WEBHOOK_SECRET=webhook_secret
```
## Current Limitations
### Known Gaps (from TODO.md)
**Security**:
- No API authentication on Waylume Server (by design - internal use)
- No rate limiting implemented
- No HTTPS/TLS support documented
**Monitoring**:
- Basic logging only
- No comprehensive health check endpoints
- No metrics collection system
**API Coverage**:
- `GET /api/peers` (list peers) - not implemented
- `GET /api/peers/{id}` (specific peer info) - not implemented
- `POST /api/peers/config` - returns 404
**Testing**:
- Manual testing only
- No automated test suite
- No unit or integration tests
## Deployment Patterns
### Docker-Based Deployment
- **Waylume Server**: Docker Compose with network capabilities
- **Required Capabilities**: `NET_ADMIN` for WireGuard interface management
- **Port Mapping**: 3000 (API), 51820 (WireGuard)
- **Volume Mounting**: WireGuard runtime directory
### Environment Detection
- Automatic environment detection in Waylume Server
- Environment variable configuration across all components
- Docker environment configuration support
---
*This manifesto documents the current state of the Waylume project based on existing implementation. It should be updated as new features are added or architectural changes are made.*

330
README.md
View File

@@ -2,115 +2,96 @@
## About ## About
Waylume Server is the backend component of the Waylume premium VPN service. It provides a REST API for WireGuard VPN management and integrates with Supabase for server registration and heartbeat monitoring. The server is designed to be easily deployed with Docker and can be spun up quickly to expand VPN infrastructure. Waylume Server is a self-managing WireGuard VPN node that operates as part of the Waylume VPN infrastructure. Each server instance automatically registers with Supabase, manages WireGuard peer configurations, and responds to connection requests through Supabase Edge Functions. The server is designed for autonomous operation with minimal maintenance requirements.
The server is written in Dart and uses process execution to interact with WireGuard tools. It is designed to be lightweight and efficient, making it suitable for deployment on various platforms, including cloud services and local servers.
## Architecture ## Architecture
``` ```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Client Apps │───▶│ Supabase ───│ Waylume Server │ Flutter Client │───▶│ Supabase Edge │───│ Waylume Server │
│ App │ │ Functions │ │ Node │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ WireGuard │ PostgreSQL │ │ WireGuard │
└─────────────────┘ Database Interface │
└─────────────────┘ └─────────────────┘
``` ```
Client applications communicate through Supabase to discover available Waylume servers. Each server manages WireGuard connections via REST API and registers itself with Supabase for automatic discovery and load distribution. **Client Flow:**
1. Flutter app requests available servers via `vpn-nodes-list` edge function
2. User selects server and requests connection via `connection-request` edge function
3. Edge function calls Waylume Server API to create WireGuard peer
4. Server responds with peer configuration through Supabase
5. Client receives WireGuard config and establishes VPN connection
## Role in Waylume Ecosystem
### Server Registration & Discovery
- **Automatic Registration**: Servers register themselves in the `waylume_servers` table on startup
- **Heartbeat Monitoring**: Continuous health reporting to maintain server availability status
- **Geolocation Integration**: Provides location data for client server selection interface
- **Load Distribution**: Multiple servers can be deployed for geographic distribution and load balancing
### Integration with Supabase Edge Functions
The server's REST API is exclusively accessed through Supabase Edge Functions:
- **`connection-request`**: Calls `POST /api/peers` to create new VPN peers
- **Session Management**: Works with `vpn_sessions` table for connection tracking
- **Configuration Delivery**: Peer configs are delivered through Supabase to clients
### WireGuard Management
- **Interface Initialization**: Automatically configures `wg0` interface on startup
- **Dynamic Peer Creation**: Creates peers with unique keys and IP assignments
- **Traffic Control**: Implements speed limiting and data caps per peer
- **Peer Isolation**: Enforces security rules to prevent peer-to-peer communication
- **Cleanup Operations**: Removes peers and associated configurations on disconnect
## Features ## Features
### Core Functionality ### Self-Managing Operations
- **WireGuard Interface Management**: Automatic initialization and configuration of WireGuard server interface - **Zero-Touch Deployment**: Fully automated setup with Docker
- **Peer Management**: Create, delete, and configure VPN peers dynamically - **Health Monitoring**: Built-in health checks and status reporting
- **Traffic Control**: Speed limiting and data cap enforcement per peer - **Automatic Recovery**: Handles WireGuard interface failures and restarts
- **Server Registration**: Automatic registration with Supabase backend - **Resource Management**: Efficient peer lifecycle management
- **Health Monitoring**: Continuous heartbeat system for server availability
### API Endpoints ### Security & Traffic Control
- **Peer Isolation**: iptables rules prevent peer-to-peer communication
- **Bandwidth Control**: HTB-based speed limiting per peer
- **Data Quotas**: iptables quota enforcement for usage limits
- **IP Management**: Automatic IP assignment from managed subnets
### API Endpoints (Internal Use Only)
> **⚠️ Important**: These endpoints are for internal server management through Supabase Edge Functions only. They are not designed for direct client access and lack authentication for security reasons.
#### `POST /api/peers` #### `POST /api/peers`
Create a new VPN peer with automatically generated WireGuard keys and IP assignment. Creates a new WireGuard peer with generated keys and IP assignment.
- **Called by**: `connection-request` edge function
**Request:** No body required - **Purpose**: Establish new VPN connection for authenticated user
**Response:**
```json
{
"success": true,
"peer": {
"privateKey": "base64_private_key",
"publicKey": "base64_public_key",
"ip": "10.0.0.x"
},
"config": "complete_wireguard_client_config"
}
```
#### `POST /api/peers/delete` #### `POST /api/peers/delete`
Remove a VPN peer and clean up associated WireGuard configuration. Removes a WireGuard peer and cleans up configuration.
- **Called by**: Session cleanup processes
**Request Body:** - **Purpose**: Terminate VPN connections and free resources
```json
{
"publicKey": "peer_public_key_here"
}
```
#### `POST /api/peers/speed-limit` #### `POST /api/peers/speed-limit`
Set bidirectional bandwidth limits for a peer (controls both upload and download speeds). Applies bandwidth limitations to specific peers.
- **Called by**: Subscription management functions
**Request Body:** - **Purpose**: Enforce plan-based speed limits
```json
{
"publicKey": "peer_public_key_here",
"bytesPerSecond": 125000
}
```
*Note: 125000 bytes/s = 1000 kbps = 1 Mbps*
*Use -1 for unlimited speed*
**Common Speed Conversions:**
- 125 KB/s = 1 Mbps
- 1,250 KB/s = 10 Mbps
- 12,500 KB/s = 100 Mbps
- -1 = Unlimited
#### `POST /api/peers/data-cap` #### `POST /api/peers/data-cap`
Set total data usage limits for a peer using iptables quota rules. Sets data usage quotas for peers.
- **Called by**: Usage monitoring systems
**Request Body:** - **Purpose**: Enforce plan-based data allowances
```json
{
"publicKey": "peer_public_key_here",
"quotaBytes": 1073741824
}
```
*Note: 1073741824 bytes = 1 GB*
*Use -1 for unlimited data*
**Common Data Conversions:**
- 1 GB = 1,073,741,824 bytes
- 10 GB = 10,737,418,240 bytes
- 100 GB = 107,374,182,400 bytes
- -1 = Unlimited
#### `POST /api/peers/config`
Retrieve peer configuration (currently not implemented - returns 404).
### Security Features
- Peer isolation using iptables rules (prevents peer-to-peer communication)
- Traffic shaping and quota enforcement
- Automatic IP address assignment within 10.0.0.0/8 range
- Secure key generation for each peer
## Prerequisites ## Prerequisites
- Docker and Docker Compose - Docker and Docker Compose
- WireGuard kernel module support - WireGuard kernel module support
- Supabase project with `waylume_servers` table - Supabase project with proper database schema
- Network capabilities for WireGuard interface management
## Environment Variables ## Environment Variables
@@ -118,122 +99,131 @@ Create a `.env` file with the following variables:
```env ```env
# Supabase Configuration # Supabase Configuration
SUPABASE_URL=your_supabase_url SUPABASE_URL=your_supabase_project_url
SUPABASE_KEY=your_supabase_anon_key SUPABASE_KEY=your_supabase_anon_key
# Server Configuration # Server Configuration
SERVER_ID=unique_server_identifier SERVER_ID=unique_server_identifier
EXTERNAL_PORT=3000 EXTERNAL_PORT=3000
# Optional: Geographic Information
SERVER_COUNTRY=United Kingdom
SERVER_CITY=London
SERVER_COORDS_LAT=51.5074
SERVER_COORDS_LON=-0.1278
``` ```
## Quick Start ## Quick Deployment
### Using Docker Compose (Recommended) ### Using Docker Compose (Recommended)
1. Clone the repository 1. Clone the repository
2. Create your `.env` file with required variables 2. Create your `.env` file with required Supabase credentials
3. Run the server: 3. Deploy the server:
```bash ```bash
docker-compose up -d docker-compose up -d
``` ```
The server will be available on port 3000 (or your configured `EXTERNAL_PORT`). The server will:
- Initialize WireGuard interface (`wg0`)
- Register with your Supabase backend
- Begin heartbeat monitoring
- Start accepting API requests on port 3000
### Building from Source ### Verification
1. Install Dart SDK (3.9.0-100.2.beta or later) Check that the server registered successfully:
2. Install dependencies:
```bash ```sql
dart pub get -- In your Supabase SQL editor
``` SELECT * FROM waylume_servers WHERE id = 'your_server_id';
3. Compile and run:
```bash
dart compile exe lib/main.dart -o waylume_server
./waylume_server
``` ```
## Docker Configuration ## Network Configuration
The Docker setup includes:
- WireGuard tools and kernel module support
- iptables for traffic control and peer isolation
- Network capabilities (`NET_ADMIN`) for interface management
- UDP port 51820 for WireGuard traffic
- TCP port 3000 for REST API
## API Usage Examples
### Create a new peer
```bash
curl -X POST http://localhost:3000/api/peers
```
Response:
```json
{
"success": true,
"peer": {
"privateKey": "...",
"publicKey": "...",
"ip": "10.0.0.2"
},
"config": "[Interface]\nPrivateKey = ...\n..."
}
```
### Delete a peer
```bash
curl -X POST http://localhost:3000/api/peers/delete \
-H "Content-Type: application/json" \
-d '{"publicKey": "PEER_PUBLIC_KEY_HERE"}'
```
### Set speed limit for peer
```bash
curl -X POST http://localhost:3000/api/peers/speed-limit \
-H "Content-Type: application/json" \
-d '{"publicKey": "PEER_PUBLIC_KEY_HERE", "bytesPerSecond": 125000}' # 125000 bytes/s = 1000 kbps
```
### Set data cap for peer
```bash
curl -X POST http://localhost:3000/api/peers/data-cap \
-H "Content-Type: application/json" \
-d '{"publicKey": "PEER_PUBLIC_KEY_HERE", "quotaBytes": 1073741824}' # 1073741824 bytes = 1024 MB
```
### Get peer config
```bash
curl -X POST http://localhost:3000/api/peers/config \
-H "Content-Type: application/json" \
-d '{"publicKey": "PEER_PUBLIC_KEY_HERE"}'
```
## Network Architecture
- **WireGuard Interface**: `wg0` on `10.0.0.1/24` - **WireGuard Interface**: `wg0` on `10.0.0.1/24`
- **Peer IP Range**: `10.0.0.2` - `10.255.255.254` - **Peer IP Range**: `10.0.0.2` - `10.255.255.254`
- **WireGuard Port**: `51820/udp` - **WireGuard Port**: `51820/udp`
- **API Port**: `3000/tcp` - **API Port**: `3000/tcp` (internal access only)
## Logging ## Production Deployment
The server provides detailed request logging including: ### Security Considerations
- Client IP addresses (with proxy header support) - Deploy behind a firewall with port 3000 restricted to Supabase Edge Functions
- User agents - Use private networks when possible for API communication
- Request methods and paths - Implement network segmentation for additional security
- Timestamps - Monitor logs for unusual API access patterns
### Scaling Strategy
- Deploy multiple servers across different geographic regions
- Use unique `SERVER_ID` values for each instance
- Monitor server load through Supabase analytics
- Implement auto-scaling based on connection demand
### Monitoring & Maintenance
- Server health is automatically reported to Supabase
- Monitor `waylume_servers` table for server availability
- Check Docker logs for operational issues
- WireGuard interface status is self-managed
## Development ## Development
The project structure: ### Project Structure
``` ```
lib/ lib/
├── config/ # Supabase configuration ├── config/ # Supabase configuration and credentials
├── core/ # Utility functions ├── core/ # Utility functions and helpers
├── services/ # Core services (server, heartbeat, wireguard) ├── services/ # Core services (server, heartbeat, wireguard)
├── web/ # HTTP routes and handlers ├── web/ # HTTP routes and API handlers
├── wireguard/ # WireGuard-specific functionality ├── wireguard/ # WireGuard-specific functionality
└── main.dart # Application entry point └── main.dart # Application entry point and server initialization
```
### Testing Server Integration
You can test the integration by triggering a connection request through your Supabase Edge Functions:
```bash
# Test server listing
curl -X GET https://your-project.supabase.co/functions/v1/vpn-nodes-list
# Test connection request (requires authentication)
curl -X POST https://your-project.supabase.co/functions/v1/connection-request \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"server_id": "your_server_id"}'
```
## Troubleshooting
### Common Issues
**Server not appearing in node list:**
- Check Supabase credentials in `.env`
- Verify server registered in `waylume_servers` table
- Ensure heartbeat process is running
**WireGuard interface issues:**
- Verify WireGuard kernel module is loaded: `lsmod | grep wireguard`
- Check Docker has `NET_ADMIN` capability
- Ensure `/dev/net/tun` is accessible
**API connectivity problems:**
- Verify port 3000 is accessible from Supabase Edge Functions
- Check firewall rules and network configuration
- Review Docker logs for startup errors
### Logging
The server provides comprehensive logging including:
- Supabase registration and heartbeat status
- WireGuard interface management
- API request processing
- Peer lifecycle events
- Error conditions and recovery attempts
Monitor logs with:
```bash
docker-compose logs -f waylume-server
``` ```

View File

@@ -1,26 +1,4 @@
import 'dart:isolate';
import 'dart:math';
import 'package:waylume_server/services/vpn_session_service.dart';
void initVpnSessionMonitor() { void initVpnSessionMonitor() {
// Run this on a separate thread // VPN session monitoring disabled - peer management now handled via API endpoints
Isolate.spawn((_) async { print('VPN Session Monitor: Monitoring disabled - using API endpoints for peer management');
// To avoid server deadlock, add random delay
await Future.delayed(Duration(seconds: Random().nextInt(10)));
print('VPN Session Monitor started');
while (true) {
try {
await VpnSessionService.detectSessions();
} catch (e) {
print('Error in VPN session monitor: $e');
}
// Check for sessions every 60 seconds
await Future.delayed(Duration(seconds: 60));
}
}, null);
} }

View File

@@ -1,63 +1,98 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:supabase/supabase.dart';
import 'package:waylume_server/config/supabase_config.dart';
class VpnSessionService { class VpnSessionService {
/// Detects existing VPN sessions for this server and prints their IDs with keepalive info /// Gets all peers currently on the WireGuard interface
static Future<void> detectSessions() async { static Future<List<Map<String, dynamic>>> getAllLocalPeers() async {
try { try {
final serverId = fromEnivronment('SERVER_ID'); final peers = <Map<String, dynamic>>[];
if (serverId == null) { // Get all peer IPs and public keys
print('ERROR: SERVER_ID environment variable not set'); final allowedIpsResult = await Process.run('wg', ['show', 'wg0', 'allowed-ips']);
return; if (allowedIpsResult.exitCode != 0) {
return peers;
} }
// Get all sessions for this server final lines = allowedIpsResult.stdout.toString().trim().split('\n');
final sessions = await SUPABASE_CLIENT
.from('vpn_sessions')
.select('id, peer_info')
.eq('server_id', serverId);
if (sessions.isEmpty) { for (final line in lines) {
print('No VPN sessions found for server: $serverId'); if (line.trim().isEmpty) continue;
} else {
print('Found ${sessions.length} VPN sessions for server: $serverId');
for (final session in sessions) { final parts = line.trim().split('\t');
final sessionId = session['id']; if (parts.length >= 2) {
final peerInfo = session['peer_info']; final publicKey = parts[0];
final ipWithMask = parts[1];
final ipAddress = ipWithMask.split('/')[0];
if (peerInfo != null && peerInfo['peer'] != null && peerInfo['peer']['publicKey'] != null) { final keepaliveInfo = await _getKeepaliveForPeer(publicKey);
final publicKey = peerInfo['peer']['publicKey'] as String; final peerData = _parseKeepaliveInfo(keepaliveInfo, publicKey, ipAddress);
final keepaliveInfo = await _getKeepaliveForPeer(publicKey);
peers.add(peerData);
print('Session ID: $sessionId - Peer: ${publicKey.substring(0, 8)}... - $keepaliveInfo');
} else {
print('Session ID: $sessionId - No peer public key available');
}
} }
} }
return peers;
} catch (e) { } catch (e) {
print('Error detecting sessions: $e'); print('Error getting local peers: $e');
return [];
} }
} }
/// Generates public key from private key /// Gets only inactive peers (no handshake in >150 seconds)
static Future<String> _getPublicKeyFromPrivateKey(String privateKey) async { static Future<List<Map<String, dynamic>>> getInactivePeers() async {
try { final allPeers = await getAllLocalPeers();
final pubProcess = await Process.start('wg', ['pubkey']); return allPeers.where((peer) => peer['status'] == 'DEAD').toList();
pubProcess.stdin.writeln(privateKey);
await pubProcess.stdin.close();
final publicKey = await pubProcess.stdout.transform(utf8.decoder).join();
return publicKey.trim();
} catch (e) {
return 'Error generating public key: $e';
}
} }
/// Parses keepalive info string into structured data
static Map<String, dynamic> _parseKeepaliveInfo(String keepaliveInfo, String publicKey, String ipAddress) {
if (keepaliveInfo.startsWith('DEAD')) {
final regex = RegExp(r'DEAD - Last handshake: ([^(]+) \((\d+)m (\d+)s ago\)');
final match = regex.firstMatch(keepaliveInfo);
if (match != null) {
final lastHandshake = match.group(1)?.trim();
final minutes = int.tryParse(match.group(2) ?? '0') ?? 0;
final seconds = int.tryParse(match.group(3) ?? '0') ?? 0;
final totalMinutes = minutes + (seconds / 60).round();
return {
'public_key': publicKey,
'ip_address': ipAddress,
'last_handshake': lastHandshake,
'minutes_since_handshake': totalMinutes,
'status': 'DEAD'
};
}
} else if (keepaliveInfo.startsWith('ALIVE')) {
final regex = RegExp(r'ALIVE - Last handshake: ([^(]+) \((\d+)s ago\)');
final match = regex.firstMatch(keepaliveInfo);
if (match != null) {
final lastHandshake = match.group(1)?.trim();
final seconds = int.tryParse(match.group(2) ?? '0') ?? 0;
final minutes = (seconds / 60).round();
return {
'public_key': publicKey,
'ip_address': ipAddress,
'last_handshake': lastHandshake,
'minutes_since_handshake': minutes,
'status': 'ALIVE'
};
}
}
// Fallback for other statuses
return {
'public_key': publicKey,
'ip_address': ipAddress,
'last_handshake': null,
'minutes_since_handshake': null,
'status': keepaliveInfo
};
}
/// Gets keepalive info for a specific peer /// Gets keepalive info for a specific peer
static Future<String> _getKeepaliveForPeer(String publicKey) async { static Future<String> _getKeepaliveForPeer(String publicKey) async {

View File

@@ -11,15 +11,52 @@ class PeerRoutes {
Router get router { Router get router {
final router = Router(); final router = Router();
router.post('/peers', _createPeer); router.get('/peers', _getPeers);
router.post('/peers/delete', _deletePeer); router.post('/peer', _createPeer);
router.post('/peers/config', _getPeerConfig); router.delete('/peer/<publicKey>', _deletePeer);
router.post('/peers/speed-limit', _setSpeedLimit); router.get('/peer/<publicKey>/config', _getPeerConfig);
router.post('/peers/data-cap', _setDataCap); router.patch('/peer/<publicKey>/speed-limit', _setSpeedLimit);
router.patch('/peer/<publicKey>/data-cap', _setDataCap);
return router; return router;
} }
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 { Future<Response> _createPeer(Request request) async {
try { try {
final peer = await createPeer(); final peer = await createPeer();
@@ -57,11 +94,9 @@ class PeerRoutes {
Future<Response> _deletePeer(Request request) async { Future<Response> _deletePeer(Request request) async {
try { try {
final body = await request.readAsString(); final publicKey = request.params['publicKey'];
final data = jsonDecode(body) as Map<String, dynamic>;
final publicKey = data['publicKey'] as String?;
if (publicKey == null) { if (publicKey == null || publicKey.isEmpty) {
return Response.badRequest( return Response.badRequest(
body: jsonEncode({ body: jsonEncode({
'success': false, 'success': false,
@@ -93,11 +128,9 @@ class PeerRoutes {
Future<Response> _getPeerConfig(Request request) async { Future<Response> _getPeerConfig(Request request) async {
try { try {
final body = await request.readAsString(); final publicKey = request.params['publicKey'];
final data = jsonDecode(body) as Map<String, dynamic>;
final publicKey = data['publicKey'] as String?;
if (publicKey == null) { if (publicKey == null || publicKey.isEmpty) {
return Response.badRequest( return Response.badRequest(
body: jsonEncode({ body: jsonEncode({
'success': false, 'success': false,
@@ -127,12 +160,12 @@ class PeerRoutes {
Future<Response> _setSpeedLimit(Request request) async { Future<Response> _setSpeedLimit(Request request) async {
try { try {
final publicKey = request.params['publicKey'];
final body = await request.readAsString(); final body = await request.readAsString();
final data = jsonDecode(body) as Map<String, dynamic>; final data = jsonDecode(body) as Map<String, dynamic>;
final publicKey = data['publicKey'] as String?;
final bytesPerSecond = data['bytesPerSecond'] as int?; final bytesPerSecond = data['bytesPerSecond'] as int?;
if (publicKey == null) { if (publicKey == null || publicKey.isEmpty) {
return Response.badRequest( return Response.badRequest(
body: jsonEncode({ body: jsonEncode({
'success': false, 'success': false,
@@ -174,12 +207,12 @@ class PeerRoutes {
Future<Response> _setDataCap(Request request) async { Future<Response> _setDataCap(Request request) async {
try { try {
final publicKey = request.params['publicKey'];
final body = await request.readAsString(); final body = await request.readAsString();
final data = jsonDecode(body) as Map<String, dynamic>; final data = jsonDecode(body) as Map<String, dynamic>;
final publicKey = data['publicKey'] as String?;
final quotaBytes = data['quotaBytes'] as int?; final quotaBytes = data['quotaBytes'] as int?;
if (publicKey == null) { if (publicKey == null || publicKey.isEmpty) {
return Response.badRequest( return Response.badRequest(
body: jsonEncode({ body: jsonEncode({
'success': false, 'success': false,