Fix first-time setup issues. (#68)
Fixes for issues found testing first time setup in the different configurations. Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/68 Co-authored-by: Greg Sarjeant <greg@subcultureofone.org> Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
This commit is contained in:
		
							parent
							
								
									801bbebf4f
								
							
						
					
					
						commit
						d3a537aa6c
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -14,6 +14,7 @@ storage/logs | ||||
| # Testing stuff | ||||
| /docker-compose.yml | ||||
| scratch | ||||
| storage.bak | ||||
| 
 | ||||
| # Build artifacts | ||||
| tkr.tgz | ||||
|  | ||||
| @ -5,6 +5,10 @@ declare(strict_types=1); | ||||
| // - define paths
 | ||||
| // - set up autoloader
 | ||||
| 
 | ||||
| // Set a couple ini settings for security
 | ||||
| ini_set('allow_url_fopen', 0); // don't allow remote files to be read
 | ||||
| ini_set('expose_php', 0);      // don't advertise the PHP version
 | ||||
| 
 | ||||
| // Define all the important paths
 | ||||
| define('APP_ROOT', dirname(dirname(__FILE__))); | ||||
| // Root-level directories
 | ||||
|  | ||||
| @ -13,6 +13,9 @@ RewriteRule ^(storage|src|templates|config)(/.*)?$ - [F,L] | ||||
| # Block access to hidden files | ||||
| RewriteRule ^\..*$ - [F,L] | ||||
| 
 | ||||
| # Block access to setup script | ||||
| RewriteRule ^tkr-setup\.php$ - [F,L] | ||||
| 
 | ||||
| # Route everything else through public/index.php | ||||
| RewriteCond %{REQUEST_FILENAME} !-f | ||||
| RewriteCond %{REQUEST_FILENAME} !-d | ||||
|  | ||||
| @ -10,6 +10,7 @@ services: | ||||
|       - ./src:/var/www/html/tkr/src | ||||
|       - ./storage:/var/www/html/tkr/storage | ||||
|       - ./templates:/var/www/html/tkr/templates | ||||
|       - ./tkr-setup.php:/var/www/html/tkr/tkr-setup.php | ||||
|       - ./docker/apache/shared-hosting/.htaccess:/var/www/html/tkr/.htaccess | ||||
|     command: > | ||||
|       bash -c "a2enmod rewrite headers expires && | ||||
|  | ||||
| @ -10,6 +10,7 @@ services: | ||||
|       - ./src:/var/www/tkr/src | ||||
|       - ./storage:/var/www/tkr/storage | ||||
|       - ./templates:/var/www/tkr/templates | ||||
|       - ./tkr-setup.php:/var/www/html/tkr/tkr-setup.php | ||||
|       - ./docker/apache/vps/root/tkr.my-domain.com.conf:/etc/apache2/sites-enabled/tkr.my-domain.com.conf | ||||
|     command: > | ||||
|       bash -c "a2enmod rewrite headers expires && | ||||
|  | ||||
| @ -10,6 +10,7 @@ services: | ||||
|       - ./src:/var/www/tkr/src | ||||
|       - ./storage:/var/www/tkr/storage | ||||
|       - ./templates:/var/www/tkr/templates | ||||
|       - ./tkr-setup.php:/var/www/html/tkr/tkr-setup.php | ||||
|       - ./docker/apache/vps/subfolder/my-domain.com.conf:/etc/apache2/sites-enabled/my-domain.com.conf | ||||
|     command: > | ||||
|       bash -c "a2enmod rewrite headers expires && | ||||
|  | ||||
| @ -20,6 +20,7 @@ services: | ||||
|       - ./src:/var/www/tkr/src | ||||
|       - ./storage:/var/www/tkr/storage | ||||
|       - ./templates:/var/www/tkr/templates | ||||
|       - ./tkr-setup.php:/var/www/html/tkr/tkr-setup.php | ||||
|     command: > | ||||
|       sh -c " | ||||
|       chown -R www-data:www-data /var/www/tkr/storage && | ||||
|  | ||||
| @ -20,6 +20,7 @@ services: | ||||
|       - ./src:/var/www/tkr/src | ||||
|       - ./storage:/var/www/tkr/storage | ||||
|       - ./templates:/var/www/tkr/templates | ||||
|       - ./tkr-setup.php:/var/www/html/tkr/tkr-setup.php | ||||
|     command: > | ||||
|       sh -c " | ||||
|       chown -R www-data:www-data /var/www/tkr/storage && | ||||
|  | ||||
| @ -14,6 +14,9 @@ RewriteRule ^(storage|src|templates|config)(/.*)?$ - [F,L] | ||||
| # Block access to hidden files | ||||
| RewriteRule ^\..*$ - [F,L] | ||||
| 
 | ||||
| # Block access to setup script | ||||
| RewriteRule ^tkr-setup\.php$ - [F,L] | ||||
| 
 | ||||
| # Route everything else through the front controller | ||||
| RewriteCond %{REQUEST_FILENAME} !-f | ||||
| RewriteCond %{REQUEST_FILENAME} !-d | ||||
|  | ||||
| @ -49,7 +49,8 @@ if (!$prerequisites->applyMigrations($db)){ | ||||
| } | ||||
| 
 | ||||
| // Check if setup is complete (user exists and URL is configured)
 | ||||
| if (!(preg_match('/tkr-setup$/', $path))) { | ||||
| // Skip the setup check for the default css
 | ||||
| if (!(preg_match('/tkr-setup$/', $path) || preg_match('/default.css$/', $path))) { | ||||
|     try { | ||||
|         $user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn(); | ||||
|         $settings = (new SettingsModel($db))->get(); | ||||
| @ -72,6 +73,10 @@ if (!(preg_match('/tkr-setup$/', $path))) { | ||||
|         echo "<p>Please check your installation or contact your hosting provider.</p>"; | ||||
|         exit; | ||||
|     } | ||||
| } else { | ||||
|     // we're heading to setup. the base path hasn't been set. autodetect it
 | ||||
|     $autodetected = Util::getAutodetectedUrl(); | ||||
|     $basePath = $autodetected['basePath']; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| @ -92,8 +97,11 @@ Session::start(); | ||||
| Session::generateCsrfToken(); | ||||
| 
 | ||||
| // Remove the base path from the URL
 | ||||
| if (strpos($path, $app['settings']->basePath) === 0) { | ||||
|     $path = substr($path, strlen($app['settings']->basePath)); | ||||
| // If basePath isn't already set (i.e. we're not autodetecting it en route to tkr-setup),
 | ||||
| // set it to the value from settings
 | ||||
| $basePath ??= $app['settings']->basePath; | ||||
| if (strpos($path, $basePath) === 0) { | ||||
|     $path = substr($path, strlen($basePath)); | ||||
| } | ||||
| 
 | ||||
| // strip the trailing slash from the resulting route
 | ||||
| @ -106,7 +114,7 @@ Log::debug("Path requested: {$path}"); | ||||
| // if this is a POST and we aren't in setup,
 | ||||
| // make sure there's a valid session
 | ||||
| // if not, redirect to /login or die as appropriate
 | ||||
| if ($method === 'POST' && $path != 'setup') { | ||||
| if ($method === 'POST' && $path != 'tkr-setup') { | ||||
|     if ($path != 'login'){ | ||||
|         if (!Session::isValid($_POST['csrf_token'])) { | ||||
|             // Invalid session - redirect to /login
 | ||||
|  | ||||
| @ -100,7 +100,7 @@ class CssController extends Controller { | ||||
|         } | ||||
| 
 | ||||
|         // Get the data for the selected CSS file
 | ||||
|         $cssId = $_POST['selectCssFile']; | ||||
|         $cssId = (int) $_POST['selectCssFile']; | ||||
|         $cssModel = new CssModel($app['db']); | ||||
|         $cssRow = $cssModel->getById($cssId); | ||||
| 
 | ||||
|  | ||||
| @ -407,7 +407,7 @@ class Prerequisites { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function createDatabase() { | ||||
|     private function createDatabase(): bool { | ||||
|         $dbFile = $this->baseDir . '/storage/db/tkr.sqlite'; | ||||
| 
 | ||||
|         // Test database connection (will create file if needed)
 | ||||
| @ -442,7 +442,7 @@ class Prerequisites { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function applyMigrations($db) { | ||||
|     public function applyMigrations($db): bool { | ||||
|         try { | ||||
|             $migrator = new Migrator($db); | ||||
|             $migrator->migrate(); | ||||
| @ -452,8 +452,6 @@ class Prerequisites { | ||||
|                 true, | ||||
|                 'All database migrations applied successfully' | ||||
|             ); | ||||
|             return true; | ||||
| 
 | ||||
|         } catch (Exception $e) { | ||||
|             $this->addCheck( | ||||
|                 'Database Migrations', | ||||
| @ -463,6 +461,8 @@ class Prerequisites { | ||||
|             ); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // Validate system requirements that can't be fixed by the script
 | ||||
| @ -511,6 +511,8 @@ class Prerequisites { | ||||
| 
 | ||||
|     // Create missing application components
 | ||||
|     public function createMissing(): bool { | ||||
|         // If we're calling this, there were likely setup validation errors
 | ||||
|         $currentErrors = count($this->errors); | ||||
|         $this->log("=== tkr setup started at " . date('Y-m-d H:i:s') . " ===", true); | ||||
| 
 | ||||
|         if ($this->isCli) { | ||||
| @ -526,8 +528,8 @@ class Prerequisites { | ||||
|             $this->generateCliSummary($results); | ||||
|         } | ||||
| 
 | ||||
|         // Return true only if no errors occurred
 | ||||
|         return count($this->errors) === 0; | ||||
|         // Return true only if no NEW errors occurred
 | ||||
|         return count($this->errors) === $currentErrors; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -7,6 +7,15 @@ class Session { | ||||
|     // global $_SESSION associative array
 | ||||
|     public static function start(): void{ | ||||
|         if (session_status() === PHP_SESSION_NONE) { | ||||
|             // Cookie security settings
 | ||||
|             ini_set('session.cookie_httponly', 1); | ||||
|             ini_set('session.cookie_samesite', 'Strict'); | ||||
| 
 | ||||
|             // Enable secure cookie flag if HTTPS is being used
 | ||||
|             if (($_SERVER['HTTPS'] ?? 'off') === 'on') { | ||||
|                 ini_set('session.cookie_secure', 1); | ||||
|             } | ||||
| 
 | ||||
|             $existingSessionId = $_COOKIE['PHPSESSID'] ?? null; | ||||
|             session_start(); | ||||
| 
 | ||||
|  | ||||
| @ -125,6 +125,13 @@ class Util { | ||||
|         $scriptName = $_SERVER['SCRIPT_NAME'] ?? '/index.php'; | ||||
|         $basePath = dirname($scriptName); | ||||
| 
 | ||||
|         // Handle shared hosting scenario where document root can't be set to public/
 | ||||
|         // If script name ends with /public/index.php, we need to go up one directory
 | ||||
|         if (str_ends_with($scriptName, '/public/index.php')) { | ||||
|             $basePath = dirname($basePath); | ||||
|         } | ||||
| 
 | ||||
|         # Ensure base path always has a trailing /
 | ||||
|         if ($basePath === '/' || $basePath === '.' || $basePath === '') { | ||||
|             $basePath = '/'; | ||||
|         } else { | ||||
| @ -132,10 +139,7 @@ class Util { | ||||
|         } | ||||
| 
 | ||||
|         // Construct full URL
 | ||||
|         $fullUrl = $baseUrl; | ||||
|         if ($basePath !== '/') { | ||||
|             $fullUrl .= ltrim($basePath, '/'); | ||||
|         } | ||||
|         $fullUrl = $baseUrl . $basePath; | ||||
| 
 | ||||
|         return [ | ||||
|             'baseUrl' => $baseUrl, | ||||
|  | ||||
| @ -1,12 +1,16 @@ | ||||
| <?php /** @var SettingsModel $settings */ ?>
 | ||||
| <?php /** @var UserModel $user */ ?>
 | ||||
| <?php /** @var isSetup bool */ ?>
 | ||||
|         <h1><?php if ($isSetup): ?>Setup<?php else: ?>Admin<?php endif; ?></h1>
 | ||||
| <?php | ||||
|     $title = $isSetup ? 'Setup' : 'Admin'; | ||||
|     $urlPath = $isSetup ? 'tkr-setup' : 'admin' | ||||
| ?>
 | ||||
|         <h1><?php echo $title ?></h1>
 | ||||
|         <main> | ||||
|             <form | ||||
|                 action="<?php echo Util::buildRelativeUrl($settings->basePath, ($isSetup ? 'setup' : 'admin')) ?>" | ||||
|                 action="<?php echo Util::buildRelativeUrl($settings->basePath, $urlPath) ?>" | ||||
|                 method="post"> | ||||
|                 <input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>"> | ||||
|                 <input type="hidden" name="csrf_token" value="<?php echo Util::escape_html($_SESSION['csrf_token']) ?>"> | ||||
|                 <fieldset> | ||||
|                     <legend>User settings</legend> | ||||
|                     <div class="fieldset-items"> | ||||
| @ -14,19 +18,19 @@ | ||||
|                         <input type="text" | ||||
|                             id="username" | ||||
|                             name="username" | ||||
|                             value="<?= Util::escape_html($user->username) ?>" | ||||
|                             value="<?php echo Util::escape_html($user->username) ?>" | ||||
|                             required> | ||||
|                         <label for="display_name">Display name <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
|                                id="display_name" | ||||
|                                name="display_name" | ||||
|                                value="<?= Util::escape_html($user->displayName) ?>" | ||||
|                                value="<?php echo Util::escape_html($user->displayName) ?>" | ||||
|                                required> | ||||
|                         <label for="website">Website </label> | ||||
|                         <input type="text" | ||||
|                             id="website" | ||||
|                             name="website" | ||||
|                             value="<?= Util::escape_html($user->website) ?>"> | ||||
|                             value="<?php echo Util::escape_html($user->website) ?>"> | ||||
|                     </div> | ||||
|                 </fieldset> | ||||
|                 <fieldset> | ||||
| @ -36,36 +40,36 @@ | ||||
|                         <input type="text" | ||||
|                             id="site_title" | ||||
|                             name="site_title" | ||||
|                             value="<?= Util::escape_html($settings->siteTitle) ?>" | ||||
|                             value="<?php echo Util::escape_html($settings->siteTitle) ?>" | ||||
|                             required> | ||||
|                         <label for="site_description">Description <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
|                             id="site_description" | ||||
|                             name="site_description" | ||||
|                             value="<?= Util::escape_html($settings->siteDescription) ?>"> | ||||
|                             value="<?php echo Util::escape_html($settings->siteDescription) ?>"> | ||||
|                         <label for="base_url">Base URL <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
|                             id="base_url" | ||||
|                             name="base_url" | ||||
|                             value="<?= Util::escape_html($settings->baseUrl) ?>" | ||||
|                             value="<?php echo Util::escape_html($settings->baseUrl) ?>" | ||||
|                             required> | ||||
|                         <label for="base_path">Base path <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
|                             id="base_path" | ||||
|                             name="base_path" | ||||
|                             value="<?= Util::escape_html($settings->basePath) ?>" | ||||
|                             value="<?php echo Util::escape_html($settings->basePath) ?>" | ||||
|                             required> | ||||
|                         <label for="items_per_page">Ticks per page (max 50) <span class=required>*</span></label> | ||||
|                         <input type="number" | ||||
|                             id="items_per_page" | ||||
|                             name="items_per_page" | ||||
|                             value="<?= $settings->itemsPerPage ?>" min="1" max="50" | ||||
|                             value="<?php echo $settings->itemsPerPage ?>" min="1" max="50" | ||||
|                             required> | ||||
|                         <label for="tick_delete_hours">Tick delete window (hours)</label> | ||||
|                         <input type="number" | ||||
|                             id="tick_delete_hours" | ||||
|                             name="tick_delete_hours" | ||||
|                             value="<?= ($settings->tickDeleteHours ?? 1) ?>" min="1"> | ||||
|                             value="<?php echo ($settings->tickDeleteHours ?? 1) ?>" min="1"> | ||||
|                         <label for="strict_accessibility">Strict accessibility</label> | ||||
|                         <input type="checkbox" | ||||
|                                id="strict_accessibility" | ||||
| @ -74,10 +78,10 @@ | ||||
|                                <?php if ($settings->strictAccessibility): ?> checked <?php endif; ?>>
 | ||||
|                         <label for="log_level">Log Level</label> | ||||
|                         <select id="log_level" name="log_level"> | ||||
|                             <option value="1" <?= ($settings->logLevel ?? 2) == 1 ? 'selected' : '' ?>>DEBUG</option>
 | ||||
|                             <option value="2" <?= ($settings->logLevel ?? 2) == 2 ? 'selected' : '' ?>>INFO</option>
 | ||||
|                             <option value="3" <?= ($settings->logLevel ?? 2) == 3 ? 'selected' : '' ?>>WARNING</option>
 | ||||
|                             <option value="4" <?= ($settings->logLevel ?? 2) == 4 ? 'selected' : '' ?>>ERROR</option>
 | ||||
|                             <option value="1" <?php echo ($settings->logLevel ?? 2) == 1 ? 'selected' : '' ?>>DEBUG</option>
 | ||||
|                             <option value="2" <?php echo ($settings->logLevel ?? 2) == 2 ? 'selected' : '' ?>>INFO</option>
 | ||||
|                             <option value="3" <?php echo ($settings->logLevel ?? 2) == 3 ? 'selected' : '' ?>>WARNING</option>
 | ||||
|                             <option value="4" <?php echo ($settings->logLevel ?? 2) == 4 ? 'selected' : '' ?>>ERROR</option>
 | ||||
|                         </select> | ||||
|                     </div> | ||||
|                 </fieldset> | ||||
|  | ||||
| @ -177,6 +177,7 @@ try { | ||||
|     // Create/update settings
 | ||||
|     $settingsModel = new SettingsModel($db); | ||||
|     $settingsModel->siteTitle = $siteTitle; | ||||
|     $settingsModel->siteDescription = $siteTitle; | ||||
|     $settingsModel->baseUrl = $baseUrl; | ||||
|     $settingsModel->basePath = $basePath; | ||||
|     $settings = $settingsModel->save(); | ||||
| @ -184,7 +185,7 @@ try { | ||||
|     // Create admin user
 | ||||
|     $userModel = new UserModel($db); | ||||
|     $userModel->username = $adminUsername; | ||||
|     $userModel->display_name = $adminUsername; | ||||
|     $userModel->displayName = $adminUsername; | ||||
|     $userModel->website = ''; | ||||
|     $userModel->mood = ''; | ||||
|     $user = $userModel->save(); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user