feat(admin): add proxy-only auth bypass and configurable auth header (closes #28)

This commit is contained in:
Ryan
2025-05-08 04:43:33 -04:00
committed by GitHub
parent d48b15a5f4
commit b4d6f01432
7 changed files with 278 additions and 75 deletions

View File

@@ -1,5 +1,56 @@
# Changelog # Changelog
## Changes 5/8/2025 v1.3.2
### config/config.php
- Added a default `define('AUTH_BYPASS', false)` at the top so the constant always exists.
- Removed the static `AUTH_HEADER` fallback; instead read the adminConfig.json at the end of the file and:
- Overwrote `AUTH_BYPASS` with the `loginOptions.authBypass` setting from disk.
- Defined `AUTH_HEADER` (normalized, e.g. `"X_REMOTE_USER"`) based on `loginOptions.authHeaderName`.
- Inserted a **proxy-only auto-login** block *before* the usual session/auth checks:
If `AUTH_BYPASS` is true and the trusted header (`$_SERVER['HTTP_' . AUTH_HEADER]`) is present, bump the session, mark the user authenticated/admin, load their permissions, and skip straight to JSON output.
### src/controllers/AdminController.php
- Ensured the returned `loginOptions` object always contains:
- `authBypass` (boolean, default false)
- `authHeaderName` (string, default `"X-Remote-User"`)
- Read `authBypass` and `authHeaderName` from the nested `loginOptions` in the request payload.
- Validated them (`authBypass` → bool; `authHeaderName` → non-empty string, fallback to `"X-Remote-User"`).
- Included them when building the `$configUpdate` array to pass to the model.
### src/models/AdminModel.php
- Normalized `loginOptions.authBypass` to a boolean (default false).
- Validated/truncated `loginOptions.authHeaderName` to a non-empty trimmed string (default `"X-Remote-User"`).
- JSON-encoded and encrypted the full config, now including the two new fields.
- After decrypting & decoding, normalized the loaded `loginOptions` to always include:
- `authBypass` (bool)
- `authHeaderName` (string, default `"X-Remote-User"`)
- Left all existing defaults & validations for the original flags intact.
### public/js/adminPanel.js
- **Login Options** section:
- Added a checkbox for **Disable All Built-in Logins (proxy only)** (`authBypass`).
- Added a text input for **Auth Header Name** (`authHeaderName`).
- In `handleSave()`:
- Included the new `authBypass` and `authHeaderName` values in the payload sent to `updateConfig.php`.
- In `openAdminPanel()`:
- Initialized those inputs from `config.loginOptions.authBypass` and `config.loginOptions.authHeaderName`.
### public/js/auth.js
- In `loadAdminConfigFunc()`:
- Stored `authBypass` and `authHeaderName` in `localStorage`.
- In `checkAuthentication()`:
- After a successful login check, called a new helper (`applyProxyBypassUI()`) which reads `localStorage.authBypass` and conditionally hides the entire login form/UI.
- In the “not authenticated” branch, only shows the login form if `authBypass` is false.
- No other core fetch/token logic changed; all existing flows remain intact.
---
## Changes 5/4/2025 v1.3.1 ## Changes 5/4/2025 v1.3.1
### Modals ### Modals
@@ -20,7 +71,7 @@
- **Inserted** inline `<style>` in `<head>` to: - **Inserted** inline `<style>` in `<head>` to:
- Hide `.main-wrapper` by default. - Hide `.main-wrapper` by default.
- Style `#loadingOverlay` as a full-viewport white overlay. - Style `#loadingOverlay` as a full-viewport white overlay.
- **Added** `addUserModal`, `removeUserModal` & `renameFileModal` modals to `style="display:none;"` - **Added** `addUserModal`, `removeUserModal` & `renameFileModal` modals to `style="display:none;"`
### `main.js` ### `main.js`

View File

@@ -35,6 +35,7 @@ define('REGEX_USER', '/^[\p{L}\p{N}_\- ]+$/u');
date_default_timezone_set(TIMEZONE); date_default_timezone_set(TIMEZONE);
// Encryption helpers // Encryption helpers
function encryptData($data, $encryptionKey) function encryptData($data, $encryptionKey)
{ {
@@ -114,6 +115,7 @@ if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
} }
// Autologin via persistent token // Autologin via persistent token
if (empty($_SESSION["authenticated"]) && !empty($_COOKIE['remember_me_token'])) { if (empty($_SESSION["authenticated"]) && !empty($_COOKIE['remember_me_token'])) {
$tokFile = USERS_DIR . 'persistent_tokens.json'; $tokFile = USERS_DIR . 'persistent_tokens.json';
@@ -140,6 +142,60 @@ if (empty($_SESSION["authenticated"]) && !empty($_COOKIE['remember_me_token']))
} }
} }
$adminConfigFile = USERS_DIR . 'adminConfig.json';
// sane defaults:
$cfgAuthBypass = false;
$cfgAuthHeader = 'X_REMOTE_USER';
if (file_exists($adminConfigFile)) {
$encrypted = file_get_contents($adminConfigFile);
$decrypted = decryptData($encrypted, $encryptionKey);
$adminCfg = json_decode($decrypted, true) ?: [];
$loginOpts = $adminCfg['loginOptions'] ?? [];
// proxy-only bypass flag
$cfgAuthBypass = ! empty($loginOpts['authBypass']);
// header name (e.g. “X-Remote-User” → HTTP_X_REMOTE_USER)
$hdr = trim($loginOpts['authHeaderName'] ?? '');
if ($hdr === '') {
$hdr = 'X-Remote-User';
}
// normalize to PHPs $_SERVER key format:
$cfgAuthHeader = 'HTTP_' . strtoupper(str_replace('-', '_', $hdr));
}
define('AUTH_BYPASS', $cfgAuthBypass);
define('AUTH_HEADER', $cfgAuthHeader);
// ─────────────────────────────────────────────────────────────────────────────
// PROXY-ONLY AUTOLOGIN now uses those constants:
if (AUTH_BYPASS) {
$hdrKey = AUTH_HEADER; // e.g. "HTTP_X_REMOTE_USER"
if (!empty($_SERVER[$hdrKey])) {
// regenerate once per session
if (empty($_SESSION['authenticated'])) {
session_regenerate_id(true);
}
$username = $_SERVER[$hdrKey];
$_SESSION['authenticated'] = true;
$_SESSION['username'] = $username;
// ◾ lookup actual role instead of forcing admin
require_once PROJECT_ROOT . '/src/models/AuthModel.php';
$role = AuthModel::getUserRole($username);
$_SESSION['isAdmin'] = ($role === '1');
// carry over any folder/read/upload perms
$perms = loadUserPermissions($username) ?: [];
$_SESSION['folderOnly'] = $perms['folderOnly'] ?? false;
$_SESSION['readOnly'] = $perms['readOnly'] ?? false;
$_SESSION['disableUpload'] = $perms['disableUpload'] ?? false;
}
}
// Share URL fallback // Share URL fallback
define('BASE_URL', 'http://yourwebsite/uploads/'); define('BASE_URL', 'http://yourwebsite/uploads/');
if (strpos(BASE_URL, 'yourwebsite') !== false) { if (strpos(BASE_URL, 'yourwebsite') !== false) {

View File

@@ -3,7 +3,7 @@ import { loadAdminConfigFunc } from './auth.js';
import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js'; import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js';
import { sendRequest } from './networkUtils.js'; import { sendRequest } from './networkUtils.js';
const version = "v1.3.1"; const version = "v1.3.2";
const adminTitle = `${t("admin_panel")} <small style="font-size:12px;color:gray;">${version}</small>`; const adminTitle = `${t("admin_panel")} <small style="font-size:12px;color:gray;">${version}</small>`;
// ————— Inject updated styles ————— // ————— Inject updated styles —————
@@ -188,8 +188,8 @@ function loadShareLinksSection() {
// on non-2xx (including 404) or network error, resolve to {} // on non-2xx (including 404) or network error, resolve to {}
function fetchMeta(fileName) { function fetchMeta(fileName) {
return fetch(`/api/admin/readMetadata.php?file=${encodeURIComponent(fileName)}`, { return fetch(`/api/admin/readMetadata.php?file=${encodeURIComponent(fileName)}`, {
credentials: "include" credentials: "include"
}) })
.then(resp => { .then(resp => {
if (!resp.ok) { if (!resp.ok) {
// 404 or any other non-OK → treat as empty // 404 or any other non-OK → treat as empty
@@ -204,9 +204,9 @@ function loadShareLinksSection() {
} }
Promise.all([ Promise.all([
fetchMeta("share_folder_links.json"), fetchMeta("share_folder_links.json"),
fetchMeta("share_links.json") fetchMeta("share_links.json")
]) ])
.then(([folders, files]) => { .then(([folders, files]) => {
// if *both* are empty, show "no shared links" // if *both* are empty, show "no shared links"
const hasAny = Object.keys(folders).length || Object.keys(files).length; const hasAny = Object.keys(folders).length || Object.keys(files).length;
@@ -257,11 +257,11 @@ function loadShareLinksSection() {
: "/api/file/deleteShareLink.php"; : "/api/file/deleteShareLink.php";
fetch(endpoint, { fetch(endpoint, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
headers: { "Content-Type": "application/x-www-form-urlencoded" }, headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ token }) body: new URLSearchParams({ token })
}) })
.then(res => { .then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`); if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json(); return res.json();
@@ -399,6 +399,14 @@ export function openAdminPanel() {
<div class="form-group"><input type="checkbox" id="disableFormLogin" /> <label for="disableFormLogin">${t("disable_login_form")}</label></div> <div class="form-group"><input type="checkbox" id="disableFormLogin" /> <label for="disableFormLogin">${t("disable_login_form")}</label></div>
<div class="form-group"><input type="checkbox" id="disableBasicAuth" /> <label for="disableBasicAuth">${t("disable_basic_http_auth")}</label></div> <div class="form-group"><input type="checkbox" id="disableBasicAuth" /> <label for="disableBasicAuth">${t("disable_basic_http_auth")}</label></div>
<div class="form-group"><input type="checkbox" id="disableOIDCLogin" /> <label for="disableOIDCLogin">${t("disable_oidc_login")}</label></div> <div class="form-group"><input type="checkbox" id="disableOIDCLogin" /> <label for="disableOIDCLogin">${t("disable_oidc_login")}</label></div>
<div class="form-group">
<input type="checkbox" id="authBypass" />
<label for="authBypass">Disable all built-in logins (proxy only)</label>
</div>
<div class="form-group">
<label for="authHeaderName">Auth header name:</label>
<input type="text" id="authHeaderName" class="form-control" placeholder="e.g. X-Remote-User" />
</div>
`; `;
// — WebDAV — // — WebDAV —
@@ -441,11 +449,20 @@ export function openAdminPanel() {
} }
}); });
}); });
// If authBypass is checked, clear the other three
document.getElementById("authBypass").addEventListener("change", e => {
if (e.target.checked) {
["disableFormLogin", "disableBasicAuth", "disableOIDCLogin"]
.forEach(i => document.getElementById(i).checked = false);
}
});
// Initialize inputs from config + capture // Initialize inputs from config + capture
document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true; document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true;
document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true; document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true;
document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true; document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true;
document.getElementById("authBypass").checked = !!config.loginOptions.authBypass;
document.getElementById("authHeaderName").value = config.loginOptions.authHeaderName || "X-Remote-User";
document.getElementById("enableWebDAV").checked = config.enableWebDAV === true; document.getElementById("enableWebDAV").checked = config.enableWebDAV === true;
document.getElementById("sharedMaxUploadSize").value = config.sharedMaxUploadSize || ""; document.getElementById("sharedMaxUploadSize").value = config.sharedMaxUploadSize || "";
captureInitialAdminConfig(); captureInitialAdminConfig();
@@ -457,6 +474,8 @@ export function openAdminPanel() {
document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true; document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true;
document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true; document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true;
document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true; document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true;
document.getElementById("authBypass").checked = !!config.loginOptions.authBypass;
document.getElementById("authHeaderName").value = config.loginOptions.authHeaderName || "X-Remote-User";
document.getElementById("enableWebDAV").checked = config.enableWebDAV === true; document.getElementById("enableWebDAV").checked = config.enableWebDAV === true;
document.getElementById("sharedMaxUploadSize").value = config.sharedMaxUploadSize || ""; document.getElementById("sharedMaxUploadSize").value = config.sharedMaxUploadSize || "";
document.getElementById("oidcProviderUrl").value = window.currentOIDCConfig.providerUrl; document.getElementById("oidcProviderUrl").value = window.currentOIDCConfig.providerUrl;
@@ -471,19 +490,21 @@ export function openAdminPanel() {
} }
function handleSave() { function handleSave() {
const dFL = document.getElementById("disableFormLogin").checked; const dFL = document.getElementById("disableFormLogin").checked;
const dBA = document.getElementById("disableBasicAuth").checked; const dBA = document.getElementById("disableBasicAuth").checked;
const dOIDC = document.getElementById("disableOIDCLogin").checked; const dOIDC = document.getElementById("disableOIDCLogin").checked;
const eWD = document.getElementById("enableWebDAV").checked; const aBypass= document.getElementById("authBypass").checked;
const sMax = parseInt(document.getElementById("sharedMaxUploadSize").value, 10) || 0; const aHeader= document.getElementById("authHeaderName").value.trim() || "X-Remote-User";
const nHT = document.getElementById("headerTitle").value.trim(); const eWD = document.getElementById("enableWebDAV").checked;
const nOIDC = { const sMax = parseInt(document.getElementById("sharedMaxUploadSize").value, 10) || 0;
const nHT = document.getElementById("headerTitle").value.trim();
const nOIDC = {
providerUrl: document.getElementById("oidcProviderUrl").value.trim(), providerUrl: document.getElementById("oidcProviderUrl").value.trim(),
clientId: document.getElementById("oidcClientId").value.trim(), clientId: document.getElementById("oidcClientId").value.trim(),
clientSecret: document.getElementById("oidcClientSecret").value.trim(), clientSecret:document.getElementById("oidcClientSecret").value.trim(),
redirectUri: document.getElementById("oidcRedirectUri").value.trim() redirectUri: document.getElementById("oidcRedirectUri").value.trim()
}; };
const gURL = document.getElementById("globalOtpauthUrl").value.trim(); const gURL = document.getElementById("globalOtpauthUrl").value.trim();
if ([dFL, dBA, dOIDC].filter(x => x).length === 3) { if ([dFL, dBA, dOIDC].filter(x => x).length === 3) {
showToast(t("at_least_one_login_method")); showToast(t("at_least_one_login_method"));
@@ -491,12 +512,22 @@ function handleSave() {
} }
sendRequest("/api/admin/updateConfig.php", "POST", { sendRequest("/api/admin/updateConfig.php", "POST", {
header_title: nHT, oidc: nOIDC, header_title: nHT,
disableFormLogin: dFL, disableBasicAuth: dBA, disableOIDCLogin: dOIDC, oidc: nOIDC,
enableWebDAV: eWD, sharedMaxUploadSize: sMax, globalOtpauthUrl: gURL loginOptions: {
disableFormLogin: dFL,
disableBasicAuth: dBA,
disableOIDCLogin: dOIDC,
authBypass: aBypass,
authHeaderName: aHeader
},
enableWebDAV: eWD,
sharedMaxUploadSize: sMax,
globalOtpauthUrl: gURL
}, { }, {
"X-CSRF-Token": window.csrfToken "X-CSRF-Token": window.csrfToken
}).then(res => { })
.then(res => {
if (res.success) { if (res.success) {
showToast(t("settings_updated_successfully"), "success"); showToast(t("settings_updated_successfully"), "success");
captureInitialAdminConfig(); captureInitialAdminConfig();
@@ -505,7 +536,7 @@ function handleSave() {
} else { } else {
showToast(t("error_updating_settings") + ": " + (res.error || t("unknown_error")), "error"); showToast(t("error_updating_settings") + ": " + (res.error || t("unknown_error")), "error");
} }
}).catch(() => {/*noop*/ }); }).catch(() => {/*noop*/});
} }
export async function closeAdminPanel() { export async function closeAdminPanel() {

View File

@@ -125,6 +125,13 @@ function updateItemsPerPageSelect() {
} }
} }
function applyProxyBypassUI() {
const bypass = localStorage.getItem("authBypass") === "true";
const loginContainer = document.getElementById("loginForm");
if (loginContainer) {
loginContainer.style.display = bypass ? "none" : "";
}
}
function updateLoginOptionsUI({ disableFormLogin, disableBasicAuth, disableOIDCLogin }) { function updateLoginOptionsUI({ disableFormLogin, disableBasicAuth, disableOIDCLogin }) {
const authForm = document.getElementById("authForm"); const authForm = document.getElementById("authForm");
@@ -146,7 +153,8 @@ function updateLoginOptionsUIFromStorage() {
updateLoginOptionsUI({ updateLoginOptionsUI({
disableFormLogin: localStorage.getItem("disableFormLogin") === "true", disableFormLogin: localStorage.getItem("disableFormLogin") === "true",
disableBasicAuth: localStorage.getItem("disableBasicAuth") === "true", disableBasicAuth: localStorage.getItem("disableBasicAuth") === "true",
disableOIDCLogin: localStorage.getItem("disableOIDCLogin") === "true" disableOIDCLogin: localStorage.getItem("disableOIDCLogin") === "true",
authBypass: localStorage.getItem("authBypass") === "true"
}); });
} }
@@ -161,6 +169,8 @@ export function loadAdminConfigFunc() {
localStorage.setItem("disableBasicAuth", config.loginOptions.disableBasicAuth); localStorage.setItem("disableBasicAuth", config.loginOptions.disableBasicAuth);
localStorage.setItem("disableOIDCLogin", config.loginOptions.disableOIDCLogin); localStorage.setItem("disableOIDCLogin", config.loginOptions.disableOIDCLogin);
localStorage.setItem("globalOtpauthUrl", config.globalOtpauthUrl || "otpauth://totp/{label}?secret={secret}&issuer=FileRise"); 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");
updateLoginOptionsUIFromStorage(); updateLoginOptionsUIFromStorage();
@@ -301,6 +311,7 @@ function checkAuthentication(showLoginToast = true) {
localStorage.setItem("readOnly", data.readOnly); localStorage.setItem("readOnly", data.readOnly);
localStorage.setItem("disableUpload", data.disableUpload); localStorage.setItem("disableUpload", data.disableUpload);
updateLoginOptionsUIFromStorage(); updateLoginOptionsUIFromStorage();
applyProxyBypassUI();
if (typeof data.totp_enabled !== "undefined") { if (typeof data.totp_enabled !== "undefined") {
localStorage.setItem("userTOTPEnabled", data.totp_enabled ? "true" : "false"); localStorage.setItem("userTOTPEnabled", data.totp_enabled ? "true" : "false");
} }
@@ -317,7 +328,7 @@ function checkAuthentication(showLoginToast = true) {
document.querySelector('.main-wrapper').style.display = ''; document.querySelector('.main-wrapper').style.display = '';
document.getElementById('loginForm').style.display = ''; document.getElementById('loginForm').style.display = '';
if (showLoginToast) showToast("Please log in to continue."); if (showLoginToast) showToast("Please log in to continue.");
toggleVisibility("loginForm", true); toggleVisibility("loginForm", ! (localStorage.getItem("authBypass")==="true"));
toggleVisibility("mainOperations", false); toggleVisibility("mainOperations", false);
toggleVisibility("uploadFileForm", false); toggleVisibility("uploadFileForm", false);
toggleVisibility("fileListContainer", false); toggleVisibility("fileListContainer", false);

View File

@@ -54,11 +54,22 @@ class AdminController
{ {
header('Content-Type: application/json'); header('Content-Type: application/json');
$config = AdminModel::getConfig(); $config = AdminModel::getConfig();
// If an error was encountered, send a 500 status.
if (isset($config['error'])) { if (isset($config['error'])) {
http_response_code(500); http_response_code(500);
} }
if (!isset($config['loginOptions']) || !is_array($config['loginOptions'])) {
$config['loginOptions'] = [];
}
if (!array_key_exists('authBypass', $config['loginOptions'])) {
$config['loginOptions']['authBypass'] = false;
}
if (!array_key_exists('authHeaderName', $config['loginOptions'])) {
$config['loginOptions']['authHeaderName'] = 'X-Remote-User';
}
// ← END INSERT
echo json_encode($config); echo json_encode($config);
exit; exit;
} }
@@ -203,6 +214,12 @@ class AdminController
$sharedMaxUploadSize = filter_var($data['features']['sharedMaxUploadSize'], FILTER_VALIDATE_INT); $sharedMaxUploadSize = filter_var($data['features']['sharedMaxUploadSize'], FILTER_VALIDATE_INT);
} }
$authBypass = filter_var(
$data['loginOptions']['authBypass'] ?? false,
FILTER_VALIDATE_BOOLEAN
);
$authHeaderName = trim($data['loginOptions']['authHeaderName'] ?? '') ?: 'X-Remote-User';
$configUpdate = [ $configUpdate = [
'header_title' => $headerTitle, 'header_title' => $headerTitle,
'oidc' => [ 'oidc' => [
@@ -215,6 +232,8 @@ class AdminController
'disableFormLogin' => $disableFormLogin, 'disableFormLogin' => $disableFormLogin,
'disableBasicAuth' => $disableBasicAuth, 'disableBasicAuth' => $disableBasicAuth,
'disableOIDCLogin' => $disableOIDCLogin, 'disableOIDCLogin' => $disableOIDCLogin,
'authBypass' => $authBypass,
'authHeaderName' => $authHeaderName,
], ],
'globalOtpauthUrl' => $globalOtpauthUrl, 'globalOtpauthUrl' => $globalOtpauthUrl,
'enableWebDAV' => $enableWebDAV, 'enableWebDAV' => $enableWebDAV,

View File

@@ -342,48 +342,48 @@ class AuthController
public function checkAuth(): void public function checkAuth(): void
{ {
// 1) Remember-me re-login // 1) Remember-me re-login
if (empty($_SESSION['authenticated']) && !empty($_COOKIE['remember_me_token'])) { if (empty($_SESSION['authenticated']) && !empty($_COOKIE['remember_me_token'])) {
$payload = AuthModel::validateRememberToken($_COOKIE['remember_me_token']); $payload = AuthModel::validateRememberToken($_COOKIE['remember_me_token']);
if ($payload) { if ($payload) {
$old = $_SESSION['csrf_token'] ?? bin2hex(random_bytes(32)); $old = $_SESSION['csrf_token'] ?? bin2hex(random_bytes(32));
session_regenerate_id(true); session_regenerate_id(true);
$_SESSION['csrf_token'] = $old; $_SESSION['csrf_token'] = $old;
$_SESSION['authenticated'] = true; $_SESSION['authenticated'] = true;
$_SESSION['username'] = $payload['username']; $_SESSION['username'] = $payload['username'];
$_SESSION['isAdmin'] = !empty($payload['isAdmin']); $_SESSION['isAdmin'] = !empty($payload['isAdmin']);
$_SESSION['folderOnly'] = $payload['folderOnly'] ?? false; $_SESSION['folderOnly'] = $payload['folderOnly'] ?? false;
$_SESSION['readOnly'] = $payload['readOnly'] ?? false; $_SESSION['readOnly'] = $payload['readOnly'] ?? false;
$_SESSION['disableUpload'] = $payload['disableUpload'] ?? false; $_SESSION['disableUpload'] = $payload['disableUpload'] ?? false;
// regenerate CSRF if you use one // regenerate CSRF if you use one
// TOTP enabled? (same logic as below)
$usersFile = USERS_DIR . USERS_FILE; // TOTP enabled? (same logic as below)
$totp = false; $usersFile = USERS_DIR . USERS_FILE;
if (file_exists($usersFile)) { $totp = false;
foreach (file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) { if (file_exists($usersFile)) {
$parts = explode(':', trim($line)); foreach (file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
if ($parts[0] === $_SESSION['username'] && !empty($parts[3])) { $parts = explode(':', trim($line));
$totp = true; if ($parts[0] === $_SESSION['username'] && !empty($parts[3])) {
break; $totp = true;
break;
}
} }
} }
}
echo json_encode([ echo json_encode([
'authenticated' => true, 'authenticated' => true,
'csrf_token' => $_SESSION['csrf_token'], 'csrf_token' => $_SESSION['csrf_token'],
'isAdmin' => $_SESSION['isAdmin'], 'isAdmin' => $_SESSION['isAdmin'],
'totp_enabled' => $totp, 'totp_enabled' => $totp,
'username' => $_SESSION['username'], 'username' => $_SESSION['username'],
'folderOnly' => $_SESSION['folderOnly'], 'folderOnly' => $_SESSION['folderOnly'],
'readOnly' => $_SESSION['readOnly'], 'readOnly' => $_SESSION['readOnly'],
'disableUpload' => $_SESSION['disableUpload'] 'disableUpload' => $_SESSION['disableUpload']
]); ]);
exit(); exit();
}
} }
}
$usersFile = USERS_DIR . USERS_FILE; $usersFile = USERS_DIR . USERS_FILE;
@@ -453,11 +453,11 @@ class AuthController
if (empty($_SESSION['csrf_token'])) { if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
} }
// 2) Emit headers // 2) Emit headers
header('Content-Type: application/json'); header('Content-Type: application/json');
header('X-CSRF-Token: ' . $_SESSION['csrf_token']); header('X-CSRF-Token: ' . $_SESSION['csrf_token']);
// 3) Return JSON payload // 3) Return JSON payload
echo json_encode([ echo json_encode([
'csrf_token' => $_SESSION['csrf_token'], 'csrf_token' => $_SESSION['csrf_token'],

View File

@@ -16,10 +16,14 @@ class AdminModel
$unit = strtolower(substr($val, -1)); $unit = strtolower(substr($val, -1));
$num = (int) rtrim($val, 'bkmgtpezyBKMGTPESY'); $num = (int) rtrim($val, 'bkmgtpezyBKMGTPESY');
switch ($unit) { switch ($unit) {
case 'g': return $num * 1024 ** 3; case 'g':
case 'm': return $num * 1024 ** 2; return $num * 1024 ** 3;
case 'k': return $num * 1024; case 'm':
default: return $num; return $num * 1024 ** 2;
case 'k':
return $num * 1024;
default:
return $num;
} }
} }
@@ -63,6 +67,24 @@ class AdminModel
$configUpdate['sharedMaxUploadSize'] = $sms; $configUpdate['sharedMaxUploadSize'] = $sms;
} }
// ── NEW: normalize authBypass & authHeaderName ─────────────────────────
if (!isset($configUpdate['loginOptions']['authBypass'])) {
$configUpdate['loginOptions']['authBypass'] = false;
}
$configUpdate['loginOptions']['authBypass'] = (bool)$configUpdate['loginOptions']['authBypass'];
if (
!isset($configUpdate['loginOptions']['authHeaderName'])
|| !is_string($configUpdate['loginOptions']['authHeaderName'])
|| trim($configUpdate['loginOptions']['authHeaderName']) === ''
) {
$configUpdate['loginOptions']['authHeaderName'] = 'X-Remote-User';
} else {
$configUpdate['loginOptions']['authHeaderName'] =
trim($configUpdate['loginOptions']['authHeaderName']);
}
// ───────────────────────────────────────────────────────────────────────────
// Convert configuration to JSON. // Convert configuration to JSON.
$plainTextConfig = json_encode($configUpdate, JSON_PRETTY_PRINT); $plainTextConfig = json_encode($configUpdate, JSON_PRETTY_PRINT);
if ($plainTextConfig === false) { if ($plainTextConfig === false) {
@@ -128,6 +150,19 @@ class AdminModel
$config['loginOptions']['disableOIDCLogin'] = (bool)$config['loginOptions']['disableOIDCLogin']; $config['loginOptions']['disableOIDCLogin'] = (bool)$config['loginOptions']['disableOIDCLogin'];
} }
if (!array_key_exists('authBypass', $config['loginOptions'])) {
$config['loginOptions']['authBypass'] = false;
} else {
$config['loginOptions']['authBypass'] = (bool)$config['loginOptions']['authBypass'];
}
if (
!array_key_exists('authHeaderName', $config['loginOptions'])
|| !is_string($config['loginOptions']['authHeaderName'])
|| trim($config['loginOptions']['authHeaderName']) === ''
) {
$config['loginOptions']['authHeaderName'] = 'X-Remote-User';
}
// Default values for other keys // Default values for other keys
if (!isset($config['globalOtpauthUrl'])) { if (!isset($config['globalOtpauthUrl'])) {
$config['globalOtpauthUrl'] = ""; $config['globalOtpauthUrl'] = "";
@@ -166,4 +201,4 @@ class AdminModel
]; ];
} }
} }
} }