- Implemented nodes management functionality in `nodes.php` including create, update, and delete actions. - Added form validation and error handling for node operations. - Created a new setup page in `setup.php` for initial administrator account creation. - Included user feedback messages for successful operations and errors. - Designed user interface for both nodes management and setup processes.
429 lines
11 KiB
PHP
429 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Auth Class
|
|
*
|
|
* Handles authentication and authorization for the Pathvector admin dashboard
|
|
*/
|
|
|
|
require_once __DIR__ . '/FlatFileDB.php';
|
|
require_once __DIR__ . '/Logger.php';
|
|
|
|
class Auth {
|
|
private FlatFileDB $db;
|
|
private Logger $logger;
|
|
private array $config;
|
|
private array $roles = [
|
|
'admin' => ['admin', 'operator', 'readonly'],
|
|
'operator' => ['operator', 'readonly'],
|
|
'readonly' => ['readonly'],
|
|
];
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param string $userFile Path to users JSON file
|
|
* @param Logger $logger Logger instance
|
|
* @param array $config Application config
|
|
*/
|
|
public function __construct(string $userFile, Logger $logger, array $config) {
|
|
$this->db = new FlatFileDB($userFile);
|
|
$this->logger = $logger;
|
|
$this->config = $config;
|
|
|
|
// Initialize users if empty
|
|
if (!$this->db->exists('users')) {
|
|
$this->initializeDefaultAdmin();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize default admin user
|
|
*
|
|
* @return void
|
|
*/
|
|
private function initializeDefaultAdmin(): void {
|
|
$defaultAdmin = $this->config['default_admin'] ?? [
|
|
'username' => 'admin',
|
|
'password' => 'pathvector',
|
|
'role' => 'admin',
|
|
];
|
|
|
|
$users = [
|
|
$defaultAdmin['username'] => [
|
|
'username' => $defaultAdmin['username'],
|
|
'password' => password_hash($defaultAdmin['password'], PASSWORD_DEFAULT),
|
|
'role' => $defaultAdmin['role'],
|
|
'created_at' => date('c'),
|
|
'updated_at' => date('c'),
|
|
'last_login' => null,
|
|
'is_active' => true,
|
|
],
|
|
];
|
|
|
|
$this->db->set('users', $users);
|
|
}
|
|
|
|
/**
|
|
* Start session if not already started
|
|
*
|
|
* @return void
|
|
*/
|
|
public function startSession(): void {
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
session_start();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate CSRF token
|
|
*
|
|
* @return string
|
|
*/
|
|
public function generateCsrfToken(): string {
|
|
$this->startSession();
|
|
|
|
if (!isset($_SESSION['csrf_token'])) {
|
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
}
|
|
|
|
return $_SESSION['csrf_token'];
|
|
}
|
|
|
|
/**
|
|
* Validate CSRF token
|
|
*
|
|
* @param string $token
|
|
* @return bool
|
|
*/
|
|
public function validateCsrfToken(string $token): bool {
|
|
$this->startSession();
|
|
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
|
|
}
|
|
|
|
/**
|
|
* Regenerate CSRF token
|
|
*
|
|
* @return string
|
|
*/
|
|
public function regenerateCsrfToken(): string {
|
|
$this->startSession();
|
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
return $_SESSION['csrf_token'];
|
|
}
|
|
|
|
/**
|
|
* Authenticate user
|
|
*
|
|
* @param string $username
|
|
* @param string $password
|
|
* @return bool
|
|
*/
|
|
public function login(string $username, string $password): bool {
|
|
$this->startSession();
|
|
|
|
$users = $this->db->get('users') ?? [];
|
|
|
|
if (!isset($users[$username])) {
|
|
$this->logger->warning('auth', "Failed login attempt for unknown user: $username");
|
|
return false;
|
|
}
|
|
|
|
$user = $users[$username];
|
|
|
|
if (!$user['is_active']) {
|
|
$this->logger->warning('auth', "Login attempt for inactive user: $username");
|
|
return false;
|
|
}
|
|
|
|
if (!password_verify($password, $user['password'])) {
|
|
$this->logger->warning('auth', "Failed login attempt for user: $username");
|
|
return false;
|
|
}
|
|
|
|
// Update last login
|
|
$users[$username]['last_login'] = date('c');
|
|
$this->db->set('users', $users);
|
|
|
|
// Set session
|
|
$_SESSION['user'] = [
|
|
'username' => $user['username'],
|
|
'role' => $user['role'],
|
|
'logged_in_at' => date('c'),
|
|
];
|
|
|
|
// Regenerate session ID for security
|
|
session_regenerate_id(true);
|
|
|
|
$this->logger->success('auth', "User logged in: $username");
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Logout user
|
|
*
|
|
* @return void
|
|
*/
|
|
public function logout(): void {
|
|
$this->startSession();
|
|
|
|
$username = $_SESSION['user']['username'] ?? 'unknown';
|
|
$this->logger->info('auth', "User logged out: $username");
|
|
|
|
$_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 if user is logged in
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isLoggedIn(): bool {
|
|
$this->startSession();
|
|
return isset($_SESSION['user']);
|
|
}
|
|
|
|
/**
|
|
* Get current user
|
|
*
|
|
* @return array|null
|
|
*/
|
|
public function getCurrentUser(): ?array {
|
|
$this->startSession();
|
|
return $_SESSION['user'] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Check if current user has role
|
|
*
|
|
* @param string $requiredRole
|
|
* @return bool
|
|
*/
|
|
public function hasRole(string $requiredRole): bool {
|
|
$this->startSession();
|
|
|
|
if (!isset($_SESSION['user']['role'])) {
|
|
return false;
|
|
}
|
|
|
|
$userRole = $_SESSION['user']['role'];
|
|
|
|
return in_array($requiredRole, $this->roles[$userRole] ?? []);
|
|
}
|
|
|
|
/**
|
|
* Require login - redirect if not logged in
|
|
*
|
|
* @return void
|
|
*/
|
|
public function requireLogin(): void {
|
|
if (!$this->isLoggedIn()) {
|
|
header('Location: login.php');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Require specific role
|
|
*
|
|
* @param string $role
|
|
* @return void
|
|
*/
|
|
public function requireRole(string $role): void {
|
|
$this->requireLogin();
|
|
|
|
if (!$this->hasRole($role)) {
|
|
header('HTTP/1.1 403 Forbidden');
|
|
echo 'Access Denied: Insufficient permissions';
|
|
exit;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create new user
|
|
*
|
|
* @param string $username
|
|
* @param string $password
|
|
* @param string $role
|
|
* @return bool
|
|
*/
|
|
public function createUser(string $username, string $password, string $role = 'readonly'): bool {
|
|
$users = $this->db->get('users') ?? [];
|
|
|
|
if (isset($users[$username])) {
|
|
return false;
|
|
}
|
|
|
|
$users[$username] = [
|
|
'username' => $username,
|
|
'password' => password_hash($password, PASSWORD_DEFAULT),
|
|
'role' => $role,
|
|
'created_at' => date('c'),
|
|
'updated_at' => date('c'),
|
|
'last_login' => null,
|
|
'is_active' => true,
|
|
];
|
|
|
|
$result = $this->db->set('users', $users);
|
|
|
|
if ($result) {
|
|
$this->logger->success('auth', "User created: $username with role: $role");
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Update user
|
|
*
|
|
* @param string $username
|
|
* @param array $data
|
|
* @return bool
|
|
*/
|
|
public function updateUser(string $username, array $data): bool {
|
|
$users = $this->db->get('users') ?? [];
|
|
|
|
if (!isset($users[$username])) {
|
|
return false;
|
|
}
|
|
|
|
if (isset($data['password'])) {
|
|
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
|
}
|
|
|
|
$data['updated_at'] = date('c');
|
|
$users[$username] = array_merge($users[$username], $data);
|
|
|
|
$result = $this->db->set('users', $users);
|
|
|
|
if ($result) {
|
|
$this->logger->info('auth', "User updated: $username");
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Delete user
|
|
*
|
|
* @param string $username
|
|
* @return bool
|
|
*/
|
|
public function deleteUser(string $username): bool {
|
|
$users = $this->db->get('users') ?? [];
|
|
|
|
if (!isset($users[$username])) {
|
|
return false;
|
|
}
|
|
|
|
unset($users[$username]);
|
|
|
|
$result = $this->db->set('users', $users);
|
|
|
|
if ($result) {
|
|
$this->logger->info('auth', "User deleted: $username");
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get user by username
|
|
*
|
|
* @param string $username
|
|
* @return array|null
|
|
*/
|
|
public function getUser(string $username): ?array {
|
|
$users = $this->db->get('users') ?? [];
|
|
return $users[$username] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Get all users
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getAllUsers(): array {
|
|
$users = $this->db->get('users') ?? [];
|
|
|
|
// Remove password hashes for security
|
|
return array_map(function($user) {
|
|
unset($user['password']);
|
|
return $user;
|
|
}, $users);
|
|
}
|
|
|
|
/**
|
|
* Change user password
|
|
*
|
|
* @param string $username
|
|
* @param string $currentPassword
|
|
* @param string $newPassword
|
|
* @return bool
|
|
*/
|
|
public function changePassword(string $username, string $currentPassword, string $newPassword): bool {
|
|
$users = $this->db->get('users') ?? [];
|
|
|
|
if (!isset($users[$username])) {
|
|
return false;
|
|
}
|
|
|
|
if (!password_verify($currentPassword, $users[$username]['password'])) {
|
|
$this->logger->warning('auth', "Password change failed for user: $username (wrong current password)");
|
|
return false;
|
|
}
|
|
|
|
$users[$username]['password'] = password_hash($newPassword, PASSWORD_DEFAULT);
|
|
$users[$username]['updated_at'] = date('c');
|
|
|
|
$result = $this->db->set('users', $users);
|
|
|
|
if ($result) {
|
|
$this->logger->success('auth', "Password changed for user: $username");
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Reset user password (admin function)
|
|
*
|
|
* @param string $username
|
|
* @param string $newPassword
|
|
* @return bool
|
|
*/
|
|
public function resetPassword(string $username, string $newPassword): bool {
|
|
$users = $this->db->get('users') ?? [];
|
|
|
|
if (!isset($users[$username])) {
|
|
return false;
|
|
}
|
|
|
|
$users[$username]['password'] = password_hash($newPassword, PASSWORD_DEFAULT);
|
|
$users[$username]['updated_at'] = date('c');
|
|
|
|
$result = $this->db->set('users', $users);
|
|
|
|
if ($result) {
|
|
$this->logger->success('auth', "Password reset for user: $username by admin");
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|