auth.php
10.92 KB
<?php
/**
* WorkersPanel Authentication
* Shared login and permission helpers
*/
function authenticateUser(string $username, string $password, bool $remember = false): array {
global $pdo;
if (!isset($pdo)) return ['success' => false, 'message' => 'Database connection failed'];
try {
$stmt = $pdo->prepare("
SELECT id, username, email, display_name, password_hash, role, secondary_group,
is_active, must_change_password
FROM users WHERE (username = ? OR email = ?) LIMIT 1
");
$stmt->execute([$username, $username]);
$user = $stmt->fetch();
} catch (Throwable $e) {
return ['success' => false, 'message' => 'Database error. Please try again.'];
}
// Validate
$success = false;
$failReason = null;
if (!$user) $failReason = 'User not found';
elseif (!$user['is_active']) $failReason = 'Account is disabled';
elseif (!password_verify($password, $user['password_hash'])) $failReason = 'Invalid password';
else $success = true;
// Log to login_logs table
_logLoginAttempt($user['id'] ?? null, $username, $user['role'] ?? null, $success, $failReason);
if (!$success) return ['success' => false, 'message' => 'Invalid username or password'];
// Update last login
try { $pdo->prepare("UPDATE users SET last_login_at = NOW() WHERE id = ?")->execute([$user['id']]); }
catch (Throwable $e) {}
// Load permissions
$permissions = _loadPermissions($user['id'], $user['role']);
// Set session
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['user_display_name'] = $user['display_name'];
$_SESSION['username'] = $user['username'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['secondary_group'] = $user['secondary_group'];
$_SESSION['permissions'] = $permissions;
$_SESSION['must_change_password'] = $user['must_change_password'];
$_SESSION['wp_instance_key'] = function_exists('wp_instance_key') ? wp_instance_key() : 'default';
if ($remember) _createRememberToken($user['id']);
return ['success' => true, 'user' => $user, 'must_change_password' => (bool)$user['must_change_password']];
}
function wp_permission_role_defaults(): array {
return [
'administrator' => ['*'],
'director' => ['*'],
'admin' => ['*'],
'superadmin' => ['*'],
'super-admin' => ['*'],
'manager' => [
'dashboard.view',
'calendar.view','calendar.create','calendar.edit',
'management.access',
'vehicles.view','vehicles.edit',
'trutrak.view','trutrak.history',
'rota.view','rota.edit',
'holidays.view','holidays.request','holidays.manage',
'incidents.view','incidents.create','incidents.manage',
'contacts.view','contacts.manage',
'alerts.view','alerts.send',
'clock.use','clock.view_team','clock.driver_start','clock.driver_checkout','clock.porter_start','clock.porter_checkout','clock.photo_upload',
],
'driver' => [
'dashboard.view',
'rota.view',
'holidays.request',
'incidents.create',
'contacts.view',
'alerts.view',
'clock.use','clock.driver_start','clock.driver_checkout','clock.photo_upload',
],
'porter' => [
'dashboard.view',
'rota.view',
'holidays.request',
'incidents.create',
'contacts.view',
'alerts.view',
'clock.use','clock.porter_start','clock.porter_checkout',
],
];
}
function _loadPermissions(int $userId, string $role): array {
global $pdo;
$normalizedRole = wp_normalize_role_name($role);
$defaults = wp_permission_role_defaults();
if (($defaults[$normalizedRole] ?? null) === ['*']) {
try {
if (isset($pdo) && $pdo instanceof PDO) {
$stmt = $pdo->query("SELECT name FROM permissions ORDER BY name");
$rows = $stmt ? $stmt->fetchAll(PDO::FETCH_COLUMN) : [];
if (!empty($rows)) {
return array_values(array_map('strval', $rows));
}
}
} catch (Throwable $e) {}
return [
'dashboard.view',
'calendar.view','calendar.create','calendar.edit','calendar.delete','calendar.categories.manage',
'management.access','staff.view','staff.edit','vehicles.view','vehicles.edit','logs.view',
'trutrak.view','trutrak.history','trutrak.manage',
'rota.view','rota.edit','holidays.view','holidays.request','holidays.manage',
'incidents.view','incidents.create','incidents.manage',
'contacts.view','contacts.manage','alerts.view','alerts.send',
'clock.use','clock.view_team','clock.driver_start','clock.driver_checkout','clock.porter_start','clock.porter_checkout','clock.photo_upload','exports.manage','exports.calendar','exports.clock','exports.rota','exports.holidays','exports.incidents','integrations.manage'
];
}
try {
$stmt = $pdo->prepare("
SELECT p.name FROM permissions p
INNER JOIN role_permissions rp ON rp.permission_id = p.id
INNER JOIN roles r ON r.id = rp.role_id
WHERE r.name = ?
");
$stmt->execute([$normalizedRole]);
$rows = array_values(array_map('strval', array_column($stmt->fetchAll(), 'name')));
if (!empty($rows)) {
return $rows;
}
} catch (Throwable $e) {
// Fall back to the baked-in role defaults if the permissions tables are missing or incomplete.
}
return $defaults[$normalizedRole] ?? [];
}
function _logLoginAttempt(?int $userId, string $username, ?string $role, bool $success, ?string $failReason): void {
global $pdo;
try {
$pdo->prepare("
INSERT INTO login_logs (user_id, username, role, ip_address, user_agent, success, failure_reason)
VALUES (?, ?, ?, ?, ?, ?, ?)
")->execute([
$userId,
$username,
$role,
$_SERVER['REMOTE_ADDR'] ?? null,
$_SERVER['HTTP_USER_AGENT'] ?? null,
$success ? 1 : 0,
$failReason,
]);
} catch (Throwable $e) {} // Graceful - login_logs may not exist on older installs
}
function _createRememberToken(int $userId): void {
global $pdo;
try {
$selector = bin2hex(random_bytes(16));
$validator = bin2hex(random_bytes(32));
$expires = date('Y-m-d H:i:s', time() + 30 * 86400);
$pdo->prepare("
INSERT INTO remember_tokens (user_id, selector, hashed_validator, expires_at)
VALUES (?, ?, ?, ?)
")->execute([$userId, $selector, password_hash($validator, PASSWORD_DEFAULT), $expires]);
setcookie(function_exists('wp_remember_cookie_name') ? wp_remember_cookie_name() : 'remember_token', $selector . ':' . $validator, [
'expires' => time() + 30 * 86400,
'path' => function_exists('wp_cookie_path') ? wp_cookie_path() : '/',
'domain' => '',
'secure' => function_exists('wp_cookie_secure') ? wp_cookie_secure() : true,
'httponly' => true,
'samesite' => 'Lax',
]);
} catch (Throwable $e) {}
}
function checkRememberToken(): bool {
$cookieName = function_exists('wp_remember_cookie_name') ? wp_remember_cookie_name() : 'remember_token';
if (!isset($_COOKIE[$cookieName])) return false;
global $pdo;
try {
$parts = explode(':', $_COOKIE[$cookieName], 2);
if (count($parts) !== 2) { deleteRememberCookie(); return false; }
[$selector, $validator] = $parts;
$stmt = $pdo->prepare("
SELECT rt.id, rt.hashed_validator, u.id AS uid, u.display_name, u.role,
u.secondary_group, u.is_active, u.must_change_password
FROM remember_tokens rt
INNER JOIN users u ON u.id = rt.user_id
WHERE rt.selector = ? AND rt.expires_at > NOW()
");
$stmt->execute([$selector]);
$row = $stmt->fetch();
if (!$row || !$row['is_active'] || !password_verify($validator, $row['hashed_validator'])) {
deleteRememberCookie();
return false;
}
$permissions = _loadPermissions($row['uid'], $row['role']);
$_SESSION['user_id'] = $row['uid'];
$_SESSION['user_role'] = $row['role'];
$_SESSION['user_display_name'] = $row['display_name'];
$_SESSION['username'] = $row['display_name'];
$_SESSION['user_email'] = '';
$_SESSION['secondary_group'] = $row['secondary_group'];
$_SESSION['permissions'] = $permissions;
$_SESSION['must_change_password'] = $row['must_change_password'];
$_SESSION['wp_instance_key'] = function_exists('wp_instance_key') ? wp_instance_key() : 'default';
$pdo->prepare("UPDATE users SET last_login_at = NOW() WHERE id = ?")->execute([$row['uid']]);
$pdo->prepare("DELETE FROM remember_tokens WHERE id = ?")->execute([$row['id']]);
_createRememberToken($row['uid']);
return true;
} catch (Throwable $e) {
return false;
}
}
function deleteRememberCookie(): void {
$cookieName = function_exists('wp_remember_cookie_name') ? wp_remember_cookie_name() : 'remember_token';
setcookie($cookieName, '', [
'expires' => time() - 3600,
'path' => function_exists('wp_cookie_path') ? wp_cookie_path() : '/',
'domain' => '',
'secure' => function_exists('wp_cookie_secure') ? wp_cookie_secure() : true,
'httponly' => true,
'samesite' => 'Lax',
]);
unset($_COOKIE[$cookieName]);
setcookie('remember_token', '', time() - 3600, '/', '', true, true);
unset($_COOKIE['remember_token']);
}
function logoutUser(): void {
global $pdo;
if (isset($_SESSION['user_id']) && isset($pdo)) {
try { $pdo->prepare("DELETE FROM remember_tokens WHERE user_id = ?")->execute([$_SESSION['user_id']]); }
catch (Throwable $e) {}
}
deleteRememberCookie();
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$p = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000, $p['path'], $p['domain'], $p['secure'], $p['httponly']);
}
if (session_status() === PHP_SESSION_ACTIVE) session_destroy();
}
function changePassword(int $userId, string $newPassword): bool {
global $pdo;
try {
return (bool) $pdo->prepare("
UPDATE users SET password_hash = ?, must_change_password = 0, updated_at = NOW() WHERE id = ?
")->execute([password_hash($newPassword, PASSWORD_DEFAULT), $userId]);
} catch (Throwable $e) { return false; }
}