Add nodes management and initial setup pages
- 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.
This commit is contained in:
428
pathvector-admin/lib/Auth.php
Normal file
428
pathvector-admin/lib/Auth.php
Normal file
@@ -0,0 +1,428 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user