getMessage()); } // Seed admin user if not exists try { $adminEmail = Config::get('ADMIN_EMAIL'); $adminPass = Config::get('ADMIN_PASSWORD'); if ($adminEmail && $adminPass) { Auth::seedAdmin($adminEmail, $adminPass); } } catch (Throwable $e) { // Ignore errors } // Initialize translator Translator::init(); // Initialize template engine $user = Auth::user(); $appName = Config::get('APP_NAME', 'Amnezia VPN Panel'); View::init(__DIR__ . '/../templates', [ 'app_name' => $appName, 'user' => $user, 'current_language' => Translator::getCurrentLanguage(), 'languages' => Translator::getSupportedLanguages(), 'current_uri' => $_SERVER['REQUEST_URI'] ?? '/dashboard', 't' => function($key, $params = []) { return Translator::t($key, $params); } ]); // Helper function for redirects function redirect(string $to): void { header('Location: ' . $to); exit; } // Helper function to require authentication function requireAuth(): void { if (!Auth::check()) { redirect('/login'); } } // Helper function to require admin function requireAdmin(): void { requireAuth(); if (!Auth::isAdmin()) { http_response_code(403); echo 'Forbidden: Admin access required'; exit; } } /** * PUBLIC ROUTES */ // Home page Router::get('/', function () { if (!Auth::check()) { redirect('/login'); } redirect('/dashboard'); }); // Login page Router::get('/login', function () { if (Auth::check()) { redirect('/dashboard'); } View::render('login.twig'); }); Router::post('/login', function () { $email = trim($_POST['email'] ?? ''); $password = $_POST['password'] ?? ''; if (Auth::login($email, $password)) { redirect('/dashboard'); } View::render('login.twig', ['error' => 'Invalid credentials']); }); // Register page Router::get('/register', function () { if (Auth::check()) { redirect('/dashboard'); } View::render('register.twig'); }); Router::post('/register', function () { $name = trim($_POST['name'] ?? ''); $email = trim($_POST['email'] ?? ''); $password = $_POST['password'] ?? ''; if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { View::render('register.twig', ['error' => 'Invalid email address']); return; } if (strlen($password) < 6) { View::render('register.twig', ['error' => 'Password must be at least 6 characters']); return; } try { $success = Auth::register($name, $email, $password); if ($success) { Auth::login($email, $password); redirect('/dashboard'); } } catch (Throwable $e) { // Email already exists or other error } View::render('register.twig', ['error' => 'Registration failed. Email may already be in use.']); }); // Logout Router::get('/logout', function () { Auth::logout(); redirect('/login'); }); /** * AUTHENTICATED ROUTES */ // Dashboard Router::get('/dashboard', function () { requireAuth(); $user = Auth::user(); // Get user's servers $servers = VpnServer::listByUser($user['id']); // Get user's clients $clients = VpnClient::listByUser($user['id']); View::render('dashboard.twig', [ 'servers' => $servers, 'clients' => $clients, ]); }); // Servers list Router::get('/servers', function () { requireAuth(); $user = Auth::user(); $servers = Auth::isAdmin() ? VpnServer::listAll() : VpnServer::listByUser($user['id']); View::render('servers/index.twig', ['servers' => $servers]); }); // Create server page Router::get('/servers/create', function () { requireAuth(); View::render('servers/create.twig'); }); // Create server action Router::post('/servers/create', function () { requireAuth(); $user = Auth::user(); $name = trim($_POST['name'] ?? ''); $host = trim($_POST['host'] ?? ''); $port = (int)($_POST['port'] ?? 22); $username = trim($_POST['username'] ?? 'root'); $password = $_POST['password'] ?? ''; if (empty($name) || empty($host) || empty($password)) { View::render('servers/create.twig', ['error' => 'All fields are required']); return; } try { $serverId = VpnServer::create([ 'user_id' => $user['id'], 'name' => $name, 'host' => $host, 'port' => $port, 'username' => $username, 'password' => $password, ]); redirect('/servers/' . $serverId . '/deploy'); } catch (Exception $e) { View::render('servers/create.twig', ['error' => $e->getMessage()]); } }); // Delete server action Router::post('/servers/{id}/delete', function ($params) { requireAuth(); $user = Auth::user(); $serverId = (int)$params['id']; try { $server = new VpnServer($serverId); $serverData = $server->getData(); // Check ownership or admin if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo 'Forbidden'; return; } $server->delete(); $_SESSION['success_message'] = 'Server deleted successfully'; redirect('/servers'); } catch (Exception $e) { $_SESSION['error_message'] = $e->getMessage(); redirect('/servers'); } }); // Deploy server page Router::get('/servers/{id}/deploy', function ($params) { requireAuth(); $serverId = (int)$params['id']; try { $server = new VpnServer($serverId); $serverData = $server->getData(); // Check ownership $user = Auth::user(); if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo 'Forbidden'; return; } View::render('servers/deploy.twig', ['server' => $serverData]); } catch (Exception $e) { http_response_code(404); echo 'Server not found'; } }); // Deploy server action (AJAX) Router::post('/servers/{id}/deploy', function ($params) { requireAuth(); header('Content-Type: application/json'); $serverId = (int)$params['id']; try { $server = new VpnServer($serverId); $serverData = $server->getData(); // Check ownership $user = Auth::user(); if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo json_encode(['error' => 'Forbidden']); return; } $result = $server->deploy(); echo json_encode($result); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); // View server Router::get('/servers/{id}', function ($params) { requireAuth(); $serverId = (int)$params['id']; try { $server = new VpnServer($serverId); $serverData = $server->getData(); // Check ownership $user = Auth::user(); if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo 'Forbidden'; return; } // Get clients for this server $clients = VpnClient::listByServer($serverId); View::render('servers/view.twig', [ 'server' => $serverData, 'clients' => $clients, ]); } catch (Exception $e) { http_response_code(404); echo 'Server not found'; } }); // Delete server Router::post('/servers/{id}/delete', function ($params) { requireAuth(); $serverId = (int)$params['id']; try { $server = new VpnServer($serverId); $serverData = $server->getData(); // Check ownership $user = Auth::user(); if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo 'Forbidden'; return; } $server->delete(); redirect('/servers'); } catch (Exception $e) { redirect('/servers'); } }); // Create client for server Router::post('/servers/{id}/clients/create', function ($params) { requireAuth(); $serverId = (int)$params['id']; $clientName = trim($_POST['name'] ?? ''); if (empty($clientName)) { redirect('/servers/' . $serverId . '?error=Client+name+is+required'); return; } try { $server = new VpnServer($serverId); $serverData = $server->getData(); // Check ownership $user = Auth::user(); if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo 'Forbidden'; return; } $clientId = VpnClient::create($serverId, $user['id'], $clientName); redirect('/clients/' . $clientId); } catch (Exception $e) { redirect('/servers/' . $serverId . '?error=' . urlencode($e->getMessage())); } }); // View client Router::get('/clients/{id}', function ($params) { requireAuth(); $clientId = (int)$params['id']; try { $client = new VpnClient($clientId); $clientData = $client->getData(); // Check ownership $user = Auth::user(); if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo 'Forbidden'; return; } View::render('clients/view.twig', ['client' => $clientData]); } catch (Exception $e) { http_response_code(404); echo 'Client not found'; } }); // Download client config Router::get('/clients/{id}/download', function ($params) { requireAuth(); $clientId = (int)$params['id']; try { $client = new VpnClient($clientId); $clientData = $client->getData(); // Check ownership $user = Auth::user(); if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo 'Forbidden'; return; } $config = $client->getConfig(); $filename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $clientData['name']) . '.conf'; header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Length: ' . strlen($config)); echo $config; } catch (Exception $e) { http_response_code(404); echo 'Client not found'; } }); // Revoke client access Router::post('/clients/{id}/revoke', function ($params) { requireAuth(); $clientId = (int)$params['id']; try { $client = new VpnClient($clientId); $clientData = $client->getData(); // Check ownership $user = Auth::user(); if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo 'Forbidden'; return; } if ($client->revoke()) { redirect('/servers/' . $clientData['server_id'] . '?success=Client+revoked'); } else { redirect('/servers/' . $clientData['server_id'] . '?error=Failed+to+revoke+client'); } } catch (Exception $e) { redirect('/dashboard?error=' . urlencode($e->getMessage())); } }); // Restore client access Router::post('/clients/{id}/restore', function ($params) { requireAuth(); $clientId = (int)$params['id']; try { $client = new VpnClient($clientId); $clientData = $client->getData(); // Check ownership $user = Auth::user(); if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo 'Forbidden'; return; } if ($client->restore()) { redirect('/servers/' . $clientData['server_id'] . '?success=Client+restored'); } else { redirect('/servers/' . $clientData['server_id'] . '?error=Failed+to+restore+client'); } } catch (Exception $e) { redirect('/dashboard?error=' . urlencode($e->getMessage())); } }); // Delete client Router::post('/clients/{id}/delete', function ($params) { requireAuth(); $clientId = (int)$params['id']; try { $client = new VpnClient($clientId); $clientData = $client->getData(); // Check ownership $user = Auth::user(); if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo 'Forbidden'; return; } $serverId = $clientData['server_id']; if ($client->delete()) { redirect('/servers/' . $serverId . '?success=Client+deleted'); } else { redirect('/servers/' . $serverId . '?error=Failed+to+delete+client'); } } catch (Exception $e) { redirect('/dashboard?error=' . urlencode($e->getMessage())); } }); // Sync client stats Router::post('/clients/{id}/sync-stats', function ($params) { requireAuth(); $clientId = (int)$params['id']; header('Content-Type: application/json'); try { $client = new VpnClient($clientId); $clientData = $client->getData(); // Check ownership $user = Auth::user(); if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo json_encode(['error' => 'Forbidden']); return; } if ($client->syncStats()) { // Reload client data $client = new VpnClient($clientId); $stats = $client->getFormattedStats(); echo json_encode(['success' => true, 'stats' => $stats]); } else { echo json_encode(['success' => false, 'error' => 'Failed to sync stats']); } } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); // Sync all stats for server Router::post('/servers/{id}/sync-stats', function ($params) { requireAuth(); $serverId = (int)$params['id']; header('Content-Type: application/json'); try { $server = new VpnServer($serverId); $serverData = $server->getData(); // Check ownership $user = Auth::user(); if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo json_encode(['error' => 'Forbidden']); return; } $synced = VpnClient::syncAllStatsForServer($serverId); echo json_encode(['success' => true, 'synced' => $synced]); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); /** * API ROUTES (for Telegram bot integration) */ // API: Generate JWT token Router::post('/api/auth/token', function () { header('Content-Type: application/json'); $email = $_POST['email'] ?? ''; $password = $_POST['password'] ?? ''; if (empty($email) || empty($password)) { http_response_code(400); echo json_encode(['error' => 'Email and password are required']); return; } $user = Auth::getUserByEmail($email); if (!$user || !password_verify($password, $user['password_hash'])) { http_response_code(401); echo json_encode(['error' => 'Invalid credentials']); return; } try { $token = JWT::generate($user['id']); echo json_encode([ 'success' => true, 'token' => $token, 'type' => 'Bearer', 'expires_in' => 30 * 24 * 3600 // 30 days ]); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => 'Token generation failed']); } }); // API: Create persistent API token Router::post('/api/tokens', function () { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $name = $_POST['name'] ?? 'API Token'; $expiresIn = isset($_POST['expires_in']) ? (int)$_POST['expires_in'] : 2592000; // 30 days default try { $tokenData = JWT::createApiToken($user['id'], $name, $expiresIn); echo json_encode([ 'success' => true, 'token' => $tokenData ]); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); // API: List user's API tokens Router::get('/api/tokens', function () { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $stmt = DB::get()->prepare(" SELECT id, name, token, expires_at, created_at, last_used_at FROM api_tokens WHERE user_id = ? AND revoked_at IS NULL ORDER BY created_at DESC "); $stmt->execute([$user['id']]); $tokens = $stmt->fetchAll(); // Don't expose full token in list foreach ($tokens as &$token) { $token['token'] = substr($token['token'], 0, 10) . '...'; } echo json_encode(['tokens' => $tokens]); }); // API: Revoke API token Router::delete('/api/tokens/{id}', function ($params) { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; try { JWT::revokeApiToken($params['id'], $user['id']); echo json_encode(['success' => true]); } catch (Exception $e) { http_response_code(404); echo json_encode(['error' => $e->getMessage()]); } }); // API: List servers Router::get('/api/servers', function () { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $servers = VpnServer::listByUser($user['id']); echo json_encode(['servers' => $servers]); }); // API: Create server Router::post('/api/servers/create', function () { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $input = json_decode(file_get_contents('php://input'), true); $name = trim($input['name'] ?? ''); $host = trim($input['host'] ?? ''); $port = (int)($input['port'] ?? 22); $username = trim($input['username'] ?? 'root'); $password = $input['password'] ?? ''; if (empty($name) || empty($host) || empty($password)) { http_response_code(400); echo json_encode(['error' => 'Missing required fields: name, host, password']); return; } try { $serverId = VpnServer::create([ 'user_id' => $user['id'], 'name' => $name, 'host' => $host, 'port' => $port, 'username' => $username, 'password' => $password, ]); http_response_code(201); echo json_encode([ 'success' => true, 'server_id' => $serverId, 'message' => 'Server created successfully' ]); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); // API: Delete server Router::delete('/api/servers/{id}/delete', function ($params) { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $serverId = (int)$params['id']; try { $server = new VpnServer($serverId); $serverData = $server->getData(); // Check ownership or admin if ($serverData['user_id'] != $user['id'] && $user['role'] !== 'admin') { http_response_code(403); echo json_encode(['error' => 'Forbidden']); return; } $server->delete(); echo json_encode([ 'success' => true, 'message' => 'Server deleted successfully' ]); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); // API: List clients Router::get('/api/clients', function () { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $clients = VpnClient::listByUser($user['id']); echo json_encode(['clients' => $clients]); }); // API: Get client details with stats Router::get('/api/clients/{id}/details', function ($params) { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $clientId = (int)$params['id']; try { $client = new VpnClient($clientId); $clientData = $client->getData(); // Check ownership if ($clientData['user_id'] != $user['id']) { http_response_code(403); echo json_encode(['error' => 'Forbidden']); return; } // Sync stats before returning $client->syncStats(); // Reload data $client = new VpnClient($clientId); $clientData = $client->getData(); $stats = $client->getFormattedStats(); echo json_encode([ 'success' => true, 'client' => [ 'id' => $clientData['id'], 'name' => $clientData['name'], 'server_id' => $clientData['server_id'], 'client_ip' => $clientData['client_ip'], 'status' => $clientData['status'], 'created_at' => $clientData['created_at'], 'stats' => $stats, 'bytes_sent' => $clientData['bytes_sent'], 'bytes_received' => $clientData['bytes_received'], 'last_handshake' => $clientData['last_handshake'], ] ]); } catch (Exception $e) { http_response_code(404); echo json_encode(['error' => 'Client not found']); } }); // API: Revoke client Router::post('/api/clients/{id}/revoke', function ($params) { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $clientId = (int)$params['id']; try { $client = new VpnClient($clientId); $clientData = $client->getData(); // Check ownership if ($clientData['user_id'] != $user['id']) { http_response_code(403); echo json_encode(['error' => 'Forbidden']); return; } if ($client->revoke()) { echo json_encode(['success' => true, 'message' => 'Client revoked']); } else { http_response_code(500); echo json_encode(['error' => 'Failed to revoke client']); } } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); // API: Restore client Router::post('/api/clients/{id}/restore', function ($params) { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $clientId = (int)$params['id']; try { $client = new VpnClient($clientId); $clientData = $client->getData(); // Check ownership if ($clientData['user_id'] != $user['id']) { http_response_code(403); echo json_encode(['error' => 'Forbidden']); return; } if ($client->restore()) { echo json_encode(['success' => true, 'message' => 'Client restored']); } else { http_response_code(500); echo json_encode(['error' => 'Failed to restore client']); } } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); // API: Get server clients Router::get('/api/servers/{id}/clients', function ($params) { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $serverId = (int)$params['id']; try { $server = new VpnServer($serverId); $serverData = $server->getData(); // Check ownership $user = Auth::user(); if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) { http_response_code(403); echo json_encode(['error' => 'Forbidden']); return; } // Sync all stats first VpnClient::syncAllStatsForServer($serverId); $clients = VpnClient::listByServer($serverId); $clientsData = []; foreach ($clients as $clientData) { $client = new VpnClient($clientData['id']); $stats = $client->getFormattedStats(); $clientsData[] = [ 'id' => $clientData['id'], 'name' => $clientData['name'], 'client_ip' => $clientData['client_ip'], 'status' => $clientData['status'], 'created_at' => $clientData['created_at'], 'stats' => $stats, 'bytes_sent' => $clientData['bytes_sent'], 'bytes_received' => $clientData['bytes_received'], 'last_handshake' => $clientData['last_handshake'], ]; } echo json_encode(['success' => true, 'clients' => $clientsData]); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); // API: Create client Router::post('/api/clients/create', function () { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $raw = file_get_contents('php://input'); $data = json_decode($raw, true); $serverId = (int)($data['server_id'] ?? 0); $name = trim($data['name'] ?? ''); if ($serverId <= 0 || empty($name)) { http_response_code(400); echo json_encode(['error' => 'server_id and name are required']); return; } try { $clientId = VpnClient::create($serverId, $user['id'], $name); $client = new VpnClient($clientId); $clientData = $client->getData(); echo json_encode(['client' => $clientData]); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); /** * SETTINGS ROUTES */ // Settings page Router::get('/settings', function () { requireAuth(); require_once __DIR__ . '/../controllers/SettingsController.php'; $controller = new SettingsController(); $controller->index(); }); // Save API key Router::post('/settings/api-key', function () { requireAdmin(); require_once __DIR__ . '/../controllers/SettingsController.php'; $controller = new SettingsController(); $controller->saveApiKey(); }); // Change password Router::post('/settings/change-password', function () { requireAuth(); require_once __DIR__ . '/../controllers/SettingsController.php'; $controller = new SettingsController(); $controller->changePassword(); }); // Add user Router::post('/settings/add-user', function () { requireAdmin(); require_once __DIR__ . '/../controllers/SettingsController.php'; $controller = new SettingsController(); $controller->addUser(); }); // Delete user Router::post('/settings/delete-user/{id}', function ($params) { requireAdmin(); require_once __DIR__ . '/../controllers/SettingsController.php'; $controller = new SettingsController(); $controller->deleteUser($params['id']); }); /** * LANGUAGE ROUTES */ // Change language Router::post('/language/change', function () { $lang = $_POST['language'] ?? ''; if (Translator::setLanguage($lang)) { $_SESSION['success'] = 'Language changed successfully'; } else { $_SESSION['error'] = 'Invalid language'; } $redirect = $_POST['redirect'] ?? '/dashboard'; redirect($redirect); }); Router::get('/language/change', function () { redirect('/dashboard'); }); // API: Get translation statistics Router::get('/api/translations/stats', function () { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $stats = Translator::getStatistics(); echo json_encode(['stats' => $stats]); }); // API: Auto-translate missing keys Router::post('/api/translations/auto-translate', function () { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $raw = file_get_contents('php://input'); $data = json_decode($raw, true); $targetLang = $data['language'] ?? ''; if (empty($targetLang)) { http_response_code(400); echo json_encode(['error' => 'Language is required']); return; } try { $stats = Translator::translateMissingKeys($targetLang); echo json_encode([ 'success' => true, 'stats' => $stats ]); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); // API: Export translations Router::get('/api/translations/export/{lang}', function ($params) { header('Content-Type: application/json'); $user = JWT::requireAuth(); if (!$user) return; $lang = $params['lang']; try { $json = Translator::exportToJson($lang); header('Content-Disposition: attachment; filename="translations_' . $lang . '.json"'); echo $json; } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } }); // Dispatch router Router::dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);