- Enable SSO with Azure AD credentials (tenant + client ID + redirect_uri) - Add JWTValidator.php: RS256 idToken validation via Azure JWKS with 1h cache - Add auth.php: POST login handler sets auth cookie, GET logout clears it - Add UserRoleManager.php: file-based role CRUD in data/user_roles.json - Add admin.php: admin-only role management panel - AuthMiddleware: add requireAdmin(), role in user array, fix MSAL redirect - header.php: hide Activity Logs + Admin Panel tabs for non-admin users - logs-viewer.php: protect with requireAdmin() instead of requireAuth() - server-setup.sh: add composer check, data/ dir, PHP extension checks, SSO validation - .gitignore: add data/ directory Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
327 lines
8.6 KiB
PHP
327 lines
8.6 KiB
PHP
<?php
|
|
/**
|
|
* Admin Panel — User Role Management
|
|
* Only accessible to users with the 'admin' role
|
|
*/
|
|
|
|
require_once __DIR__ . '/vendor/autoload.php';
|
|
require_once __DIR__ . '/AuthMiddleware.php';
|
|
require_once __DIR__ . '/UserRoleManager.php';
|
|
|
|
$auth = new AuthMiddleware();
|
|
$user = $auth->requireAdmin();
|
|
|
|
$roleManager = new UserRoleManager();
|
|
$pageTitle = 'Admin Panel - L\'Oréal OMG';
|
|
|
|
$message = '';
|
|
$messageType = '';
|
|
|
|
// Handle POST — update role
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$targetEmail = trim($_POST['email'] ?? '');
|
|
$targetRole = trim($_POST['role'] ?? '');
|
|
|
|
if ($targetEmail && in_array($targetRole, ['admin', 'user'], true)) {
|
|
// Prevent admin from demoting themselves
|
|
if (strtolower($targetEmail) === strtolower($user['email']) && $targetRole !== 'admin') {
|
|
$message = 'You cannot remove your own admin role.';
|
|
$messageType = 'error';
|
|
} else {
|
|
$roleManager->setRole($targetEmail, $targetRole);
|
|
$message = "Role for " . htmlspecialchars($targetEmail) . " updated to " . htmlspecialchars($targetRole) . ".";
|
|
$messageType = 'success';
|
|
}
|
|
} else {
|
|
$message = 'Invalid email or role.';
|
|
$messageType = 'error';
|
|
}
|
|
}
|
|
|
|
$allUsers = $roleManager->getAllUsers();
|
|
|
|
require_once __DIR__ . '/header.php';
|
|
?>
|
|
|
|
<div class="admin-container">
|
|
<div class="admin-section">
|
|
<h2>Admin Panel — User Role Management</h2>
|
|
|
|
<?php if ($message): ?>
|
|
<div class="alert alert-<?php echo $messageType; ?>"><?php echo $message; ?></div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Add / Update Role Form -->
|
|
<div class="admin-card">
|
|
<h3>Set User Role</h3>
|
|
<form method="POST" class="role-form">
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="emailInput">User Email</label>
|
|
<input type="email" id="emailInput" name="email" required
|
|
placeholder="user@oliver.agency" class="form-input"
|
|
list="existingEmails">
|
|
<datalist id="existingEmails">
|
|
<?php foreach ($allUsers as $u): ?>
|
|
<option value="<?php echo htmlspecialchars($u['email']); ?>">
|
|
<?php endforeach; ?>
|
|
</datalist>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="roleSelect">Role</label>
|
|
<select id="roleSelect" name="role" class="form-select">
|
|
<option value="user">User</option>
|
|
<option value="admin">Admin</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group form-group--action">
|
|
<button type="submit" class="action-btn save-btn">Save Role</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Users Table -->
|
|
<div class="admin-card">
|
|
<h3>All Users (<?php echo count($allUsers); ?>)</h3>
|
|
<?php if (empty($allUsers)): ?>
|
|
<p class="empty-state">No users have been assigned roles yet. Roles are created automatically on first login.</p>
|
|
<?php else: ?>
|
|
<div class="users-table-wrapper">
|
|
<table class="users-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Email</th>
|
|
<th>Role</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($allUsers as $u): ?>
|
|
<tr>
|
|
<td><?php echo htmlspecialchars($u['email']); ?></td>
|
|
<td>
|
|
<span class="role-pill role-<?php echo $u['role']; ?>">
|
|
<?php echo ucfirst($u['role']); ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<form method="POST" style="display:inline;">
|
|
<input type="hidden" name="email" value="<?php echo htmlspecialchars($u['email']); ?>">
|
|
<select name="role" class="inline-select">
|
|
<option value="user" <?php echo $u['role'] === 'user' ? 'selected' : ''; ?>>User</option>
|
|
<option value="admin" <?php echo $u['role'] === 'admin' ? 'selected' : ''; ?>>Admin</option>
|
|
</select>
|
|
<button type="submit" class="action-btn small-btn">Update</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.admin-container {
|
|
max-width: 900px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.admin-section {
|
|
padding: 0 20px;
|
|
}
|
|
|
|
.admin-section h2 {
|
|
color: #000;
|
|
margin-bottom: 24px;
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.admin-card {
|
|
background: #fff;
|
|
border-radius: 10px;
|
|
box-shadow: 0 4px 6px rgba(255, 196, 7, 0.3);
|
|
padding: 30px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.admin-card h3 {
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
margin-bottom: 20px;
|
|
color: #000;
|
|
border-bottom: 2px solid #FFC407;
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
.alert {
|
|
padding: 14px 20px;
|
|
border-radius: 6px;
|
|
margin-bottom: 20px;
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.alert-success {
|
|
background: #e8f5e9;
|
|
color: #2e7d32;
|
|
border-left: 4px solid #4CAF50;
|
|
}
|
|
|
|
.alert-error {
|
|
background: #ffebee;
|
|
color: #c62828;
|
|
border-left: 4px solid #ff4444;
|
|
}
|
|
|
|
.role-form .form-row {
|
|
display: flex;
|
|
gap: 20px;
|
|
align-items: flex-end;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.form-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.form-group label {
|
|
font-weight: 600;
|
|
font-size: 13px;
|
|
color: #333;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.form-input, .form-select {
|
|
padding: 10px 14px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 5px;
|
|
font-family: 'Montserrat', sans-serif;
|
|
font-size: 14px;
|
|
min-width: 240px;
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
.form-input:focus, .form-select:focus {
|
|
outline: none;
|
|
border-color: #FFC407;
|
|
}
|
|
|
|
.form-group--action {
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.save-btn {
|
|
padding: 10px 24px;
|
|
background: #FFC407;
|
|
color: #000;
|
|
border: none;
|
|
border-radius: 5px;
|
|
font-family: 'Montserrat', sans-serif;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.save-btn:hover {
|
|
background: #000;
|
|
color: #FFC407;
|
|
}
|
|
|
|
.users-table-wrapper {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.users-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.users-table th {
|
|
background: #000;
|
|
color: #FFC407;
|
|
padding: 12px 16px;
|
|
text-align: left;
|
|
font-weight: 700;
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.users-table td {
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid #eee;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.users-table tr:hover td {
|
|
background: rgba(255, 196, 7, 0.05);
|
|
}
|
|
|
|
.role-pill {
|
|
display: inline-block;
|
|
padding: 3px 12px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.role-pill.role-admin {
|
|
background: #FFC407;
|
|
color: #000;
|
|
}
|
|
|
|
.role-pill.role-user {
|
|
background: #f0f0f0;
|
|
color: #555;
|
|
}
|
|
|
|
.inline-select {
|
|
padding: 6px 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-family: 'Montserrat', sans-serif;
|
|
font-size: 13px;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.small-btn {
|
|
padding: 6px 14px;
|
|
background: #FFC407;
|
|
color: #000;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-family: 'Montserrat', sans-serif;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.small-btn:hover {
|
|
background: #000;
|
|
color: #FFC407;
|
|
}
|
|
|
|
.empty-state {
|
|
color: #777;
|
|
font-size: 14px;
|
|
text-align: center;
|
|
padding: 20px;
|
|
}
|
|
</style>
|
|
|
|
</body>
|
|
</html>
|