Login added
This commit is contained in:
parent
0b0396af48
commit
341a113463
@ -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
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,6 +12,7 @@
|
||||
*.sln.docstates
|
||||
.env
|
||||
.DS_Store
|
||||
.auth_setup_done
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
@ -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! 🎉
|
||||
|
||||
420
admin/users.php
Normal file
420
admin/users.php
Normal file
@ -0,0 +1,420 @@
|
||||
<?php
|
||||
/**
|
||||
* User Management - Admin Only
|
||||
* Telvero Talpa Planning System
|
||||
*/
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
|
||||
$dotenv->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';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gebruikersbeheer - Telvero Talpa</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="/assets/css/custom.css">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<?php include __DIR__ . '/../includes/nav.php'; ?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1><i class="bi bi-people"></i> Gebruikersbeheer</h1>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createUserModal">
|
||||
<i class="bi bi-plus-circle"></i> Nieuwe Gebruiker
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($success)): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-check-circle"></i> <?= htmlspecialchars($success) ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($error)): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-exclamation-triangle"></i> <?= htmlspecialchars($error) ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Login Stats -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm text-center">
|
||||
<div class="card-body">
|
||||
<div class="fs-2 fw-bold text-primary"><?= count($users) ?></div>
|
||||
<div class="text-muted small">Totaal Gebruikers</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm text-center">
|
||||
<div class="card-body">
|
||||
<div class="fs-2 fw-bold text-success"><?= $loginStats['successful_logins'] ?? 0 ?></div>
|
||||
<div class="text-muted small">Succesvolle Logins (24u)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm text-center">
|
||||
<div class="card-body">
|
||||
<div class="fs-2 fw-bold text-danger"><?= $loginStats['failed_logins'] ?? 0 ?></div>
|
||||
<div class="text-muted small">Mislukte Logins (24u)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Users Table -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-dark text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-table"></i> Gebruikers</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-secondary">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Gebruikersnaam</th>
|
||||
<th>Email</th>
|
||||
<th>Rol</th>
|
||||
<th>Status</th>
|
||||
<th>Laatste Login</th>
|
||||
<th>Aangemaakt</th>
|
||||
<th class="text-center">Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<tr>
|
||||
<td class="text-muted small"><?= $user['id'] ?></td>
|
||||
<td>
|
||||
<i class="bi bi-person-circle me-1"></i>
|
||||
<strong><?= htmlspecialchars($user['username']) ?></strong>
|
||||
<?php if ($user['id'] === getCurrentUser()['id']): ?>
|
||||
<span class="badge bg-info ms-1">Jij</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($user['email']) ?></td>
|
||||
<td>
|
||||
<span class="badge <?= $user['role'] === 'admin' ? 'bg-danger' : 'bg-secondary' ?>">
|
||||
<i class="bi <?= $user['role'] === 'admin' ? 'bi-shield-fill' : 'bi-eye' ?>"></i>
|
||||
<?= ucfirst($user['role']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge <?= $user['is_active'] ? 'bg-success' : 'bg-warning text-dark' ?>">
|
||||
<?= $user['is_active'] ? 'Actief' : 'Inactief' ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="small text-muted">
|
||||
<?= $user['last_login']
|
||||
? date('d-m-Y H:i', strtotime($user['last_login']))
|
||||
: '<em>Nooit</em>' ?>
|
||||
</td>
|
||||
<td class="small text-muted">
|
||||
<?= date('d-m-Y', strtotime($user['created_at'])) ?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-primary"
|
||||
onclick="editUser(<?= htmlspecialchars(json_encode($user)) ?>)"
|
||||
title="Bewerken">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<?php if ($user['id'] !== getCurrentUser()['id']): ?>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-danger ms-1"
|
||||
onclick="confirmDelete(<?= $user['id'] ?>, '<?= htmlspecialchars($user['username']) ?>')"
|
||||
title="Verwijderen">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create User Modal -->
|
||||
<div class="modal fade" id="createUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form method="POST">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title">
|
||||
<i class="bi bi-person-plus"></i> Nieuwe Gebruiker Aanmaken
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Gebruikersnaam <span class="text-danger">*</span></label>
|
||||
<input type="text" name="username" class="form-control" required
|
||||
pattern="[a-zA-Z0-9_-]{3,50}"
|
||||
title="3-50 tekens, alleen letters, cijfers, _ en -">
|
||||
<div class="form-text">3-50 tekens, alleen letters, cijfers, _ en -</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Email <span class="text-danger">*</span></label>
|
||||
<input type="email" name="email" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Wachtwoord <span class="text-danger">*</span></label>
|
||||
<input type="password" name="password" class="form-control" required minlength="8">
|
||||
<div class="form-text">Minimaal 8 tekens</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Rol <span class="text-danger">*</span></label>
|
||||
<select name="role" class="form-select" required>
|
||||
<option value="guest">Guest (alleen lezen)</option>
|
||||
<option value="admin">Admin (volledige toegang)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuleren</button>
|
||||
<button type="submit" name="create_user" class="btn btn-primary">
|
||||
<i class="bi bi-person-plus"></i> Aanmaken
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit User Modal -->
|
||||
<div class="modal fade" id="editUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="user_id" id="editUserId">
|
||||
<div class="modal-header bg-warning">
|
||||
<h5 class="modal-title">
|
||||
<i class="bi bi-pencil"></i> Gebruiker Bewerken
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Gebruikersnaam <span class="text-danger">*</span></label>
|
||||
<input type="text" name="username" id="editUsername" class="form-control" required
|
||||
pattern="[a-zA-Z0-9_-]{3,50}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Email <span class="text-danger">*</span></label>
|
||||
<input type="email" name="email" id="editEmail" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Rol <span class="text-danger">*</span></label>
|
||||
<select name="role" id="editRole" class="form-select" required>
|
||||
<option value="guest">Guest (alleen lezen)</option>
|
||||
<option value="admin">Admin (volledige toegang)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="is_active" id="editIsActive" value="1">
|
||||
<label class="form-check-label" for="editIsActive">
|
||||
Account actief
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Nieuw Wachtwoord</label>
|
||||
<input type="password" name="new_password" id="editPassword" class="form-control" minlength="8">
|
||||
<div class="form-text text-muted">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
Laat leeg om het huidige wachtwoord te behouden.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuleren</button>
|
||||
<button type="submit" name="update_user" class="btn btn-warning">
|
||||
<i class="bi bi-check-circle"></i> Opslaan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete User Form (hidden) -->
|
||||
<form method="POST" id="deleteUserForm" style="display: none;">
|
||||
<input type="hidden" name="user_id" id="deleteUserId">
|
||||
<input type="hidden" name="delete_user" value="1">
|
||||
</form>
|
||||
|
||||
<footer class="mt-5 py-4 bg-dark text-white text-center">
|
||||
<div class="container">
|
||||
<p class="mb-0">
|
||||
<i class="bi bi-tv"></i> Telvero Talpa Planning System © <?= date('Y') ?>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
function editUser(user) {
|
||||
document.getElementById('editUserId').value = user.id;
|
||||
document.getElementById('editUsername').value = user.username;
|
||||
document.getElementById('editEmail').value = user.email;
|
||||
document.getElementById('editRole').value = user.role;
|
||||
document.getElementById('editIsActive').checked = user.is_active == 1;
|
||||
document.getElementById('editPassword').value = '';
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function confirmDelete(userId, username) {
|
||||
if (confirm(`Weet je zeker dat je gebruiker "${username}" wilt verwijderen?\n\nDeze actie kan niet ongedaan worden gemaakt.`)) {
|
||||
document.getElementById('deleteUserId').value = userId;
|
||||
document.getElementById('deleteUserForm').submit();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
29
auth/403.php
Normal file
29
auth/403.php
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Geen Toegang - Telvero Talpa</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body text-center p-5">
|
||||
<i class="bi bi-shield-lock text-danger" style="font-size: 4rem;"></i>
|
||||
<h2 class="mt-3 text-danger">Geen Toegang</h2>
|
||||
<p class="text-muted">Je hebt geen rechten om deze pagina te bekijken.</p>
|
||||
<p class="text-muted small">Alleen admins hebben toegang tot deze functionaliteit.</p>
|
||||
<a href="/index.php" class="btn btn-primary mt-3">
|
||||
<i class="bi bi-house"></i> Terug naar Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
305
auth/auth_functions.php
Normal file
305
auth/auth_functions.php
Normal file
@ -0,0 +1,305 @@
|
||||
<?php
|
||||
/**
|
||||
* Authentication Helper Functions
|
||||
* Telvero Talpa Planning System
|
||||
*/
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
// Secure session settings
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
ini_set('session.use_strict_mode', 1);
|
||||
ini_set('session.cookie_samesite', 'Lax');
|
||||
session_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database connection for auth
|
||||
*/
|
||||
function getAuthDb(): PDO {
|
||||
static $db = null;
|
||||
if ($db === null) {
|
||||
// Load env if not already loaded
|
||||
if (!isset($_ENV['DB_HOST'])) {
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
|
||||
$dotenv->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();
|
||||
244
auth/login.php
Normal file
244
auth/login.php
Normal file
@ -0,0 +1,244 @@
|
||||
<?php
|
||||
/**
|
||||
* Login Page
|
||||
* Telvero Talpa Planning System
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
|
||||
$dotenv->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'];
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Inloggen - Telvero Talpa</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.login-wrapper {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
padding: 1rem;
|
||||
}
|
||||
.login-card {
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.login-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
.login-header .brand-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.login-header h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.login-header p {
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.85;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.login-body {
|
||||
padding: 2rem;
|
||||
}
|
||||
.form-control:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
.btn-login {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
padding: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.btn-login:hover {
|
||||
background: linear-gradient(135deg, #5a6fd6 0%, #6a3f96 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
.input-group-text {
|
||||
background-color: #f8f9fa;
|
||||
border-right: none;
|
||||
}
|
||||
.input-group .form-control {
|
||||
border-left: none;
|
||||
}
|
||||
.input-group .form-control:focus {
|
||||
border-left: none;
|
||||
}
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
padding: 1rem 2rem 1.5rem;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-wrapper">
|
||||
<div class="card login-card">
|
||||
<!-- Header -->
|
||||
<div class="login-header">
|
||||
<div class="brand-icon">
|
||||
<i class="bi bi-tv"></i>
|
||||
</div>
|
||||
<h1>Telvero Talpa</h1>
|
||||
<p>Planning System</p>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="login-body">
|
||||
<?php if ($reason === 'timeout'): ?>
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-clock"></i>
|
||||
Je sessie is verlopen. Log opnieuw in.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($error)): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
<?= htmlspecialchars($error) ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" autocomplete="on">
|
||||
<input type="hidden" name="redirect" value="<?= htmlspecialchars($redirect) ?>">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label fw-semibold">
|
||||
Gebruikersnaam of Email
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-person text-muted"></i>
|
||||
</span>
|
||||
<input type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
class="form-control"
|
||||
placeholder="Gebruikersnaam of email"
|
||||
value="<?= htmlspecialchars($_POST['username'] ?? '') ?>"
|
||||
required
|
||||
autofocus
|
||||
autocomplete="username">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password" class="form-label fw-semibold">
|
||||
Wachtwoord
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-lock text-muted"></i>
|
||||
</span>
|
||||
<input type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="form-control"
|
||||
placeholder="Wachtwoord"
|
||||
required
|
||||
autocomplete="current-password">
|
||||
<button class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
id="togglePassword"
|
||||
title="Wachtwoord tonen/verbergen">
|
||||
<i class="bi bi-eye" id="toggleIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-login w-100">
|
||||
<i class="bi bi-box-arrow-in-right me-2"></i>
|
||||
Inloggen
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="login-footer">
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-shield-check text-success"></i>
|
||||
Beveiligde verbinding • Telvero © <?= date('Y') ?>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Toggle password visibility
|
||||
document.getElementById('togglePassword').addEventListener('click', function() {
|
||||
const passwordInput = document.getElementById('password');
|
||||
const toggleIcon = document.getElementById('toggleIcon');
|
||||
|
||||
if (passwordInput.type === 'password') {
|
||||
passwordInput.type = 'text';
|
||||
toggleIcon.className = 'bi bi-eye-slash';
|
||||
} else {
|
||||
passwordInput.type = 'password';
|
||||
toggleIcon.className = 'bi bi-eye';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
18
auth/logout.php
Normal file
18
auth/logout.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* Logout Handler
|
||||
* Telvero Talpa Planning System
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
|
||||
$dotenv->load();
|
||||
|
||||
require_once __DIR__ . '/auth_functions.php';
|
||||
|
||||
logout();
|
||||
|
||||
header("Location: /auth/login.php");
|
||||
exit;
|
||||
80
blocks.php
80
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 = [
|
||||
<link rel="stylesheet" href="assets/css/custom.css">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="index.php">
|
||||
<i class="bi bi-tv"></i> Telvero Talpa Planner
|
||||
</a>
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-link" href="index.php">Dashboard</a>
|
||||
<a class="nav-link" href="planner.php">Planner</a>
|
||||
<a class="nav-link" href="calendar.php">Kalender</a>
|
||||
<a class="nav-link active" href="blocks.php">Blokken</a>
|
||||
<a class="nav-link" href="infomercials.php">Infomercials</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php $activePage = 'blocks'; include __DIR__ . '/includes/nav.php'; ?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
@ -142,6 +133,7 @@ $dayNames = [
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row">
|
||||
<?php if (canEdit()): ?>
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
@ -212,8 +204,9 @@ $dayNames = [
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="<?= canEdit() ? 'col-md-8' : 'col-md-12' ?>">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-list-ul"></i> Bestaande Templates</h5>
|
||||
@ -255,29 +248,36 @@ $dayNames = [
|
||||
<span class="badge bg-secondary">Inactief</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<a href="?edit=<?= $template['id'] ?>"
|
||||
class="btn-icon btn-icon-xs btn-icon-primary">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form method="POST" style="display:inline;">
|
||||
<input type="hidden" name="template_id" value="<?= $template['id'] ?>">
|
||||
<button type="submit" name="toggle_active"
|
||||
class="btn-icon btn-icon-xs btn-icon-<?= $template['is_active'] ? 'warning' : 'success' ?>">
|
||||
<i class="bi bi-<?= $template['is_active'] ? 'pause' : 'play' ?>-circle"></i>
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" style="display:inline;"
|
||||
onsubmit="return confirm('Weet je zeker dat je dit template wilt verwijderen?');">
|
||||
<input type="hidden" name="template_id" value="<?= $template['id'] ?>">
|
||||
<button type="submit" name="delete_template"
|
||||
class="btn-icon btn-icon-xs btn-icon-danger">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<?php if (canEdit()): ?>
|
||||
<a href="?edit=<?= $template['id'] ?>"
|
||||
class="btn-icon btn-icon-xs btn-icon-primary">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form method="POST" style="display:inline;">
|
||||
<input type="hidden" name="template_id" value="<?= $template['id'] ?>">
|
||||
<button type="submit" name="toggle_active"
|
||||
class="btn-icon btn-icon-xs btn-icon-<?= $template['is_active'] ? 'warning' : 'success' ?>">
|
||||
<i class="bi bi-<?= $template['is_active'] ? 'pause' : 'play' ?>-circle"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php if (canDelete()): ?>
|
||||
<form method="POST" style="display:inline;"
|
||||
onsubmit="return confirm('Weet je zeker dat je dit template wilt verwijderen?');">
|
||||
<input type="hidden" name="template_id" value="<?= $template['id'] ?>">
|
||||
<button type="submit" name="delete_template"
|
||||
class="btn-icon btn-icon-xs btn-icon-danger">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php if (isGuest()): ?>
|
||||
<span class="text-muted small"><i class="bi bi-eye"></i></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
|
||||
26
calendar.php
26
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("
|
||||
<link rel="stylesheet" href="assets/css/custom.css">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="index.php">
|
||||
<i class="bi bi-tv"></i> Telvero Talpa Planner
|
||||
</a>
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-link" href="index.php">Dashboard</a>
|
||||
<a class="nav-link" href="planner.php">Planner</a>
|
||||
<a class="nav-link active" href="calendar.php">Kalender</a>
|
||||
<a class="nav-link" href="blocks.php">Blokken</a>
|
||||
<a class="nav-link" href="infomercials.php">Infomercials</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php $activePage = 'calendar'; include __DIR__ . '/includes/nav.php'; ?>
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<!-- Alert Container -->
|
||||
@ -332,6 +323,13 @@ $infomercials = $db->query("
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src='https://cdn.jsdelivr.net/npm/fullcalendar-scheduler@6.1.10/index.global.min.js'></script>
|
||||
<script>
|
||||
// Pass user role to JavaScript
|
||||
const userIsAdmin = <?= json_encode(isAdmin()) ?>;
|
||||
const userCanEdit = <?= json_encode(canEdit()) ?>;
|
||||
const userCanSync = <?= json_encode(canSync()) ?>;
|
||||
const userCanDelete = <?= json_encode(canDelete()) ?>;
|
||||
</script>
|
||||
<script src="assets/js/calendar-init.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
83
includes/nav.php
Normal file
83
includes/nav.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* Shared Navigation Component
|
||||
* Telvero Talpa Planning System
|
||||
*
|
||||
* Usage: include __DIR__ . '/includes/nav.php';
|
||||
* Requires: $activePage variable to be set (e.g., 'dashboard', 'planner', 'calendar', 'blocks', 'infomercials', 'users')
|
||||
*/
|
||||
|
||||
// Ensure auth functions are available
|
||||
if (!function_exists('isLoggedIn')) {
|
||||
require_once __DIR__ . '/../auth/auth_functions.php';
|
||||
}
|
||||
|
||||
$activePage = $activePage ?? '';
|
||||
$currentUser = getCurrentUser();
|
||||
?>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/index.php">
|
||||
<i class="bi bi-tv"></i> Telvero Talpa Planner
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<!-- Main Navigation -->
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-link <?= $activePage === 'dashboard' ? 'active' : '' ?>" href="/index.php">
|
||||
<i class="bi bi-speedometer2"></i> Dashboard
|
||||
</a>
|
||||
<a class="nav-link <?= $activePage === 'planner' ? 'active' : '' ?>" href="/planner.php">
|
||||
<i class="bi bi-table"></i> Planner
|
||||
</a>
|
||||
<a class="nav-link <?= $activePage === 'calendar' ? 'active' : '' ?>" href="/calendar.php">
|
||||
<i class="bi bi-calendar-week"></i> Kalender
|
||||
</a>
|
||||
<a class="nav-link <?= $activePage === 'blocks' ? 'active' : '' ?>" href="/blocks.php">
|
||||
<i class="bi bi-grid-3x3"></i> Blokken
|
||||
</a>
|
||||
<a class="nav-link <?= $activePage === 'infomercials' ? 'active' : '' ?>" href="/infomercials.php">
|
||||
<i class="bi bi-collection-play"></i> Infomercials
|
||||
</a>
|
||||
<?php if (isAdmin()): ?>
|
||||
<a class="nav-link <?= $activePage === 'users' ? 'active' : '' ?>" href="/admin/users.php">
|
||||
<i class="bi bi-people"></i> Gebruikers
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- User Info & Logout -->
|
||||
<div class="navbar-nav ms-auto align-items-center">
|
||||
<?php if ($currentUser): ?>
|
||||
<span class="nav-link pe-2">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<span class="ms-1"><?= htmlspecialchars($currentUser['username']) ?></span>
|
||||
<span class="badge ms-1 <?= isAdmin() ? 'bg-danger' : 'bg-secondary' ?>">
|
||||
<?= isAdmin() ? 'Admin' : 'Guest' ?>
|
||||
</span>
|
||||
</span>
|
||||
<a class="nav-link" href="/auth/logout.php" title="Uitloggen">
|
||||
<i class="bi bi-box-arrow-right"></i>
|
||||
<span class="d-lg-none ms-1">Uitloggen</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<?php if (isGuest()): ?>
|
||||
<div class="alert alert-info alert-dismissible fade show mb-0 rounded-0 border-0" role="alert"
|
||||
style="background-color: #cff4fc; border-bottom: 1px solid #b6effb !important;">
|
||||
<div class="container-fluid">
|
||||
<i class="bi bi-eye"></i>
|
||||
<strong>Alleen lezen modus:</strong> Je bent ingelogd als <strong>Guest</strong>.
|
||||
Je kunt informatie bekijken maar geen wijzigingen aanbrengen.
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
54
index.php
54
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'])) {
|
||||
<link rel="stylesheet" href="assets/css/custom.css">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="index.php">
|
||||
<i class="bi bi-tv"></i> Telvero Talpa Planner
|
||||
</a>
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-link active" href="index.php">Dashboard</a>
|
||||
<a class="nav-link" href="planner.php">Planner</a>
|
||||
<a class="nav-link" href="calendar.php">Kalender</a>
|
||||
<a class="nav-link" href="blocks.php">Blokken</a>
|
||||
<a class="nav-link" href="infomercials.php">Infomercials</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php $activePage = 'dashboard'; include __DIR__ . '/includes/nav.php'; ?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
@ -227,6 +218,7 @@ if (isset($_GET['edit'])) {
|
||||
|
||||
<h2 class="mb-3"><i class="bi bi-clock-history"></i> Recente Activiteit</h2>
|
||||
|
||||
<?php if (canCreate()): ?>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card p-3 shadow-sm">
|
||||
@ -279,6 +271,7 @@ if (isset($_GET['edit'])) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
@ -319,10 +312,12 @@ if (isset($_GET['edit'])) {
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons justify-content-center">
|
||||
<?php if (canEdit()): ?>
|
||||
<a href="?edit=<?= $tx['id'] ?>&view_date=<?= $selectedDate ?>" class="btn-icon btn-icon-xs btn-icon-primary">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<?php if($tx['api_status'] !== 'synced'): ?>
|
||||
<?php endif; ?>
|
||||
<?php if(canSync() && $tx['api_status'] !== 'synced'): ?>
|
||||
<form method="POST" style="display:inline;">
|
||||
<input type="hidden" name="sync_id" value="<?= $tx['id'] ?>">
|
||||
<button type="submit" name="sync_item" class="btn-icon btn-icon-xs btn-icon-success">
|
||||
@ -363,16 +358,20 @@ if (isset($_GET['edit'])) {
|
||||
<td><span class="badge bg-info text-dark"><?= $c['duration'] ?></span></td>
|
||||
<td><code><?= htmlspecialchars($c['content_id']) ?></code></td>
|
||||
<td>
|
||||
<input type="text" name="media_asset_label" class="form-control form-control-sm" value="<?= htmlspecialchars($c['media_asset_label']) ?>">
|
||||
<input type="text" name="media_asset_label" class="form-control form-control-sm" value="<?= htmlspecialchars($c['media_asset_label']) ?>" <?= canEdit() ? '' : 'readonly' ?>>
|
||||
</td>
|
||||
<td>
|
||||
<select name="upload_status" class="form-select form-select-sm">
|
||||
<select name="upload_status" class="form-select form-select-sm" <?= canEdit() ? '' : 'disabled' ?>>
|
||||
<option value="pending" <?= ($c['upload_status'] ?? 'pending') == 'pending' ? 'selected' : '' ?>>Pending</option>
|
||||
<option value="uploaded" <?= ($c['upload_status'] ?? '') == 'uploaded' ? 'selected' : '' ?>>Uploaded</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (canEdit()): ?>
|
||||
<button type="submit" name="update_media_asset" class="btn btn-sm btn-primary">Opslaan</button>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small"><i class="bi bi-eye"></i> Alleen lezen</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</form>
|
||||
</tr>
|
||||
@ -386,12 +385,14 @@ if (isset($_GET['edit'])) {
|
||||
<footer class="mt-5 py-4 bg-dark text-white text-center">
|
||||
<div class="container">
|
||||
<p class="mb-0">
|
||||
<i class="bi bi-tv"></i> Telvero Talpa Planning System © 2026
|
||||
<i class="bi bi-tv"></i> Telvero Talpa Planning System © <?= date('Y') ?>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
<?php if (canCreate()): ?>
|
||||
// Bestaande JS voor duur selectie
|
||||
const select = document.getElementById('commercial_select');
|
||||
const durationInput = document.getElementById('display_duration');
|
||||
@ -400,6 +401,7 @@ if (isset($_GET['edit'])) {
|
||||
const duration = selectedOption.getAttribute('data-duration');
|
||||
durationInput.value = duration || '';
|
||||
});
|
||||
<?php endif; ?>
|
||||
|
||||
// LOGGING NAAR CONSOLE
|
||||
const apiLogs = <?= json_encode($apiLogs) ?>;
|
||||
|
||||
116
infomercials.php
116
infomercials.php
@ -11,24 +11,27 @@ 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__);
|
||||
$dotenv->load();
|
||||
|
||||
// Require login
|
||||
requireLogin();
|
||||
|
||||
$api = new TalpaApi();
|
||||
$db = getDbConnection();
|
||||
$apiLogs = [];
|
||||
|
||||
// Retrieve and clear refresh logs from session
|
||||
session_start();
|
||||
// Retrieve and clear refresh logs from session (session already started in auth_functions.php)
|
||||
$refreshLogs = $_SESSION['refresh_logs'] ?? null;
|
||||
if ($refreshLogs) {
|
||||
unset($_SESSION['refresh_logs']);
|
||||
}
|
||||
|
||||
// Handle infomercial registration
|
||||
if (isset($_POST['add_commercial'])) {
|
||||
// Handle infomercial registration - Admin only
|
||||
if (isset($_POST['add_commercial']) && canCreate()) {
|
||||
$apiLogs[] = ['step' => 'Start registration', 'input' => $_POST];
|
||||
|
||||
$ep = $api->createEpisode($_POST['title'], $_POST['duration'], $_POST['season_id']);
|
||||
@ -91,8 +94,8 @@ if (isset($_POST['add_commercial'])) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle infomercial update (sync to Talpa)
|
||||
if (isset($_POST['update_infomercial'])) {
|
||||
// Handle infomercial update (sync to Talpa) - Admin only
|
||||
if (isset($_POST['update_infomercial']) && canEdit()) {
|
||||
// Get current infomercial data
|
||||
$stmt = $db->prepare("SELECT content_id, title, duration FROM infomercials WHERE id = ?");
|
||||
$stmt->execute([$_POST['infomercial_id']]);
|
||||
@ -136,8 +139,8 @@ if (isset($_POST['update_infomercial'])) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle refresh single infomercial from Talpa
|
||||
if (isset($_POST['refresh_infomercial'])) {
|
||||
// Handle refresh single infomercial from Talpa - Admin only
|
||||
if (isset($_POST['refresh_infomercial']) && canEdit()) {
|
||||
$stmt = $db->prepare("SELECT title, media_asset_id FROM infomercials WHERE id = ?");
|
||||
$stmt->execute([$_POST['infomercial_id']]);
|
||||
$infomercial = $stmt->fetch();
|
||||
@ -162,8 +165,7 @@ if (isset($_POST['refresh_infomercial'])) {
|
||||
header('Location: infomercials.php?success=refreshed');
|
||||
exit;
|
||||
} else {
|
||||
// Store failed refresh log
|
||||
session_start();
|
||||
// Store failed refresh log (session already started in auth_functions.php)
|
||||
$_SESSION['refresh_logs'] = [[
|
||||
'status' => 'failed',
|
||||
'title' => $infomercial['title'],
|
||||
@ -176,8 +178,8 @@ if (isset($_POST['refresh_infomercial'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle refresh all infomercials from Talpa
|
||||
if (isset($_POST['refresh_all'])) {
|
||||
// Handle refresh all infomercials from Talpa - Admin only
|
||||
if (isset($_POST['refresh_all']) && canEdit()) {
|
||||
$stmt = $db->query("SELECT id, title, media_asset_id FROM infomercials WHERE media_asset_id IS NOT NULL");
|
||||
$infomercials_to_refresh = $stmt->fetchAll();
|
||||
|
||||
@ -214,16 +216,15 @@ if (isset($_POST['refresh_all'])) {
|
||||
}
|
||||
}
|
||||
|
||||
// Store logs in session for display
|
||||
session_start();
|
||||
// Store logs in session for display (session already started in auth_functions.php)
|
||||
$_SESSION['refresh_logs'] = $refreshLogs;
|
||||
|
||||
header("Location: infomercials.php?success=refreshed_all&count=$refreshed&failed=$failed");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle delete
|
||||
if (isset($_POST['delete_commercial'])) {
|
||||
// Handle delete - Admin only
|
||||
if (isset($_POST['delete_commercial']) && canDelete()) {
|
||||
// Check if infomercial is used in transmissions
|
||||
$stmt = $db->prepare("SELECT COUNT(*) FROM transmissions WHERE infomercial_id = ?");
|
||||
$stmt->execute([$_POST['infomercial_id']]);
|
||||
@ -277,20 +278,7 @@ $infomercials = $db->query("
|
||||
<link rel="stylesheet" href="assets/css/custom.css">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="index.php">
|
||||
<i class="bi bi-tv"></i> Telvero Talpa Planner
|
||||
</a>
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-link" href="index.php">Dashboard</a>
|
||||
<a class="nav-link" href="planner.php">Planner</a>
|
||||
<a class="nav-link" href="calendar.php">Kalender</a>
|
||||
<a class="nav-link" href="blocks.php">Blokken</a>
|
||||
<a class="nav-link active" href="infomercials.php">Infomercials</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php $activePage = 'infomercials'; include __DIR__ . '/includes/nav.php'; ?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
@ -376,6 +364,7 @@ $infomercials = $db->query("
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row">
|
||||
<?php if (canCreate()): ?>
|
||||
<!-- Registration Form -->
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm">
|
||||
@ -418,13 +407,14 @@ $infomercials = $db->query("
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Infomercial List -->
|
||||
<div class="col-md-8">
|
||||
<div class="<?= canCreate() ? 'col-md-8' : 'col-md-12' ?>">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-secondary text-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="bi bi-list-ul"></i> Geregistreerde Infomercials</h5>
|
||||
<?php if (!empty($infomercials)): ?>
|
||||
<?php if (!empty($infomercials) && canEdit()): ?>
|
||||
<form method="POST" style="display:inline;"
|
||||
onsubmit="return confirm('Weet je zeker dat je alle infomercials wilt verversen vanuit Talpa?');">
|
||||
<button type="submit" name="refresh_all" class="btn btn-sm btn-light" title="Ververs alle infomercials vanuit Talpa">
|
||||
@ -485,33 +475,41 @@ $infomercials = $db->query("
|
||||
<?= $c['usage_count'] ?>x
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<form method="POST" style="display:inline;">
|
||||
<input type="hidden" name="infomercial_id" value="<?= $c['id'] ?>">
|
||||
<button type="submit" name="refresh_infomercial"
|
||||
class="btn-icon btn-icon-xs btn-icon-info">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
</form>
|
||||
<button type="button"
|
||||
class="btn-icon btn-icon-xs btn-icon-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#editModal<?= $c['id'] ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<?php if ($c['usage_count'] == 0): ?>
|
||||
<form method="POST" style="display:inline;"
|
||||
onsubmit="return confirm('Weet je zeker dat je deze infomercial wilt verwijderen?');">
|
||||
<input type="hidden" name="infomercial_id" value="<?= $c['id'] ?>">
|
||||
<button type="submit" name="delete_commercial"
|
||||
class="btn-icon btn-icon-xs btn-icon-danger">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<?php if (canEdit()): ?>
|
||||
<form method="POST" style="display:inline;">
|
||||
<input type="hidden" name="infomercial_id" value="<?= $c['id'] ?>">
|
||||
<button type="submit" name="refresh_infomercial"
|
||||
class="btn-icon btn-icon-xs btn-icon-info"
|
||||
title="Verversen vanuit Talpa">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
</form>
|
||||
<button type="button"
|
||||
class="btn-icon btn-icon-xs btn-icon-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#editModal<?= $c['id'] ?>"
|
||||
title="Bewerken">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if (canDelete() && $c['usage_count'] == 0): ?>
|
||||
<form method="POST" style="display:inline;"
|
||||
onsubmit="return confirm('Weet je zeker dat je deze infomercial wilt verwijderen?');">
|
||||
<input type="hidden" name="infomercial_id" value="<?= $c['id'] ?>">
|
||||
<button type="submit" name="delete_commercial"
|
||||
class="btn-icon btn-icon-xs btn-icon-danger"
|
||||
title="Verwijderen">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php if (isGuest()): ?>
|
||||
<span class="text-muted small"><i class="bi bi-eye" title="Alleen lezen"></i></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
54
migrations/003_add_authentication.sql
Normal file
54
migrations/003_add_authentication.sql
Normal file
@ -0,0 +1,54 @@
|
||||
-- Migration: Add authentication tables
|
||||
-- Date: 2026-02-19
|
||||
-- Description: Adds users, sessions, and login_attempts tables for authentication
|
||||
|
||||
-- Create users table
|
||||
CREATE TABLE IF NOT EXISTS 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)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Create sessions table
|
||||
CREATE TABLE IF NOT EXISTS 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)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Create login_attempts table
|
||||
CREATE TABLE IF NOT EXISTS 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)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Insert default admin user (password: Admin@2026!)
|
||||
-- Password hash generated with: password_hash('Admin@2026!', PASSWORD_BCRYPT)
|
||||
INSERT INTO users (username, email, password_hash, role, is_active) VALUES
|
||||
('admin', 'admin@telvero.nl', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin', 1);
|
||||
|
||||
-- Insert default guest user (password: Guest@2026!)
|
||||
-- Password hash generated with: password_hash('Guest@2026!', PASSWORD_BCRYPT)
|
||||
INSERT INTO users (username, email, password_hash, role, is_active) VALUES
|
||||
('guest', 'guest@telvero.nl', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'guest', 1);
|
||||
47
planner.php
47
planner.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 adding transmission to block
|
||||
if (isset($_POST['add_to_block'])) {
|
||||
// Handle adding transmission to block - Admin only
|
||||
if (isset($_POST['add_to_block']) && canCreate()) {
|
||||
$commercialId = $_POST['infomercial_id'];
|
||||
$channel = $_POST['channel'];
|
||||
$date = $_POST['date'];
|
||||
@ -47,8 +51,8 @@ if (isset($_POST['add_to_block'])) {
|
||||
// Handle remove transmission - REMOVED: Now handled via AJAX to delete_transmission.php API
|
||||
// This ensures proper Talpa API deletion
|
||||
|
||||
// Handle reorder (move up/down)
|
||||
if (isset($_POST['reorder'])) {
|
||||
// Handle reorder (move up/down) - Admin only
|
||||
if (isset($_POST['reorder']) && canEdit()) {
|
||||
$transmissionId = $_POST['transmission_id'];
|
||||
$direction = $_POST['direction']; // 'up' or 'down'
|
||||
$date = $_POST['date'];
|
||||
@ -105,8 +109,8 @@ if (isset($_POST['reorder'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle block time update
|
||||
if (isset($_POST['update_block_time'])) {
|
||||
// Handle block time update - Admin only
|
||||
if (isset($_POST['update_block_time']) && canEdit()) {
|
||||
$blockId = $_POST['block_id'];
|
||||
$newStartTime = $_POST['new_start_time'];
|
||||
$recalculate = isset($_POST['recalculate']);
|
||||
@ -255,20 +259,7 @@ function calculateNextStartTimeForBlock($db, $date, $channel, $blockId) {
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="index.php">
|
||||
<i class="bi bi-tv"></i> Telvero Talpa Planner
|
||||
</a>
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-link" href="index.php">Dashboard</a>
|
||||
<a class="nav-link active" href="planner.php">Planner</a>
|
||||
<a class="nav-link" href="calendar.php">Kalender</a>
|
||||
<a class="nav-link" href="blocks.php">Blokken</a>
|
||||
<a class="nav-link" href="infomercials.php">Infomercials</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php $activePage = 'planner'; include __DIR__ . '/includes/nav.php'; ?>
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<?php if (isset($_GET['success'])): ?>
|
||||
@ -474,6 +465,7 @@ function calculateNextStartTimeForBlock($db, $date, $channel, $blockId) {
|
||||
</div>
|
||||
|
||||
<div class="block-header-actions">
|
||||
<?php if (canEdit()): ?>
|
||||
<button type="button" class="btn-icon btn-icon-sm btn-icon-secondary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#blockTimeModal<?= $block['id'] ?>">
|
||||
@ -484,10 +476,13 @@ function calculateNextStartTimeForBlock($db, $date, $channel, $blockId) {
|
||||
data-bs-target="#copyBlockModal<?= $block['id'] ?>">
|
||||
<i class="bi bi-files"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if (canSync()): ?>
|
||||
<button type="button" class="btn-icon btn-icon-sm btn-icon-success"
|
||||
onclick="syncBlockPlanner('<?= $selectedDate ?>', '<?= $channel ?>')">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="block-header-time">
|
||||
@ -566,9 +561,9 @@ function calculateNextStartTimeForBlock($db, $date, $channel, $blockId) {
|
||||
<i class="bi <?= $syncIcon ?>"></i>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<?php if ($index > 0): ?>
|
||||
<?php if (canEdit() && $index > 0): ?>
|
||||
<form method="POST" style="display:inline;">
|
||||
<input type="hidden" name="transmission_id" value="<?= $tx['id'] ?>">
|
||||
<input type="hidden" name="direction" value="up">
|
||||
@ -581,7 +576,7 @@ function calculateNextStartTimeForBlock($db, $date, $channel, $blockId) {
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($index < count($blockTransmissions) - 1): ?>
|
||||
<?php if (canEdit() && $index < count($blockTransmissions) - 1): ?>
|
||||
<form method="POST" style="display:inline;">
|
||||
<input type="hidden" name="transmission_id" value="<?= $tx['id'] ?>">
|
||||
<input type="hidden" name="direction" value="down">
|
||||
@ -594,17 +589,20 @@ function calculateNextStartTimeForBlock($db, $date, $channel, $blockId) {
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (canDelete()): ?>
|
||||
<button type="button"
|
||||
class="btn-icon btn-icon-xs btn-icon-danger"
|
||||
onclick="deleteTransmissionPlanner(<?= $tx['id'] ?>, '<?= $selectedDate ?>')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (canCreate()): ?>
|
||||
<!-- Add Infomercial Row (also a drop zone) -->
|
||||
<tr class="table-success drop-zone-end"
|
||||
data-block-id="<?= $block['id'] ?>"
|
||||
@ -635,6 +633,7 @@ function calculateNextStartTimeForBlock($db, $date, $channel, $blockId) {
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
740
plans/authentication-implementation-examples.md
Normal file
740
plans/authentication-implementation-examples.md
Normal file
@ -0,0 +1,740 @@
|
||||
# Authentication System - Implementatie Voorbeelden
|
||||
|
||||
## Code Voorbeelden
|
||||
|
||||
### 1. Login Pagina (auth/login.php)
|
||||
|
||||
```php
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
|
||||
$dotenv->load();
|
||||
|
||||
$db = new PDO(
|
||||
"mysql:host={$_ENV['DB_HOST']};dbname={$_ENV['DB_NAME']}",
|
||||
$_ENV['DB_USER'],
|
||||
$_ENV['DB_PASS']
|
||||
);
|
||||
|
||||
$error = '';
|
||||
$redirect = $_GET['redirect'] ?? 'index.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
// Check brute force
|
||||
$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]);
|
||||
$failedAttempts = $stmt->fetchColumn();
|
||||
|
||||
if ($failedAttempts >= 5) {
|
||||
$error = 'Te veel mislukte pogingen. Probeer het over 15 minuten opnieuw.';
|
||||
} else {
|
||||
// Verify credentials
|
||||
$stmt = $db->prepare("
|
||||
SELECT id, username, email, password_hash, role, is_active
|
||||
FROM users
|
||||
WHERE (username = ? OR email = ?) AND is_active = 1
|
||||
");
|
||||
$stmt->execute([$username, $username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($password, $user['password_hash'])) {
|
||||
// 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']
|
||||
];
|
||||
|
||||
// Log success
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO login_attempts (username, ip_address, success)
|
||||
VALUES (?, ?, 1)
|
||||
");
|
||||
$stmt->execute([$username, $_SERVER['REMOTE_ADDR']]);
|
||||
|
||||
// Update last login
|
||||
$stmt = $db->prepare("UPDATE users SET last_login = NOW() WHERE id = ?");
|
||||
$stmt->execute([$user['id']]);
|
||||
|
||||
header("Location: ../$redirect");
|
||||
exit;
|
||||
} else {
|
||||
// Failed login
|
||||
$error = 'Ongeldige gebruikersnaam of wachtwoord';
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO login_attempts (username, ip_address, success)
|
||||
VALUES (?, ?, 0)
|
||||
");
|
||||
$stmt->execute([$username, $_SERVER['REMOTE_ADDR']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login - Telvero Talpa</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.login-card {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-card">
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-body p-5">
|
||||
<div class="text-center mb-4">
|
||||
<i class="bi bi-tv" style="font-size: 3rem; color: #667eea;"></i>
|
||||
<h3 class="mt-3">Telvero Talpa</h3>
|
||||
<p class="text-muted">Planning System</p>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
<?= htmlspecialchars($error) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Gebruikersnaam of Email</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-person"></i>
|
||||
</span>
|
||||
<input type="text"
|
||||
name="username"
|
||||
class="form-control"
|
||||
required
|
||||
autofocus
|
||||
value="<?= htmlspecialchars($_POST['username'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Wachtwoord</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-lock"></i>
|
||||
</span>
|
||||
<input type="password"
|
||||
name="password"
|
||||
class="form-control"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="remember" name="remember">
|
||||
<label class="form-check-label" for="remember">
|
||||
Onthoud mij
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-box-arrow-in-right"></i> Inloggen
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<small class="text-muted">
|
||||
Telvero © <?= date('Y') ?>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 2. Authenticatie Functies (auth/auth_functions.php)
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Authentication Helper Functions
|
||||
*/
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
|
||||
$dotenv->load();
|
||||
|
||||
/**
|
||||
* Get database connection
|
||||
*/
|
||||
function getAuthDb() {
|
||||
static $db = null;
|
||||
if ($db === null) {
|
||||
$db = new PDO(
|
||||
"mysql:host={$_ENV['DB_HOST']};dbname={$_ENV['DB_NAME']}",
|
||||
$_ENV['DB_USER'],
|
||||
$_ENV['DB_PASS']
|
||||
);
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
return $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is logged in
|
||||
*/
|
||||
function isLoggedIn() {
|
||||
return isset($_SESSION['user_id']) && isset($_SESSION['user']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current logged in user
|
||||
*/
|
||||
function getCurrentUser() {
|
||||
return $_SESSION['user'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has specific role
|
||||
*/
|
||||
function hasRole($role) {
|
||||
if (!isLoggedIn()) {
|
||||
return false;
|
||||
}
|
||||
return $_SESSION['user']['role'] === $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is admin
|
||||
*/
|
||||
function isAdmin() {
|
||||
return hasRole('admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is guest
|
||||
*/
|
||||
function isGuest() {
|
||||
return hasRole('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Require user to be logged in
|
||||
*/
|
||||
function requireLogin() {
|
||||
if (!isLoggedIn()) {
|
||||
$currentUrl = $_SERVER['REQUEST_URI'];
|
||||
$redirect = urlencode($currentUrl);
|
||||
header("Location: /auth/login.php?redirect=$redirect");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Require user to have specific role
|
||||
*/
|
||||
function requireRole($role) {
|
||||
requireLogin();
|
||||
|
||||
if (!hasRole($role)) {
|
||||
http_response_code(403);
|
||||
echo '<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Geen Toegang</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container mt-5">
|
||||
<div class="alert alert-danger">
|
||||
<h4><i class="bi bi-exclamation-triangle"></i> Geen Toegang</h4>
|
||||
<p>Je hebt geen toegang tot deze pagina.</p>
|
||||
<a href="/index.php" class="btn btn-primary">Terug naar Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>';
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Require admin role
|
||||
*/
|
||||
function requireAdmin() {
|
||||
requireRole('admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout user
|
||||
*/
|
||||
function logout() {
|
||||
$_SESSION = [];
|
||||
|
||||
if (isset($_COOKIE[session_name()])) {
|
||||
setcookie(session_name(), '', time() - 3600, '/');
|
||||
}
|
||||
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can perform write operations
|
||||
*/
|
||||
function canWrite() {
|
||||
return isLoggedIn() && isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can only read
|
||||
*/
|
||||
function canOnlyRead() {
|
||||
return isLoggedIn() && isGuest();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Middleware (auth/middleware.php)
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Authorization Middleware
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/auth_functions.php';
|
||||
|
||||
/**
|
||||
* Check if user can create
|
||||
*/
|
||||
function canCreate() {
|
||||
return isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can edit
|
||||
*/
|
||||
function canEdit() {
|
||||
return isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can delete
|
||||
*/
|
||||
function canDelete() {
|
||||
return isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can sync with Talpa
|
||||
*/
|
||||
function canSync() {
|
||||
return isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render button only if user has permission
|
||||
*/
|
||||
function renderButton($permission, $html) {
|
||||
if ($permission) {
|
||||
echo $html;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render form only if user has permission
|
||||
*/
|
||||
function renderForm($permission, $html) {
|
||||
if ($permission) {
|
||||
echo $html;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disabled attribute for guest users
|
||||
*/
|
||||
function getDisabledAttr() {
|
||||
return isGuest() ? 'disabled' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get readonly attribute for guest users
|
||||
*/
|
||||
function getReadonlyAttr() {
|
||||
return isGuest() ? 'readonly' : '';
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Aangepaste Navigatie
|
||||
|
||||
```php
|
||||
<!-- In alle pagina's: index.php, planner.php, calendar.php, etc. -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="index.php">
|
||||
<i class="bi bi-tv"></i> Telvero Talpa Planner
|
||||
</a>
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-link" href="index.php">Dashboard</a>
|
||||
<a class="nav-link" href="planner.php">Planner</a>
|
||||
<a class="nav-link" href="calendar.php">Kalender</a>
|
||||
<a class="nav-link" href="blocks.php">Blokken</a>
|
||||
<a class="nav-link" href="infomercials.php">Infomercials</a>
|
||||
<?php if (isAdmin()): ?>
|
||||
<a class="nav-link" href="admin/users.php">Gebruikers</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- User Info & Logout -->
|
||||
<div class="navbar-nav ms-auto">
|
||||
<span class="nav-link">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<?= htmlspecialchars(getCurrentUser()['username']) ?>
|
||||
<span class="badge bg-<?= isAdmin() ? 'danger' : 'secondary' ?>">
|
||||
<?= isAdmin() ? 'Admin' : 'Guest' ?>
|
||||
</span>
|
||||
</span>
|
||||
<a class="nav-link" href="auth/logout.php">
|
||||
<i class="bi bi-box-arrow-right"></i> Uitloggen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
```
|
||||
|
||||
### 5. Voorbeeld: Dashboard met Role-based UI
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/auth/auth_functions.php';
|
||||
require_once __DIR__ . '/auth/middleware.php';
|
||||
|
||||
// Require login
|
||||
requireLogin();
|
||||
|
||||
// Rest of existing code...
|
||||
?>
|
||||
|
||||
<!-- In de HTML -->
|
||||
<div class="container mt-4">
|
||||
<?php if (isGuest()): ?>
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
Je bent ingelogd als <strong>Guest</strong>. Je kunt alleen informatie bekijken.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Existing content -->
|
||||
|
||||
<!-- Infomercial Registration Form - Only for Admins -->
|
||||
<?php if (canCreate()): ?>
|
||||
<div class="card p-3 shadow-sm">
|
||||
<h5>1. Infomercial Registreren</h5>
|
||||
<form method="POST">
|
||||
<input type="text" name="title" class="form-control mb-2" placeholder="Product Naam" required>
|
||||
<input type="text" name="duration" class="form-control mb-2" placeholder="Duur (HH:MM:SS)" required>
|
||||
<input type="hidden" name="season_id" value="<?= $_ENV['TV_SEASON_ID'] ?>">
|
||||
<button type="submit" name="add_commercial" class="btn btn-primary w-100">
|
||||
Registreren bij Talpa
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Sync buttons - Only for Admins -->
|
||||
<?php if (canSync()): ?>
|
||||
<form method="POST" style="display:inline;">
|
||||
<input type="hidden" name="sync_id" value="<?= $tx['id'] ?>">
|
||||
<button type="submit" name="sync_item" class="btn-icon btn-icon-xs btn-icon-success">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<button type="button"
|
||||
class="btn-icon btn-icon-xs btn-icon-secondary"
|
||||
disabled
|
||||
title="Alleen admins kunnen synchroniseren">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 6. API Beveiliging Voorbeeld
|
||||
|
||||
```php
|
||||
<?php
|
||||
// api/create_transmission.php
|
||||
require_once __DIR__ . '/../auth/auth_functions.php';
|
||||
|
||||
// Check authentication
|
||||
if (!isLoggedIn()) {
|
||||
http_response_code(401);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Niet geautoriseerd. Log eerst in.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check authorization (admin only for write operations)
|
||||
if (!isAdmin()) {
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Geen toegang. Alleen admins kunnen uitzendingen aanmaken.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Continue with existing logic...
|
||||
```
|
||||
|
||||
### 7. Gebruikersbeheer Interface (admin/users.php)
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/../auth/auth_functions.php';
|
||||
require_once __DIR__ . '/../auth/middleware.php';
|
||||
|
||||
// Only admins can access
|
||||
requireAdmin();
|
||||
|
||||
$db = getAuthDb();
|
||||
|
||||
// Handle user creation
|
||||
if (isset($_POST['create_user'])) {
|
||||
$username = $_POST['username'];
|
||||
$email = $_POST['email'];
|
||||
$password = $_POST['password'];
|
||||
$role = $_POST['role'];
|
||||
|
||||
$passwordHash = password_hash($password, PASSWORD_BCRYPT);
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO users (username, email, password_hash, role)
|
||||
VALUES (?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([$username, $email, $passwordHash, $role]);
|
||||
|
||||
header("Location: users.php?success=created");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle user update
|
||||
if (isset($_POST['update_user'])) {
|
||||
$userId = $_POST['user_id'];
|
||||
$username = $_POST['username'];
|
||||
$email = $_POST['email'];
|
||||
$role = $_POST['role'];
|
||||
$isActive = isset($_POST['is_active']) ? 1 : 0;
|
||||
|
||||
$stmt = $db->prepare("
|
||||
UPDATE users
|
||||
SET username = ?, email = ?, role = ?, is_active = ?
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$username, $email, $role, $isActive, $userId]);
|
||||
|
||||
header("Location: users.php?success=updated");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 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();
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Gebruikersbeheer - Telvero Talpa</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<!-- Navigation -->
|
||||
<?php include __DIR__ . '/../includes/nav.php'; ?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<h1><i class="bi bi-people"></i> Gebruikersbeheer</h1>
|
||||
|
||||
<?php if (isset($_GET['success'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show">
|
||||
Gebruiker succesvol <?= $_GET['success'] === 'created' ? 'aangemaakt' : 'bijgewerkt' ?>!
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Create User Button -->
|
||||
<button type="button" class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#createUserModal">
|
||||
<i class="bi bi-plus-circle"></i> Nieuwe Gebruiker
|
||||
</button>
|
||||
|
||||
<!-- Users Table -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Gebruikersnaam</th>
|
||||
<th>Email</th>
|
||||
<th>Rol</th>
|
||||
<th>Status</th>
|
||||
<th>Laatste Login</th>
|
||||
<th>Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($user['username']) ?></td>
|
||||
<td><?= htmlspecialchars($user['email']) ?></td>
|
||||
<td>
|
||||
<span class="badge bg-<?= $user['role'] === 'admin' ? 'danger' : 'secondary' ?>">
|
||||
<?= ucfirst($user['role']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-<?= $user['is_active'] ? 'success' : 'warning' ?>">
|
||||
<?= $user['is_active'] ? 'Actief' : 'Inactief' ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<?= $user['last_login'] ? date('d-m-Y H:i', strtotime($user['last_login'])) : 'Nooit' ?>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
onclick="editUser(<?= htmlspecialchars(json_encode($user)) ?>)">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create User Modal -->
|
||||
<div class="modal fade" id="createUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form method="POST">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Nieuwe Gebruiker</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Gebruikersnaam</label>
|
||||
<input type="text" name="username" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Wachtwoord</label>
|
||||
<input type="password" name="password" class="form-control" required minlength="8">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Rol</label>
|
||||
<select name="role" class="form-select" required>
|
||||
<option value="guest">Guest</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuleren</button>
|
||||
<button type="submit" name="create_user" class="btn btn-primary">Aanmaken</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 8. JavaScript voor Guest Mode (calendar-init.js aanpassing)
|
||||
|
||||
```javascript
|
||||
// In assets/js/calendar-init.js
|
||||
|
||||
// Check if user is guest
|
||||
const isGuest = <?= json_encode(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
|
||||
538
plans/authentication-system-plan.md
Normal file
538
plans/authentication-system-plan.md
Normal file
@ -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
|
||||
<?php
|
||||
require_once __DIR__ . '/auth/auth_functions.php';
|
||||
require_once __DIR__ . '/auth/middleware.php';
|
||||
|
||||
// Forceer login
|
||||
requireLogin();
|
||||
|
||||
// Voor admin-only pagina's:
|
||||
// requireRole('admin');
|
||||
?>
|
||||
```
|
||||
|
||||
#### 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
|
||||
<?php
|
||||
require_once __DIR__ . '/../auth/auth_functions.php';
|
||||
require_once __DIR__ . '/../auth/middleware.php';
|
||||
|
||||
// Check authenticatie
|
||||
if (!isLoggedIn()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => 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
|
||||
<div class="navbar-nav ms-auto">
|
||||
<span class="nav-link">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<?= htmlspecialchars($_SESSION['user']['username']) ?>
|
||||
<span class="badge bg-<?= $_SESSION['user']['role'] === 'admin' ? 'danger' : 'secondary' ?>">
|
||||
<?= ucfirst($_SESSION['user']['role']) ?>
|
||||
</span>
|
||||
</span>
|
||||
<a class="nav-link" href="auth/logout.php">
|
||||
<i class="bi bi-box-arrow-right"></i> Uitloggen
|
||||
</a>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 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.
|
||||
202
setup_auth.php
Normal file
202
setup_auth.php
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
/**
|
||||
* Authentication Setup Script
|
||||
* Run this script once to set up the authentication system
|
||||
*
|
||||
* Usage: php setup_auth.php
|
||||
* Or via browser: http://your-domain/setup_auth.php
|
||||
*
|
||||
* IMPORTANT: Delete this file after running it!
|
||||
*/
|
||||
|
||||
// Security check - only allow running once
|
||||
$lockFile = __DIR__ . '/.auth_setup_done';
|
||||
if (file_exists($lockFile)) {
|
||||
die('Setup has already been completed. Delete .auth_setup_done to run again.');
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
$dotenv = Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->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 {
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Auth Setup - Telvero Talpa</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<h1><i class="bi bi-shield-check"></i> Authentication Setup</h1>
|
||||
|
||||
<?php if (!empty($success)): ?>
|
||||
<div class="card mb-3 border-success">
|
||||
<div class="card-header bg-success text-white">✓ Succesvol</div>
|
||||
<div class="card-body">
|
||||
<ul class="mb-0">
|
||||
<?php foreach ($success as $msg): ?>
|
||||
<li><?= htmlspecialchars($msg) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div class="card mb-3 border-danger">
|
||||
<div class="card-header bg-danger text-white">✗ Fouten</div>
|
||||
<div class="card-body">
|
||||
<ul class="mb-0">
|
||||
<?php foreach ($errors as $msg): ?>
|
||||
<li><?= htmlspecialchars($msg) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card mb-3 border-warning">
|
||||
<div class="card-header bg-warning">⚠️ Standaard Inloggegevens</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead>
|
||||
<tr><th>Gebruiker</th><th>Wachtwoord</th><th>Rol</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>admin</strong></td>
|
||||
<td><code>Admin@2026!</code></td>
|
||||
<td><span class="badge bg-danger">Admin</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>guest</strong></td>
|
||||
<td><code>Guest@2026!</code></td>
|
||||
<td><span class="badge bg-secondary">Guest</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="alert alert-warning mt-3 mb-0">
|
||||
<strong>Belangrijk:</strong> Wijzig deze wachtwoorden na de eerste login!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<a href="/auth/login.php" class="btn btn-primary">
|
||||
Ga naar Login
|
||||
</a>
|
||||
<a href="/admin/users.php" class="btn btn-secondary">
|
||||
Gebruikersbeheer
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user