diff --git a/cms/config.php b/cms/config.php index 3927482..d53e307 100644 --- a/cms/config.php +++ b/cms/config.php @@ -6,8 +6,9 @@ $config = [ 'authentication' => [ - // Add more users as needed - its 'username'=>'password' if you hadn't have guessed - 'admin' => 'password123', + // Store password hashes generated with password_hash(...), not plaintext passwords. + // Default password for this sample hash is: change-this-password + 'admin' => '$2y$10$.MiUOFPR.L9osXyKuUjqx.VeZeeD3bHrQrmPQAOzXSfWj.nene1ey', ], // Enable the auditing system 'audit' => true, diff --git a/cms/css/editor.css b/cms/css/editor.css index deb823d..8efb1b2 100644 --- a/cms/css/editor.css +++ b/cms/css/editor.css @@ -4,23 +4,44 @@ body { margin: 0; } +body { + min-height: 100vh; +} + .pageContainer { display: flex; flex-flow: column; height: 100%; + min-height: 100vh; + width: 100%; + min-width: 0; } .pageContainer .controls { - flex: 0 1 auto; - height: 60px; + flex: 0 0 auto; + min-height: 60px; + position: relative; background-color: #1a1d20; border-bottom: 1px solid #1a1a1a; -webkit-box-shadow: 0 10px 13px -7px #000000, -1px -1px 15px 5px rgba(0, 0, 0, 0); box-shadow: 0 10px 13px -7px #000000, -1px -1px 15px 5px rgba(0, 0, 0, 0); + display: flex; + align-items: center; + gap: 16px; + padding: 10px 16px; + box-sizing: border-box; + width: 100%; + min-width: 0; + flex-wrap: wrap; } .pageContainer .frame { flex: 1 1 auto; + display: block; + width: 100%; + height: 100%; + min-width: 0; + min-height: 0; border: 0; } @@ -29,26 +50,34 @@ body { width: 60px; height: 60px; display: block; - position: absolute; + flex: 0 0 60px; + background-size: contain; + background-repeat: no-repeat; + background-position: center; } .controls .buttonContainer { - width: 400px; - margin: 12px 0; - position: absolute; - right: 110px; + margin-left: auto; + flex: 0 1 auto; + min-width: 0; } /* CSS */ .headerButton { - /*display: flex;*/ - flex-direction: column; + display: inline-flex; align-items: center; - padding: 6px 14px; + justify-content: center; + min-height: 38px; + padding: 8px 14px; font-family: -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif; + font-size: 14px; + font-weight: 600; + line-height: 1.2; border-radius: 6px; border: none; + white-space: nowrap; + cursor: pointer; background: #6E6D70; box-shadow: 0px 0.5px 1px rgba(0, 0, 0, 0.1), inset 0px 0.5px 0.5px rgba(255, 255, 255, 0.5), 0px 0px 0px 0.5px rgba(0, 0, 0, 0.12); @@ -67,35 +96,50 @@ body { background-color: #6E4D50; } +.headerButton:hover { + filter: brightness(1.08); +} + .controls #urlbox { - margin: 15px 70px; + flex: 1 1 320px; + min-width: 0; text-align: center; - font-size: 1.5em; - position: absolute; + font-size: 1.1rem; + line-height: 1.3; color: #dee2e6; } .controls #urlbox div { - float: right; + display: inline-block; margin-left: 10px; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: bottom; + white-space: nowrap; } .controls .loggedInDetails { - position: absolute; - top: 20px; - right: 10px; + flex: 0 1 auto; color: #dee2e6; + text-align: right; + font-size: 0.9rem; + line-height: 1.4; + max-width: 260px; } .controls .buttonContainer ul { list-style-type: none; margin: 0; padding: 0; + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; } .controls .buttonContainer li { - display: inline; - margin-right: 10px; + display: block; } /** @@ -166,4 +210,56 @@ File List Modal .redText { color: #ff4d4d; -} \ No newline at end of file +} + +@media (max-width: 1100px) { + .pageContainer .controls { + gap: 12px; + } + + .controls #urlbox { + order: 3; + flex-basis: 100%; + text-align: left; + } + + .controls .buttonContainer { + margin-left: 0; + } +} + +@media (max-width: 760px) { + .pageContainer .controls { + align-items: flex-start; + padding: 12px; + } + + .controls .logo { + width: 48px; + height: 48px; + flex-basis: 48px; + } + + .controls .buttonContainer, + .controls .loggedInDetails, + .controls #urlbox { + flex-basis: 100%; + max-width: none; + text-align: left; + } + + .controls .buttonContainer ul { + gap: 8px; + } + + .controls .buttonContainer li, + .controls .buttonContainer .headerButton { + width: 100%; + } + + .controls #urlbox div { + display: block; + margin-left: 0; + margin-top: 4px; + } +} diff --git a/cms/image_upload.php b/cms/image_upload.php index e64ed86..2f7beab 100644 --- a/cms/image_upload.php +++ b/cms/image_upload.php @@ -20,18 +20,32 @@ if (!$authentication->isLoggedIn()) { exit; } +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + http_response_code(405); + header('Allow: POST'); + header('Content-Type: application/json'); + echo json_encode(['error' => 'Invalid request method']); + exit; +} + +$csrfToken = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null; +if (!$authentication->isValidCsrfToken($csrfToken)) { + http_response_code(403); + header('Content-Type: application/json'); + echo json_encode(['error' => 'Invalid CSRF token']); + exit; +} + // Set appropriate headers header('Content-Type: application/json'); +header('X-Content-Type-Options: nosniff'); -// Allowed MIME types and extensions +// TinyMCE uses this endpoint for images only. $allowed_types = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', - 'text/pdf' => 'pdf', - 'text/docx' => 'docx', - 'text/xlsx' => 'xlsx', - 'text/doc' => 'doc', + 'image/webp' => 'webp', ]; // Maximum file size in bytes (50MB) @@ -68,19 +82,25 @@ if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) { // Generate a unique name for the image $extension = $allowed_types[$mime_type]; - $filename = uniqid('', true) . '.' . $extension; + $filename = bin2hex(random_bytes(16)) . '.' . $extension; // Set the upload directory - $upload_dir = realpath(__DIR__ . '/../uploads') . DIRECTORY_SEPARATOR; - - // Ensure the upload directory exists - if (!is_dir($upload_dir) && !mkdir($upload_dir, 0755, true)) { + $uploadRoot = __DIR__ . '/../uploads'; + if (!is_dir($uploadRoot) && !mkdir($uploadRoot, 0755, true)) { http_response_code(500); echo json_encode(['error' => 'Failed to create upload directory.']); exit; } - // Set the target file path + $upload_dir = realpath($uploadRoot); + if ($upload_dir === false) { + http_response_code(500); + echo json_encode(['error' => 'Upload directory is invalid.']); + exit; + } + $upload_dir .= DIRECTORY_SEPARATOR; + + // Ensure the upload directory exists $target_file = $upload_dir . $filename; // Move the uploaded file to the target directory diff --git a/cms/index.php b/cms/index.php index 729f54f..5c43b4f 100644 --- a/cms/index.php +++ b/cms/index.php @@ -10,6 +10,7 @@ use NeoCMS\Authentication; // Instantiate the Authentication class $authentication = new Authentication($config['authentication'] ?? []); +$csrfToken = $authentication->getCsrfToken(); // Check if the user is logged in if (!$authentication->isLoggedIn()) { @@ -23,6 +24,7 @@ if (!$authentication->isLoggedIn()) {