diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php index 8aa051c..d21b5ba 100644 --- a/app/Controllers/AdminController.php +++ b/app/Controllers/AdminController.php @@ -64,6 +64,22 @@ class AdminController extends Controller { return $db->query("SELECT SUM(total_downloads) FROM apks")->fetchColumn() ?: 0; } + // Member Management + public function users() { + $this->checkAuth(); + $db = db_pdo(); + $users = $db->query("SELECT * FROM users ORDER BY created_at DESC")->fetchAll(); + $this->view('admin/users/index', ['users' => $users]); + } + + public function toggleBan($params) { + $this->checkAuth(); + $db = db_pdo(); + $stmt = $db->prepare("UPDATE users SET is_banned = NOT is_banned WHERE id = ? AND role != 'admin'"); + $stmt->execute([$params['id']]); + $this->redirect('/admin/users'); + } + // APK Management public function apks() { $this->checkAuth(); @@ -345,4 +361,4 @@ class AdminController extends Controller { $text = strtolower($text); return empty($text) ? 'n-a' : $text; } -} \ No newline at end of file +} diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php index 5d29d06..ebe7bce 100644 --- a/app/Controllers/AuthController.php +++ b/app/Controllers/AuthController.php @@ -25,18 +25,67 @@ class AuthController extends Controller { public function login() { $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; + $ip = get_client_ip(); $db = db_pdo(); - $stmt = $db->prepare("SELECT * FROM users WHERE username = ? AND role = 'user'"); + + // Anti-Brute Force check by IP (for non-existent users) + $stmt = $db->prepare("SELECT attempts, last_attempt FROM login_logs WHERE ip_address = ?"); + $stmt->execute([$ip]); + $ip_log = $stmt->fetch(); + + if ($ip_log && $ip_log['attempts'] >= 10 && (time() - strtotime($ip_log['last_attempt'])) < 900) { + $this->view('auth/login', ['error' => 'Too many failed attempts from this IP. Please try again in 15 minutes.']); + return; + } + + $stmt = $db->prepare("SELECT * FROM users WHERE username = ?"); $stmt->execute([$username]); $user = $stmt->fetch(); - if ($user && password_verify($password, $user['password'])) { - $_SESSION['user_id'] = $user['id']; - $_SESSION['username'] = $user['username']; - $_SESSION['role'] = $user['role']; - $this->redirect('/profile'); + if ($user) { + // Check if account is banned + if ($user['is_banned']) { + $this->view('auth/login', ['error' => 'Your account has been banned. Please contact support.']); + return; + } + + // Check brute force for specific user + if ($user['login_attempts'] >= 5 && (time() - strtotime($user['last_attempt_time'])) < 900) { + $this->view('auth/login', ['error' => 'Too many failed attempts. Please try again in 15 minutes.']); + return; + } + + if (password_verify($password, $user['password'])) { + // Reset attempts + $stmt = $db->prepare("UPDATE users SET login_attempts = 0, last_ip = ? WHERE id = ?"); + $stmt->execute([$ip, $user['id']]); + + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $user['username']; + $_SESSION['role'] = $user['role']; + + if ($user['role'] === 'admin') { + $this->redirect('/admin'); + } else { + $this->redirect('/profile'); + } + } else { + // Increment attempts + $stmt = $db->prepare("UPDATE users SET login_attempts = login_attempts + 1, last_attempt_time = NOW() WHERE id = ?"); + $stmt->execute([$user['id']]); + + $this->view('auth/login', ['error' => __('error_invalid_login')]); + } } else { + // Log failed attempt by IP + if ($ip_log) { + $stmt = $db->prepare("UPDATE login_logs SET attempts = attempts + 1, last_attempt = NOW() WHERE ip_address = ?"); + $stmt->execute([$ip]); + } else { + $stmt = $db->prepare("INSERT INTO login_logs (ip_address, attempts) VALUES (?, 1)"); + $stmt->execute([$ip]); + } $this->view('auth/login', ['error' => __('error_invalid_login')]); } } @@ -46,6 +95,15 @@ class AuthController extends Controller { $password = $_POST['password'] ?? ''; $confirm_password = $_POST['confirm_password'] ?? ''; $ref_code = $_POST['ref_code'] ?? ''; + $honeypot = $_POST['full_name'] ?? ''; // Hidden field + $ip = get_client_ip(); + + // Bot protection (Honeypot) + if (!empty($honeypot)) { + // Silent fail or show error + $this->view('auth/register', ['error' => 'Bot detected.', 'ref' => $ref_code]); + return; + } if ($password !== $confirm_password) { $this->view('auth/register', ['error' => __('error_password_mismatch'), 'ref' => $ref_code]); @@ -54,6 +112,14 @@ class AuthController extends Controller { $db = db_pdo(); + // Multi-account check (Anti-bot/Anti-cheat) + $stmt = $db->prepare("SELECT COUNT(*) FROM users WHERE registration_ip = ? AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)"); + $stmt->execute([$ip]); + if ($stmt->fetchColumn() >= 3) { + $this->view('auth/register', ['error' => 'Too many registrations from this IP. Please try again later.', 'ref' => $ref_code]); + return; + } + // Check if username exists $stmt = $db->prepare("SELECT id FROM users WHERE username = ?"); $stmt->execute([$username]); @@ -72,15 +138,27 @@ class AuthController extends Controller { $referrer = $stmt->fetch(); if ($referrer) { $referred_by = $referrer['id']; + + // Anti-self referral check + $stmt_ip = $db->prepare("SELECT registration_ip FROM users WHERE id = ?"); + $stmt_ip->execute([$referred_by]); + $referrer_ip = $stmt_ip->fetchColumn(); + + if ($referrer_ip === $ip) { + // Possible self-referral, mark but allow or block? + // Let's block if IP matches exactly + $this->view('auth/register', ['error' => 'Self-referral is not allowed.', 'ref' => $ref_code]); + return; + } } } - $stmt = $db->prepare("INSERT INTO users (username, password, referral_code, referred_by, role, balance) VALUES (?, ?, ?, ?, 'user', 0)"); - $stmt->execute([$username, $hashed_password, $referral_code, $referred_by]); + $stmt = $db->prepare("INSERT INTO users (username, password, referral_code, referred_by, role, balance, registration_ip, last_ip) VALUES (?, ?, ?, ?, 'user', 0, ?, ?)"); + $stmt->execute([$username, $hashed_password, $referral_code, $referred_by, $ip, $ip]); $userId = $db->lastInsertId(); if ($referred_by) { - // Reward referrer with points (not balance yet, as per previous logic) + // Reward referrer $stmt = $db->prepare("UPDATE users SET points = points + 10, total_referrals = total_referrals + 1 WHERE id = ?"); $stmt->execute([$referred_by]); } @@ -155,4 +233,4 @@ class AuthController extends Controller { $_SESSION['success'] = __('success_withdraw_submitted'); $this->redirect('/profile'); } -} \ No newline at end of file +} diff --git a/app/Helpers/functions.php b/app/Helpers/functions.php index 3c24914..1dec2e6 100644 --- a/app/Helpers/functions.php +++ b/app/Helpers/functions.php @@ -47,4 +47,31 @@ function compress_image($source, $destination, $quality) { imagejpeg($image, $destination, $quality); return $destination; -} \ No newline at end of file +} + +function get_client_ip() { + $ipaddress = ''; + if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) + $ipaddress = $_SERVER['HTTP_CF_CONNECTING_IP']; + else if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) + $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR']; + else if(isset($_SERVER['HTTP_X_FORWARDED'])) + $ipaddress = $_SERVER['HTTP_X_FORWARDED']; + else if(isset($_SERVER['HTTP_X_CLUSTER_CLIENT_IP'])) + $ipaddress = $_SERVER['HTTP_X_CLUSTER_CLIENT_IP']; + else if(isset($_SERVER['HTTP_FORWARDED_FOR'])) + $ipaddress = $_SERVER['HTTP_FORWARDED_FOR']; + else if(isset($_SERVER['HTTP_FORWARDED'])) + $ipaddress = $_SERVER['HTTP_FORWARDED']; + else if(isset($_SERVER['REMOTE_ADDR'])) + $ipaddress = $_SERVER['REMOTE_ADDR']; + else + $ipaddress = 'UNKNOWN'; + + if (strpos($ipaddress, ',') !== false) { + $ips = explode(',', $ipaddress); + $ipaddress = trim($ips[0]); + } + + return $ipaddress; +} diff --git a/assets/css/custom.css b/assets/css/custom.css index b1514e6..606ae33 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,9 +1,29 @@ body { font-family: 'Inter', sans-serif; - background-color: #F8FAFC; + /* Animated gradient background for a subtle dynamic feel */ + background: linear-gradient(-45deg, #ffffff, #f8fafc, #f1f5f9, #f8fafc); + background-size: 400% 400%; + animation: gradientBG 15s ease infinite; + background-attachment: fixed; color: #1E293B; + position: relative; + min-height: 100vh; } +@keyframes gradientBG { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +/* Background blobs are visible and animated */ +.bg-blob { + display: block !important; + pointer-events: none; +} + +.opacity-10 { opacity: 0.1; } + .hover-lift { transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; } @@ -48,72 +68,56 @@ body { background-color: #10B981 !important; } +.bg-primary { + background-color: #3B82F6 !important; +} + +.bg-warning { + background-color: #F59E0B !important; +} + .bg-success-subtle { background-color: #ECFDF5 !important; } -.btn-white { - background-color: #FFFFFF; - color: #10B981; - border: none; -} - -.btn-white:hover { - background-color: #F8FAFC; - color: #059669; -} - .rounded-4 { border-radius: 1rem !important; } .rounded-5 { border-radius: 1.5rem !important; } -.navbar-brand i { - font-size: 1.5rem; - vertical-align: middle; +/* Navbar styling with glassmorphism */ +.navbar { + background: rgba(255, 255, 255, 0.8) !important; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border-bottom: 1px solid rgba(255, 255, 255, 0.3); } -/* Specific styles for APK Detail matching the image */ -.breadcrumb-item + .breadcrumb-item::before { - content: "/"; +.navbar-brand { + font-size: 1.25rem; } -.x-small { - font-size: 0.75rem; +/* Card styling */ +.card { + border: none; + background-color: #FFFFFF; } -.bg-light { - background-color: #F1F5F9 !important; -} - -.shadow-sm { - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important; -} - -.badge { - font-weight: 500; -} - -.border-success-subtle { - border-color: #A7F3D0 !important; -} - -.border-info-subtle { - border-color: #BAE6FD !important; -} - -/* Mobile adjustments */ +/* Mobile adjustments to match the screenshot */ @media (max-width: 767.98px) { - .container { - padding-left: 1.25rem; - padding-right: 1.25rem; + .display-4 { + font-size: 2rem !important; + line-height: 1.2; } - .display-5 { - font-size: 1.75rem; + .lead { + font-size: 1rem; } .btn-lg { - padding-top: 0.75rem; - padding-bottom: 0.75rem; + padding: 0.75rem 1.5rem; font-size: 1rem; } + + .card-title { + font-size: 0.85rem; + } } \ No newline at end of file diff --git a/assets/pasted-20260225-124200-16a7c097.jpg b/assets/pasted-20260225-124200-16a7c097.jpg new file mode 100644 index 0000000..638838d Binary files /dev/null and b/assets/pasted-20260225-124200-16a7c097.jpg differ diff --git a/db/migrations/20260225_security_updates.sql b/db/migrations/20260225_security_updates.sql new file mode 100644 index 0000000..c7d02aa --- /dev/null +++ b/db/migrations/20260225_security_updates.sql @@ -0,0 +1,16 @@ +-- Add security and tracking columns to users table +ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `is_banned` TINYINT(1) DEFAULT 0; +ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `registration_ip` VARCHAR(45) DEFAULT NULL; +ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `last_ip` VARCHAR(45) DEFAULT NULL; +ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `login_attempts` INT DEFAULT 0; +ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `last_attempt_time` TIMESTAMP NULL DEFAULT NULL; +ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `registration_date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP; + +-- Create a table for brute force tracking by IP (for non-existent users) +CREATE TABLE IF NOT EXISTS `login_logs` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `ip_address` VARCHAR(45) NOT NULL, + `attempts` INT DEFAULT 1, + `last_attempt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX (`ip_address`) +); diff --git a/index.php b/index.php index 6b92362..56a200d 100644 --- a/index.php +++ b/index.php @@ -72,6 +72,10 @@ $router->get('/admin/dashboard', 'AdminController@dashboard'); $router->get('/admin/settings', 'AdminController@settingsForm'); $router->post('/admin/settings', 'AdminController@saveSettings'); +// Admin Users +$router->get('/admin/users', 'AdminController@users'); +$router->post('/admin/users/toggle-ban/:id', 'AdminController@toggleBan'); + // Admin APKs $router->get('/admin/apks', 'AdminController@apks'); $router->get('/admin/apks/mass-upload', 'AdminController@massUploadForm'); @@ -93,4 +97,4 @@ $router->get('/admin/withdrawals', 'AdminController@withdrawals'); $router->get('/admin/withdrawals/approve/:id', 'AdminController@approveWithdrawal'); $router->get('/admin/withdrawals/reject/:id', 'AdminController@rejectWithdrawal'); -$router->dispatch(); \ No newline at end of file +$router->dispatch(); diff --git a/views/admin/header.php b/views/admin/header.php index 3ee897d..06d81fc 100644 --- a/views/admin/header.php +++ b/views/admin/header.php @@ -56,6 +56,9 @@ + @@ -99,4 +102,4 @@ - \ No newline at end of file + diff --git a/views/admin/users/index.php b/views/admin/users/index.php new file mode 100644 index 0000000..c75ecd0 --- /dev/null +++ b/views/admin/users/index.php @@ -0,0 +1,72 @@ + + +
+
+

Member Management

+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
UserStatusIP AddressesBalanceJoinedActions
+
+
+ +
+
+
+ +
+
+
+ + Banned + + Active + + +
+ Reg IP: + +
+ Last IP: + +
+
Rp + +
+ +
+ +
+
+
+
+
+ + diff --git a/views/auth/register.php b/views/auth/register.php index 58ae47c..4172fac 100644 --- a/views/auth/register.php +++ b/views/auth/register.php @@ -11,6 +11,11 @@
+ +
+ +
+
@@ -40,4 +45,4 @@
- \ No newline at end of file + diff --git a/views/header.php b/views/header.php index 8ceba9c..1033dbc 100644 --- a/views/header.php +++ b/views/header.php @@ -12,7 +12,7 @@ - + @@ -20,41 +20,47 @@ -