mirror of
https://github.com/error311/FileRise.git
synced 2025-12-30 07:49:33 -06:00
release(v2.5.2): new user management hub & relocated shared upload limits
This commit is contained in:
62
CHANGELOG.md
62
CHANGELOG.md
@@ -1,5 +1,67 @@
|
||||
# Changelog
|
||||
|
||||
## Changes 12/10/2025 (v2.5.2)
|
||||
|
||||
release(v2.5.2): new user management hub & relocated shared upload limits
|
||||
|
||||
**Admin panel – User Management**
|
||||
|
||||
- Added a new **“Manage users” hub** in the Users section, replacing the old separate “Add user / Remove user / User permissions” buttons.
|
||||
- Introduced an inline **Add user dropdown card** anchored directly under the “Add user” button:
|
||||
- Opens as a small card right under the button.
|
||||
- Creates the user via `/api/addUser.php` and auto-selects them in the dropdown on success.
|
||||
- Closes the card after a successful create.
|
||||
- Added an inline **User Permissions (flags) row** for the selected user:
|
||||
- Toggles `readOnly`, `disableUpload`, `canShare`, and `bypassOwnership`.
|
||||
- Changes are saved immediately via `updateUserPermissions.php` and cached in `__userFlagsCacheHub`.
|
||||
- Admin users are detected and treated as full-access (toggles disabled with a note).
|
||||
|
||||
**User creation & password resets**
|
||||
|
||||
- Improved `/api/addUser.php` responses:
|
||||
- Returns proper HTTP status codes (e.g. **422** for validation failures).
|
||||
- Normalized JSON shape to `{ ok: false, error: "…" }` for errors and `{ ok: true, data: … }` for success.
|
||||
- Enforces **minimum 6-character passwords** for new users; invalid usernames and short passwords surface as clear error messages.
|
||||
- Updated the admin “Add user” form in the hub to:
|
||||
- Use `fetch` directly so 4xx responses (like 422) are correctly parsed.
|
||||
- Show a toast (or `alert` fallback) on both success and failure, including backend validation messages.
|
||||
- Added `UserModel::adminResetPassword()` to reset a user’s password from the admin hub without the old password, preserving TOTP/extra fields in the users file.
|
||||
- Added new endpoint `public/api/admin/changeUserPassword.php`:
|
||||
- Admin-only, with CSRF header check.
|
||||
- Resets a user’s password via `adminResetPassword()` and returns consistent JSON.
|
||||
|
||||
**Shared links & upload limits**
|
||||
|
||||
- Reworked **shared upload size limit** UI:
|
||||
- Removed the “Shared upload limits” block from the **Upload** section.
|
||||
- Moved the **Shared Max Upload Size (bytes)** input into the **Shared links** section, above the links table.
|
||||
- Renamed the section label to **“Manage Shared Links & Upload Size Limit”** and added a new `manage_shared_links_size` i18n key.
|
||||
- Updated the Upload section label to **“Antivirus”** / “Antivirus upload scanning” since that section now focuses purely on AV configuration.
|
||||
|
||||
**Admin UI & Pro integrations**
|
||||
|
||||
- Updated the **Users** tab toolbar:
|
||||
- Replaced old “Add user / Remove user / User permissions” buttons with:
|
||||
- **Manage users** (opens the new hub).
|
||||
- **Folder Access** (per-folder ACLs).
|
||||
- **User Groups** (Pro).
|
||||
- **Client Portals** (Pro).
|
||||
- Wired **Client Portals** to open the new hub for user management:
|
||||
- “Manage users…” button now triggers the global `adminOpenUserHub` instead of the old Add-User modal.
|
||||
- “Folder access…” and “User groups…” buttons now link to `adminOpenFolderAccess` and the Pro groups modal respectively.
|
||||
- Increased `#adminPanelModal` base width slightly (60% → 64%) for a bit more breathing room in the new layouts.
|
||||
- Slight visual polish:
|
||||
- Thicker `admin-divider` and OIDC debug snapshot border.
|
||||
- **Admin subsection titles** bumped to 1.15rem.
|
||||
- Toggle “ON” state uses `--filr-accent-400` for a slightly softer accent.
|
||||
|
||||
**Miscellaneous**
|
||||
|
||||
- Bumped the **upload card/modal z-index** so the upload UI always sits cleanly above other overlays.
|
||||
- Added styling for `#adminUserHubModal .modal-content` in both light and dark mode so it matches existing admin panel modals.
|
||||
|
||||
---
|
||||
|
||||
## Changes 12/9/2025 (v2.5.1)
|
||||
|
||||
release(v2.5.1): upgrade vendor libs and enhance OIDC + admin UX
|
||||
|
||||
10
public/api/admin/changeUserPassword.php
Normal file
10
public/api/admin/changeUserPassword.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
// public/api/admin/changeUserPassword.php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
require_once PROJECT_ROOT . '/src/controllers/UserController.php';
|
||||
require_once PROJECT_ROOT . '/src/models/UserModel.php';
|
||||
|
||||
$controller = new UserController();
|
||||
$controller->adminChangeUserPassword();
|
||||
@@ -6,6 +6,7 @@ img.logo{width:50px; height:50px; display:block;}
|
||||
#userPanelModal .modal-content,
|
||||
#adminPanelModal .modal-content,
|
||||
#userPermissionsModal .modal-content,
|
||||
#adminUserHubModal .modal-content,
|
||||
#userFlagsModal .modal-content,
|
||||
#userGroupsModal .modal-content,
|
||||
#groupAclModal .modal-content,
|
||||
@@ -2066,7 +2067,7 @@ body.dark-mode #deleteSelectedBtn,body.dark-mode #deleteAllBtn,body.dark-mode #d
|
||||
body.dark-mode .folder-strip-container.folder-strip-mobile{background:var(--fr-surface-dark-2)!important;border:1px solid var(--fr-border-dark)!important}
|
||||
body.dark-mode #customToast{background:#212121!important;border:1px solid var(--fr-border-dark)!important;box-shadow:0 8px 20px rgba(0,0,0,.9)!important}
|
||||
body.dark-mode #fileSummary{color:var(--fr-muted-dark)!important}
|
||||
body.dark-mode #createMenu,body.dark-mode .user-dropdown .user-menu,body.dark-mode #fileContextMenu,body.dark-mode #folderContextMenu,body.dark-mode #folderManagerContextMenu,body.dark-mode #adminPanelModal .modal-content,body.dark-mode #userPermissionsModal .modal-content,body.dark-mode #userFlagsModal .modal-content,body.dark-mode #userGroupsModal .modal-content,body.dark-mode #userPanelModal .modal-content,body.dark-mode #groupAclModal .modal-content,body.dark-mode .editor-modal,body.dark-mode #filePreviewModal .modal-content,body.dark-mode #loginForm,body.dark-mode .editor-header,#clientPortalsModal .modal-content{background:var(--fr-surface-dark)!important;border:1px solid var(--fr-border-dark)!important;color:#f1f1f1!important;border-radius:12px!important;box-shadow:0 8px 24px rgba(0,0,0,.9)!important}
|
||||
body.dark-mode #createMenu,body.dark-mode .user-dropdown .user-menu,body.dark-mode #fileContextMenu,body.dark-mode #folderContextMenu,body.dark-mode #folderManagerContextMenu,body.dark-mode #adminPanelModal .modal-content,body.dark-mode #userPermissionsModal .modal-content,body.dark-mode #userPermissionsModal .modal-content,body.dark-mode #userFlagsModal .modal-content,body.dark-mode #userGroupsModal .modal-content,body.dark-mode #userPanelModal .modal-content,body.dark-mode #groupAclModal .modal-content,body.dark-mode .editor-modal,body.dark-mode #filePreviewModal .modal-content,body.dark-mode #loginForm,body.dark-mode .editor-header,#clientPortalsModal .modal-content{background:var(--fr-surface-dark)!important;border:1px solid var(--fr-border-dark)!important;color:#f1f1f1!important;border-radius:12px!important;box-shadow:0 8px 24px rgba(0,0,0,.9)!important}
|
||||
body.dark-mode .user-dropdown .user-menu,body.dark-mode #createMenu,body.dark-mode #fileContextMenu,body.dark-mode #folderContextMenu,body.dark-mode #folderManagerContextMenu{background-clip:padding-box}
|
||||
body:not(.dark-mode){background:var(--fr-bg-light)!important;color:#111!important;background-image:none!important}
|
||||
body:not(.dark-mode) #fileListContainer,body:not(.dark-mode) #uploadCard,body:not(.dark-mode) #folderManagementCard,body:not(.dark-mode) .card,body:not(.dark-mode) .admin-panel-content{background:var(--fr-surface-light)!important;border-color:var(--fr-border-light)!important;box-shadow:0 3px 8px rgba(0,0,0,.04)!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}
|
||||
@@ -2084,7 +2085,7 @@ body:not(.dark-mode) #deleteSelectedBtn,body:not(.dark-mode) #deleteAllBtn,body:
|
||||
body:not(.dark-mode) .folder-strip-container.folder-strip-mobile{background:#f1f1f1!important;border:1px solid var(--fr-border-light)!important}
|
||||
body:not(.dark-mode) #customToast{background:#212121!important;color:#fff!important;border:1px solid #000!important;box-shadow:0 8px 18px rgba(0,0,0,.45)!important}
|
||||
body:not(.dark-mode) #fileSummary{color:var(--fr-muted-light)!important}
|
||||
body:not(.dark-mode) #createMenu,body:not(.dark-mode) .user-dropdown .user-menu,body:not(.dark-mode) #fileContextMenu,body:not(.dark-mode) #folderContextMenu,body:not(.dark-mode) #folderManagerContextMenu,body:not(.dark-mode) #adminPanelModal .modal-content,body:not(.dark-mode) #userPermissionsModal .modal-content,body:not(.dark-mode) #userFlagsModal .modal-content,body:not(.dark-mode) #userGroupsModal .modal-content,body:not(.dark-mode) #userPanelModal .modal-content,body:not(.dark-mode) #groupAclModal .modal-content,body:not(.dark-mode) .editor-modal,body:not(.dark-mode) #filePreviewModal .modal-content,body:not(.dark-mode) #loginForm,body:not(.dark-mode) .editor-header,body:not(.dark-mode) #clientPortalsModal .modal-content{background:var(--fr-surface-light)!important;border:1px solid var(--fr-border-light)!important;color:#111!important;border-radius:12px!important;box-shadow:0 4px 12px rgba(0,0,0,.12)!important}
|
||||
body:not(.dark-mode) #createMenu,body:not(.dark-mode) .user-dropdown .user-menu,body:not(.dark-mode) #fileContextMenu,body:not(.dark-mode) #folderContextMenu,body:not(.dark-mode) #folderManagerContextMenu,body:not(.dark-mode) #adminPanelModal .modal-content,body:not(.dark-mode) #userPermissionsModal .modal-content,body:not(.dark-mode) #userPermissionsModal .modal-content,body:not(.dark-mode) #userFlagsModal .modal-content,body:not(.dark-mode) #userGroupsModal .modal-content,body:not(.dark-mode) #userPanelModal .modal-content,body:not(.dark-mode) #groupAclModal .modal-content,body:not(.dark-mode) .editor-modal,body:not(.dark-mode) #filePreviewModal .modal-content,body:not(.dark-mode) #loginForm,body:not(.dark-mode) .editor-header,body:not(.dark-mode) #clientPortalsModal .modal-content{background:var(--fr-surface-light)!important;border:1px solid var(--fr-border-light)!important;color:#111!important;border-radius:12px!important;box-shadow:0 4px 12px rgba(0,0,0,.12)!important}
|
||||
#searchIcon{display:inline-flex;align-items:center;justify-content:center;width:38px;height:36px;padding:0;border-radius:999px 0 0 999px;border:1px solid rgba(0,0,0,.18);border-right:none;background:#fff;cursor:pointer;box-shadow:none;transform:none}
|
||||
#searchIcon .material-icons{font-size:20px;line-height:1;color:#555}
|
||||
#searchIcon:hover{background:#f5f5f5}
|
||||
@@ -2251,7 +2252,7 @@ body:not(.dark-mode) .header-zoom-controls .btn-icon.zoom-btn .material-icons{
|
||||
/* Modal sizing */
|
||||
#adminPanelModal .modal-content {
|
||||
max-width: 1100px;
|
||||
width: 60% !important;
|
||||
width: 64% !important;
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
border: 1px solid #ccc !important;
|
||||
@@ -2964,8 +2965,9 @@ body.dark-mode .oidc-debug-snapshot {
|
||||
/* admin section separators */
|
||||
.admin-divider {
|
||||
border: 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
margin: 10px 0;
|
||||
border-top: 2px solid rgba(0, 0, 0, 0.08);
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dark-mode .admin-divider {
|
||||
@@ -2975,7 +2977,7 @@ body.dark-mode .oidc-debug-snapshot {
|
||||
/* OIDC debug JSON snapshot */
|
||||
#oidcContent .oidc-debug-snapshot {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border: 2px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -3028,8 +3030,8 @@ body.dark-mode .oidc-debug-snapshot {
|
||||
|
||||
/* ON state */
|
||||
#adminPanelModal .form-check.fr-toggle .fr-toggle-input:checked {
|
||||
background-color: var(--filr-accent-500, #0d6efd);
|
||||
border-color: var(--filr-accent-500, #0d6efd);
|
||||
background-color: var(--filr-accent-400, #0d6efd);
|
||||
border-color: var(--filr-accent-400, #0d6efd);
|
||||
}
|
||||
|
||||
#adminPanelModal .form-check.fr-toggle .fr-toggle-input:checked::before {
|
||||
@@ -3062,7 +3064,7 @@ body.dark-mode .oidc-debug-snapshot {
|
||||
}
|
||||
#adminPanelModal .admin-subsection-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
font-size: 1.15rem;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -291,15 +291,15 @@ export async function openClientPortalsModal() {
|
||||
|
||||
<button type="button"
|
||||
id="clientPortalsQuickAddUser"
|
||||
class="btn btn-sm btn-outline-primary ms-1">
|
||||
<i class="material-icons" style="font-size:16px; vertical-align:middle;">person_add</i>
|
||||
<span style="margin-left:4px;">Add user…</span>
|
||||
class="btn btn-sm btn-primary ms-1">
|
||||
<i class="material-icons" style="font-size:16px; vertical-align:middle;">people</i>
|
||||
<span style="margin-left:4px;">Manage users…</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="clientPortalsOpenUserPerms"
|
||||
class="btn btn-sm btn-outline-secondary ms-1">
|
||||
class="btn btn-sm btn-secondary ms-1">
|
||||
<i class="material-icons" style="font-size:16px; vertical-align:middle;">folder_shared</i>
|
||||
<span style="margin-left:4px;">Folder access…</span>
|
||||
</button>
|
||||
@@ -307,7 +307,7 @@ export async function openClientPortalsModal() {
|
||||
<button
|
||||
type="button"
|
||||
id="clientPortalsOpenUserGroups"
|
||||
class="btn btn-sm btn-outline-secondary ms-1">
|
||||
class="btn btn-sm btn-secondary ms-1">
|
||||
<i class="material-icons" style="font-size:16px; vertical-align:middle;">groups</i>
|
||||
<span style="margin-left:4px;">User groups…</span>
|
||||
</button>
|
||||
@@ -335,7 +335,7 @@ export async function openClientPortalsModal() {
|
||||
if (quickAddUserBtn) {
|
||||
quickAddUserBtn.onclick = () => {
|
||||
// Reuse existing admin add-user button / modal
|
||||
const globalBtn = document.getElementById('adminOpenAddUser');
|
||||
const globalBtn = document.getElementById('adminOpenUserHub');
|
||||
if (globalBtn) {
|
||||
globalBtn.click();
|
||||
} else {
|
||||
@@ -346,7 +346,7 @@ export async function openClientPortalsModal() {
|
||||
const openPermsBtn = document.getElementById('clientPortalsOpenUserPerms');
|
||||
if (openPermsBtn) {
|
||||
openPermsBtn.onclick = () => {
|
||||
const btn = document.getElementById('adminOpenUserPermissions');
|
||||
const btn = document.getElementById('adminOpenFolderAccess');
|
||||
if (btn) {
|
||||
btn.click();
|
||||
} else {
|
||||
|
||||
@@ -192,6 +192,7 @@ const translations = {
|
||||
"shared_max_upload_size_bytes": "Shared Max Upload Size (bytes)",
|
||||
"max_bytes_shared_uploads_note": "Enter maximum bytes allowed for shared-folder uploads",
|
||||
"manage_shared_links": "Manage Shared Links",
|
||||
"manage_shared_links_size": "Manage Shared Links & Upload Size Limit",
|
||||
"folder_shares": "Folder Shares",
|
||||
"file_shares": "File Shares",
|
||||
"loading": "Loading…",
|
||||
|
||||
@@ -53,7 +53,7 @@ function showVirusScanNotice() {
|
||||
transform: 'translate(-50%, -50%)',
|
||||
maxWidth: '420px',
|
||||
width: 'calc(100% - 32px)', // nice on mobile too
|
||||
zIndex: '1080',
|
||||
zIndex: '11080',
|
||||
padding: '16px 18px',
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0 4px 24px rgba(0,0,0,0.35)',
|
||||
|
||||
@@ -144,15 +144,17 @@ class UserController
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
|
||||
// Setup mode detection (first-run bootstrap)
|
||||
$usersFile = USERS_DIR . USERS_FILE;
|
||||
$isSetup = (isset($_GET['setup']) && $_GET['setup'] === '1');
|
||||
$setupMode = false;
|
||||
|
||||
if (
|
||||
$isSetup && (!file_exists($usersFile)
|
||||
|| filesize($usersFile) === 0
|
||||
|| trim(@file_get_contents($usersFile)) === ''
|
||||
$isSetup && (
|
||||
!file_exists($usersFile) ||
|
||||
filesize($usersFile) === 0 ||
|
||||
trim(@file_get_contents($usersFile)) === ''
|
||||
)
|
||||
) {
|
||||
$setupMode = true;
|
||||
@@ -160,48 +162,110 @@ class UserController
|
||||
// Not setup: enforce CSRF + admin auth
|
||||
$h = self::headersLower();
|
||||
$receivedToken = trim($h['x-csrf-token'] ?? '');
|
||||
|
||||
// Soft-fail CSRF: on mismatch, regenerate and return new token (preserve your current UX)
|
||||
|
||||
// Soft-fail CSRF: on mismatch, regenerate and return new token
|
||||
if ($receivedToken !== $_SESSION['csrf_token']) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
header('X-CSRF-Token: ' . $_SESSION['csrf_token']);
|
||||
echo json_encode([
|
||||
'csrf_expired' => true,
|
||||
'csrf_token' => $_SESSION['csrf_token']
|
||||
'csrf_token' => $_SESSION['csrf_token'],
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
self::requireAdmin();
|
||||
}
|
||||
|
||||
|
||||
$data = self::readJson();
|
||||
$newUsername = trim($data['username'] ?? '');
|
||||
$newPassword = trim($data['password'] ?? '');
|
||||
|
||||
|
||||
$isAdmin = $setupMode ? '1' : (!empty($data['isAdmin']) ? '1' : '0');
|
||||
|
||||
if ($newUsername === '' || $newPassword === '') {
|
||||
echo json_encode(["error" => "Username and password required"]);
|
||||
exit;
|
||||
}
|
||||
if (!preg_match(REGEX_USER, $newUsername)) {
|
||||
|
||||
// Helper for validation errors (new + legacy shape)
|
||||
$fail = function (string $message, int $status = 422) {
|
||||
http_response_code($status);
|
||||
echo json_encode([
|
||||
"error" => "Invalid username. Only letters, numbers, underscores, dashes, and spaces are allowed."
|
||||
'ok' => false,
|
||||
'error' => $message,
|
||||
// legacy-friendly fields:
|
||||
'success' => false,
|
||||
'message' => $message,
|
||||
]);
|
||||
exit;
|
||||
};
|
||||
|
||||
if ($newUsername === '' || $newPassword === '') {
|
||||
$fail('Username and password required');
|
||||
}
|
||||
// Keep password rules lenient to avoid breaking existing flows; enforce at least 6 chars
|
||||
|
||||
if (!preg_match(REGEX_USER, $newUsername)) {
|
||||
$fail('Invalid username. Only letters, numbers, underscores, dashes, and spaces are allowed.');
|
||||
}
|
||||
|
||||
// Enforce at least 6 chars (for new accounts)
|
||||
if (strlen($newPassword) < 6) {
|
||||
echo json_encode(["error" => "Password must be at least 6 characters."]);
|
||||
exit;
|
||||
$fail('Password must be at least 6 characters.');
|
||||
}
|
||||
|
||||
|
||||
$result = UserModel::addUser($newUsername, $newPassword, $isAdmin, $setupMode);
|
||||
echo json_encode($result);
|
||||
|
||||
// If the model returns an error, also go through the same fail() helper
|
||||
if (isset($result['error']) && $result['error'] !== '') {
|
||||
$fail($result['error']);
|
||||
}
|
||||
|
||||
// Normalize success shape: new + legacy fields
|
||||
$msg = $result['success'] ?? 'User added successfully';
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'data' => $result, // keeps your new structure
|
||||
// legacy-friendly:
|
||||
'success' => true,
|
||||
'message' => $msg,
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
public function adminChangeUserPassword()
|
||||
{
|
||||
self::jsonHeaders();
|
||||
self::requireMethod(['POST']);
|
||||
self::requireAdmin();
|
||||
self::requireCsrf();
|
||||
|
||||
$data = self::readJson();
|
||||
$username = trim((string)($data['username'] ?? ''));
|
||||
$newPassword = (string)($data['newPassword'] ?? '');
|
||||
|
||||
if ($username === '' || $newPassword === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Username and newPassword are required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Optional: enforce a minimum length, like addUser
|
||||
if (strlen($newPassword) < 6) {
|
||||
http_response_code(422);
|
||||
echo json_encode(['error' => 'Password must be at least 6 characters.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = UserModel::adminResetPassword($username, $newPassword);
|
||||
|
||||
if (!empty($result['error'])) {
|
||||
http_response_code(400);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
}
|
||||
|
||||
echo json_encode($result);
|
||||
exit;
|
||||
}
|
||||
|
||||
public function removeUser()
|
||||
{
|
||||
self::jsonHeaders();
|
||||
|
||||
@@ -329,6 +329,54 @@ class userModel
|
||||
return ["success" => "Password updated successfully."];
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin-only password reset (no old password required).
|
||||
*/
|
||||
public static function adminResetPassword($targetUsername, $newPassword)
|
||||
{
|
||||
if (!preg_match(REGEX_USER, $targetUsername)) {
|
||||
return ["error" => "Invalid username"];
|
||||
}
|
||||
|
||||
$usersFile = USERS_DIR . USERS_FILE;
|
||||
if (!file_exists($usersFile)) {
|
||||
return ["error" => "Users file not found"];
|
||||
}
|
||||
|
||||
$lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
$userFound = false;
|
||||
$newLines = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$parts = explode(':', trim($line));
|
||||
if (count($parts) < 3) {
|
||||
$newLines[] = $line;
|
||||
continue;
|
||||
}
|
||||
$storedUser = $parts[0];
|
||||
|
||||
if ($storedUser === $targetUsername) {
|
||||
$userFound = true;
|
||||
// index 1 is the hash; preserve TOTP/extra fields in the rest of the line
|
||||
$parts[1] = password_hash($newPassword, PASSWORD_BCRYPT);
|
||||
$newLines[] = implode(':', $parts);
|
||||
} else {
|
||||
$newLines[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$userFound) {
|
||||
return ["error" => "User not found."];
|
||||
}
|
||||
|
||||
$payload = implode(PHP_EOL, $newLines) . PHP_EOL;
|
||||
if (file_put_contents($usersFile, $payload, LOCK_EX) === false) {
|
||||
return ["error" => "Could not update password."];
|
||||
}
|
||||
|
||||
return ["success" => "Password updated successfully."];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update panel: if TOTP disabled, clear secret.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user