BROOKO icon
BROOKO UK NETWORK
Where code meets creativity & adventure
File viewer

auth.php

Type
php
Size
10.92 KB
Modified
15 May
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; }
}