Tell the three auth failures apart, and give the Auth tab a settings card
Not set, rejected and unreachable all reached the page as one message about checking credentials, which sends you to a password when the field is simply empty — as both of HOST1's were, with no card on the page to fill them in from.
This commit is contained in:
@@ -45,14 +45,20 @@
|
||||
//
|
||||
// Auth failure is reported, not retried into a lockout.
|
||||
// A failed token fetch returns an _err string immediately. Nothing loops on bad
|
||||
// credentials against a service that may rate-limit or lock the account.
|
||||
// credentials against a service that may rate-limit or lock the account. A blank
|
||||
// credential is caught before the request rather than sent as a guess.
|
||||
//
|
||||
// The three auth failures are told apart.
|
||||
// Not set, rejected, and unreachable all reach a caller as an empty token and need
|
||||
// three different fixes. vv_auth_creds_missing() and vv_auth_token_err() name which.
|
||||
//
|
||||
// Every remote call has a timeout, and every function returns a structured result —
|
||||
// ['ok' => bool] or an _err key — so no caller has to distinguish an exception from a
|
||||
// legitimately empty list.
|
||||
//
|
||||
// EXPORTS
|
||||
// Config vv_auth_conf()
|
||||
// Config vv_auth_conf(), vv_auth_creds_missing(), vv_auth_token_err(),
|
||||
// vv_auth_last_transport()
|
||||
// NPM vv_npm_list_proxies(), vv_npm_list_certs(), vv_npm_create_proxy(),
|
||||
// vv_npm_update_proxy(), vv_npm_delete_proxy(), vv_npm_toggle_proxy()
|
||||
// LLDAP vv_lldap_list_users(), vv_lldap_list_groups(), vv_lldap_create_user(),
|
||||
@@ -92,6 +98,48 @@ function vv_auth_conf(): array {
|
||||
];
|
||||
}
|
||||
|
||||
// ── Credential state ──────────────────────────────────────────────────────────
|
||||
|
||||
// Three different failures arrive at a token fetch as the same empty string: the credential was
|
||||
// never filled in, the service rejected it, or the service is not answering. They need three
|
||||
// different actions, and "check credentials" sends someone to look at a password that is fine
|
||||
// while the container is down — or at a container that is fine while the field is empty.
|
||||
//
|
||||
// Blank is checked first and without a request, because there is nothing to ask: a login with an
|
||||
// empty identity is a guess against a service that may rate-limit or lock the account, and
|
||||
// vv_npm_raw() would report its 401 as if a real password had been rejected.
|
||||
function vv_auth_creds_missing(string $svc): string {
|
||||
$conf = vv_auth_conf();
|
||||
$h = strtoupper(vv_detect_host());
|
||||
if ($svc === 'npm')
|
||||
return ($conf['npm_user'] === '' || $conf['npm_pass'] === '')
|
||||
? "NPM credentials are not set — {$h}_NPM_USER / {$h}_NPM_PASS are empty. Fill them in Auth settings, below."
|
||||
: '';
|
||||
return ($conf['lldap_user'] === '' || $conf['lldap_pass'] === '')
|
||||
? "lldap credentials are not set — {$h}_LLDAP_USER / {$h}_LLDAP_PASS are empty. Fill them in Auth settings, below."
|
||||
: '';
|
||||
}
|
||||
|
||||
// The transport result of the last auth-stack curl, so a caller holding an empty token can say
|
||||
// which of the two remaining failures it was. Static rather than returned through every signature
|
||||
// because the token functions return a plain string and always have; widening them would touch
|
||||
// every call site to carry a value only the failure path reads.
|
||||
function vv_auth_last_transport(?array $set = null): array {
|
||||
static $last = ['errno' => 0, 'error' => '', 'code' => 0];
|
||||
if ($set !== null) $last = $set;
|
||||
return $last;
|
||||
}
|
||||
|
||||
function vv_auth_token_err(string $svc, string $url): string {
|
||||
$t = vv_auth_last_transport();
|
||||
$name = $svc === 'npm' ? 'NPM' : 'lldap';
|
||||
if ($t['errno'])
|
||||
return "$name unreachable at $url — " . ($t['error'] ?: 'connection failed');
|
||||
$h = strtoupper(vv_detect_host());
|
||||
$k = $svc === 'npm' ? "{$h}_NPM_USER / {$h}_NPM_PASS" : "{$h}_LLDAP_USER / {$h}_LLDAP_PASS";
|
||||
return "$name rejected the login — check $k in Auth settings, below.";
|
||||
}
|
||||
|
||||
// ── NPM ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
function vv_npm_token(): string {
|
||||
@@ -129,13 +177,19 @@ function vv_npm_raw(string $method, string $path, array $data, string $token, ar
|
||||
if ($data && in_array($method, ['POST', 'PUT'], true))
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
$body = curl_exec($ch);
|
||||
vv_auth_last_transport([
|
||||
'errno' => curl_errno($ch),
|
||||
'error' => curl_error($ch),
|
||||
'code' => (int) curl_getinfo($ch, CURLINFO_HTTP_CODE),
|
||||
]);
|
||||
curl_close($ch);
|
||||
return json_decode($body ?: '{}', true) ?: [];
|
||||
}
|
||||
|
||||
function vv_npm_req(string $method, string $path, array $data = []): array {
|
||||
if ($miss = vv_auth_creds_missing('npm')) return ['_err' => $miss];
|
||||
$token = vv_npm_token();
|
||||
if (!$token) return ['_err' => 'NPM auth failed — check credentials in host conf'];
|
||||
if (!$token) return ['_err' => vv_auth_token_err('npm', vv_auth_conf()['npm_url'])];
|
||||
return vv_npm_raw($method, $path, $data, $token);
|
||||
}
|
||||
|
||||
@@ -146,9 +200,14 @@ function vv_npm_list_proxies(): array {
|
||||
return ['ok' => true, 'proxies' => $list];
|
||||
}
|
||||
|
||||
// Returns a list, always. An auth failure arrives here as a map with an _err key, and the
|
||||
// is_array() check passed it straight through as if it were the certificates — the caller then
|
||||
// held an object where it expected an array and lost .find() on it. The contract is a list, so a
|
||||
// failure is an empty one; vv_npm_list_proxies() runs on the same page and reports the reason.
|
||||
function vv_npm_list_certs(): array {
|
||||
$list = vv_npm_req('GET', '/api/nginx/certificates');
|
||||
return is_array($list) ? $list : [];
|
||||
if (!is_array($list) || isset($list['_err'])) return [];
|
||||
return array_values($list);
|
||||
}
|
||||
|
||||
function vv_npm_create_proxy(array $data): array {
|
||||
@@ -189,6 +248,11 @@ function vv_lldap_token(): string {
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||||
]);
|
||||
$body = curl_exec($ch);
|
||||
vv_auth_last_transport([
|
||||
'errno' => curl_errno($ch),
|
||||
'error' => curl_error($ch),
|
||||
'code' => (int) curl_getinfo($ch, CURLINFO_HTTP_CODE),
|
||||
]);
|
||||
curl_close($ch);
|
||||
$resp = json_decode($body ?: '{}', true) ?: [];
|
||||
$token = $resp['token'] ?? '';
|
||||
@@ -201,8 +265,9 @@ function vv_lldap_token(): string {
|
||||
|
||||
function vv_lldap_gql(string $query, array $variables = []): array {
|
||||
$conf = vv_auth_conf();
|
||||
if ($miss = vv_auth_creds_missing('lldap')) return ['errors' => [['message' => $miss]]];
|
||||
$token = vv_lldap_token();
|
||||
if (!$token) return ['errors' => [['message' => 'lldap auth failed — check credentials']]];
|
||||
if (!$token) return ['errors' => [['message' => vv_auth_token_err('lldap', $conf['lldap_url'])]]];
|
||||
|
||||
$ch = curl_init($conf['lldap_url'] . '/api/graphql');
|
||||
curl_setopt_array($ch, [
|
||||
@@ -259,8 +324,9 @@ function vv_lldap_delete_user(string $id): array {
|
||||
|
||||
function vv_lldap_set_password(string $userId, string $password): array {
|
||||
$conf = vv_auth_conf();
|
||||
if ($miss = vv_auth_creds_missing('lldap')) return ['ok' => false, 'error' => $miss];
|
||||
$token = vv_lldap_token();
|
||||
if (!$token) return ['ok' => false, 'error' => 'lldap auth failed'];
|
||||
if (!$token) return ['ok' => false, 'error' => vv_auth_token_err('lldap', $conf['lldap_url'])];
|
||||
|
||||
$ch = curl_init($conf['lldap_url'] . '/auth/admin/resetPassword');
|
||||
curl_setopt_array($ch, [
|
||||
|
||||
@@ -124,6 +124,12 @@ const VV_UI_SECTION_SURFACES = [
|
||||
'route' => 'Media Stack tab → Media settings'],
|
||||
['match' => 'docker', 'tab' => 'Docker',
|
||||
'route' => 'Docker tab → Docker settings'],
|
||||
// NPM, lldap and Authelia are the three services the Auth tab drives, and Certificate Monitor
|
||||
// is what its Certs panel reports. The credentials in particular belong on the page that fails
|
||||
// without them: a blank NPM_USER surfaces there as a refused login, and the fix is two panels
|
||||
// away rather than in a hundred-and-twenty-field catch-all.
|
||||
['match' => 'NginxProxyManager|lldap|Authelia|Certificate Monitor', 'tab' => 'Auth',
|
||||
'route' => 'Auth tab → Auth settings'],
|
||||
|
||||
// Everything that is not structurally excluded. The catch-all exists so no ordinary setting
|
||||
// is reachable only by editing a file — a settings page whose answer to a third of the conf
|
||||
|
||||
Reference in New Issue
Block a user