Files
Pathvector.app/pathvector-admin/lib/Auth.php
Joseph.Rawlings d8b76233c0 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.
2025-12-14 01:33:12 -05:00

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;
}
}