setupCertbotPlugins() wrote a credentials file for every DNS-01 certificate
each time the backend started, using flag "wx" so it only filled in missing
ones. That existed because the renew path did not write the file itself, so
something had to put it back before `certbot renew` looked for it.
With the previous commit the renew path writes the file immediately before
invoking certbot, so this is now the only thing putting those credentials back
on disk - and it does so for every certificate on every restart, which undoes
the cleanup entirely.
Removing the write leaves the `fs` import and the `promises` array unused. The
"Added Certbot plugins" log line is kept but now gates on plugins.length, since
it was previously gated on a promise array that only ever held credential
writes.
The credentials file written for a DNS-01 challenge was only cleaned up when
certbot failed - the unlink sat in a catch block. On success the file stayed in
/etc/letsencrypt/credentials for the entire life of the certificate, holding a
live DNS provider API token in plaintext.
The file cannot simply be deleted at issuance, because certbot records its path
in the renewal config and reads it back on every `certbot renew`. So the renew
path now writes the file itself immediately before invoking certbot, and both
paths remove it in a finally block.
Net effect: the credentials exist on disk for the duration of a certbot run
rather than permanently. The value still lives in the certificates table, which
is unavoidable - it has to come from somewhere to be written at all.
renewLetsEncryptSslWithDnsChallenge reads the row directly from the model
because renew() sources its certificate from internalCertificate.get(), which
strips meta.dns_provider_credentials via omissions().
The catch block in `access.can()` constructed `errs.PermissionError`
without `new`. The error constructors in `backend/lib/error.js` are plain
constructor functions that assign to `this` and return nothing, so calling
one without `new` evaluates to `undefined`.
The statement therefore did `throw undefined`, the express error handler
in `backend/app.js` received undefined (and could not read `.status` or
`.public` off it), and the request fell through to the catch-all 404
handler in `backend/routes/main.js`.
Net effect: every authorization failure raised by `access.can(...)` was
reported to clients as `404 Not Found` instead of `403 Forbidden`.
Line 45 of the same file already used `new` correctly, which shows this
was an oversight rather than deliberate resource-existence hiding. A grep
over `backend/` confirms this was the only error constructor invoked
without `new`.
The Custom Locations tab rendered every location fully expanded, which
becomes unusable once a host has more than a handful of them. Each row is
now a collapsible card showing its path and forward target, collapsed by
default, with the Add Location button moved to the top so it stays
reachable without scrolling to the end of the list.
A filter appears once there are five or more locations and matches on the
path as well as the forward scheme, host and port.
New locations are still appended to the end of the list, so the generated
nginx config is unchanged.
Also fixes two existing issues in this component: the per-location inputs
shared the same DOM ids across rows, and the advanced-config toggle state
was keyed by array index, so removing a location moved it onto the wrong
row.