release(v2.0.4): harden sessions and align Pro paths with USERS_DIR

This commit is contained in:
Ryan
2025-11-26 21:41:59 -05:00
committed by GitHub
parent 6b93d65d6a
commit f967134631
4 changed files with 39 additions and 10 deletions

View File

@@ -1,5 +1,17 @@
# Changelog # Changelog
## Changes 11/26/2025 (v2.0.4)
release(v2.0.4): harden sessions and align Pro paths with USERS_DIR
- Enable strict_types in config.php and AdminController
- Decouple PHP session lifetime from "remember me" window
- Regenerate session ID on persistent token auto-login
- Point Pro license / bundle paths at USERS_DIR instead of hardcoded /users
- Tweak folder management card drag offset for better alignment
---
## Changes 11/26/2025 (v2.0.3) ## Changes 11/26/2025 (v2.0.3)
release(v2.0.3): polish uploads, header dock, and panel fly animations release(v2.0.3): polish uploads, header dock, and panel fly animations

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
// config.php // config.php
// Define constants // Define constants
@@ -101,10 +102,15 @@ $secure = ($envSecure !== false)
? filter_var($envSecure, FILTER_VALIDATE_BOOLEAN) ? filter_var($envSecure, FILTER_VALIDATE_BOOLEAN)
: (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'); : (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
// Choose session lifetime based on "remember me" cookie
// PHP session lifetime (independent of "remember me")
// Keep this reasonably short; "remember me" uses its own token.
$defaultSession = 7200; // 2 hours $defaultSession = 7200; // 2 hours
$sessionLifetime = $defaultSession;
// "Remember me" window (how long the persistent token itself is valid)
// This is used in persistent_tokens.json, *not* for PHP session lifetime.
$persistentDays = 30 * 24 * 60 * 60; // 30 days $persistentDays = 30 * 24 * 60 * 60; // 30 days
$sessionLifetime = isset($_COOKIE['remember_me_token']) ? $persistentDays : $defaultSession;
/** /**
* Start session idempotently: * Start session idempotently:
@@ -155,6 +161,11 @@ if (empty($_SESSION["authenticated"]) && !empty($_COOKIE['remember_me_token']))
if (!empty($tokens[$token])) { if (!empty($tokens[$token])) {
$data = $tokens[$token]; $data = $tokens[$token];
if ($data['expiry'] >= time()) { if ($data['expiry'] >= time()) {
// NEW: mitigate session fixation
if (session_status() === PHP_SESSION_ACTIVE) {
session_regenerate_id(true);
}
$_SESSION["authenticated"] = true; $_SESSION["authenticated"] = true;
$_SESSION["username"] = $data["username"]; $_SESSION["username"] = $data["username"];
$_SESSION["folderOnly"] = loadUserPermissions($data["username"]); $_SESSION["folderOnly"] = loadUserPermissions($data["username"]);
@@ -162,7 +173,11 @@ if (empty($_SESSION["authenticated"]) && !empty($_COOKIE['remember_me_token']))
} else { } else {
// expired — clean up // expired — clean up
unset($tokens[$token]); unset($tokens[$token]);
file_put_contents($tokFile, encryptData(json_encode($tokens, JSON_PRETTY_PRINT), $encryptionKey), LOCK_EX); file_put_contents(
$tokFile,
encryptData(json_encode($tokens, JSON_PRETTY_PRINT), $encryptionKey),
LOCK_EX
);
setcookie('remember_me_token', '', time() - 3600, '/', '', $secure, true); setcookie('remember_me_token', '', time() - 3600, '/', '', $secure, true);
} }
} }
@@ -253,14 +268,14 @@ if (!defined('FR_PRO_LICENSE')) {
// JSON license file used by AdminController::setLicense() // JSON license file used by AdminController::setLicense()
if (!defined('PRO_LICENSE_FILE')) { if (!defined('PRO_LICENSE_FILE')) {
define('PRO_LICENSE_FILE', PROJECT_ROOT . '/users/proLicense.json'); define('PRO_LICENSE_FILE', rtrim(USERS_DIR, "/\\") . '/proLicense.json');
} }
// Optional plain-text license file (used as fallback in bootstrap) // Optional plain-text license file (used as fallback in bootstrap)
if (!defined('FR_PRO_LICENSE_FILE')) { if (!defined('FR_PRO_LICENSE_FILE')) {
$lf = getenv('FR_PRO_LICENSE_FILE'); $lf = getenv('FR_PRO_LICENSE_FILE');
if ($lf === false || $lf === '') { if ($lf === false || $lf === '') {
$lf = PROJECT_ROOT . '/users/proLicense.txt'; $lf = rtrim(USERS_DIR, "/\\") . '/proLicense.txt';
} }
define('FR_PRO_LICENSE_FILE', $lf); define('FR_PRO_LICENSE_FILE', $lf);
} }
@@ -268,7 +283,7 @@ if (!defined('FR_PRO_LICENSE_FILE')) {
// Where Pro code lives by default → inside users volume // Where Pro code lives by default → inside users volume
$proDir = getenv('FR_PRO_BUNDLE_DIR'); $proDir = getenv('FR_PRO_BUNDLE_DIR');
if ($proDir === false || $proDir === '') { if ($proDir === false || $proDir === '') {
$proDir = PROJECT_ROOT . '/users/pro'; $proDir = rtrim(USERS_DIR, "/\\") . '/pro';
} }
$proDir = rtrim($proDir, "/\\"); $proDir = rtrim($proDir, "/\\");
if (!defined('FR_PRO_BUNDLE_DIR')) { if (!defined('FR_PRO_BUNDLE_DIR')) {

View File

@@ -524,7 +524,7 @@ function animateCardsOutOfHeaderThen(done) {
if (card.id === 'uploadCard') { if (card.id === 'uploadCard') {
toCy -= 48; // a bit higher toCy -= 48; // a bit higher
} else if (card.id === 'folderManagementCard') { } else if (card.id === 'folderManagementCard') {
toCy += 60; // a bit lower toCy += 48; // a bit lower
} }
} }

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
// src/controllers/AdminController.php // src/controllers/AdminController.php
require_once __DIR__ . '/../../config/config.php'; require_once __DIR__ . '/../../config/config.php';
@@ -241,7 +242,7 @@ public function setLicense(): void
// Store license + updatedAt in JSON file // Store license + updatedAt in JSON file
if (!defined('PRO_LICENSE_FILE')) { if (!defined('PRO_LICENSE_FILE')) {
// Fallback if constant not defined for some reason // Fallback if constant not defined for some reason
define('PRO_LICENSE_FILE', PROJECT_ROOT . '/users/proLicense.json'); define('PRO_LICENSE_FILE', rtrim(USERS_DIR, "/\\") . '/proLicense.json');
} }
$payload = [ $payload = [
@@ -566,10 +567,11 @@ public function installProBundle(): void
$projectRoot = rtrim(PROJECT_ROOT, DIRECTORY_SEPARATOR); $projectRoot = rtrim(PROJECT_ROOT, DIRECTORY_SEPARATOR);
// Where Pro bundle code lives (defaults to PROJECT_ROOT . '/users/pro') // Where Pro bundle code lives (defaults to USERS_DIR . '/pro')
$projectRoot = rtrim(PROJECT_ROOT, DIRECTORY_SEPARATOR);
$bundleRoot = defined('FR_PRO_BUNDLE_DIR') $bundleRoot = defined('FR_PRO_BUNDLE_DIR')
? rtrim(FR_PRO_BUNDLE_DIR, DIRECTORY_SEPARATOR) ? rtrim(FR_PRO_BUNDLE_DIR, DIRECTORY_SEPARATOR)
: ($projectRoot . DIRECTORY_SEPARATOR . 'users' . DIRECTORY_SEPARATOR . 'pro'); : (rtrim(USERS_DIR, "/\\") . DIRECTORY_SEPARATOR . 'pro');
// Put README-Pro.txt / LICENSE-Pro.txt inside the bundle dir as well // Put README-Pro.txt / LICENSE-Pro.txt inside the bundle dir as well
$proDocsDir = $bundleRoot; $proDocsDir = $bundleRoot;