Add Pushover notification channel and improve status detection

Introduces Pushover as a new notification channel with priority-based alerts, device targeting, and custom sounds. Enhances domain status detection for .nl and .eu domains, ensuring accurate handling when expiration dates or explicit status flags are missing. Fixes PHP 8.x compatibility issues with null parameters in date functions and improves error handling and logging by replacing error_log() with a centralized Logger service. Updates documentation and migrations for version 1.1.1.
This commit is contained in:
Hosteroid
2025-11-18 13:22:49 +02:00
parent 5b932aa565
commit 2b4035dd29
31 changed files with 684 additions and 97 deletions

View File

@@ -94,7 +94,11 @@ class AuthController extends Controller
}
if (!$user) {
error_log("Login failed: User '$username' not found or not active");
$logger = new \App\Services\Logger();
$logger->warning("Login failed - User not found or not active", [
'username' => $username,
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
]);
$_SESSION['error'] = 'Invalid username or password';
$this->redirect('/login');
return;
@@ -102,14 +106,22 @@ class AuthController extends Controller
// Verify password
if (!$this->userModel->verifyPassword($password, $user['password'])) {
error_log("Login failed: Password verification failed for user '$username'");
error_log("Stored hash: {$user['password']}");
$logger = new \App\Services\Logger();
$logger->warning("Login failed - Password verification failed", [
'username' => $username,
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
]);
$_SESSION['error'] = 'Invalid username or password';
$this->redirect('/login');
return;
}
error_log("Login successful for user '$username'");
$logger = new \App\Services\Logger();
$logger->info("Login successful", [
'username' => $username,
'user_id' => $user['id'],
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
]);
// Check if email verification is required
$requireVerification = $this->settingModel->getValue('require_email_verification');
@@ -310,7 +322,11 @@ class AuthController extends Controller
$notificationService->notifyWelcome($userId, $username);
} catch (\Exception $e) {
// Don't fail registration if notification fails
error_log("Failed to create welcome notification: " . $e->getMessage());
$logger = new \App\Services\Logger();
$logger->error("Failed to create welcome notification", [
'user_id' => $userId,
'error' => $e->getMessage()
]);
}
// Check if email verification is required
@@ -684,7 +700,11 @@ class AuthController extends Controller
} catch (\Exception $e) {
// Silently fail - remember me is not critical
error_log("Failed to create remember token: " . $e->getMessage());
$logger = new \App\Services\Logger();
$logger->error("Failed to create remember token", [
'user_id' => $userId,
'error' => $e->getMessage()
]);
}
}

View File

@@ -212,7 +212,7 @@ class DomainController extends Controller
}
// Create domain
$status = $this->whoisService->getDomainStatus($whoisData['expiration_date'], $whoisData['status'] ?? []);
$status = $this->whoisService->getDomainStatus($whoisData['expiration_date'], $whoisData['status'] ?? [], $whoisData);
// Warn if domain is available (not registered)
if ($status === 'available') {
@@ -462,7 +462,7 @@ class DomainController extends Controller
// Use WHOIS expiration date if available, otherwise preserve manual expiration date
$expirationDate = $whoisData['expiration_date'] ?? $domain['expiration_date'];
$status = $this->whoisService->getDomainStatus($expirationDate, $whoisData['status'] ?? []);
$status = $this->whoisService->getDomainStatus($expirationDate, $whoisData['status'] ?? [], $whoisData);
$this->domainModel->update($id, [
'registrar' => $whoisData['registrar'],
@@ -673,7 +673,7 @@ class DomainController extends Controller
continue;
}
$status = $this->whoisService->getDomainStatus($whoisData['expiration_date'], $whoisData['status'] ?? []);
$status = $this->whoisService->getDomainStatus($whoisData['expiration_date'], $whoisData['status'] ?? [], $whoisData);
// Track available domains
if ($status === 'available') {
@@ -792,7 +792,7 @@ class DomainController extends Controller
// Use WHOIS expiration date if available, otherwise preserve manual expiration date
$expirationDate = $whoisData['expiration_date'] ?? $domain['expiration_date'];
$status = $this->whoisService->getDomainStatus($expirationDate, $whoisData['status'] ?? []);
$status = $this->whoisService->getDomainStatus($expirationDate, $whoisData['status'] ?? [], $whoisData);
$this->domainModel->update($id, [
'registrar' => $whoisData['registrar'],

View File

@@ -52,6 +52,8 @@ class InstallerController extends Controller
'019_add_webhook_channel_type.sql',
'020_create_tags_system.sql',
'021_add_avatar_field.sql',
'022_add_pushover_channel_type.sql',
'023_update_app_version_to_1.1.1.sql',
];
try {
@@ -175,7 +177,7 @@ class InstallerController extends Controller
$stmt->execute([$migration]);
}
// Return only new migrations for v1.1.0
// Return only new migrations for v1.1.x
return [
'009_add_authentication_features.sql',
'010_add_app_version_setting.sql',
@@ -189,7 +191,9 @@ class InstallerController extends Controller
'018_add_user_isolation.sql',
'019_add_webhook_channel_type.sql',
'020_create_tags_system.sql',
'021_add_avatar_field.sql'
'021_add_avatar_field.sql',
'022_add_pushover_channel_type.sql',
'023_update_app_version_to_1.1.1.sql',
];
}
@@ -373,6 +377,8 @@ class InstallerController extends Controller
'019_add_webhook_channel_type.sql',
'020_create_tags_system.sql',
'021_add_avatar_field.sql',
'022_add_pushover_channel_type.sql',
'023_update_app_version_to_1.1.1.sql',
];
$stmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?) ON DUPLICATE KEY UPDATE migration=migration");
@@ -591,10 +597,12 @@ class InstallerController extends Controller
// Determine from/to versions based on migrations
$fromVersion = '1.0.0';
$toVersion = '1.1.0';
$toVersion = '1.1.1';
// Detect version based on which migrations were run
if (in_array('011_create_sessions_table.sql', $executed) ||
if (in_array('022_add_pushover_channel_type.sql', $executed)) {
$toVersion = '1.1.1';
} elseif (in_array('011_create_sessions_table.sql', $executed) ||
in_array('012_link_remember_tokens_to_sessions.sql', $executed) ||
in_array('013_create_user_notifications_table.sql', $executed)) {
$toVersion = '1.1.0';

View File

@@ -305,6 +305,9 @@ class NotificationGroupController extends Controller
case 'webhook':
$missingField = 'webhook URL';
break;
case 'pushover':
$missingField = empty($_POST['pushover_api_token']) ? 'API token' : 'user key';
break;
}
$_SESSION['error'] = "Invalid channel configuration: Missing {$missingField}";
@@ -545,6 +548,39 @@ class NotificationGroupController extends Controller
}
return ['webhook_url' => $webhookUrl];
case 'pushover':
$apiToken = trim($data['pushover_api_token'] ?? '');
$userKey = trim($data['pushover_user_key'] ?? '');
// Both API token and user key are required
if (empty($apiToken) || empty($userKey)) {
return null;
}
// Basic validation for Pushover token format (30 characters, alphanumeric)
if (!preg_match('/^[a-zA-Z0-9]{30}$/', $apiToken) || !preg_match('/^[a-zA-Z0-9]{30}$/', $userKey)) {
return null;
}
$config = [
'api_token' => $apiToken,
'user_key' => $userKey
];
// Optional: Device name
$device = trim($data['pushover_device'] ?? '');
if (!empty($device)) {
$config['device'] = $device;
}
// Optional: Sound
$sound = trim($data['pushover_sound'] ?? '');
if (!empty($sound)) {
$config['sound'] = $sound;
}
return $config;
case 'webhook':
$webhookUrl = trim($data['webhook_url'] ?? '');
if (empty($webhookUrl) || !filter_var($webhookUrl, FILTER_VALIDATE_URL)) {

View File

@@ -45,7 +45,11 @@ class ProfileController extends Controller
$this->sessionModel->cleanOldSessions();
} catch (\Exception $e) {
// Silent fail - don't break the page
error_log("Session cleanup failed: " . $e->getMessage());
$logger = new \App\Services\Logger();
$logger->error('Session cleanup failed', [
'user_id' => \Core\Auth::id(),
'error' => $e->getMessage()
]);
}
// Get all active sessions

View File

@@ -179,7 +179,11 @@ class UserController extends Controller
$notificationService->notifyWelcome($userId, $username);
} catch (\Exception $e) {
// Don't fail user creation if notification fails
error_log("Failed to create welcome notification: " . $e->getMessage());
$logger = new \App\Services\Logger();
$logger->error("Failed to create welcome notification", [
'user_id' => $userId,
'error' => $e->getMessage()
]);
}
$_SESSION['success'] = 'User created successfully';