Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f391d11db | ||
|
|
8c70783d5a | ||
|
|
b4d6f01432 | ||
|
|
d48b15a5f4 | ||
|
|
d1726f0160 | ||
|
|
bd1841b788 | ||
|
|
bde35d1d31 |
132
CHANGELOG.md
@@ -1,5 +1,133 @@
|
||||
# Changelog
|
||||
|
||||
## Changes 5/8/2025 v1.3.3
|
||||
|
||||
### Enhancements
|
||||
|
||||
- **Admin API** (`updateConfig.php`):
|
||||
- Now merges incoming payload onto existing on-disk settings instead of overwriting blanks.
|
||||
- Preserves `clientId`, `clientSecret`, `providerUrl` and `redirectUri` when those fields are omitted or empty in the request.
|
||||
|
||||
- **Admin API** (`getConfig.php`):
|
||||
- Returns only a safe subset of admin settings (omits `clientSecret`) to prevent accidental exposure of sensitive data.
|
||||
|
||||
- **Frontend** (`auth.js`):
|
||||
- Update UI based on merged loginOptions from the server, ensuring blank or missing fields no longer revert your existing config.
|
||||
|
||||
- **Auth API** (`auth.php`):
|
||||
- Added `$oidc->addScope(['openid','profile','email']);` to OIDC flow. (This should resolve authentik issue)
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
- Relax filename validation regex to allow broader Unicode and special chars
|
||||
|
||||
### 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.
|
||||
|
||||
### Security
|
||||
|
||||
- **Admin API**: `getConfig.php` now returns only a safe subset of admin settings (omits `clientSecret`) to prevent accidental exposure of sensitive data.
|
||||
|
||||
---
|
||||
|
||||
## Changes 5/4/2025 v1.3.1
|
||||
|
||||
### Modals
|
||||
|
||||
- **Added** a shared `.editor-close-btn` component for all modals:
|
||||
- File Tags
|
||||
- User Panel
|
||||
- TOTP Login & Setup
|
||||
- Change Password
|
||||
- **Truncated** long filenames in the File Tags modal header using CSS `text-overflow: ellipsis`.
|
||||
- **Resized** File Tags modal from 400px to 450px wide (with `max-width: 90vw` fallback).
|
||||
- **Capped** User Panel height at 381px and hidden scrollbars to eliminate layout jumps on hover.
|
||||
|
||||
### HTML
|
||||
|
||||
- **Moved** `<div id="loginForm">…</div>` out of `.main-wrapper` so the login form can show independently of the app shell.
|
||||
- **Added** `<div id="loadingOverlay"></div>` immediately inside `<body>` to cover the UI during auth checks.
|
||||
- **Inserted** inline `<style>` in `<head>` to:
|
||||
- Hide `.main-wrapper` by default.
|
||||
- Style `#loadingOverlay` as a full-viewport white overlay.
|
||||
|
||||
- **Added** `addUserModal`, `removeUserModal` & `renameFileModal` modals to `style="display:none;"`
|
||||
|
||||
### `main.js`
|
||||
|
||||
- **Extracted** `initializeApp()` helper to centralize post-auth startup (tag search, file list, drag-and-drop, folder tree, upload, trash/restore, admin config).
|
||||
- **Updated** DOMContentLoaded `checkAuthentication()` flow to call `initializeApp()` when already authenticated.
|
||||
- **Extended** `updateAuthenticatedUI()` to call `initializeApp()` after a fresh login so all UI modules re-hydrate.
|
||||
- **Enhanced** setup-mode in `checkAuthentication()`:
|
||||
- Show `#addUserModal` as a flex overlay (`style.display = 'flex'`).
|
||||
- Keep `.main-wrapper` hidden until setup completes.
|
||||
- **Added** post-setup handler in the Add-User modal’s save button:
|
||||
- Hide setup modal.
|
||||
- Show login form.
|
||||
- Keep app shell hidden.
|
||||
- Pre-fill and focus the new username in the login inputs.
|
||||
|
||||
### `auth.js` / Auth Logic
|
||||
|
||||
- **Refactored** `checkAuthentication()` to handle three states:
|
||||
1. **`data.setup`** remove overlay, hide main UI, show setup modal.
|
||||
2. **`data.authenticated`** remove overlay, call `updateAuthenticatedUI()`.
|
||||
3. **not authenticated** remove overlay, show login form, keep main UI hidden.
|
||||
- **Refined** `updateAuthenticatedUI()` to:
|
||||
- Remove loading overlay.
|
||||
- Show `.main-wrapper` and main operations.
|
||||
- Hide `#loginForm`.
|
||||
- Reveal header buttons.
|
||||
- Initialize dynamic header buttons (restore, admin, user-panel).
|
||||
- Call `initializeApp()` to load all modules after login.
|
||||
|
||||
---
|
||||
|
||||
## Changes 5/3/2025 v1.3.0
|
||||
|
||||
**Admin Panel Refactor & Enhancements**
|
||||
@@ -48,6 +176,10 @@
|
||||
- Adjusted endpoint paths to match controller filenames
|
||||
- Fix FolderController readOnly create folder permission
|
||||
|
||||
### Additional changes
|
||||
|
||||
- Extend clean up expired shared entries
|
||||
|
||||
---
|
||||
|
||||
## Changes 4/30/2025 v1.2.8
|
||||
|
||||
@@ -30,11 +30,12 @@ define('DATE_TIME_FORMAT','m/d/y h:iA');
|
||||
define('TOTAL_UPLOAD_SIZE','5G');
|
||||
define('REGEX_FOLDER_NAME', '/^[\p{L}\p{N}_\-\s\/\\\\]+$/u');
|
||||
define('PATTERN_FOLDER_NAME','[\p{L}\p{N}_\-\s\/\\\\]+');
|
||||
define('REGEX_FILE_NAME', '/^[\p{L}\p{N}\p{M}%\-\.\(\) _]+$/u');
|
||||
define('REGEX_FILE_NAME', '/^[^\x00-\x1F\/\\\\]{1,255}$/u');
|
||||
define('REGEX_USER', '/^[\p{L}\p{N}_\- ]+$/u');
|
||||
|
||||
date_default_timezone_set(TIMEZONE);
|
||||
|
||||
|
||||
// Encryption helpers
|
||||
function encryptData($data, $encryptionKey)
|
||||
{
|
||||
@@ -114,6 +115,7 @@ if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
|
||||
// Auto‑login via persistent token
|
||||
if (empty($_SESSION["authenticated"]) && !empty($_COOKIE['remember_me_token'])) {
|
||||
$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
|
||||
define('BASE_URL', 'http://yourwebsite/uploads/');
|
||||
if (strpos(BASE_URL, 'yourwebsite') !== false) {
|
||||
|
||||
@@ -3,42 +3,61 @@
|
||||
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
|
||||
// Simple auth‐check: only admins may read these
|
||||
// Only admins may read these
|
||||
if (empty($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error'=>'Forbidden']);
|
||||
echo json_encode(['error' => 'Forbidden']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Expect a ?file=share_links.json or share_folder_links.json
|
||||
// Must supply ?file=share_links.json or share_folder_links.json
|
||||
if (empty($_GET['file'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error'=>'Missing `file` parameter']);
|
||||
echo json_encode(['error' => 'Missing `file` parameter']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$file = basename($_GET['file']);
|
||||
$allowed = ['share_links.json','share_folder_links.json'];
|
||||
$allowed = ['share_links.json', 'share_folder_links.json'];
|
||||
if (!in_array($file, $allowed, true)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error'=>'Invalid file requested']);
|
||||
echo json_encode(['error' => 'Invalid file requested']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$path = META_DIR . $file;
|
||||
if (!file_exists($path)) {
|
||||
http_response_code(404);
|
||||
echo json_encode((object)[]); // return empty object
|
||||
// Return empty object so JS sees `{}` not an error
|
||||
http_response_code(200);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode((object)[]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = file_get_contents($path);
|
||||
$json = json_decode($data, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$jsonData = file_get_contents($path);
|
||||
$data = json_decode($jsonData, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error'=>'Corrupted JSON']);
|
||||
echo json_encode(['error' => 'Corrupted JSON']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ——— Clean up expired entries ———
|
||||
$now = time();
|
||||
$changed = false;
|
||||
foreach ($data as $token => $entry) {
|
||||
if (!empty($entry['expires']) && $entry['expires'] < $now) {
|
||||
unset($data[$token]);
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
if ($changed) {
|
||||
// overwrite file with cleaned data
|
||||
file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
// ——— Send cleaned data back ———
|
||||
http_response_code(200);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($json);
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
@@ -1,2 +0,0 @@
|
||||
cd /var/www/public
|
||||
ln -s ../uploads uploads
|
||||
@@ -9,6 +9,21 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg">
|
||||
<meta name="csrf-token" content="">
|
||||
<meta name="share-url" content="">
|
||||
<style>
|
||||
/* hide the app shell until JS says otherwise */
|
||||
.main-wrapper { display: none; }
|
||||
|
||||
/* full-screen white overlay while we check auth */
|
||||
#loadingOverlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: var(--bg-color,#fff);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
<!-- Google Fonts and Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
@@ -165,10 +180,42 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="loadingOverlay"></div>
|
||||
|
||||
<!-- Custom Toast Container -->
|
||||
<div id="customToast"></div>
|
||||
<div id="hiddenCardsContainer" style="display:none;"></div>
|
||||
|
||||
<div class="row mt-4" id="loginForm">
|
||||
<div class="col-12">
|
||||
<form id="authForm" method="post">
|
||||
<div class="form-group">
|
||||
<label for="loginUsername" data-i18n-key="user">User:</label>
|
||||
<input type="text" class="form-control" id="loginUsername" name="username" required autofocus />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="loginPassword" data-i18n-key="password">Password:</label>
|
||||
<input type="password" class="form-control" id="loginPassword" name="password" required />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block btn-login" data-i18n-key="login">Login</button>
|
||||
<div class="form-group remember-me-container">
|
||||
<input type="checkbox" id="rememberMeCheckbox" name="remember_me" />
|
||||
<label for="rememberMeCheckbox" data-i18n-key="remember_me">Remember me</label>
|
||||
</div>
|
||||
</form>
|
||||
<!-- OIDC Login Option -->
|
||||
<div class="text-center mt-3">
|
||||
<button id="oidcLoginBtn" class="btn btn-secondary" data-i18n-key="login_oidc">Login with OIDC</button>
|
||||
</div>
|
||||
<!-- Basic HTTP Login Option -->
|
||||
<div class="text-center mt-3">
|
||||
<a href="/api/auth/login_basic.php" class="btn btn-secondary" data-i18n-key="basic_http_login">Use Basic
|
||||
HTTP
|
||||
Login</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Wrapper: Hidden by default; remove "display: none;" after login -->
|
||||
<div class="main-wrapper">
|
||||
<!-- Sidebar Drop Zone: Hidden until you drag a card (display controlled by JS) -->
|
||||
@@ -176,37 +223,6 @@
|
||||
<!-- Main Column -->
|
||||
<div id="mainColumn" class="main-column">
|
||||
<div class="container-fluid">
|
||||
<!-- Login Form (unchanged) -->
|
||||
<div class="row" id="loginForm">
|
||||
<div class="col-12">
|
||||
<form id="authForm" method="post">
|
||||
<div class="form-group">
|
||||
<label for="loginUsername" data-i18n-key="user">User:</label>
|
||||
<input type="text" class="form-control" id="loginUsername" name="username" required autofocus />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="loginPassword" data-i18n-key="password">Password:</label>
|
||||
<input type="password" class="form-control" id="loginPassword" name="password" required />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block btn-login" data-i18n-key="login">Login</button>
|
||||
<div class="form-group remember-me-container">
|
||||
<input type="checkbox" id="rememberMeCheckbox" name="remember_me" />
|
||||
<label for="rememberMeCheckbox" data-i18n-key="remember_me">Remember me</label>
|
||||
</div>
|
||||
</form>
|
||||
<!-- OIDC Login Option -->
|
||||
<div class="text-center mt-3">
|
||||
<button id="oidcLoginBtn" class="btn btn-secondary" data-i18n-key="login_oidc">Login with OIDC</button>
|
||||
</div>
|
||||
<!-- Basic HTTP Login Option -->
|
||||
<div class="text-center mt-3">
|
||||
<a href="/api/auth/login_basic.php" class="btn btn-secondary" data-i18n-key="basic_http_login">Use Basic
|
||||
HTTP
|
||||
Login</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Operations: Upload and Folder Management -->
|
||||
<div id="mainOperations">
|
||||
<div class="container" style="max-width: 1400px; margin: 0 auto;">
|
||||
@@ -428,7 +444,7 @@
|
||||
<div id="changePasswordModal" class="modal" style="display:none;">
|
||||
<div class="modal-content" style="max-width:400px; margin:auto;">
|
||||
<span id="closeChangePasswordModal"
|
||||
style="position:absolute; top:10px; right:10px; cursor:pointer; font-size:24px;">×</span>
|
||||
class="editor-close-btn">×</span>
|
||||
<h3 data-i18n-key="change_password_title">Change Password</h3>
|
||||
<input type="password" id="oldPassword" class="form-control" data-i18n-placeholder="old_password"
|
||||
placeholder="Old Password" style="width:100%; margin: 5px 0;" />
|
||||
@@ -439,7 +455,7 @@
|
||||
<button id="saveNewPasswordBtn" class="btn btn-primary" data-i18n-key="save" style="width:100%;">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="addUserModal" class="modal">
|
||||
<div id="addUserModal" class="modal" style="display:none;">
|
||||
<div class="modal-content">
|
||||
<h3 data-i18n-key="create_new_user_title">Create New User</h3>
|
||||
<!-- 1) Add a form around these fields -->
|
||||
@@ -468,7 +484,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="removeUserModal" class="modal">
|
||||
<div id="removeUserModal" class="modal" style="display:none;">
|
||||
<div class="modal-content">
|
||||
<h3 data-i18n-key="remove_user_title">Remove User</h3>
|
||||
<label for="removeUsernameSelect" data-i18n-key="select_user_remove">Select a user to remove:</label>
|
||||
@@ -479,7 +495,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="renameFileModal" class="modal">
|
||||
<div id="renameFileModal" class="modal" style="display:none;">
|
||||
<div class="modal-content">
|
||||
<h4 data-i18n-key="rename_file_title">Rename File</h4>
|
||||
<input type="text" id="newFileName" class="form-control" data-i18n-placeholder="rename_file_placeholder"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { loadAdminConfigFunc } from './auth.js';
|
||||
import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js';
|
||||
import { sendRequest } from './networkUtils.js';
|
||||
|
||||
const version = "v1.3.0";
|
||||
const version = "v1.3.3";
|
||||
const adminTitle = `${t("admin_panel")} <small style="font-size:12px;color:gray;">${version}</small>`;
|
||||
|
||||
// ————— Inject updated styles —————
|
||||
@@ -184,49 +184,63 @@ function loadShareLinksSection() {
|
||||
const container = document.getElementById("shareLinksContent");
|
||||
container.textContent = t("loading") + "...";
|
||||
|
||||
// Helper to fetch a metadata file or return {} on any error
|
||||
const fetchMeta = file =>
|
||||
fetch(`/api/admin/readMetadata.php?file=${file}`, { credentials: "include" })
|
||||
.then(r => r.ok ? r.json() : {}) // non-2xx → treat as empty
|
||||
.catch(() => ({}));
|
||||
// helper: fetch one metadata file, but never throw —
|
||||
// on non-2xx (including 404) or network error, resolve to {}
|
||||
function fetchMeta(fileName) {
|
||||
return fetch(`/api/admin/readMetadata.php?file=${encodeURIComponent(fileName)}`, {
|
||||
credentials: "include"
|
||||
})
|
||||
.then(resp => {
|
||||
if (!resp.ok) {
|
||||
// 404 or any other non-OK → treat as empty
|
||||
return {};
|
||||
}
|
||||
return resp.json();
|
||||
})
|
||||
.catch(() => {
|
||||
// network failure, parse error, etc → also empty
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
fetchMeta("share_folder_links.json"),
|
||||
fetchMeta("share_links.json")
|
||||
])
|
||||
.then(([folders, files]) => {
|
||||
// If nothing at all, show a friendly message
|
||||
if (Object.keys(folders).length === 0 && Object.keys(files).length === 0) {
|
||||
container.textContent = t("no_shared_links_available");
|
||||
// if *both* are empty, show "no shared links"
|
||||
const hasAny = Object.keys(folders).length || Object.keys(files).length;
|
||||
if (!hasAny) {
|
||||
container.innerHTML = `<p>${t("no_shared_links_available")}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `<h5>${t("folder_shares")}</h5><ul>`;
|
||||
Object.entries(folders).forEach(([token, o]) => {
|
||||
const lock = o.password ? `🔒 ` : "";
|
||||
const lock = o.password ? "🔒 " : "";
|
||||
html += `
|
||||
<li>
|
||||
${lock}<strong>${o.folder}</strong>
|
||||
<small>(${new Date(o.expires * 1000).toLocaleString()})</small>
|
||||
<button type="button"
|
||||
data-key="${token}"
|
||||
data-type="folder"
|
||||
class="btn btn-sm btn-link delete-share">🗑️</button>
|
||||
</li>`;
|
||||
<li>
|
||||
${lock}<strong>${o.folder}</strong>
|
||||
<small>(${new Date(o.expires * 1000).toLocaleString()})</small>
|
||||
<button type="button"
|
||||
data-key="${token}"
|
||||
data-type="folder"
|
||||
class="btn btn-sm btn-link delete-share">🗑️</button>
|
||||
</li>`;
|
||||
});
|
||||
|
||||
html += `</ul><h5 style="margin-top:1em;">${t("file_shares")}</h5><ul>`;
|
||||
Object.entries(files).forEach(([token, o]) => {
|
||||
const lock = o.password ? `🔒 ` : "";
|
||||
const lock = o.password ? "🔒 " : "";
|
||||
html += `
|
||||
<li>
|
||||
${lock}<strong>${o.folder}/${o.file}</strong>
|
||||
<small>(${new Date(o.expires * 1000).toLocaleString()})</small>
|
||||
<button type="button"
|
||||
data-key="${token}"
|
||||
data-type="file"
|
||||
class="btn btn-sm btn-link delete-share">🗑️</button>
|
||||
</li>`;
|
||||
<li>
|
||||
${lock}<strong>${o.folder}/${o.file}</strong>
|
||||
<small>(${new Date(o.expires * 1000).toLocaleString()})</small>
|
||||
<button type="button"
|
||||
data-key="${token}"
|
||||
data-type="file"
|
||||
class="btn btn-sm btn-link delete-share">🗑️</button>
|
||||
</li>`;
|
||||
});
|
||||
html += `</ul>`;
|
||||
|
||||
@@ -375,7 +389,7 @@ export function openAdminPanel() {
|
||||
// — Header Settings —
|
||||
document.getElementById("headerSettingsContent").innerHTML = `
|
||||
<div class="form-group">
|
||||
<label for="headerTitle">${t("header_title")}:</label>
|
||||
<label for="headerTitle">${t("header_title_text")}:</label>
|
||||
<input type="text" id="headerTitle" class="form-control" value="${window.headerTitle}" />
|
||||
</div>
|
||||
`;
|
||||
@@ -385,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="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="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 —
|
||||
@@ -403,6 +425,9 @@ export function openAdminPanel() {
|
||||
|
||||
// — OIDC & TOTP —
|
||||
document.getElementById("oidcContent").innerHTML = `
|
||||
<div class="form-text text-muted" style="margin-top:8px;">
|
||||
<small>Note: OIDC credentials (Client ID/Secret) will show blank here after saving, but remain unchanged until you explicitly edit and save them.</small>
|
||||
</div>
|
||||
<div class="form-group"><label for="oidcProviderUrl">${t("oidc_provider_url")}:</label><input type="text" id="oidcProviderUrl" class="form-control" value="${window.currentOIDCConfig.providerUrl}" /></div>
|
||||
<div class="form-group"><label for="oidcClientId">${t("oidc_client_id")}:</label><input type="text" id="oidcClientId" class="form-control" value="${window.currentOIDCConfig.clientId}" /></div>
|
||||
<div class="form-group"><label for="oidcClientSecret">${t("oidc_client_secret")}:</label><input type="text" id="oidcClientSecret" class="form-control" value="${window.currentOIDCConfig.clientSecret}" /></div>
|
||||
@@ -427,11 +452,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
|
||||
document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true;
|
||||
document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === 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("sharedMaxUploadSize").value = config.sharedMaxUploadSize || "";
|
||||
captureInitialAdminConfig();
|
||||
@@ -443,6 +477,8 @@ export function openAdminPanel() {
|
||||
document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true;
|
||||
document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === 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("sharedMaxUploadSize").value = config.sharedMaxUploadSize || "";
|
||||
document.getElementById("oidcProviderUrl").value = window.currentOIDCConfig.providerUrl;
|
||||
@@ -457,19 +493,21 @@ export function openAdminPanel() {
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
const dFL = document.getElementById("disableFormLogin").checked;
|
||||
const dBA = document.getElementById("disableBasicAuth").checked;
|
||||
const dOIDC = document.getElementById("disableOIDCLogin").checked;
|
||||
const eWD = document.getElementById("enableWebDAV").checked;
|
||||
const sMax = parseInt(document.getElementById("sharedMaxUploadSize").value, 10) || 0;
|
||||
const nHT = document.getElementById("headerTitle").value.trim();
|
||||
const nOIDC = {
|
||||
const dFL = document.getElementById("disableFormLogin").checked;
|
||||
const dBA = document.getElementById("disableBasicAuth").checked;
|
||||
const dOIDC = document.getElementById("disableOIDCLogin").checked;
|
||||
const aBypass= document.getElementById("authBypass").checked;
|
||||
const aHeader= document.getElementById("authHeaderName").value.trim() || "X-Remote-User";
|
||||
const eWD = document.getElementById("enableWebDAV").checked;
|
||||
const sMax = parseInt(document.getElementById("sharedMaxUploadSize").value, 10) || 0;
|
||||
const nHT = document.getElementById("headerTitle").value.trim();
|
||||
const nOIDC = {
|
||||
providerUrl: document.getElementById("oidcProviderUrl").value.trim(),
|
||||
clientId: document.getElementById("oidcClientId").value.trim(),
|
||||
clientSecret: document.getElementById("oidcClientSecret").value.trim(),
|
||||
clientId: document.getElementById("oidcClientId").value.trim(),
|
||||
clientSecret:document.getElementById("oidcClientSecret").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) {
|
||||
showToast(t("at_least_one_login_method"));
|
||||
@@ -477,12 +515,22 @@ function handleSave() {
|
||||
}
|
||||
|
||||
sendRequest("/api/admin/updateConfig.php", "POST", {
|
||||
header_title: nHT, oidc: nOIDC,
|
||||
disableFormLogin: dFL, disableBasicAuth: dBA, disableOIDCLogin: dOIDC,
|
||||
enableWebDAV: eWD, sharedMaxUploadSize: sMax, globalOtpauthUrl: gURL
|
||||
header_title: nHT,
|
||||
oidc: nOIDC,
|
||||
loginOptions: {
|
||||
disableFormLogin: dFL,
|
||||
disableBasicAuth: dBA,
|
||||
disableOIDCLogin: dOIDC,
|
||||
authBypass: aBypass,
|
||||
authHeaderName: aHeader
|
||||
},
|
||||
enableWebDAV: eWD,
|
||||
sharedMaxUploadSize: sMax,
|
||||
globalOtpauthUrl: gURL
|
||||
}, {
|
||||
"X-CSRF-Token": window.csrfToken
|
||||
}).then(res => {
|
||||
})
|
||||
.then(res => {
|
||||
if (res.success) {
|
||||
showToast(t("settings_updated_successfully"), "success");
|
||||
captureInitialAdminConfig();
|
||||
@@ -491,7 +539,7 @@ function handleSave() {
|
||||
} else {
|
||||
showToast(t("error_updating_settings") + ": " + (res.error || t("unknown_error")), "error");
|
||||
}
|
||||
}).catch(() => {/*noop*/ });
|
||||
}).catch(() => {/*noop*/});
|
||||
}
|
||||
|
||||
export async function closeAdminPanel() {
|
||||
@@ -534,7 +582,7 @@ export function openUserPermissionsModal() {
|
||||
`;
|
||||
userPermissionsModal.innerHTML = `
|
||||
<div class="modal-content" style="${modalContentStyles}">
|
||||
<span id="closeUserPermissionsModal" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
||||
<span id="closeUserPermissionsModal" class="editor-close-btn">×</span>
|
||||
<h3>${t("user_permissions")}</h3>
|
||||
<div id="userPermissionsList" style="max-height: 300px; overflow-y: auto; margin-bottom: 15px;">
|
||||
<!-- User rows will be loaded here -->
|
||||
|
||||
@@ -18,12 +18,13 @@ import {
|
||||
setLastLoginData
|
||||
} from './authModals.js';
|
||||
import { openAdminPanel } from './adminPanel.js';
|
||||
import { initializeApp } from './main.js';
|
||||
|
||||
// Production OIDC configuration (override via API as needed)
|
||||
const currentOIDCConfig = {
|
||||
providerUrl: "https://your-oidc-provider.com",
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
clientSecret: "YOUR_CLIENT_SECRET",
|
||||
clientId: "",
|
||||
clientSecret: "",
|
||||
redirectUri: "https://yourdomain.com/api/auth/auth.php?oidc=callback",
|
||||
globalOtpauthUrl: ""
|
||||
};
|
||||
@@ -124,16 +125,23 @@ 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 }) {
|
||||
const authForm = document.getElementById("authForm");
|
||||
if
|
||||
if
|
||||
(authForm) {
|
||||
authForm.style.display = disableFormLogin ? "none" : "block";
|
||||
setTimeout(() => {
|
||||
const loginInput = document.getElementById('loginUsername');
|
||||
if (loginInput) loginInput.focus();
|
||||
}, 0);
|
||||
authForm.style.display = disableFormLogin ? "none" : "block";
|
||||
setTimeout(() => {
|
||||
const loginInput = document.getElementById('loginUsername');
|
||||
if (loginInput) loginInput.focus();
|
||||
}, 0);
|
||||
}
|
||||
const basicAuthLink = document.querySelector("a[href='/api/auth/login_basic.php']");
|
||||
if (basicAuthLink) basicAuthLink.style.display = disableBasicAuth ? "none" : "inline-block";
|
||||
@@ -145,7 +153,8 @@ function updateLoginOptionsUIFromStorage() {
|
||||
updateLoginOptionsUI({
|
||||
disableFormLogin: localStorage.getItem("disableFormLogin") === "true",
|
||||
disableBasicAuth: localStorage.getItem("disableBasicAuth") === "true",
|
||||
disableOIDCLogin: localStorage.getItem("disableOIDCLogin") === "true"
|
||||
disableOIDCLogin: localStorage.getItem("disableOIDCLogin") === "true",
|
||||
authBypass: localStorage.getItem("authBypass") === "true"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -160,6 +169,8 @@ export function loadAdminConfigFunc() {
|
||||
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");
|
||||
|
||||
updateLoginOptionsUIFromStorage();
|
||||
|
||||
@@ -189,6 +200,11 @@ function insertAfter(newNode, referenceNode) {
|
||||
}
|
||||
|
||||
function updateAuthenticatedUI(data) {
|
||||
document.getElementById('loadingOverlay').remove();
|
||||
|
||||
// show the wrapper (so the login form can be visible)
|
||||
document.querySelector('.main-wrapper').style.display = '';
|
||||
document.getElementById('loginForm').style.display = 'none';
|
||||
toggleVisibility("loginForm", false);
|
||||
toggleVisibility("mainOperations", true);
|
||||
toggleVisibility("uploadFileForm", true);
|
||||
@@ -263,6 +279,7 @@ function updateAuthenticatedUI(data) {
|
||||
userPanelBtn.style.display = "block";
|
||||
}
|
||||
}
|
||||
initializeApp();
|
||||
applyTranslations();
|
||||
updateItemsPerPageSelect();
|
||||
updateLoginOptionsUIFromStorage();
|
||||
@@ -272,6 +289,11 @@ function checkAuthentication(showLoginToast = true) {
|
||||
return sendRequest("/api/auth/checkAuth.php")
|
||||
.then(data => {
|
||||
if (data.setup) {
|
||||
document.getElementById('loadingOverlay').remove();
|
||||
|
||||
// show the wrapper (so the login form can be visible)
|
||||
document.querySelector('.main-wrapper').style.display = '';
|
||||
document.getElementById('loginForm').style.display = 'none';
|
||||
window.setupMode = true;
|
||||
if (showLoginToast) showToast("Setup mode: No users found. Please add an admin user.");
|
||||
toggleVisibility("loginForm", false);
|
||||
@@ -283,11 +305,13 @@ function checkAuthentication(showLoginToast = true) {
|
||||
}
|
||||
window.setupMode = false;
|
||||
if (data.authenticated) {
|
||||
|
||||
localStorage.setItem('isAdmin', data.isAdmin ? 'true' : 'false');
|
||||
localStorage.setItem("folderOnly", data.folderOnly);
|
||||
localStorage.setItem("readOnly", data.readOnly);
|
||||
localStorage.setItem("disableUpload", data.disableUpload);
|
||||
updateLoginOptionsUIFromStorage();
|
||||
applyProxyBypassUI();
|
||||
if (typeof data.totp_enabled !== "undefined") {
|
||||
localStorage.setItem("userTOTPEnabled", data.totp_enabled ? "true" : "false");
|
||||
}
|
||||
@@ -298,8 +322,13 @@ function checkAuthentication(showLoginToast = true) {
|
||||
updateAuthenticatedUI(data);
|
||||
return data;
|
||||
} else {
|
||||
document.getElementById('loadingOverlay').remove();
|
||||
|
||||
// show the wrapper (so the login form can be visible)
|
||||
document.querySelector('.main-wrapper').style.display = '';
|
||||
document.getElementById('loginForm').style.display = '';
|
||||
if (showLoginToast) showToast("Please log in to continue.");
|
||||
toggleVisibility("loginForm", true);
|
||||
toggleVisibility("loginForm", ! (localStorage.getItem("authBypass")==="true"));
|
||||
toggleVisibility("mainOperations", false);
|
||||
toggleVisibility("uploadFileForm", false);
|
||||
toggleVisibility("fileListContainer", false);
|
||||
@@ -443,52 +472,55 @@ function initAuth() {
|
||||
submitLogin(formData);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
document.getElementById("addUserBtn").addEventListener("click", function () {
|
||||
resetUserForm();
|
||||
toggleVisibility("addUserModal", true);
|
||||
document.getElementById("newUsername").focus();
|
||||
});
|
||||
|
||||
// remove your old saveUserBtn click-handler…
|
||||
|
||||
// instead:
|
||||
const addUserForm = document.getElementById("addUserForm");
|
||||
addUserForm.addEventListener("submit", function (e) {
|
||||
e.preventDefault(); // stop the browser from reloading the page
|
||||
// remove your old saveUserBtn click-handler…
|
||||
|
||||
const newUsername = document.getElementById("newUsername").value.trim();
|
||||
const newPassword = document.getElementById("addUserPassword").value.trim();
|
||||
const isAdmin = document.getElementById("isAdmin").checked;
|
||||
// instead:
|
||||
const addUserForm = document.getElementById("addUserForm");
|
||||
addUserForm.addEventListener("submit", function (e) {
|
||||
e.preventDefault(); // stop the browser from reloading the page
|
||||
|
||||
if (!newUsername || !newPassword) {
|
||||
showToast("Username and password are required!");
|
||||
return;
|
||||
}
|
||||
const newUsername = document.getElementById("newUsername").value.trim();
|
||||
const newPassword = document.getElementById("addUserPassword").value.trim();
|
||||
const isAdmin = document.getElementById("isAdmin").checked;
|
||||
|
||||
let url = "/api/addUser.php";
|
||||
if (window.setupMode) url += "?setup=1";
|
||||
if (!newUsername || !newPassword) {
|
||||
showToast("Username and password are required!");
|
||||
return;
|
||||
}
|
||||
|
||||
fetchWithCsrf(url, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username: newUsername, password: newPassword, isAdmin })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showToast("User added successfully!");
|
||||
closeAddUserModal();
|
||||
checkAuthentication(false);
|
||||
} else {
|
||||
showToast("Error: " + (data.error || "Could not add user"));
|
||||
}
|
||||
let url = "/api/addUser.php";
|
||||
if (window.setupMode) url += "?setup=1";
|
||||
|
||||
fetchWithCsrf(url, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username: newUsername, password: newPassword, isAdmin })
|
||||
})
|
||||
.catch(() => {
|
||||
showToast("Error: Could not add user");
|
||||
});
|
||||
});
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showToast("User added successfully!");
|
||||
closeAddUserModal();
|
||||
checkAuthentication(false);
|
||||
if (window.setupMode) {
|
||||
toggleVisibility("loginForm", true);
|
||||
}
|
||||
} else {
|
||||
showToast("Error: " + (data.error || "Could not add user"));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
showToast("Error: Could not add user");
|
||||
});
|
||||
});
|
||||
document.getElementById("cancelUserBtn").addEventListener("click", closeAddUserModal);
|
||||
|
||||
document.getElementById("removeUserBtn").addEventListener("click", function () {
|
||||
|
||||
@@ -30,7 +30,7 @@ export function openTOTPLoginModal() {
|
||||
`;
|
||||
totpLoginModal.innerHTML = `
|
||||
<div style="background: ${modalBg}; padding:20px; border-radius:8px; text-align:center; position:relative; color:${textColor};">
|
||||
<span id="closeTOTPLoginModal" style="position:absolute; top:10px; right:10px; cursor:pointer; font-size:24px;">×</span>
|
||||
<span id="closeTOTPLoginModal" class="editor-close-btn">×</span>
|
||||
<div id="totpSection">
|
||||
<h3>${t("enter_totp_code")}</h3>
|
||||
<input type="text" id="totpLoginInput" maxlength="6"
|
||||
@@ -172,11 +172,13 @@ export function openUserPanel() {
|
||||
max-width: 600px;
|
||||
width: 90%;
|
||||
border-radius: 8px;
|
||||
position: fixed;
|
||||
overflow-y: auto;
|
||||
max-height: 400px !important;
|
||||
overflow-x: hidden;
|
||||
max-height: 383px !important;
|
||||
flex-shrink: 0 !important;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
border: ${isDarkMode ? "1px solid #444" : "1px solid #ccc"};
|
||||
transform: none;
|
||||
box-sizing: border-box;
|
||||
transition: none;
|
||||
`;
|
||||
const savedLanguage = localStorage.getItem("language") || "en";
|
||||
@@ -186,19 +188,17 @@ export function openUserPanel() {
|
||||
userPanelModal.id = "userPanelModal";
|
||||
userPanelModal.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
top: 0; right: 0; bottom: 0; left: 0;
|
||||
background-color: ${overlayBackground};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 3000;
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
`;
|
||||
userPanelModal.innerHTML = `
|
||||
<div class="modal-content user-panel-content" style="${modalContentStyles}">
|
||||
<span id="closeUserPanel" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
||||
<div class="modal-content user-panel-content" style="${modalContentStyles}">
|
||||
<span id="closeUserPanel" class="editor-close-btn">×</span>
|
||||
<h3>${t("user_panel")} (${username})</h3>
|
||||
|
||||
<button type="button" id="openChangePasswordModalBtn" class="btn btn-primary" style="margin-bottom: 15px;">
|
||||
@@ -369,7 +369,7 @@ export function openTOTPModal() {
|
||||
`;
|
||||
totpModal.innerHTML = `
|
||||
<div class="modal-content" style="${modalContentStyles}">
|
||||
<span id="closeTOTPModal" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
||||
<span id="closeTOTPModal" class="editor-close-btn">×</span>
|
||||
<h3>${t("totp_setup")}</h3>
|
||||
<p>${t("scan_qr_code")}</p>
|
||||
<!-- Create an image placeholder without the CSRF token in the src -->
|
||||
|
||||
@@ -13,10 +13,19 @@ export function openTagModal(file) {
|
||||
modal.id = 'tagModal';
|
||||
modal.className = 'modal';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content" style="width: 400px; max-width:90vw;">
|
||||
<div class="modal-content" style="width: 450px; max-width:90vw;">
|
||||
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<h3 style="margin:0;">${t("tag_file")}: ${file.name}</h3>
|
||||
<span id="closeTagModal" style="cursor:pointer; font-size:24px;">×</span>
|
||||
<h3 style="
|
||||
margin:0;
|
||||
display:inline-block;
|
||||
max-width: calc(100% - 40px);
|
||||
overflow:hidden;
|
||||
text-overflow:ellipsis;
|
||||
white-space:nowrap;
|
||||
">
|
||||
${t("tag_file")}: ${escapeHTML(file.name)}
|
||||
</h3>
|
||||
<span id="closeTagModal" class="editor-close-btn">×</span>
|
||||
</div>
|
||||
<div class="modal-body" style="margin-top:10px;">
|
||||
<label for="tagNameInput">${t("tag_name")}</label>
|
||||
@@ -83,10 +92,10 @@ export function openMultiTagModal(files) {
|
||||
modal.id = 'multiTagModal';
|
||||
modal.className = 'modal';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content" style="width: 400px; max-width:90vw;">
|
||||
<div class="modal-content" style="width: 450px; max-width:90vw;">
|
||||
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<h3 style="margin:0;">Tag Selected Files (${files.length})</h3>
|
||||
<span id="closeMultiTagModal" style="cursor:pointer; font-size:24px;">×</span>
|
||||
<span id="closeMultiTagModal" class="editor-close-btn">×</span>
|
||||
</div>
|
||||
<div class="modal-body" style="margin-top:10px;">
|
||||
<label for="multiTagNameInput">Tag Name:</label>
|
||||
|
||||
@@ -55,6 +55,7 @@ const translations = {
|
||||
// Additional keys for HTML translations:
|
||||
"title": "FileRise",
|
||||
"header_title": "FileRise",
|
||||
"header_title_text": "Header Title",
|
||||
"logout": "Logout",
|
||||
"change_password": "Change Password",
|
||||
"restore_text": "Restore or",
|
||||
@@ -316,6 +317,7 @@ const translations = {
|
||||
// Additional keys for HTML translations:
|
||||
"title": "FileRise",
|
||||
"header_title": "FileRise",
|
||||
"header_title_text": "Header Title",
|
||||
"logout": "Cerrar sesión",
|
||||
"change_password": "Cambiar contraseña",
|
||||
"restore_text": "Restaurar o",
|
||||
|
||||
@@ -14,6 +14,28 @@ import { initFileActions, renameFile, openDownloadModal, confirmSingleDownload }
|
||||
import { editFile, saveFile } from './fileEditor.js';
|
||||
import { t, applyTranslations, setLocale } from './i18n.js';
|
||||
|
||||
export function initializeApp() {
|
||||
window.currentFolder = "root";
|
||||
initTagSearch();
|
||||
loadFileList(window.currentFolder);
|
||||
initDragAndDrop();
|
||||
loadSidebarOrder();
|
||||
loadHeaderOrder();
|
||||
initFileActions();
|
||||
initUpload();
|
||||
loadFolderTree();
|
||||
setupTrashRestoreDelete();
|
||||
loadAdminConfigFunc();
|
||||
|
||||
const helpBtn = document.getElementById("folderHelpBtn");
|
||||
const helpTooltip = document.getElementById("folderHelpTooltip");
|
||||
if (helpBtn && helpTooltip) {
|
||||
helpBtn.addEventListener("click", () => {
|
||||
helpTooltip.style.display =
|
||||
helpTooltip.style.display === "block" ? "none" : "block";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function loadCsrfToken() {
|
||||
return fetchWithCsrf('/api/auth/token.php', {
|
||||
@@ -100,31 +122,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
// Continue with initializations that rely on a valid CSRF token:
|
||||
checkAuthentication().then(authenticated => {
|
||||
if (authenticated) {
|
||||
window.currentFolder = "root";
|
||||
initTagSearch();
|
||||
loadFileList(window.currentFolder);
|
||||
initDragAndDrop();
|
||||
loadSidebarOrder();
|
||||
loadHeaderOrder();
|
||||
initFileActions();
|
||||
initUpload();
|
||||
loadFolderTree();
|
||||
setupTrashRestoreDelete();
|
||||
loadAdminConfigFunc();
|
||||
|
||||
const helpBtn = document.getElementById("folderHelpBtn");
|
||||
const helpTooltip = document.getElementById("folderHelpTooltip");
|
||||
helpBtn.addEventListener("click", function () {
|
||||
// Toggle display of the tooltip.
|
||||
if (helpTooltip.style.display === "none" || helpTooltip.style.display === "") {
|
||||
helpTooltip.style.display = "block";
|
||||
} else {
|
||||
helpTooltip.style.display = "none";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn("User not authenticated. Data loading deferred.");
|
||||
}
|
||||
document.getElementById('loadingOverlay').remove();
|
||||
initializeApp();
|
||||
}
|
||||
});
|
||||
|
||||
// Other DOM initialization that can happen after CSRF is ready.
|
||||
|
||||
|
Before Width: | Height: | Size: 410 KiB After Width: | Height: | Size: 287 KiB |
|
Before Width: | Height: | Size: 626 KiB After Width: | Height: | Size: 764 KiB |
|
Before Width: | Height: | Size: 662 KiB After Width: | Height: | Size: 736 KiB |
|
Before Width: | Height: | Size: 499 KiB After Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 4.0 MiB After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 560 KiB After Width: | Height: | Size: 438 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 330 KiB |
|
Before Width: | Height: | Size: 438 KiB After Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 370 KiB After Width: | Height: | Size: 428 KiB |
|
Before Width: | Height: | Size: 4.0 MiB After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 412 KiB After Width: | Height: | Size: 369 KiB |
|
Before Width: | Height: | Size: 403 KiB After Width: | Height: | Size: 397 KiB |
|
Before Width: | Height: | Size: 457 KiB After Width: | Height: | Size: 504 KiB |
BIN
resources/light-user-panel.png
Normal file
|
After Width: | Height: | Size: 426 KiB |
@@ -54,12 +54,27 @@ class AdminController
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
$config = AdminModel::getConfig();
|
||||
|
||||
// If an error was encountered, send a 500 status.
|
||||
if (isset($config['error'])) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $config['error']]);
|
||||
exit;
|
||||
}
|
||||
echo json_encode($config);
|
||||
|
||||
// Build a safe subset for the front-end
|
||||
$safe = [
|
||||
'header_title' => $config['header_title'],
|
||||
'loginOptions' => $config['loginOptions'],
|
||||
'globalOtpauthUrl' => $config['globalOtpauthUrl'],
|
||||
'enableWebDAV' => $config['enableWebDAV'],
|
||||
'sharedMaxUploadSize' => $config['sharedMaxUploadSize'],
|
||||
'oidc' => [
|
||||
'providerUrl' => $config['oidc']['providerUrl'],
|
||||
'redirectUri' => $config['oidc']['redirectUri'],
|
||||
// clientSecret and clientId never exposed here
|
||||
],
|
||||
];
|
||||
|
||||
echo json_encode($safe);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -122,111 +137,106 @@ class AdminController
|
||||
* @return void Outputs a JSON response indicating success or failure.
|
||||
*/
|
||||
public function updateConfig(): void
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Ensure the user is authenticated and is an admin.
|
||||
if (
|
||||
!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true ||
|
||||
!isset($_SESSION['isAdmin']) || !$_SESSION['isAdmin']
|
||||
) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Unauthorized access.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validate CSRF token.
|
||||
$headersArr = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||
$receivedToken = isset($headersArr['x-csrf-token']) ? trim($headersArr['x-csrf-token']) : '';
|
||||
if (!isset($_SESSION['csrf_token']) || $receivedToken !== $_SESSION['csrf_token']) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Invalid CSRF token.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Retrieve and decode JSON input.
|
||||
$input = file_get_contents('php://input');
|
||||
$data = json_decode($input, true);
|
||||
if (!is_array($data)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid input.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Prepare existing settings
|
||||
$headerTitle = isset($data['header_title']) ? trim($data['header_title']) : "";
|
||||
$oidc = isset($data['oidc']) ? $data['oidc'] : [];
|
||||
$oidcProviderUrl = isset($oidc['providerUrl']) ? filter_var($oidc['providerUrl'], FILTER_SANITIZE_URL) : '';
|
||||
$oidcClientId = isset($oidc['clientId']) ? trim($oidc['clientId']) : '';
|
||||
$oidcClientSecret = isset($oidc['clientSecret']) ? trim($oidc['clientSecret']) : '';
|
||||
$oidcRedirectUri = isset($oidc['redirectUri']) ? filter_var($oidc['redirectUri'], FILTER_SANITIZE_URL) : '';
|
||||
if (!$oidcProviderUrl || !$oidcClientId || !$oidcClientSecret || !$oidcRedirectUri) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Incomplete OIDC configuration.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$disableFormLogin = false;
|
||||
if (isset($data['loginOptions']['disableFormLogin'])) {
|
||||
$disableFormLogin = filter_var($data['loginOptions']['disableFormLogin'], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif (isset($data['disableFormLogin'])) {
|
||||
$disableFormLogin = filter_var($data['disableFormLogin'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
$disableBasicAuth = false;
|
||||
if (isset($data['loginOptions']['disableBasicAuth'])) {
|
||||
$disableBasicAuth = filter_var($data['loginOptions']['disableBasicAuth'], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif (isset($data['disableBasicAuth'])) {
|
||||
$disableBasicAuth = filter_var($data['disableBasicAuth'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
$disableOIDCLogin = false;
|
||||
if (isset($data['loginOptions']['disableOIDCLogin'])) {
|
||||
$disableOIDCLogin = filter_var($data['loginOptions']['disableOIDCLogin'], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif (isset($data['disableOIDCLogin'])) {
|
||||
$disableOIDCLogin = filter_var($data['disableOIDCLogin'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
$globalOtpauthUrl = isset($data['globalOtpauthUrl']) ? trim($data['globalOtpauthUrl']) : "";
|
||||
|
||||
// ── NEW: enableWebDAV flag ──────────────────────────────────────
|
||||
$enableWebDAV = false;
|
||||
if (array_key_exists('enableWebDAV', $data)) {
|
||||
$enableWebDAV = filter_var($data['enableWebDAV'], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif (isset($data['features']['enableWebDAV'])) {
|
||||
$enableWebDAV = filter_var($data['features']['enableWebDAV'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
// ── NEW: sharedMaxUploadSize ──────────────────────────────────────
|
||||
$sharedMaxUploadSize = null;
|
||||
if (array_key_exists('sharedMaxUploadSize', $data)) {
|
||||
$sharedMaxUploadSize = filter_var($data['sharedMaxUploadSize'], FILTER_VALIDATE_INT);
|
||||
} elseif (isset($data['features']['sharedMaxUploadSize'])) {
|
||||
$sharedMaxUploadSize = filter_var($data['features']['sharedMaxUploadSize'], FILTER_VALIDATE_INT);
|
||||
}
|
||||
|
||||
$configUpdate = [
|
||||
'header_title' => $headerTitle,
|
||||
'oidc' => [
|
||||
'providerUrl' => $oidcProviderUrl,
|
||||
'clientId' => $oidcClientId,
|
||||
'clientSecret' => $oidcClientSecret,
|
||||
'redirectUri' => $oidcRedirectUri,
|
||||
],
|
||||
'loginOptions' => [
|
||||
'disableFormLogin' => $disableFormLogin,
|
||||
'disableBasicAuth' => $disableBasicAuth,
|
||||
'disableOIDCLogin' => $disableOIDCLogin,
|
||||
],
|
||||
'globalOtpauthUrl' => $globalOtpauthUrl,
|
||||
'enableWebDAV' => $enableWebDAV,
|
||||
'sharedMaxUploadSize' => $sharedMaxUploadSize // ← NEW
|
||||
];
|
||||
|
||||
// Delegate to the model.
|
||||
$result = AdminModel::updateConfig($configUpdate);
|
||||
if (isset($result['error'])) {
|
||||
http_response_code(500);
|
||||
}
|
||||
echo json_encode($result);
|
||||
// —– auth & CSRF checks —–
|
||||
if (
|
||||
!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true ||
|
||||
!isset($_SESSION['isAdmin']) || !$_SESSION['isAdmin']
|
||||
) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Unauthorized access.']);
|
||||
exit;
|
||||
}
|
||||
$headersArr = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||
$receivedToken = trim($headersArr['x-csrf-token'] ?? '');
|
||||
if (!isset($_SESSION['csrf_token']) || $receivedToken !== $_SESSION['csrf_token']) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Invalid CSRF token.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// —– fetch payload —–
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
if (!is_array($data)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid input.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// —– load existing on-disk config —–
|
||||
$existing = AdminModel::getConfig();
|
||||
|
||||
// —– start merge with existing as base —–
|
||||
$merged = $existing;
|
||||
|
||||
// header_title
|
||||
if (array_key_exists('header_title', $data)) {
|
||||
$merged['header_title'] = trim($data['header_title']);
|
||||
}
|
||||
|
||||
// loginOptions: inherit existing then override if provided
|
||||
$merged['loginOptions'] = $existing['loginOptions'] ?? [
|
||||
'disableFormLogin' => false,
|
||||
'disableBasicAuth' => false,
|
||||
'disableOIDCLogin'=> false,
|
||||
'authBypass' => false,
|
||||
'authHeaderName' => 'X-Remote-User'
|
||||
];
|
||||
foreach (['disableFormLogin','disableBasicAuth','disableOIDCLogin','authBypass'] as $flag) {
|
||||
if (isset($data['loginOptions'][$flag])) {
|
||||
$merged['loginOptions'][$flag] = filter_var(
|
||||
$data['loginOptions'][$flag],
|
||||
FILTER_VALIDATE_BOOLEAN
|
||||
);
|
||||
}
|
||||
}
|
||||
if (isset($data['loginOptions']['authHeaderName'])) {
|
||||
$hdr = trim($data['loginOptions']['authHeaderName']);
|
||||
if ($hdr !== '') {
|
||||
$merged['loginOptions']['authHeaderName'] = $hdr;
|
||||
}
|
||||
}
|
||||
|
||||
// globalOtpauthUrl
|
||||
if (array_key_exists('globalOtpauthUrl', $data)) {
|
||||
$merged['globalOtpauthUrl'] = trim($data['globalOtpauthUrl']);
|
||||
}
|
||||
|
||||
// enableWebDAV
|
||||
if (array_key_exists('enableWebDAV', $data)) {
|
||||
$merged['enableWebDAV'] = filter_var($data['enableWebDAV'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
// sharedMaxUploadSize
|
||||
if (array_key_exists('sharedMaxUploadSize', $data)) {
|
||||
$sms = filter_var($data['sharedMaxUploadSize'], FILTER_VALIDATE_INT);
|
||||
if ($sms !== false) {
|
||||
$merged['sharedMaxUploadSize'] = $sms;
|
||||
}
|
||||
}
|
||||
|
||||
// oidc: only overwrite non-empty inputs
|
||||
$merged['oidc'] = $existing['oidc'] ?? [
|
||||
'providerUrl'=>'','clientId'=>'','clientSecret'=>'','redirectUri'=>''
|
||||
];
|
||||
foreach (['providerUrl','clientId','clientSecret','redirectUri'] as $f) {
|
||||
if (!empty($data['oidc'][$f])) {
|
||||
$val = trim($data['oidc'][$f]);
|
||||
if ($f === 'providerUrl' || $f === 'redirectUri') {
|
||||
$val = filter_var($val, FILTER_SANITIZE_URL);
|
||||
}
|
||||
$merged['oidc'][$f] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// —– persist merged config —–
|
||||
$result = AdminModel::updateConfig($merged);
|
||||
if (isset($result['error'])) {
|
||||
http_response_code(500);
|
||||
}
|
||||
echo json_encode($result);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,8 @@ class AuthController
|
||||
$cfg['oidc']['clientSecret']
|
||||
);
|
||||
$oidc->setRedirectURL($cfg['oidc']['redirectUri']);
|
||||
$oidc->addScope(['openid','profile','email']);
|
||||
|
||||
|
||||
if ($oidcAction === 'callback') {
|
||||
try {
|
||||
@@ -342,48 +344,48 @@ class AuthController
|
||||
public function checkAuth(): void
|
||||
{
|
||||
|
||||
// 1) Remember-me re-login
|
||||
if (empty($_SESSION['authenticated']) && !empty($_COOKIE['remember_me_token'])) {
|
||||
$payload = AuthModel::validateRememberToken($_COOKIE['remember_me_token']);
|
||||
if ($payload) {
|
||||
$old = $_SESSION['csrf_token'] ?? bin2hex(random_bytes(32));
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['csrf_token'] = $old;
|
||||
$_SESSION['authenticated'] = true;
|
||||
$_SESSION['username'] = $payload['username'];
|
||||
$_SESSION['isAdmin'] = !empty($payload['isAdmin']);
|
||||
$_SESSION['folderOnly'] = $payload['folderOnly'] ?? false;
|
||||
$_SESSION['readOnly'] = $payload['readOnly'] ?? false;
|
||||
$_SESSION['disableUpload'] = $payload['disableUpload'] ?? false;
|
||||
// regenerate CSRF if you use one
|
||||
|
||||
// 1) Remember-me re-login
|
||||
if (empty($_SESSION['authenticated']) && !empty($_COOKIE['remember_me_token'])) {
|
||||
$payload = AuthModel::validateRememberToken($_COOKIE['remember_me_token']);
|
||||
if ($payload) {
|
||||
$old = $_SESSION['csrf_token'] ?? bin2hex(random_bytes(32));
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['csrf_token'] = $old;
|
||||
$_SESSION['authenticated'] = true;
|
||||
$_SESSION['username'] = $payload['username'];
|
||||
$_SESSION['isAdmin'] = !empty($payload['isAdmin']);
|
||||
$_SESSION['folderOnly'] = $payload['folderOnly'] ?? false;
|
||||
$_SESSION['readOnly'] = $payload['readOnly'] ?? false;
|
||||
$_SESSION['disableUpload'] = $payload['disableUpload'] ?? false;
|
||||
// regenerate CSRF if you use one
|
||||
|
||||
// TOTP enabled? (same logic as below)
|
||||
$usersFile = USERS_DIR . USERS_FILE;
|
||||
$totp = false;
|
||||
if (file_exists($usersFile)) {
|
||||
foreach (file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
|
||||
$parts = explode(':', trim($line));
|
||||
if ($parts[0] === $_SESSION['username'] && !empty($parts[3])) {
|
||||
$totp = true;
|
||||
break;
|
||||
|
||||
// TOTP enabled? (same logic as below)
|
||||
$usersFile = USERS_DIR . USERS_FILE;
|
||||
$totp = false;
|
||||
if (file_exists($usersFile)) {
|
||||
foreach (file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
|
||||
$parts = explode(':', trim($line));
|
||||
if ($parts[0] === $_SESSION['username'] && !empty($parts[3])) {
|
||||
$totp = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'authenticated' => true,
|
||||
'csrf_token' => $_SESSION['csrf_token'],
|
||||
'isAdmin' => $_SESSION['isAdmin'],
|
||||
'totp_enabled' => $totp,
|
||||
'username' => $_SESSION['username'],
|
||||
'folderOnly' => $_SESSION['folderOnly'],
|
||||
'readOnly' => $_SESSION['readOnly'],
|
||||
'disableUpload' => $_SESSION['disableUpload']
|
||||
]);
|
||||
exit();
|
||||
echo json_encode([
|
||||
'authenticated' => true,
|
||||
'csrf_token' => $_SESSION['csrf_token'],
|
||||
'isAdmin' => $_SESSION['isAdmin'],
|
||||
'totp_enabled' => $totp,
|
||||
'username' => $_SESSION['username'],
|
||||
'folderOnly' => $_SESSION['folderOnly'],
|
||||
'readOnly' => $_SESSION['readOnly'],
|
||||
'disableUpload' => $_SESSION['disableUpload']
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$usersFile = USERS_DIR . USERS_FILE;
|
||||
|
||||
@@ -453,11 +455,11 @@ class AuthController
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
|
||||
// 2) Emit headers
|
||||
header('Content-Type: application/json');
|
||||
header('X-CSRF-Token: ' . $_SESSION['csrf_token']);
|
||||
|
||||
|
||||
// 3) Return JSON payload
|
||||
echo json_encode([
|
||||
'csrf_token' => $_SESSION['csrf_token'],
|
||||
|
||||
@@ -1582,6 +1582,31 @@ class FileController
|
||||
echo json_encode($shareFile, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
public function getAllShareLinks(): void
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
$shareFile = META_DIR . 'share_links.json';
|
||||
$links = file_exists($shareFile)
|
||||
? json_decode(file_get_contents($shareFile), true) ?? []
|
||||
: [];
|
||||
$now = time();
|
||||
$cleaned = [];
|
||||
|
||||
// remove expired
|
||||
foreach ($links as $token => $record) {
|
||||
if (!empty($record['expires']) && $record['expires'] < $now) {
|
||||
continue;
|
||||
}
|
||||
$cleaned[$token] = $record;
|
||||
}
|
||||
|
||||
if (count($cleaned) !== count($links)) {
|
||||
file_put_contents($shareFile, json_encode($cleaned, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
echo json_encode($cleaned);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/file/deleteShareLink.php
|
||||
*/
|
||||
|
||||
@@ -1082,11 +1082,30 @@ class FolderController
|
||||
/**
|
||||
* GET /api/folder/getShareFolderLinks.php
|
||||
*/
|
||||
public function getShareFolderLinks()
|
||||
public function getAllShareFolderLinks(): void
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
$links = FolderModel::getAllShareFolderLinks();
|
||||
echo json_encode($links, JSON_PRETTY_PRINT);
|
||||
$shareFile = META_DIR . 'share_folder_links.json';
|
||||
$links = file_exists($shareFile)
|
||||
? json_decode(file_get_contents($shareFile), true) ?? []
|
||||
: [];
|
||||
$now = time();
|
||||
$cleaned = [];
|
||||
|
||||
// 1) Remove expired
|
||||
foreach ($links as $token => $record) {
|
||||
if (!empty($record['expires']) && $record['expires'] < $now) {
|
||||
continue;
|
||||
}
|
||||
$cleaned[$token] = $record;
|
||||
}
|
||||
|
||||
// 2) Persist back if anything was pruned
|
||||
if (count($cleaned) !== count($links)) {
|
||||
file_put_contents($shareFile, json_encode($cleaned, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
echo json_encode($cleaned);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,10 +16,14 @@ class AdminModel
|
||||
$unit = strtolower(substr($val, -1));
|
||||
$num = (int) rtrim($val, 'bkmgtpezyBKMGTPESY');
|
||||
switch ($unit) {
|
||||
case 'g': return $num * 1024 ** 3;
|
||||
case 'm': return $num * 1024 ** 2;
|
||||
case 'k': return $num * 1024;
|
||||
default: return $num;
|
||||
case 'g':
|
||||
return $num * 1024 ** 3;
|
||||
case 'm':
|
||||
return $num * 1024 ** 2;
|
||||
case 'k':
|
||||
return $num * 1024;
|
||||
default:
|
||||
return $num;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +67,24 @@ class AdminModel
|
||||
$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.
|
||||
$plainTextConfig = json_encode($configUpdate, JSON_PRETTY_PRINT);
|
||||
if ($plainTextConfig === false) {
|
||||
@@ -128,6 +150,19 @@ class AdminModel
|
||||
$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
|
||||
if (!isset($config['globalOtpauthUrl'])) {
|
||||
$config['globalOtpauthUrl'] = "";
|
||||
@@ -151,8 +186,8 @@ class AdminModel
|
||||
'header_title' => "FileRise",
|
||||
'oidc' => [
|
||||
'providerUrl' => 'https://your-oidc-provider.com',
|
||||
'clientId' => 'YOUR_CLIENT_ID',
|
||||
'clientSecret' => 'YOUR_CLIENT_SECRET',
|
||||
'clientId' => '',
|
||||
'clientSecret' => '',
|
||||
'redirectUri' => 'https://yourdomain.com/api/auth/auth.php?oidc=callback'
|
||||
],
|
||||
'loginOptions' => [
|
||||
@@ -166,4 +201,4 @@ class AdminModel
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||