From 13de1dab828b2faed7bd0b11385c0ff0fdcfb881 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sat, 15 Aug 2026 15:51:29 -0400 Subject: [PATCH] Stop a scalar NPM response killing the request, and add certificate deletion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NPM answers a successful DELETE with the bare literal true, which vv_npm_raw() is typed to return an array from — so deleting or toggling a proxy host has always thrown a TypeError rather than doing the thing. --- Plugin/unraid/include/auth.php | 36 +++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/Plugin/unraid/include/auth.php b/Plugin/unraid/include/auth.php index 9a861e8..92cda7c 100644 --- a/Plugin/unraid/include/auth.php +++ b/Plugin/unraid/include/auth.php @@ -234,7 +234,17 @@ function vv_npm_raw(string $method, string $path, array $data, string $token, ar 'code' => (int) curl_getinfo($ch, CURLINFO_HTTP_CODE), ]); curl_close($ch); - return json_decode($body ?: '{}', true) ?: []; + + // NPM answers a successful DELETE with the bare JSON literal `true`, not an object. This + // function is declared `: array`, so decoding that and returning it threw a TypeError and + // killed the request — which is what deleting a certificate did, and what deleting or toggling + // a proxy host has always done, since those three are the only callers whose endpoint answers + // with a scalar. Wrapped rather than returned raw, so every caller still gets an array and the + // outcome is readable as ['result' => true]. + $decoded = json_decode($body ?: '{}', true); + if (is_array($decoded)) return $decoded; + if ($decoded === null) return []; + return ['result' => $decoded]; } function vv_npm_req(string $method, string $path, array $data = []): array { @@ -261,6 +271,30 @@ function vv_npm_list_certs(): array { return array_values($list); } +// Deleting a certificate is not undoable — the private key goes with it and a replacement means a +// fresh issuance against Let's Encrypt's rate limits. The caller is expected to have checked that +// no proxy host still points at it; NPM will happily remove one that is in use and leave the host +// serving nothing. +function vv_npm_delete_cert(int $id): array { + $r = vv_npm_req('DELETE', "/api/nginx/certificates/$id"); + if (is_array($r) && isset($r['_err'])) return ['ok' => false, 'error' => $r['_err']]; + // NPM answers `true` for a successful delete and an error object otherwise. + if (is_array($r) && isset($r['error'])) + return ['ok' => false, 'error' => $r['error']['message'] ?? 'Delete failed']; + return ['ok' => true]; +} + +// Which proxy hosts reference a certificate. The guard that belongs with the delete above, so a +// caller cannot forget to ask the question. +function vv_npm_cert_users(int $id): array { + $p = vv_npm_list_proxies(); + if (!($p['ok'] ?? false)) return []; + $out = []; + foreach ($p['proxies'] as $h) + if ((int) ($h['certificate_id'] ?? 0) === $id) $out[] = $h; + return $out; +} + function vv_npm_create_proxy(array $data): array { $r = vv_npm_req('POST', '/api/nginx/proxy-hosts', $data); return isset($r['id']) ? ['ok' => true, 'proxy' => $r] : ['ok' => false, 'error' => $r['error'] ?? ($r['_err'] ?? 'Create failed')];