diff --git a/.env.example b/.env.example index 95d1fcf..4e1554d 100644 --- a/.env.example +++ b/.env.example @@ -15,3 +15,7 @@ TV_SEASON_ID=your_season_id_here # Application Settings APP_ENV=production APP_DEBUG=false + +# Authentication Settings +# Session timeout in seconds (default: 7200 = 2 hours) +AUTH_SESSION_TIMEOUT=7200 diff --git a/.gitignore b/.gitignore index 887a2c2..f786d2c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ *.sln.docstates .env .DS_Store +.auth_setup_done # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/INSTALLATION.md b/INSTALLATION.md index e6363b0..7eff7e5 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -61,9 +61,11 @@ EXIT; ### Stap 4: Database Migratie -**4.1 Voer migratie uit:** +**4.1 Voer migraties uit:** ```bash mysql -u talpa_user -p talpa_planning < migrations/001_add_blocks_and_colors.sql +mysql -u talpa_user -p talpa_planning < migrations/002_add_talpa_transmission_id.sql +mysql -u talpa_user -p talpa_planning < migrations/003_add_authentication.sql ``` **4.2 Controleer of tabellen zijn aangemaakt:** @@ -73,9 +75,12 @@ mysql -u talpa_user -p talpa_planning -e "SHOW TABLES;" Je zou moeten zien: - block_templates -- infomercials - daily_blocks +- infomercials +- login_attempts +- sessions - transmissions +- users ### Stap 5: Environment Configuratie @@ -189,9 +194,34 @@ server { sudo systemctl restart nginx ``` -### Stap 8: Test de Installatie +### Stap 8: Authenticatie Setup -**8.1 Open in browser:** +**8.1 Voer de setup script uit:** + +Via browser: +``` +http://localhost/telvero_whatson_talpa/setup_auth.php +``` + +Of via command line: +```bash +php setup_auth.php +``` + +Dit maakt de standaard gebruikers aan: +- **Admin**: gebruikersnaam `admin`, wachtwoord `Admin@2026!` +- **Guest**: gebruikersnaam `guest`, wachtwoord `Guest@2026!` + +⚠️ **Wijzig deze wachtwoorden na de eerste login!** + +**8.2 Verwijder de setup script na gebruik:** +```bash +rm setup_auth.php +``` + +### Stap 9: Test de Installatie + +**9.1 Open in browser:** ``` http://localhost/telvero_whatson_talpa/ ``` @@ -349,13 +379,20 @@ Bij problemen: - [ ] PHP 7.4+ geïnstalleerd - [ ] MySQL database aangemaakt - [ ] Composer dependencies geïnstalleerd -- [ ] Database migratie uitgevoerd +- [ ] Database migraties uitgevoerd (001, 002, 003) - [ ] .env file geconfigureerd - [ ] Bestandspermissies ingesteld - [ ] Webserver geconfigureerd -- [ ] Dashboard bereikbaar in browser +- [ ] setup_auth.php uitgevoerd +- [ ] setup_auth.php verwijderd +- [ ] Login pagina bereikbaar +- [ ] Admin login werkt +- [ ] Guest login werkt +- [ ] Dashboard bereikbaar na login - [ ] Blok templates zichtbaar -- [ ] Test infomercial aangemaakt +- [ ] Test infomercial aangemaakt (admin) - [ ] Kalender laadt correct +- [ ] Gebruikersbeheer bereikbaar (admin) +- [ ] Standaard wachtwoorden gewijzigd Als alle items zijn afgevinkt, is de installatie succesvol! 🎉 diff --git a/admin/users.php b/admin/users.php new file mode 100644 index 0000000..982d21f --- /dev/null +++ b/admin/users.php @@ -0,0 +1,420 @@ +load(); + +require_once __DIR__ . '/../auth/auth_functions.php'; + +// Only admins can access this page +requireAdmin(); + +$db = getAuthDb(); +$success = ''; +$error = ''; + +// Handle create user +if (isset($_POST['create_user'])) { + $username = trim($_POST['username'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $password = $_POST['password'] ?? ''; + $role = $_POST['role'] ?? 'guest'; + + if (empty($username) || empty($email) || empty($password)) { + $error = 'Vul alle verplichte velden in.'; + } elseif (strlen($password) < 8) { + $error = 'Wachtwoord moet minimaal 8 tekens lang zijn.'; + } elseif (!in_array($role, ['admin', 'guest'])) { + $error = 'Ongeldige rol geselecteerd.'; + } else { + try { + $passwordHash = password_hash($password, PASSWORD_BCRYPT); + $stmt = $db->prepare(" + INSERT INTO users (username, email, password_hash, role, is_active) + VALUES (?, ?, ?, ?, 1) + "); + $stmt->execute([$username, $email, $passwordHash, $role]); + $success = "Gebruiker '{$username}' succesvol aangemaakt."; + } catch (PDOException $e) { + if ($e->getCode() == 23000) { + $error = 'Gebruikersnaam of email is al in gebruik.'; + } else { + $error = 'Er is een fout opgetreden bij het aanmaken van de gebruiker.'; + } + } + } +} + +// Handle update user +if (isset($_POST['update_user'])) { + $userId = (int)($_POST['user_id'] ?? 0); + $username = trim($_POST['username'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $role = $_POST['role'] ?? 'guest'; + $isActive = isset($_POST['is_active']) ? 1 : 0; + $newPassword = $_POST['new_password'] ?? ''; + + if (empty($username) || empty($email)) { + $error = 'Vul alle verplichte velden in.'; + } elseif (!in_array($role, ['admin', 'guest'])) { + $error = 'Ongeldige rol geselecteerd.'; + } else { + try { + if (!empty($newPassword)) { + if (strlen($newPassword) < 8) { + $error = 'Nieuw wachtwoord moet minimaal 8 tekens lang zijn.'; + } else { + $passwordHash = password_hash($newPassword, PASSWORD_BCRYPT); + $stmt = $db->prepare(" + UPDATE users + SET username = ?, email = ?, password_hash = ?, role = ?, is_active = ? + WHERE id = ? + "); + $stmt->execute([$username, $email, $passwordHash, $role, $isActive, $userId]); + $success = "Gebruiker succesvol bijgewerkt (inclusief wachtwoord)."; + } + } else { + $stmt = $db->prepare(" + UPDATE users + SET username = ?, email = ?, role = ?, is_active = ? + WHERE id = ? + "); + $stmt->execute([$username, $email, $role, $isActive, $userId]); + $success = "Gebruiker succesvol bijgewerkt."; + } + } catch (PDOException $e) { + if ($e->getCode() == 23000) { + $error = 'Gebruikersnaam of email is al in gebruik.'; + } else { + $error = 'Er is een fout opgetreden bij het bijwerken van de gebruiker.'; + } + } + } +} + +// Handle delete user +if (isset($_POST['delete_user'])) { + $userId = (int)($_POST['user_id'] ?? 0); + $currentUserId = getCurrentUser()['id']; + + if ($userId === $currentUserId) { + $error = 'Je kunt je eigen account niet verwijderen.'; + } else { + try { + $stmt = $db->prepare("DELETE FROM users WHERE id = ?"); + $stmt->execute([$userId]); + $success = "Gebruiker succesvol verwijderd."; + } catch (PDOException $e) { + $error = 'Er is een fout opgetreden bij het verwijderen van de gebruiker.'; + } + } +} + +// Get all users +$users = $db->query(" + SELECT id, username, email, role, is_active, last_login, created_at + FROM users + ORDER BY created_at DESC +")->fetchAll(); + +// Get login stats +$loginStats = $db->query(" + SELECT + COUNT(*) as total_attempts, + SUM(success) as successful_logins, + COUNT(*) - SUM(success) as failed_logins + FROM login_attempts + WHERE attempted_at > DATE_SUB(NOW(), INTERVAL 24 HOUR) +")->fetch(); + +$activePage = 'users'; +?> + + + + + + Gebruikersbeheer - Telvero Talpa + + + + + + + +
+
+

Gebruikersbeheer

+ +
+ + + + + + + + + + +
+
+
+
+
+
Totaal Gebruikers
+
+
+
+
+
+
+
+
Succesvolle Logins (24u)
+
+
+
+
+
+
+
+
Mislukte Logins (24u)
+
+
+
+
+ + +
+
+
Gebruikers
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDGebruikersnaamEmailRolStatusLaatste LoginAangemaaktActies
+ + + + Jij + + + + + + + + + + + + Nooit' ?> + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + diff --git a/api/assign_color.php b/api/assign_color.php index 918ea18..60041ad 100644 --- a/api/assign_color.php +++ b/api/assign_color.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,20 @@ $dotenv->load(); header('Content-Type: application/json'); +// Authentication check +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd. Log eerst in.']); + exit; +} + +// Authorization check - admin only +if (!canEdit()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'Geen toegang. Alleen admins kunnen kleuren toewijzen.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/copy_block.php b/api/copy_block.php index 2e7f919..62a92b3 100644 --- a/api/copy_block.php +++ b/api/copy_block.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,20 @@ $dotenv->load(); header('Content-Type: application/json'); +// Authentication check +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd. Log eerst in.']); + exit; +} + +// Authorization check - admin only +if (!canEdit()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'Geen toegang. Alleen admins kunnen blokken kopiëren.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/create_transmission.php b/api/create_transmission.php index de436bb..3b57689 100644 --- a/api/create_transmission.php +++ b/api/create_transmission.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,20 @@ $dotenv->load(); header('Content-Type: application/json'); +// Authentication check +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd. Log eerst in.']); + exit; +} + +// Authorization check - admin only for write operations +if (!canCreate()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'Geen toegang. Alleen admins kunnen uitzendingen aanmaken.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/delete_transmission.php b/api/delete_transmission.php index 7048147..60f83c3 100644 --- a/api/delete_transmission.php +++ b/api/delete_transmission.php @@ -7,6 +7,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; require_once __DIR__ . '/../TalpaAPI.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -15,6 +16,20 @@ $dotenv->load(); header('Content-Type: application/json'); +// Authentication check +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd. Log eerst in.']); + exit; +} + +// Authorization check - admin only +if (!canDelete()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'Geen toegang. Alleen admins kunnen uitzendingen verwijderen.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/get_available_source_blocks.php b/api/get_available_source_blocks.php index 43964e0..ca0172f 100644 --- a/api/get_available_source_blocks.php +++ b/api/get_available_source_blocks.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,12 @@ $dotenv->load(); header('Content-Type: application/json'); +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/get_block_details.php b/api/get_block_details.php index 085fea7..70f26cc 100644 --- a/api/get_block_details.php +++ b/api/get_block_details.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,12 @@ $dotenv->load(); header('Content-Type: application/json'); +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/get_block_time.php b/api/get_block_time.php index fa8ce72..1447a91 100644 --- a/api/get_block_time.php +++ b/api/get_block_time.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,12 @@ $dotenv->load(); header('Content-Type: application/json'); +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/get_daily_blocks.php b/api/get_daily_blocks.php index cd72f72..1bff893 100644 --- a/api/get_daily_blocks.php +++ b/api/get_daily_blocks.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,12 @@ $dotenv->load(); header('Content-Type: application/json'); +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/get_next_start_time.php b/api/get_next_start_time.php index 3c47c9f..c900a16 100644 --- a/api/get_next_start_time.php +++ b/api/get_next_start_time.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,12 @@ $dotenv->load(); header('Content-Type: application/json'); +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/get_transmissions.php b/api/get_transmissions.php index e2867ca..dc3419f 100644 --- a/api/get_transmissions.php +++ b/api/get_transmissions.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,13 @@ $dotenv->load(); header('Content-Type: application/json'); +// Authentication check - all logged in users can read +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd. Log eerst in.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/insert_transmission_at_position.php b/api/insert_transmission_at_position.php index c46a3ec..4a3845c 100644 --- a/api/insert_transmission_at_position.php +++ b/api/insert_transmission_at_position.php @@ -7,6 +7,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../TalpaAPI.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -15,6 +16,20 @@ $dotenv->load(); header('Content-Type: application/json'); +// Authentication check +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd. Log eerst in.']); + exit; +} + +// Authorization check - admin only +if (!canCreate()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'Geen toegang. Alleen admins kunnen uitzendingen invoegen.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/sync_block.php b/api/sync_block.php index d387a32..2e0843e 100644 --- a/api/sync_block.php +++ b/api/sync_block.php @@ -11,6 +11,7 @@ error_reporting(E_ALL); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../TalpaAPI.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; $dotenv = Dotenv::createImmutable(__DIR__ . '/..'); @@ -18,6 +19,20 @@ $dotenv->load(); header('Content-Type: application/json'); +// Authentication check +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd. Log eerst in.']); + exit; +} + +// Authorization check - admin only +if (!canSync()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'Geen toegang. Alleen admins kunnen synchroniseren.']); + exit; +} + $api = new TalpaApi(); $db = getDbConnection(); diff --git a/api/update_block_time.php b/api/update_block_time.php index 437e515..01c6007 100644 --- a/api/update_block_time.php +++ b/api/update_block_time.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,20 @@ $dotenv->load(); header('Content-Type: application/json'); +// Authentication check +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd. Log eerst in.']); + exit; +} + +// Authorization check - admin only +if (!canEdit()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'Geen toegang. Alleen admins kunnen bloktijden bijwerken.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/update_transmission.php b/api/update_transmission.php index 5fb3111..f1da1a7 100644 --- a/api/update_transmission.php +++ b/api/update_transmission.php @@ -7,6 +7,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../TalpaAPI.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -15,6 +16,20 @@ $dotenv->load(); header('Content-Type: application/json'); +// Authentication check +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd. Log eerst in.']); + exit; +} + +// Authorization check - admin only +if (!canEdit()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'Geen toegang. Alleen admins kunnen uitzendingen bijwerken.']); + exit; +} + try { $db = getDbConnection(); diff --git a/api/validate_transmission_time.php b/api/validate_transmission_time.php index 234991f..23b8f20 100644 --- a/api/validate_transmission_time.php +++ b/api/validate_transmission_time.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../helpers.php'; +require_once __DIR__ . '/../auth/auth_functions.php'; use Dotenv\Dotenv; @@ -14,6 +15,12 @@ $dotenv->load(); header('Content-Type: application/json'); +if (!isLoggedIn()) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Niet geautoriseerd.']); + exit; +} + try { $db = getDbConnection(); diff --git a/auth/403.php b/auth/403.php new file mode 100644 index 0000000..3e7f318 --- /dev/null +++ b/auth/403.php @@ -0,0 +1,29 @@ + + + + + + Geen Toegang - Telvero Talpa + + + + +
+
+
+
+
+ +

Geen Toegang

+

Je hebt geen rechten om deze pagina te bekijken.

+

Alleen admins hebben toegang tot deze functionaliteit.

+ + Terug naar Dashboard + +
+
+
+
+
+ + diff --git a/auth/auth_functions.php b/auth/auth_functions.php new file mode 100644 index 0000000..fdc95a1 --- /dev/null +++ b/auth/auth_functions.php @@ -0,0 +1,305 @@ +load(); + } + $db = new PDO( + "mysql:host={$_ENV['DB_HOST']};dbname={$_ENV['DB_NAME']};charset=utf8mb4", + $_ENV['DB_USER'], + $_ENV['DB_PASS'], + [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ] + ); + } + return $db; +} + +/** + * Check if user is logged in + */ +function isLoggedIn(): bool { + return isset($_SESSION['user_id']) && isset($_SESSION['user']) && !empty($_SESSION['user']); +} + +/** + * Get current logged in user + */ +function getCurrentUser(): ?array { + return $_SESSION['user'] ?? null; +} + +/** + * Check if user has specific role + */ +function hasRole(string $role): bool { + if (!isLoggedIn()) { + return false; + } + return ($_SESSION['user']['role'] ?? '') === $role; +} + +/** + * Check if user is admin + */ +function isAdmin(): bool { + return hasRole('admin'); +} + +/** + * Check if user is guest + */ +function isGuest(): bool { + return hasRole('guest'); +} + +/** + * Check if user can perform write operations + */ +function canWrite(): bool { + return isLoggedIn() && isAdmin(); +} + +/** + * Check if user can create + */ +function canCreate(): bool { + return isAdmin(); +} + +/** + * Check if user can edit + */ +function canEdit(): bool { + return isAdmin(); +} + +/** + * Check if user can delete + */ +function canDelete(): bool { + return isAdmin(); +} + +/** + * Check if user can sync with Talpa + */ +function canSync(): bool { + return isAdmin(); +} + +/** + * Require user to be logged in, redirect to login if not + */ +function requireLogin(): void { + if (!isLoggedIn()) { + $currentUrl = $_SERVER['REQUEST_URI'] ?? '/'; + $redirect = urlencode(ltrim($currentUrl, '/')); + header("Location: /auth/login.php?redirect=$redirect"); + exit; + } +} + +/** + * Require user to have specific role + */ +function requireRole(string $role): void { + requireLogin(); + + if (!hasRole($role)) { + http_response_code(403); + include __DIR__ . '/403.php'; + exit; + } +} + +/** + * Require admin role + */ +function requireAdmin(): void { + requireRole('admin'); +} + +/** + * Check brute force protection + * Returns true if user is blocked + */ +function isBlockedByBruteForce(string $username, string $ip): bool { + try { + $db = getAuthDb(); + + // Check by username + $stmt = $db->prepare(" + SELECT COUNT(*) FROM login_attempts + WHERE username = ? AND success = 0 + AND attempted_at > DATE_SUB(NOW(), INTERVAL 15 MINUTE) + "); + $stmt->execute([$username]); + if ($stmt->fetchColumn() >= 5) { + return true; + } + + // Check by IP + $stmt = $db->prepare(" + SELECT COUNT(*) FROM login_attempts + WHERE ip_address = ? AND success = 0 + AND attempted_at > DATE_SUB(NOW(), INTERVAL 15 MINUTE) + "); + $stmt->execute([$ip]); + if ($stmt->fetchColumn() >= 10) { + return true; + } + } catch (Exception $e) { + // If we can't check, allow the attempt + } + + return false; +} + +/** + * Log a login attempt + */ +function logLoginAttempt(string $username, string $ip, bool $success): void { + try { + $db = getAuthDb(); + $stmt = $db->prepare(" + INSERT INTO login_attempts (username, ip_address, success) + VALUES (?, ?, ?) + "); + $stmt->execute([$username, $ip, $success ? 1 : 0]); + } catch (Exception $e) { + // Silently fail + } +} + +/** + * Attempt to login a user + * Returns array with 'success', 'user', 'error' keys + */ +function attemptLogin(string $usernameOrEmail, string $password): array { + $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; + + // Check brute force + if (isBlockedByBruteForce($usernameOrEmail, $ip)) { + return [ + 'success' => false, + 'error' => 'Te veel mislukte inlogpogingen. Probeer het over 15 minuten opnieuw.' + ]; + } + + try { + $db = getAuthDb(); + + // Find user by username or email + $stmt = $db->prepare(" + SELECT id, username, email, password_hash, role, is_active + FROM users + WHERE (username = ? OR email = ?) + LIMIT 1 + "); + $stmt->execute([$usernameOrEmail, $usernameOrEmail]); + $user = $stmt->fetch(); + + if (!$user) { + logLoginAttempt($usernameOrEmail, $ip, false); + return ['success' => false, 'error' => 'Ongeldige gebruikersnaam of wachtwoord.']; + } + + if (!$user['is_active']) { + logLoginAttempt($usernameOrEmail, $ip, false); + return ['success' => false, 'error' => 'Dit account is gedeactiveerd. Neem contact op met de beheerder.']; + } + + if (!password_verify($password, $user['password_hash'])) { + logLoginAttempt($usernameOrEmail, $ip, false); + return ['success' => false, 'error' => 'Ongeldige gebruikersnaam of wachtwoord.']; + } + + // Success - create session + session_regenerate_id(true); + $_SESSION['user_id'] = $user['id']; + $_SESSION['user'] = [ + 'id' => $user['id'], + 'username' => $user['username'], + 'email' => $user['email'], + 'role' => $user['role'], + ]; + $_SESSION['login_time'] = time(); + + // Log success + logLoginAttempt($usernameOrEmail, $ip, true); + + // Update last login + $stmt = $db->prepare("UPDATE users SET last_login = NOW() WHERE id = ?"); + $stmt->execute([$user['id']]); + + return ['success' => true, 'user' => $_SESSION['user']]; + + } catch (Exception $e) { + return ['success' => false, 'error' => 'Er is een fout opgetreden. Probeer het opnieuw.']; + } +} + +/** + * Logout the current user + */ +function logout(): void { + $_SESSION = []; + + if (ini_get('session.use_cookies')) { + $params = session_get_cookie_params(); + setcookie( + session_name(), + '', + time() - 42000, + $params['path'], + $params['domain'], + $params['secure'], + $params['httponly'] + ); + } + + session_destroy(); +} + +/** + * Check session timeout (2 hours of inactivity) + */ +function checkSessionTimeout(): void { + $timeout = 7200; // 2 hours + + if (isLoggedIn()) { + if (isset($_SESSION['login_time']) && (time() - $_SESSION['login_time']) > $timeout) { + logout(); + header("Location: /auth/login.php?reason=timeout"); + exit; + } + // Update last activity + $_SESSION['login_time'] = time(); + } +} + +// Check session timeout on every request +checkSessionTimeout(); diff --git a/auth/login.php b/auth/login.php new file mode 100644 index 0000000..3893567 --- /dev/null +++ b/auth/login.php @@ -0,0 +1,244 @@ +load(); + +require_once __DIR__ . '/auth_functions.php'; + +// If already logged in, redirect to dashboard +if (isLoggedIn()) { + header("Location: /index.php"); + exit; +} + +$error = ''; +$redirect = $_GET['redirect'] ?? 'index.php'; +$reason = $_GET['reason'] ?? ''; + +// Handle login form submission +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $username = trim($_POST['username'] ?? ''); + $password = $_POST['password'] ?? ''; + + if (empty($username) || empty($password)) { + $error = 'Vul je gebruikersnaam en wachtwoord in.'; + } else { + $result = attemptLogin($username, $password); + + if ($result['success']) { + // Redirect to original page or dashboard + $redirectUrl = '/' . ltrim(urldecode($redirect), '/'); + // Security: only allow relative redirects + if (strpos($redirectUrl, '//') !== false || strpos($redirectUrl, 'http') === 0) { + $redirectUrl = '/index.php'; + } + header("Location: $redirectUrl"); + exit; + } else { + $error = $result['error']; + } + } +} +?> + + + + + + Inloggen - Telvero Talpa + + + + + +
+
+ + + + + + + + +
+
+ + + + + diff --git a/auth/logout.php b/auth/logout.php new file mode 100644 index 0000000..5811869 --- /dev/null +++ b/auth/logout.php @@ -0,0 +1,18 @@ +load(); + +require_once __DIR__ . '/auth_functions.php'; + +logout(); + +header("Location: /auth/login.php"); +exit; diff --git a/blocks.php b/blocks.php index 50f0da0..42334c3 100644 --- a/blocks.php +++ b/blocks.php @@ -10,15 +10,19 @@ error_reporting(E_ALL); require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/helpers.php'; +require_once __DIR__ . '/auth/auth_functions.php'; use Dotenv\Dotenv; $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); +// Require login +requireLogin(); + $db = getDbConnection(); -// Handle form submissions -if ($_SERVER['REQUEST_METHOD'] === 'POST') { +// Handle form submissions - Admin only +if ($_SERVER['REQUEST_METHOD'] === 'POST' && canEdit()) { if (isset($_POST['create_template'])) { $stmt = $db->prepare(" INSERT INTO block_templates @@ -104,20 +108,7 @@ $dayNames = [ - +
@@ -142,6 +133,7 @@ $dayNames = [
+
@@ -212,8 +204,9 @@ $dayNames = [
+ -
+
Bestaande Templates
@@ -255,29 +248,36 @@ $dayNames = [ Inactief - -
- - - -
- - -
-
- - -
-
- + +
+ + + + +
+ + +
+ + +
+ + +
+ + + + +
+ diff --git a/calendar.php b/calendar.php index 4a7732e..0660c61 100644 --- a/calendar.php +++ b/calendar.php @@ -10,11 +10,15 @@ error_reporting(E_ALL); require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/helpers.php'; +require_once __DIR__ . '/auth/auth_functions.php'; use Dotenv\Dotenv; $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); +// Require login +requireLogin(); + $db = getDbConnection(); // Get all infomercials with colors for sidebar @@ -64,20 +68,7 @@ $infomercials = $db->query(" - +
@@ -332,6 +323,13 @@ $infomercials = $db->query(" + diff --git a/includes/nav.php b/includes/nav.php new file mode 100644 index 0000000..df61483 --- /dev/null +++ b/includes/nav.php @@ -0,0 +1,83 @@ + + + + + + diff --git a/index.php b/index.php index 4ddb10c..bdc66eb 100644 --- a/index.php +++ b/index.php @@ -5,19 +5,23 @@ error_reporting(E_ALL); require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/TalpaAPI.php'; +require_once __DIR__ . '/auth/auth_functions.php'; use Dotenv\Dotenv; $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); +// Require login +requireLogin(); + $api = new TalpaApi(); $db = new PDO("mysql:host={$_ENV['DB_HOST']};dbname={$_ENV['DB_NAME']}", $_ENV['DB_USER'], $_ENV['DB_PASS']); // Array om logs voor console te verzamelen $apiLogs = []; -// 1. Registratie Infomercial (Stap 1, 2, 4) -if (isset($_POST['add_commercial'])) { +// 1. Registratie Infomercial (Stap 1, 2, 4) - Admin only +if (isset($_POST['add_commercial']) && canCreate()) { $ep = $api->createEpisode($_POST['title'], $_POST['duration'], $_POST['season_id']); $apiLogs[] = ['call' => 'Create Episode', 'response' => $api->lastResponse]; @@ -36,8 +40,8 @@ if (isset($_POST['add_commercial'])) { } } -// 2. LOKALE Planning opslaan of bewerken -if (isset($_POST['schedule_transmission'])) { +// 2. LOKALE Planning opslaan of bewerken - Admin only +if (isset($_POST['schedule_transmission']) && canCreate()) { $stmt = $db->prepare("SELECT duration FROM infomercials WHERE id = ?"); $stmt->execute([$_POST['infomercial_id']]); $commDuration = $stmt->fetchColumn(); @@ -53,8 +57,8 @@ if (isset($_POST['schedule_transmission'])) { exit; } -// 3. Handmatige Sync naar Talpa (Stap 3 op verzoek) -if (isset($_POST['sync_item'])) { +// 3. Handmatige Sync naar Talpa (Stap 3 op verzoek) - Admin only +if (isset($_POST['sync_item']) && canSync()) { $stmt = $db->prepare("SELECT t.*, c.content_id FROM transmissions t JOIN infomercials c ON t.infomercial_id = c.id WHERE t.id = ?"); $stmt->execute([$_POST['sync_id']]); $tx = $stmt->fetch(); @@ -74,8 +78,8 @@ if (isset($_POST['sync_item'])) { ->execute([$status, json_encode($res), $_POST['sync_id']]); } -// 4. Media Asset Label en Status bijwerken -if (isset($_POST['update_media_asset'])) { +// 4. Media Asset Label en Status bijwerken - Admin only +if (isset($_POST['update_media_asset']) && canEdit()) { $stmt = $db->prepare("UPDATE infomercials SET media_asset_label = ?, upload_status = ? WHERE id = ?"); $stmt->execute([$_POST['media_asset_label'], $_POST['upload_status'], $_POST['infomercial_id']]); header("Location: index.php?view_date=" . $selectedDate); @@ -108,20 +112,7 @@ if (isset($_GET['edit'])) { - +
@@ -227,6 +218,7 @@ if (isset($_GET['edit'])) {

Recente Activiteit

+
@@ -279,6 +271,7 @@ if (isset($_GET['edit'])) {
+
@@ -319,10 +312,12 @@ if (isset($_GET['edit'])) {
+ - + +
+ + Alleen lezen +
@@ -386,12 +385,14 @@ if (isset($_GET['edit'])) {

- Telvero Talpa Planning System © 2026 + Telvero Talpa Planning System ©

+ + + +``` + +### 8. JavaScript voor Guest Mode (calendar-init.js aanpassing) + +```javascript +// In assets/js/calendar-init.js + +// Check if user is guest +const isGuest = ; + +// Disable drag and drop for guests +if (isGuest) { + // Disable external events + document.querySelectorAll('.infomercial-item').forEach(item => { + item.style.cursor = 'not-allowed'; + item.style.opacity = '0.6'; + item.removeAttribute('draggable'); + }); + + // Make calendar read-only + calendar = new FullCalendar.Calendar(calendarEl, { + // ... existing config + editable: false, + droppable: false, + eventStartEditable: false, + eventDurationEditable: false, + eventResourceEditable: false + }); + + // Show tooltip on hover + document.querySelectorAll('.infomercial-item').forEach(item => { + item.title = 'Alleen admins kunnen infomercials slepen'; + }); +} +``` + +## Visuele Voorbeelden + +### Login Pagina +``` +┌─────────────────────────────────────┐ +│ │ +│ 📺 Telvero Talpa │ +│ Planning System \ No newline at end of file diff --git a/plans/authentication-system-plan.md b/plans/authentication-system-plan.md new file mode 100644 index 0000000..cf8a86c --- /dev/null +++ b/plans/authentication-system-plan.md @@ -0,0 +1,538 @@ +# Authentication & Authorization System Plan + +## Overzicht + +Dit plan beschrijft de implementatie van een login systeem met rollen en rechten voor de Telvero Talpa Planning applicatie. + +## Rollen & Rechten + +### Admin Rol +- **Volledige toegang** tot alle functionaliteiten +- Kan infomercials toevoegen, bewerken en verwijderen +- Kan uitzendingen plannen, bewerken en verwijderen +- Kan synchroniseren met Talpa API +- Kan blokken beheren +- Kan gebruikers beheren (toevoegen, bewerken, verwijderen) + +### Guest Rol +- **Alleen lezen** toegang +- Kan dashboards bekijken +- Kan planning bekijken (kalender, planner) +- Kan infomercials bekijken +- Kan blokken bekijken +- **Geen** toevoeg-, bewerk- of verwijderfunctionaliteit +- **Geen** sync functionaliteit +- **Geen** gebruikersbeheer + +## Database Schema + +### Tabel: `users` +```sql +CREATE TABLE users ( + id INT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(50) UNIQUE NOT NULL, + email VARCHAR(100) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + role ENUM('admin', 'guest') DEFAULT 'guest', + is_active BOOLEAN DEFAULT 1, + last_login TIMESTAMP NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_username (username), + INDEX idx_email (email), + INDEX idx_role (role) +); +``` + +### Tabel: `sessions` +```sql +CREATE TABLE sessions ( + id VARCHAR(128) PRIMARY KEY, + user_id INT NOT NULL, + ip_address VARCHAR(45), + user_agent VARCHAR(255), + last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_user_id (user_id), + INDEX idx_last_activity (last_activity) +); +``` + +### Tabel: `login_attempts` +```sql +CREATE TABLE login_attempts ( + id INT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(50), + ip_address VARCHAR(45), + success BOOLEAN DEFAULT 0, + attempted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_username (username), + INDEX idx_ip_address (ip_address), + INDEX idx_attempted_at (attempted_at) +); +``` + +## Bestandsstructuur + +``` +/ +├── auth/ +│ ├── login.php # Login pagina +│ ├── logout.php # Logout handler +│ ├── auth_functions.php # Authenticatie functies +│ └── middleware.php # Access control middleware +├── admin/ +│ └── users.php # Gebruikersbeheer (admin only) +├── migrations/ +│ └── 003_add_authentication.sql +└── (bestaande bestanden worden aangepast) +``` + +## Implementatie Details + +### 1. Authenticatie Functies (`auth/auth_functions.php`) + +```php +// Kernfuncties: +- login($username, $password) // Inloggen +- logout() // Uitloggen +- isLoggedIn() // Check of gebruiker ingelogd is +- getCurrentUser() // Haal huidige gebruiker op +- hasRole($role) // Check of gebruiker rol heeft +- requireLogin() // Forceer login (redirect) +- requireRole($role) // Forceer specifieke rol +- hashPassword($password) // Wachtwoord hashen +- verifyPassword($password, $hash) // Wachtwoord verifiëren +- createSession($userId) // Sessie aanmaken +- destroySession() // Sessie vernietigen +- logLoginAttempt($username, $success) // Log inlogpoging +- checkBruteForce($username, $ip) // Brute force bescherming +``` + +### 2. Middleware (`auth/middleware.php`) + +```php +// Access control functies: +- checkAuthentication() // Check authenticatie +- checkAuthorization($requiredRole) // Check autorisatie +- canCreate() // Kan aanmaken? +- canEdit() // Kan bewerken? +- canDelete() // Kan verwijderen? +- canSync() // Kan synchroniseren? +``` + +### 3. Login Pagina (`auth/login.php`) + +**Features:** +- Username/email + wachtwoord formulier +- "Onthoud mij" functionaliteit +- Foutmeldingen bij ongeldige credentials +- Brute force bescherming (max 5 pogingen per 15 minuten) +- Redirect naar oorspronkelijke pagina na login +- Responsive design (Bootstrap) + +### 4. Gebruikersbeheer (`admin/users.php`) + +**Features (alleen voor admins):** +- Lijst van alle gebruikers +- Gebruiker toevoegen +- Gebruiker bewerken (username, email, rol) +- Gebruiker deactiveren/activeren +- Wachtwoord resetten +- Laatste login datum tonen +- Zoek- en filterfunctionaliteit + +### 5. Bestaande Pagina's Aanpassen + +Alle bestaande pagina's moeten worden aangepast: + +#### Bovenaan elke pagina toevoegen: +```php + +``` + +#### Navigatie aanpassen: +- Gebruikersnaam tonen +- Logout knop toevoegen +- Rol badge tonen (Admin/Guest) + +#### UI Aanpassingen voor Guest rol: + +**Dashboard (`index.php`):** +- Verberg "Infomercial Registreren" formulier +- Verberg "Tijdblok Reserveren" formulier +- Verberg "Sync" knoppen +- Verberg "Opslaan" knoppen in Media Asset Management + +**Planner (`planner.php`):** +- Verberg "Toevoegen" formulieren +- Verberg "Verwijderen" knoppen +- Verberg "Reorder" knoppen (pijltjes) +- Verberg "Sync" knoppen +- Verberg "Blok tijd aanpassen" knoppen +- Verberg "Kopieer blok" knoppen +- Disable drag-and-drop functionaliteit + +**Kalender (`calendar.php`):** +- Disable drag-and-drop functionaliteit +- Verberg "Sync" knoppen +- Verberg "Verwijderen" knoppen in event modal +- Maak kalender read-only (geen event creation/editing) + +**Blokken (`blocks.php`):** +- Verberg "Nieuw Template" knop +- Verberg "Bewerken" knoppen +- Verberg "Verwijderen" knoppen +- Verberg formulieren + +**Infomercials (`infomercials.php`):** +- Verberg "Registreren" formulier +- Verberg "Bewerken" knoppen +- Verberg "Verwijderen" knoppen +- Verberg "Kleur aanpassen" functionaliteit + +### 6. API Endpoints Beveiligen + +Alle API endpoints in `/api/` moeten worden beveiligd: + +```php + false, 'error' => 'Niet geautoriseerd']); + exit; +} + +// Voor write operations (POST/PUT/DELETE): +if (!hasRole('admin')) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'Geen toegang']); + exit; +} +?> +``` + +**API endpoints die bescherming nodig hebben:** +- `create_transmission.php` - Admin only +- `update_transmission.php` - Admin only +- `delete_transmission.php` - Admin only +- `sync_block.php` - Admin only +- `copy_block.php` - Admin only +- `update_block_time.php` - Admin only +- `assign_color.php` - Admin only +- Read-only endpoints (`get_*.php`) - Alle ingelogde gebruikers + +## Beveiliging + +### Wachtwoord Beveiliging +- Gebruik `password_hash()` met `PASSWORD_BCRYPT` of `PASSWORD_ARGON2ID` +- Minimum wachtwoordlengte: 8 karakters +- Wachtwoord sterkte validatie (optioneel: hoofdletters, cijfers, speciale tekens) + +### Sessie Beveiliging +- Gebruik PHP native sessions met secure settings +- Session timeout: 2 uur inactiviteit +- Regenerate session ID na login +- HttpOnly en Secure flags voor cookies (in productie) +- Session fixation bescherming + +### Brute Force Bescherming +- Maximum 5 mislukte inlogpogingen per 15 minuten per username +- Maximum 10 mislukte inlogpogingen per 15 minuten per IP +- Lockout periode: 15 minuten +- Log alle inlogpogingen + +### CSRF Bescherming +- CSRF tokens voor alle formulieren +- Token validatie bij POST requests +- Token regeneratie na gebruik + +### SQL Injection Bescherming +- Gebruik prepared statements (reeds geïmplementeerd) +- Input validatie en sanitization + +### XSS Bescherming +- Output escaping met `htmlspecialchars()` (reeds geïmplementeerd) +- Content Security Policy headers (optioneel) + +## Environment Variabelen + +Toevoegen aan `.env`: + +```env +# Authentication Settings +AUTH_SESSION_TIMEOUT=7200 +AUTH_MAX_LOGIN_ATTEMPTS=5 +AUTH_LOCKOUT_DURATION=900 +AUTH_REMEMBER_ME_DURATION=2592000 +``` + +## Standaard Gebruikers + +Bij installatie worden de volgende gebruikers aangemaakt: + +```sql +-- Admin gebruiker +INSERT INTO users (username, email, password_hash, role) VALUES +('admin', 'admin@telvero.nl', '$2y$10$...', 'admin'); + +-- Guest gebruiker (voor demo) +INSERT INTO users (username, email, password_hash, role) VALUES +('guest', 'guest@telvero.nl', '$2y$10$...', 'guest'); +``` + +**Standaard wachtwoorden:** +- Admin: `admin123` (moet bij eerste login worden gewijzigd) +- Guest: `guest123` + +## UI/UX Overwegingen + +### Login Pagina Design +- Centraal gepositioneerd formulier +- Telvero branding (logo, kleuren) +- Duidelijke foutmeldingen +- Loading indicator tijdens login +- Responsive design voor mobiel + +### Navigatie Updates +```html + +``` + +### Guest Mode Indicators +- Toon duidelijk dat gebruiker in "alleen lezen" modus is +- Tooltip bij disabled knoppen: "Alleen admins kunnen deze actie uitvoeren" +- Optioneel: banner bovenaan pagina voor guest gebruikers + +## Testing Checklist + +### Authenticatie Tests +- [ ] Login met geldige credentials (admin) +- [ ] Login met geldige credentials (guest) +- [ ] Login met ongeldige credentials +- [ ] Logout functionaliteit +- [ ] Session timeout na inactiviteit +- [ ] Brute force bescherming +- [ ] "Onthoud mij" functionaliteit +- [ ] Redirect naar oorspronkelijke pagina na login + +### Autorisatie Tests +- [ ] Admin kan alle pagina's bezoeken +- [ ] Guest kan alleen lezen +- [ ] Guest kan geen create/update/delete acties uitvoeren +- [ ] Guest kan niet synchroniseren met Talpa +- [ ] API endpoints zijn beveiligd +- [ ] Direct URL access wordt geblokkeerd voor ongeautoriseerde acties + +### UI Tests +- [ ] Knoppen zijn verborgen/disabled voor guests +- [ ] Formulieren zijn verborgen voor guests +- [ ] Drag-and-drop is disabled voor guests +- [ ] Navigatie toont correcte gebruikersinformatie +- [ ] Rol badge wordt correct getoond + +### Gebruikersbeheer Tests (Admin only) +- [ ] Admin kan gebruikers toevoegen +- [ ] Admin kan gebruikers bewerken +- [ ] Admin kan gebruikers deactiveren +- [ ] Admin kan wachtwoorden resetten +- [ ] Guest kan gebruikersbeheer niet benaderen + +## Migratie Strategie + +### Stap 1: Database Migratie +```bash +mysql -u username -p talpa_planning < migrations/003_add_authentication.sql +``` + +### Stap 2: Standaard Gebruikers Aanmaken +Via SQL of via setup script + +### Stap 3: Bestaande Pagina's Updaten +Alle PHP bestanden updaten met authenticatie checks + +### Stap 4: Testing +Uitgebreid testen van alle functionaliteiten + +### Stap 5: Deployment +- Backup maken van database +- Code deployen +- Migratie uitvoeren +- Testen in productie + +## Toekomstige Uitbreidingen + +Mogelijke uitbreidingen voor de toekomst: + +1. **Meer Rollen:** + - Editor: Kan plannen maar niet synchroniseren + - Viewer: Alleen lezen (zoals guest) + - Manager: Kan alles behalve gebruikersbeheer + +2. **Granulaire Rechten:** + - Per module rechten toewijzen + - Custom permission sets + +3. **Two-Factor Authentication (2FA):** + - TOTP via Google Authenticator + - SMS verificatie + +4. **Audit Log:** + - Log alle acties van gebruikers + - Wie heeft wat wanneer gedaan + +5. **Wachtwoord Beleid:** + - Wachtwoord expiratie + - Wachtwoord geschiedenis + - Complexiteitseisen + +6. **Single Sign-On (SSO):** + - LDAP/Active Directory integratie + - OAuth2 providers (Google, Microsoft) + +7. **API Keys:** + - Voor externe integraties + - Rate limiting + +## Documentatie Updates + +Na implementatie moeten de volgende documenten worden bijgewerkt: + +1. **README.md:** + - Sectie over authenticatie toevoegen + - Standaard credentials documenteren + - Gebruikersbeheer uitleggen + +2. **INSTALLATION.md:** + - Authenticatie migratie stappen + - Eerste admin gebruiker aanmaken + +3. **Nieuwe documentatie:** + - `docs/AUTHENTICATION.md` - Uitgebreide authenticatie documentatie + - `docs/USER_MANAGEMENT.md` - Gebruikersbeheer handleiding + +## Mermaid Diagram: Authenticatie Flow + +```mermaid +graph TD + A[Gebruiker bezoekt pagina] --> B{Ingelogd?} + B -->|Nee| C[Redirect naar login.php] + C --> D[Gebruiker vult credentials in] + D --> E{Credentials geldig?} + E -->|Nee| F[Toon foutmelding] + F --> G{Max pogingen bereikt?} + G -->|Ja| H[Account tijdelijk geblokkeerd] + G -->|Nee| D + E -->|Ja| I[Maak sessie aan] + I --> J[Redirect naar oorspronkelijke pagina] + B -->|Ja| K{Juiste rol?} + K -->|Nee| L[Toon 403 Forbidden] + K -->|Ja| M{Admin of Guest?} + M -->|Admin| N[Volledige toegang] + M -->|Guest| O[Alleen lezen toegang] + N --> P[Toon alle functionaliteit] + O --> Q[Verberg write acties] +``` + +## Mermaid Diagram: Database Schema + +```mermaid +erDiagram + users ||--o{ sessions : has + users ||--o{ login_attempts : has + + users { + int id PK + varchar username UK + varchar email UK + varchar password_hash + enum role + boolean is_active + timestamp last_login + timestamp created_at + timestamp updated_at + } + + sessions { + varchar id PK + int user_id FK + varchar ip_address + varchar user_agent + timestamp last_activity + timestamp created_at + } + + login_attempts { + int id PK + varchar username + varchar ip_address + boolean success + timestamp attempted_at + } +``` + +## Prioriteit & Volgorde + +### Fase 1: Basis Authenticatie (Hoge Prioriteit) +1. Database migratie +2. Authenticatie functies +3. Login pagina +4. Logout functionaliteit +5. Sessie management + +### Fase 2: Autorisatie (Hoge Prioriteit) +1. Middleware implementatie +2. Rol checks in bestaande pagina's +3. API beveiliging + +### Fase 3: UI Aanpassingen (Gemiddelde Prioriteit) +1. Navigatie updates +2. Guest mode UI restrictions +3. Tooltips en indicators + +### Fase 4: Gebruikersbeheer (Gemiddelde Prioriteit) +1. Gebruikersbeheer interface +2. CRUD operaties voor gebruikers +3. Wachtwoord reset functionaliteit + +### Fase 5: Extra Beveiliging (Lage Prioriteit) +1. Brute force bescherming +2. CSRF tokens +3. Audit logging + +## Conclusie + +Dit plan biedt een complete oplossing voor authenticatie en autorisatie in de Telvero Talpa Planning applicatie. De implementatie zorgt voor: + +- **Veilige authenticatie** met moderne best practices +- **Duidelijke rolscheiding** tussen admin en guest +- **Gebruiksvriendelijke interface** met duidelijke feedback +- **Uitbreidbaarheid** voor toekomstige requirements +- **Minimale impact** op bestaande functionaliteit + +De implementatie kan gefaseerd worden uitgevoerd, waarbij eerst de basis authenticatie wordt geïmplementeerd en daarna stapsgewijs de autorisatie en UI aanpassingen worden toegevoegd. diff --git a/setup_auth.php b/setup_auth.php new file mode 100644 index 0000000..93406d9 --- /dev/null +++ b/setup_auth.php @@ -0,0 +1,202 @@ +load(); + +$db = new PDO( + "mysql:host={$_ENV['DB_HOST']};dbname={$_ENV['DB_NAME']};charset=utf8mb4", + $_ENV['DB_USER'], + $_ENV['DB_PASS'], + [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] +); + +$errors = []; +$success = []; + +// Run migration +try { + $sql = file_get_contents(__DIR__ . '/migrations/003_add_authentication.sql'); + + // Split by semicolons and execute each statement + $statements = array_filter(array_map('trim', explode(';', $sql))); + + foreach ($statements as $statement) { + if (!empty($statement)) { + try { + $db->exec($statement); + } catch (PDOException $e) { + // Ignore duplicate entry errors (tables already exist) + if ($e->getCode() != '42S01' && $e->getCode() != 23000) { + $errors[] = "SQL Error: " . $e->getMessage(); + } + } + } + } + + $success[] = "Database migration completed"; +} catch (Exception $e) { + $errors[] = "Migration failed: " . $e->getMessage(); +} + +// Create admin user with proper password hash +$adminPassword = 'Admin@2026!'; +$adminHash = password_hash($adminPassword, PASSWORD_BCRYPT); + +try { + $stmt = $db->prepare(" + INSERT INTO users (username, email, password_hash, role, is_active) + VALUES ('admin', 'admin@telvero.nl', ?, 'admin', 1) + ON DUPLICATE KEY UPDATE password_hash = VALUES(password_hash) + "); + $stmt->execute([$adminHash]); + $success[] = "Admin user created/updated (username: admin, password: {$adminPassword})"; +} catch (Exception $e) { + $errors[] = "Failed to create admin user: " . $e->getMessage(); +} + +// Create guest user with proper password hash +$guestPassword = 'Guest@2026!'; +$guestHash = password_hash($guestPassword, PASSWORD_BCRYPT); + +try { + $stmt = $db->prepare(" + INSERT INTO users (username, email, password_hash, role, is_active) + VALUES ('guest', 'guest@telvero.nl', ?, 'guest', 1) + ON DUPLICATE KEY UPDATE password_hash = VALUES(password_hash) + "); + $stmt->execute([$guestHash]); + $success[] = "Guest user created/updated (username: guest, password: {$guestPassword})"; +} catch (Exception $e) { + $errors[] = "Failed to create guest user: " . $e->getMessage(); +} + +// Create lock file +if (empty($errors)) { + file_put_contents($lockFile, date('Y-m-d H:i:s')); +} + +// Output results +$isCli = php_sapi_name() === 'cli'; + +if ($isCli) { + echo "\n=== Authentication Setup ===\n\n"; + + if (!empty($success)) { + echo "✓ SUCCESS:\n"; + foreach ($success as $msg) { + echo " - {$msg}\n"; + } + } + + if (!empty($errors)) { + echo "\n✗ ERRORS:\n"; + foreach ($errors as $msg) { + echo " - {$msg}\n"; + } + } + + echo "\n=== Default Credentials ===\n"; + echo "Admin: admin / Admin@2026!\n"; + echo "Guest: guest / Guest@2026!\n"; + echo "\n⚠️ IMPORTANT: Change these passwords after first login!\n"; + echo "\n✓ Setup complete. This script can now be deleted.\n\n"; +} else { + ?> + + + + + Auth Setup - Telvero Talpa + + + +
+
+
+

Authentication Setup

+ + +
+
✓ Succesvol
+
+
    + +
  • + +
+
+
+ + + +
+
✗ Fouten
+
+
    + +
  • + +
+
+
+ + +
+
⚠️ Standaard Inloggegevens
+
+ + + + + + + + + + + + + + + + +
GebruikerWachtwoordRol
adminAdmin@2026!Admin
guestGuest@2026!Guest
+
+ Belangrijk: Wijzig deze wachtwoorden na de eerste login! +
+
+
+ + +
+
+
+ + +