feat(admin): add proxy-only auth bypass and configurable auth header (closes #28)
This commit is contained in:
53
CHANGELOG.md
53
CHANGELOG.md
@@ -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`
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Auto‑login via persistent token
|
// Auto‑login 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 PHP’s $_SERVER key format:
|
||||||
|
$cfgAuthHeader = 'HTTP_' . strtoupper(str_replace('-', '_', $hdr));
|
||||||
|
}
|
||||||
|
|
||||||
|
define('AUTH_BYPASS', $cfgAuthBypass);
|
||||||
|
define('AUTH_HEADER', $cfgAuthHeader);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// PROXY-ONLY AUTO–LOGIN 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) {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -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
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user