four fixes from a review, and the first one was leaking out of the plugin. TYPING. `respond` derived its context from `Parameters<typeof router.get>[1]`. hono's .get is overloaded, `Parameters<>` resolves the FIRST overload — `[path: string]` — so index 1 was `undefined`, ctx was `never`, and all nine handlers errored. twelve errors from one line, in a tree where `bunx tsgo` is shared with the platform and expected clean. imports hono's `Context` instead. tsgo now exits 0 tree-wide, which it has not done since this plugin landed. READONLYWRITES. `/_config/test` was declared as a read-only write so a member would not meet a 403 on the test button. they always would: authorize.ts asks isOwnerOnlyRoute before isRequestAllowedAtLevel, and ownerOnly is prefix matched, so `/_config` already covered it and the entry was inert. removed, and the reason it SHOULD be inert written down — that request carries the admin secret in its body, so a member who could call it would hold an oracle pointed at somebody else's password. STATUS WROTE ON EVERY POLL. recordProbe ran unconditionally on a GET whose own comment invites polling, so a read endpoint issued an UPDATE per poll per open panel. the probe is cached 30s and the row is written only when the verdict or the version changes — a flapping NPM still records every transition, which is the case the column exists for. the cache is dropped when the credential does. DEAD TERNARY. `likelyAuth ? 502 : 502`. and the tests. 22 over a stubbed fetch, 99% of npm-client.ts. the two that matter are the 404-means-credential rule and the mint-once-and-retry: both are silent when broken, and a refactor of either turns a working panel into one that says "not found" forever. every test uses its own credential because the token cache is module state — sharing one would let an earlier test's token satisfy the retry test, which would then pass for the wrong reason. still not installed, still no settings form, still no db:push. README says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
52 lines
2.4 KiB
TypeScript
52 lines
2.4 KiB
TypeScript
import type { PluginManifest } from '@@/plugins/manifest';
|
|
|
|
// Nginx Proxy Manager, inside Officer.
|
|
//
|
|
// ── The name ──
|
|
//
|
|
// The container is called `npm` and the owner calls it NPM. This plugin is `proxy-manager` anyway: in a
|
|
// repository where every other sentence is about bun, lockfiles and `npm install`, a plugin directory
|
|
// called `npm` would collide with the package manager in every grep and every conversation. The label
|
|
// says the real name; the id says what it does. Same reasoning that renamed `officer-claude` to
|
|
// `officer-anthropic-proxy`.
|
|
//
|
|
// ── The permission, and why settings are carved out ──
|
|
//
|
|
// Reading which hosts exist, whether their certificates are healthy and whether one is offline is
|
|
// genuinely useful to somebody who is not the owner. The CREDENTIAL is not: it is an NPM admin login,
|
|
// and NPM has no read-only role, so anyone holding it can redirect any hostname on this machine.
|
|
//
|
|
// So the permission is grantable and `/_config` is owner-only — the split `ownerOnly` exists for, and
|
|
// the same shape Jellyfin uses for its admin token.
|
|
export const manifest: PluginManifest = {
|
|
publisher: 'officerdev',
|
|
version: '0.1.0',
|
|
platform: '>=1.0.0',
|
|
|
|
label: 'Proxy Manager',
|
|
summary: 'Nginx Proxy Manager — hosts, certificates and access lists',
|
|
icon: 'Route',
|
|
color: '#f97316',
|
|
|
|
permissions: [
|
|
{
|
|
key: 'proxy-manager',
|
|
label: 'Proxy Manager',
|
|
description: 'Nginx Proxy Manager — proxy hosts, redirections, streams and certificates',
|
|
|
|
// The connection form and the probe. Everything under here reads or writes the admin credential,
|
|
// and the match is by PREFIX — so `/_config/test` is covered too, deliberately.
|
|
//
|
|
// There is no `readOnlyWrites` here, and the omission is the decision rather than an oversight.
|
|
// `POST /_config/test` is a genuine read-only write: it validates a credential and stores nothing.
|
|
// Declaring it would still change nothing, because `authorize.ts` asks `isOwnerOnlyRoute` before
|
|
// `isRequestAllowedAtLevel` — ownerOnly wins, so the entry would be inert.
|
|
//
|
|
// And it should be inert. The test request carries the admin secret in its BODY. A member who can
|
|
// call it can offer this server a credential and be told whether it works, which is an oracle
|
|
// pointed at somebody else's password. Testing a connection belongs to whoever owns it.
|
|
ownerOnly: ['/_config'],
|
|
},
|
|
],
|
|
};
|