diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c0c2bc..fb8fbd2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,49 @@
# Changelog
+## Changes 10/19/2025 (v1.5.1)
+
+fix(config/ui): serve safe public config to non-admins; init early; gate trash UI to admins; dynamic title; demo toast (closes #56)
+
+Regular users were getting 403s from `/api/admin/getConfig.php`, breaking header title and login option rendering. Issue #56 tracks this.
+
+### What changed
+
+- **AdminController::getConfig**
+ - Return a **public, non-sensitive subset** of config for everyone (incl. unauthenticated and non-admin users): `header_title`, minimal `loginOptions` (disable* flags only), `globalOtpauthUrl`, `enableWebDAV`, `sharedMaxUploadSize`, and OIDC `providerUrl`/`redirectUri`.
+ - For **admins**, merge in admin-only fields (`authBypass`, `authHeaderName`).
+ - Never expose secrets or client IDs.
+- **auth.js**
+ - `loadAdminConfigFunc()` now robustly handles empty/204 responses, writes sane defaults, and sets `document.title` from `header_title`.
+ - `showToast()` override: on `demo.filerise.net` shows a longer demo-creds toast; keeps TOTP “don’t nag” behavior.
+- **main.js**
+ - Call `loadAdminConfigFunc()` early during app init.
+ - Run `setupTrashRestoreDelete()` **only for admins** (based on `localStorage.isAdmin`).
+- **adminPanel.js**
+ - Bump visible version to **v1.5.1**.
+- **index.html**
+ - Keep `
FileRise` static; runtime title now driven by `loadAdminConfigFunc()`.
+
+### Security v1.5.1
+
+- Prevents info disclosure by strictly limiting non-admin fields.
+- Avoids noisy 403 for regular users while keeping admin-only data protected.
+
+### QA
+
+- As a non-admin:
+ - Opening the app no longer triggers a 403 on `getConfig.php`.
+ - Header title and login options render; document tab title updates to configured `header_title`.
+ - Trash/restore UI is not initialized.
+- As an admin:
+ - Admin Panel loads extra fields; trash/restore UI initializes.
+ - Title updates correctly.
+- On `demo.filerise.net`:
+ - Pre-login toast shows demo credentials for ~12s.
+
+Closes #56.
+
+---
+
## Changes 10/17/2025 (v1.5.0)
Security and permission model overhaul. Tightens access controls with explicit, server‑side ACL checks across controllers and WebDAV. Introduces `read_own` for own‑only visibility and separates view from write so uploaders can’t automatically see others’ files. Fixes session warnings and aligns the admin UI with the new capabilities.
diff --git a/public/index.html b/public/index.html
index 5222fc4..b7c878b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,7 +4,7 @@
- FileRise
+ FileRise
diff --git a/public/js/adminPanel.js b/public/js/adminPanel.js
index 1396385..fd22c11 100644
--- a/public/js/adminPanel.js
+++ b/public/js/adminPanel.js
@@ -4,7 +4,7 @@ import { loadAdminConfigFunc } from './auth.js';
import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js';
import { sendRequest } from './networkUtils.js';
-const version = "v1.5.0";
+const version = "v1.5.1";
const adminTitle = `${t("admin_panel")} ${version}`;
// Translate with fallback: if t(key) just echos the key, use a readable string.
diff --git a/public/js/auth.js b/public/js/auth.js
index cde8499..d085f31 100644
--- a/public/js/auth.js
+++ b/public/js/auth.js
@@ -36,13 +36,33 @@ window.currentOIDCConfig = currentOIDCConfig;
window.pendingTOTP = new URLSearchParams(window.location.search).get('totp_required') === '1';
// override showToast to suppress the "Please log in to continue." toast during TOTP
-function showToast(msgKey) {
- const msg = t(msgKey);
- if (window.pendingTOTP && msgKey === "please_log_in_to_continue") {
+
+function showToast(msgKeyOrText, type) {
+ const isDemoHost = window.location.hostname.toLowerCase() === "demo.filerise.net";
+
+ // If it's the pre-login prompt and we're on the demo site, show demo creds instead.
+ if (isDemoHost) {
+ return originalShowToast("Demo site — use: \nUsername: demo\nPassword: demo", 12000);
+ }
+
+ // Don’t nag during pending TOTP, as you already had
+ if (window.pendingTOTP && msgKeyOrText === "please_log_in_to_continue") {
return;
}
- originalShowToast(msg);
+
+ // Translate if a key; otherwise pass through the raw text
+ let msg = msgKeyOrText;
+ try {
+ const translated = t(msgKeyOrText);
+ // If t() changed it or it's a key-like string, use the translation
+ if (typeof translated === "string" && translated !== msgKeyOrText) {
+ msg = translated;
+ }
+ } catch { /* if t() isn’t available here, just use the original */ }
+
+ return originalShowToast(msg);
}
+
window.showToast = showToast;
const originalFetch = window.fetch;
@@ -161,27 +181,31 @@ function updateLoginOptionsUIFromStorage() {
export function loadAdminConfigFunc() {
return fetch("/api/admin/getConfig.php", { credentials: "include" })
- .then(response => response.json())
- .then(config => {
- localStorage.setItem("headerTitle", config.header_title || "FileRise");
+ .then(async (response) => {
+ // If a proxy or some edge returns 204/empty, handle gracefully
+ let config = {};
+ try { config = await response.json(); } catch { config = {}; }
- // Update login options using the nested loginOptions object.
- localStorage.setItem("disableFormLogin", config.loginOptions.disableFormLogin);
- localStorage.setItem("disableBasicAuth", config.loginOptions.disableBasicAuth);
- localStorage.setItem("disableOIDCLogin", config.loginOptions.disableOIDCLogin);
- localStorage.setItem("globalOtpauthUrl", config.globalOtpauthUrl || "otpauth://totp/{label}?secret={secret}&issuer=FileRise");
- localStorage.setItem("authBypass", String(!!config.loginOptions.authBypass));
- localStorage.setItem("authHeaderName", config.loginOptions.authHeaderName || "X-Remote-User");
+ const headerTitle = config.header_title || "FileRise";
+ localStorage.setItem("headerTitle", headerTitle);
+
+ document.title = headerTitle;
+ const lo = config.loginOptions || {};
+ localStorage.setItem("disableFormLogin", String(!!lo.disableFormLogin));
+ localStorage.setItem("disableBasicAuth", String(!!lo.disableBasicAuth));
+ localStorage.setItem("disableOIDCLogin", String(!!lo.disableOIDCLogin));
+ localStorage.setItem("globalOtpauthUrl", config.globalOtpauthUrl || "otpauth://totp/{label}?secret={secret}&issuer=FileRise");
+ // These may be absent for non-admins; default them
+ localStorage.setItem("authBypass", String(!!lo.authBypass));
+ localStorage.setItem("authHeaderName", lo.authHeaderName || "X-Remote-User");
updateLoginOptionsUIFromStorage();
const headerTitleElem = document.querySelector(".header-title h1");
- if (headerTitleElem) {
- headerTitleElem.textContent = config.header_title || "FileRise";
- }
+ if (headerTitleElem) headerTitleElem.textContent = headerTitle;
})
.catch(() => {
- // Use defaults.
+ // Fallback defaults if request truly fails
localStorage.setItem("headerTitle", "FileRise");
localStorage.setItem("disableFormLogin", "false");
localStorage.setItem("disableBasicAuth", "false");
@@ -190,9 +214,7 @@ export function loadAdminConfigFunc() {
updateLoginOptionsUIFromStorage();
const headerTitleElem = document.querySelector(".header-title h1");
- if (headerTitleElem) {
- headerTitleElem.textContent = "FileRise";
- }
+ if (headerTitleElem) headerTitleElem.textContent = "FileRise";
});
}
diff --git a/public/js/main.js b/public/js/main.js
index 0f1024b..d4721bb 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -108,7 +108,7 @@ export function initializeApp() {
window.currentFolder = "root";
const stored = localStorage.getItem('showFoldersInList');
window.showFoldersInList = stored === null ? true : stored === 'true';
-
+ loadAdminConfigFunc();
initTagSearch();
loadFileList(window.currentFolder);
@@ -139,8 +139,12 @@ export function initializeApp() {
initFileActions();
initUpload();
loadFolderTree();
- setupTrashRestoreDelete();
- // NOTE: loadAdminConfigFunc() is called once in DOMContentLoaded; calling here would duplicate requests.
+ // Only run trash/restore for admins
+ const isAdmin =
+ localStorage.getItem('isAdmin') === '1' || localStorage.getItem('isAdmin') === 'true';
+ if (isAdmin) {
+ setupTrashRestoreDelete();
+ }
const helpBtn = document.getElementById("folderHelpBtn");
const helpTooltip = document.getElementById("folderHelpTooltip");
@@ -216,7 +220,7 @@ window.openDownloadModal = openDownloadModal;
window.currentFolder = "root";
document.addEventListener("DOMContentLoaded", function () {
- // Load admin config once here; non-admins may get 403, which is fine.
+ // Load admin config early
loadAdminConfigFunc();
// i18n
diff --git a/src/controllers/AdminController.php b/src/controllers/AdminController.php
index 759afbd..3d283db 100644
--- a/src/controllers/AdminController.php
+++ b/src/controllers/AdminController.php
@@ -51,44 +51,55 @@ class AdminController
* @return void Outputs a JSON response with configuration data.
*/
public function getConfig(): void
- {
- header('Content-Type: application/json');
+{
+ header('Content-Type: application/json');
- // Require authenticated admin to read config (prevents information disclosure)
- if (
- empty($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true ||
- empty($_SESSION['isAdmin'])
- ) {
- http_response_code(403);
- echo json_encode(['error' => 'Unauthorized access.']);
- exit;
- }
-
- $config = AdminModel::getConfig();
- if (isset($config['error'])) {
- http_response_code(500);
- echo json_encode(['error' => $config['error']]);
- exit;
- }
-
- // Build a safe subset for the front-end
- $safe = [
- 'header_title' => $config['header_title'] ?? '',
- 'loginOptions' => $config['loginOptions'] ?? [],
- 'globalOtpauthUrl' => $config['globalOtpauthUrl'] ?? '',
- 'enableWebDAV' => $config['enableWebDAV'] ?? false,
- 'sharedMaxUploadSize' => $config['sharedMaxUploadSize'] ?? 0,
- 'oidc' => [
- 'providerUrl' => $config['oidc']['providerUrl'] ?? '',
- 'redirectUri' => $config['oidc']['redirectUri'] ?? '',
- // clientSecret and clientId never exposed here
- ],
- ];
-
- echo json_encode($safe);
+ // Load raw config (no disclosure yet)
+ $config = AdminModel::getConfig();
+ if (isset($config['error'])) {
+ http_response_code(500);
+ echo json_encode(['error' => $config['error']]);
exit;
}
+ // Minimal, safe subset for all callers (unauth users and regular users)
+ $public = [
+ 'header_title' => $config['header_title'] ?? 'FileRise',
+ 'loginOptions' => [
+ // expose only what the login page / header needs
+ 'disableFormLogin' => (bool)($config['loginOptions']['disableFormLogin'] ?? false),
+ 'disableBasicAuth' => (bool)($config['loginOptions']['disableBasicAuth'] ?? false),
+ 'disableOIDCLogin' => (bool)($config['loginOptions']['disableOIDCLogin'] ?? false),
+ ],
+ 'globalOtpauthUrl' => $config['globalOtpauthUrl'] ?? '',
+ 'enableWebDAV' => (bool)($config['enableWebDAV'] ?? false),
+ 'sharedMaxUploadSize' => (int)($config['sharedMaxUploadSize'] ?? 0),
+
+ 'oidc' => [
+ 'providerUrl' => (string)($config['oidc']['providerUrl'] ?? ''),
+ 'redirectUri' => (string)($config['oidc']['redirectUri'] ?? ''),
+ // never expose clientId / clientSecret
+ ],
+ ];
+
+ $isAdmin = !empty($_SESSION['authenticated']) && !empty($_SESSION['isAdmin']);
+
+ if ($isAdmin) {
+ // Add admin-only fields (used by Admin Panel UI)
+ $adminExtra = [
+ 'loginOptions' => array_merge($public['loginOptions'], [
+ 'authBypass' => (bool)($config['loginOptions']['authBypass'] ?? false),
+ 'authHeaderName' => (string)($config['loginOptions']['authHeaderName'] ?? 'X-Remote-User'),
+ ]),
+ ];
+ echo json_encode(array_merge($public, $adminExtra));
+ return;
+ }
+
+ // Non-admins / unauthenticated: only the public subset
+ echo json_encode($public);
+}
+
/**
* @OA\Put(
* path="/api/admin/updateConfig.php",