diff --git a/CHANGELOG.md b/CHANGELOG.md index 3186009..2146020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # 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 @@ -50,6 +69,10 @@ - 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 diff --git a/public/js/adminPanel.js b/public/js/adminPanel.js index c9a13a5..12b1804 100644 --- a/public/js/adminPanel.js +++ b/public/js/adminPanel.js @@ -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.2"; +const version = "v1.3.3"; const adminTitle = `${t("admin_panel")} ${version}`; // ————— Inject updated styles ————— @@ -425,6 +425,9 @@ export function openAdminPanel() { // — OIDC & TOTP — document.getElementById("oidcContent").innerHTML = ` +
+ Note: OIDC credentials (Client ID/Secret) will show blank here after saving, but remain unchanged until you explicitly edit and save them. +
diff --git a/public/js/auth.js b/public/js/auth.js index 6066dab..b505f32 100644 --- a/public/js/auth.js +++ b/public/js/auth.js @@ -23,8 +23,8 @@ 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: "" }; diff --git a/src/controllers/AdminController.php b/src/controllers/AdminController.php index 3ee5a42..997b1d3 100644 --- a/src/controllers/AdminController.php +++ b/src/controllers/AdminController.php @@ -54,23 +54,27 @@ class AdminController { header('Content-Type: application/json'); $config = AdminModel::getConfig(); - if (isset($config['error'])) { http_response_code(500); + echo json_encode(['error' => $config['error']]); + exit; } - 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 + // 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($config); + echo json_encode($safe); exit; } @@ -133,119 +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); - } - - $authBypass = filter_var( - $data['loginOptions']['authBypass'] ?? false, - FILTER_VALIDATE_BOOLEAN - ); - $authHeaderName = trim($data['loginOptions']['authHeaderName'] ?? '') ?: 'X-Remote-User'; - - $configUpdate = [ - 'header_title' => $headerTitle, - 'oidc' => [ - 'providerUrl' => $oidcProviderUrl, - 'clientId' => $oidcClientId, - 'clientSecret' => $oidcClientSecret, - 'redirectUri' => $oidcRedirectUri, - ], - 'loginOptions' => [ - 'disableFormLogin' => $disableFormLogin, - 'disableBasicAuth' => $disableBasicAuth, - 'disableOIDCLogin' => $disableOIDCLogin, - 'authBypass' => $authBypass, - 'authHeaderName' => $authHeaderName, - ], - '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; +} } \ No newline at end of file diff --git a/src/controllers/AuthController.php b/src/controllers/AuthController.php index f07c2d6..cb99d6a 100644 --- a/src/controllers/AuthController.php +++ b/src/controllers/AuthController.php @@ -111,6 +111,8 @@ class AuthController $cfg['oidc']['clientSecret'] ); $oidc->setRedirectURL($cfg['oidc']['redirectUri']); + $oidc->addScope(['openid','profile','email']); + if ($oidcAction === 'callback') { try { diff --git a/src/models/AdminModel.php b/src/models/AdminModel.php index 5f0b483..a8eec7b 100644 --- a/src/models/AdminModel.php +++ b/src/models/AdminModel.php @@ -186,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' => [