Commit Graph

250 Commits

Author SHA1 Message Date
wmantly 1e38ef8dc5 feat: SSO group autocomplete for per-host SSO allow-lists (v1.34.0)
Pull Request Tests / Run Tests (18.x) (push) Successful in 33s
Pull Request Tests / Run Tests (20.x) (push) Successful in 28s
Pull Request Tests / Run Tests (22.x) (push) Successful in 31s
Pull Request Tests / Test Summary (push) Successful in 3s
The per-host "Allowed groups" field suggested only local groups,
permission subjects and conf.auth maps. None of those can ever match an
SSO-gated host: its allow-list is checked against the `groups` claim the
SSO issues (utils/host_sso.js), so only SSO groups are candidates.

Adds a conf.sso block (url + read-only apiToken, minted by theta-suite's
bootstrap) and a cached /api/group lookup merged into the suggestions.
Degrades silently to the previous local-only list when unset, and never
fails the request.

Authenticates with `Authorization: Bearer <token>` -- the SSO's
`auth-token` header is for browser session UUIDs and rejects a minted
API token.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:42:50 -04:00
wmantly eb9388a4b1 chore: sync package.json + lockfile to v1.33.0 tag
Pull Request Tests / Run Tests (18.x) (push) Successful in 32s
Pull Request Tests / Run Tests (20.x) (push) Successful in 26s
Pull Request Tests / Run Tests (22.x) (push) Successful in 29s
Pull Request Tests / Test Summary (push) Successful in 4s
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 16:50:43 -04:00
wmantly 1bbf593232 feat: error page, admin-only DNS page, navbar active styling (v1.33.0)
Pull Request Tests / Run Tests (18.x) (push) Successful in 30s
Pull Request Tests / Run Tests (20.x) (push) Successful in 26s
Pull Request Tests / Run Tests (22.x) (push) Successful in 33s
Pull Request Tests / Test Summary (push) Successful in 4s
- Add SSO-style error page (views/error.ejs) and render it for browser
  navigation in the error handler (API still returns JSON).
- DNS page admin-only: forceLogin(['admin']) + nav groups ['admin'].
- Navbar: username not underlined; only the active nav link is bold+underlined.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 13:26:36 -04:00
wmantly 1b8ef1f848 fix: sync package version to v1.32.0 tag
The v1.32.0 release tag was created but nodejs/package.json was left at
1.14.3 (lockfile at 1.13.3), so the deployed app's buildVersion lags its
own release tag and the update-check banner falsely reports a newer
version. Bump the version fields to match the tag.
2026-08-03 21:31:31 -04:00
wmantly 63be1f1020 feat(proxy): remove standalone users UI/nav, refactor permissions to list view with live reload, and add dynamic reload to groups v1.32.0
Pull Request Tests / Run Tests (18.x) (push) Successful in 30s
Pull Request Tests / Run Tests (20.x) (push) Successful in 25s
Pull Request Tests / Run Tests (22.x) (push) Successful in 28s
Pull Request Tests / Test Summary (push) Successful in 4s
2026-08-03 15:27:21 -04:00
wmantly 13a02e6850 chore: bump package.json version to 1.14.3 2026-08-03 02:35:21 -04:00
wmantly 3cc769cc3c fix: remove DEPLOYMENT.md and docs/ from Docker build context
Pull Request Tests / Run Tests (18.x) (push) Successful in 27s
Pull Request Tests / Run Tests (20.x) (push) Successful in 27s
Pull Request Tests / Run Tests (22.x) (push) Successful in 29s
Pull Request Tests / Test Summary (push) Successful in 4s
2026-08-02 11:53:48 -04:00
wmantly 9029de825c test: add tests for DNS API Keys in OpenBao 2026-08-02 00:34:56 -04:00
wmantly b50a1de76f feat: securely store DNS API keys in OpenBao instead of Redis 2026-08-02 00:26:22 -04:00
wmantly c419249e98 chore: release v1.13.2 2026-08-02 00:16:19 -04:00
wmantly 2e92f58750 v1.13.1: bump @simpleworkjs/bao-conf to 1.0.1
bao-conf 1.0.0's init() threw when VAULT_TOKEN was unset, crashing boot
(.catch -> process.exit(1)) in any deployment without an OpenBao sidecar
(standalone Docker, bare metal). 1.0.1 makes init() fail-soft on a
missing token (warn + continue from CONF_SECRETS). The theta-env stack
is unaffected (it always sets a scoped VAULT_TOKEN).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 12:48:10 -04:00
wmantly 15b154fc8d v1.13.0: load secrets from OpenBao at boot via @simpleworkjs/bao-conf
bin/www now defers require('../app') until bao-conf.init({ path: 'proxy' })
resolves, so models + createOidcClient see the OpenBao-merged config (the
OIDC clientSecret is captured at require time). Authenticates to OpenBao
with a scoped VAULT_TOKEN (policy proxy), never the root token; fail-soft
to CONF_SECRETS if OpenBao is unreachable. config/proxy-secrets.js becomes
an operator-edit seed artifact (OpenBao authoritative). README gains a
Secrets section.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 12:29:22 -04:00
dependabot[bot] b54a738524 Bump brace-expansion and ejs in /nodejs (#179)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) to 5.0.9 and updates ancestor dependency [ejs](https://github.com/mde/ejs). These dependencies need to be updated together.


Updates `brace-expansion` from 5.0.7 to 5.0.9
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.7...v5.0.9)

Updates `ejs` from 3.1.10 to 6.0.1
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/RELEASE_NOTES_v5.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.10...v6.0.1)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.8
  dependency-type: indirect
- dependency-name: ejs
  dependency-version: 6.0.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Mantly <wmantly@gmail.com>
2026-08-01 11:11:21 -04:00
dependabot[bot] 8bf963f48b Bump body-parser from 2.2.2 to 2.3.0 in /nodejs (#175)
Bumps [body-parser](https://github.com/expressjs/body-parser) from 2.2.2 to 2.3.0.
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-version: 2.3.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Mantly <wmantly@gmail.com>
2026-08-01 11:02:25 -04:00
wmantly 8c81128235 Release 1.9.0: cross-app super admin, modal add-buttons, LE key persistence 2026-07-30 12:01:50 -04:00
wmantly be666f5b2f Recognize app_super_admin; add-user/add-permission as modal buttons; persist LE key
- app_super_admin is a new cross-app LDAP group (also recognized by
  sso-manager-node and jump-host): added to conf.auth.adminGroups so
  members are always global admins here, same as the existing anti-lockout
  adminUsers/adminGroups mechanism.
- Users and Permissions pages: the always-visible sidebar "Add" forms are
  now an "Add User"/"Add Permission" button in the list header that opens
  an app.modal dialog, matching the hosts.ejs convention.
- The Let's Encrypt ACME account key now defaults to the already-persisted
  /data volume (models/host.js) instead of a CWD-relative path
  (./le_key.cert -> /app/le_key.cert in the container), which was lost on
  every image rebuild. Falls back to the old relative path when /data isn't
  present (e.g. local dev outside docker).
2026-07-30 11:57:44 -04:00
wmantly 8cee583da3 Release 1.8.0: page width standardization, card layouts, external-user marking 2026-07-29 22:12:30 -04:00
wmantly c7ec65e0d9 Standardize page width, card layouts; mark SSO users external and read-only
- All pages now wrap their content in <div class="container mt-4">,
  matching sso-manager-node's width instead of rendering full-bleed inside
  the fluid shell.
- Users and Permissions pages converted from bare <table>s to the same
  card-grid convention already used on the Groups page.
- Users backed by SSO/OIDC login (backing === 'oidc', set by the redis
  user model's JIT-provisioning path) are now marked "External (SSO)" and
  their password-change control is hidden; PUT /password/:username also
  rejects with 403 server-side for such users. Deletion stays allowed.
  Redis-backend only -- LDAP/PAM deployments have no per-record marker for
  this today.
- app-base.js (byte-identical across the 3 apps): added
  app.util.revealItem(), wired into the Users/Permissions create flows.
- Bumped @simpleworkjs/frontend to ^0.2.7.
2026-07-29 22:09:50 -04:00
wmantly 3f2ef57c78 Release 1.7.0: API-token modal-based create, Edit modal, reveal-modal fix (#192) 2026-07-28 20:43:42 -04:00
wmantly c0e04d1a56 Unify API-token UI: modal-based create, new Edit modal (#191)
Continues the cross-app API-token UI unification (jump-host landed first).
proxy already had the card grid and a description field, so this is a
smaller diff: converts the always-visible inline create-form card into a
"+ New Token" button + app.modal (matching the Add-Resource/Add-Host
convention used stack-wide, per explicit direction to standardize on the
modal-button approach rather than sso-manager-node's inline-card style),
adds a net-new Edit modal (proxy's PUT /api-token/:id already fully
supported it -- no route change needed), and replaces the static
#secretModal with the same bare app.modal showToken()/copyFieldValue()
pattern jump-host uses.

Found and fixed a real timing bug along the way: the create flow's
evalAJAX called app.modal.close() immediately before showToken() (which
calls app.modal.open()) in the same synchronous tick. app.modal is a
singleton, and close() immediately followed by open() collides with
Bootstrap's hide-transition guard -- show() silently no-ops while
_isTransitioning is still true from the just-started hide(), so the
"secret revealed" modal never actually appeared after creating a token.
Confirmed via a live click-through: the reveal modal stayed invisible
(title set, `.show` class never added) with the close() call, and rendered
correctly with it removed.

Also fixed the same latent bug in jump-host's already-shipped v1.10.0
(submitApiToken() had the identical close()-then-open() sequence) and in
sso-manager-node's directory.ejs (saveResource()'s OAuth-secret-reveal
path, softened there by an intervening `await loadResources()` but not
guaranteed race-free).

Verified live: create -> reveal modal now appears correctly; Edit modal
shows real created-by/on data, saves a description change, card refreshes.
2026-07-28 20:40:39 -04:00
wmantly 130d4a2c9b Release 1.6.0: host modal standardization (#190) 2026-07-28 19:06:51 -04:00
wmantly 3b729295b0 Standardize the host modal: tabs onto app.modal, footer, linkable URL (#189)
Migrates proxy's hostModal (the modal this session's app.modal tabs/footer/
url support was originally modeled on) off its old always-in-DOM static
element and onto the shared app.modal component (@simpleworkjs/frontend
0.2.6), continuing the entity-modal standardization started with
sso-manager-node's resource modal.

Unlike the resource modal, this one already had 6 tabs and Host's audit
trail (created_by/created_on/updated_by/updated_on) already existed and was
already populated correctly by routes/host.js -- no model or route changes
needed there. The tab markup itself is kept as one hand-built bodyHtml
string rather than going through app.modal's own `tabs` array option: that
option builds the tab-content div itself, and there'd be no way to wrap a
<form> around just that piece without extending app.modal again, whereas
this modal's tabs already are exactly the pattern app.modal's own tabs
option was modeled on.

Key correctness points, found and handled:
- The one JS binding that was unsafe under DOM rebuild -- a `keyup` handler
  driving the Let's-Encrypt challenge-type/wildcard visibility, previously
  bound once directly against a captured selector -- is now delegated via
  app.modal.on(), the same bug class already found and fixed in the
  resource modal.
- hostLoadAuthSuggestions() (fills the SSO allow-list autocomplete
  datalists) now re-runs on every modal open, not just once at page load,
  since the datalists are rebuilt fresh (empty) each time.
- .actionMessage moved from a sibling of the old modal's <form> into a
  descendant of it: formAJAX's error/success target resolution
  (app.messages.action's closest('div.card') then a find('.actionMessage')
  fallback) only succeeds via the fallback path once app.modal owns the
  DOM, since app.modal's .modal-content carries no 'card' class.
- The footer's audit dates needed the explicit 'x' (unix ms) format token
  for moment() -- Host's created_on/updated_on come back as redis-hash
  strings, and moment's bare fallback parser silently produces "Invalid
  date" for a numeric string without it (this app's own hostParseRow
  already does this correctly elsewhere; the new footer code needed the
  same treatment).

Also adds GET /hosts/:host (mirroring sso-manager-node's /directory/:slug)
plus a client-side deep-link check, threaded through a new onLoaded
callback on hostPopulate().

Verified live against the running dev stack: all 6 tabs render and save
correctly; the footer shows real created/updated-by/on dates; the SSO
autocomplete has options on a second modal open (not just the first);
the challenge-type keyup logic fires correctly on a second-ever modal
open (confirmed via the actual GET /api/host/wildcard-parent/... network
request); the address bar updates to /hosts/{host} and reverts on close;
a direct load of /hosts/{host} auto-opens the right host's modal; and a
real save (PUT) closes the modal and live-updates the row via the existing
pubsub subscription, end to end.
2026-07-28 19:04:56 -04:00
wmantly e9032ec4fb Release 1.5.3: fix HTML-escaped loading indicator
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 13:31:39 -04:00
wmantly 8350ab9b57 Fix HTML-escaped loading indicator in formAJAX
Same fix as sso-manager-node: formAJAX's loading indicator passed a raw
<div class="spinner-border"> string to app.messages.action, which
HTML-escapes its message by design (@simpleworkjs/frontend) -- so every
form submit briefly showed the literal markup as text instead of a
spinner. Replaced with plain text ("Saving…").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 12:52:39 -04:00
wmantly e05ec1d2ad Release 1.5.2: add no-native-dialogs regression test
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 00:18:35 -04:00
wmantly 9370734084 Add regression test: no native alert()/confirm()/prompt()
Native confirm() blocks all further browser events on the page (found
live, mid browser-automation testing, on sso-manager-node's equivalent
secret-rotate flow -- it froze the tab). Every call site in this app was
already removed in favor of app.messages.action/confirm/toast and
app.modal.open; this static check (scans views/ and public/js|lib/js for
bare alert(/confirm(/prompt() calls) keeps a regression from shipping
unnoticed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 23:36:34 -04:00
wmantly 2d7eadec60 Release 1.5.1: remove native confirm() calls
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 17:23:07 -04:00
wmantly 36c3f7a881 Remove native confirm() calls in revokeToken/rotateToken
Native confirm() blocks browser automation entirely (found live, mid
browser-test of the app.messages/app.modal adoption, on sso-manager-node's
equivalent flow). Both functions already receive btn, whose .closest('.card')
is already used for the error path, so app.messages.confirm targets the
same card.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 16:50:33 -04:00
wmantly ce013d7e31 Release 1.5.0: adopt @simpleworkjs/frontend messages/modal/validate
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 14:16:47 -04:00
wmantly 0a659428dd Adopt @simpleworkjs/frontend's messages/modal/validate modules
Same swap as sso-manager-node/jump-host: vendored app.util.actionMessage/
actionConfirm replaced by @simpleworkjs/frontend's app.messages.action/
confirm (real HTML-escaping, toast fallback); vendored val.js replaced by
the package's app.validate.js.

proxy's host/target/hostname validation rules (mirrored from the backend's
utils/hostname_validate.js — wildcard DNS patterns, not something other
apps need) move to public/js/app.js, registered via $.validateSettings,
since they're proxy-specific and don't belong in the shared package's
generic rule set (eq/user/password/ip).

app.api/app.auth/app.pubsub/app.socket in app-base.js are untouched, same
reasoning as sso-manager-node's PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 14:07:14 -04:00
wmantly 2e959f7c48 Release 1.4.0: unified front-end UI shell across the theta42 apps
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 00:21:51 -04:00
wmantly fcc09d1e4b logInRedirect: keep the query string on the legacy /login/<path> form
The OIDC provider sends an unauthenticated authorize request through
/login/oauth/authorize?client_id=…&state=…; dropping the query there
loses the whole authorization request. The ?redirect= form is unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 23:55:08 -04:00
wmantly a83a5fd39a app.api.delete: accept the (url, data, callback) form formAJAX uses; defer the login-card reveal to DOM ready
formAJAX always passes the serialized form as the second argument, so a
DELETE-method form (the host/DNS delete buttons) landed its callback in
the data slot and never ran.

The login page's "reveal the card once we know you're logged out" branch
touched an element further down the same page, which threw when
isLoggedIn answered before the parser got there (it always did without a
stored token). It now runs on DOM ready.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 23:15:07 -04:00
wmantly 3a307c1563 Unify the front-end UI shell across the theta42 apps
views/top.ejs, views/bottom.ejs and public/lib/js/app-base.js are now
byte-identical across sso-manager-node, proxy and jump-host. Everything
per-app moved into utils/ui.js, exposed to every render as `ui` via
app.locals (nav items + their group gates, footer repo/docs/ToS links,
favicon, profile/logout targets, update-banner on/off + label).

Client framework changes:
- One gating model everywhere: app-base.js reveals .group-required-<cn>
  for each of the current user user/me groups. sso-manager-node sends LDAP
  DNs in memberOf, the OIDC clients send CNs in groups; both normalise to
  CNs, and the clients isAdmin flag becomes a synthetic `admin` group, so
  proxy nav-admin items are now group-required-admin.
- user/me is fetched once per page load and cached (app.auth.loadUser);
  nav, forceLogin and group-required elements all read that one promise.
- isLoggedIn is dual-mode (Promise + node-style callback), so the async
  and callback call styles both work from one shared top.ejs.
- forceLogin no longer uses $.holdReady (removed in jQuery 4): it redirects
  to /login?redirect=<path>, and still enforces required groups.
- logOut only clears the session; the caller decides where to go next.
- post/put/delete are dual-mode Promise/callback, which also removes the
  undefined `callback2` reference that threw on a non-function callback.

Dependencies: jquery ^4.0.0 and ejs ^3.1.10 in all three apps.

proxy specifics:
- .group-required base rule added to styles.css; the admin nav items lost
  their inline display:none in favour of it.
- The brand link points at / instead of #.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 22:57:34 -04:00
wmantly f4efdfb957 Release 1.3.0: adopt shared @simpleworkjs/* packages; fix LDAP filter injection
Rewire onto the shared @simpleworkjs/oidc-client, /ldap, and /app-stack
packages (deleting the byte-identical local forks of the same code), close the
LDAP filter-injection in User.get by routing the username through escapeFilter
(RFC 4515), align model-redis ^1.6.0 and ldapts ^8.1.8, and unify build_info to
{buildVersion, buildHash, buildYear}. package-lock regenerated from the npm
registry (no file:/link:), so npm ci is clean in docker builds.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-25 15:53:30 -04:00
wmantly ecdced16fc Release 1.2.2: fix load-balancing crash (wrong lua-resty-balancer module)
Every request to a host with additional load-balancing targets 500'd:
targetinfo.lua required 'resty.balancer.round_robin', which does not
exist in the lua-resty-balancer rock actually installed by the
Dockerfile/install.sh. That rock provides resty.roundrobin instead,
with a different constructor (roundrobin:new(nodes), not
:new() + :reinit(nodes)).

Verified end-to-end in a rebuilt image: requests to a load-balanced
host now return 200 and alternate across both backend targets, with
no Lua errors in the OpenResty log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 16:21:03 -04:00
wmantly 3f46a807e5 Correct version to 1.2.1: v1.2.0 was already tagged for load balancing
The load-balancing feature (merged in #172/#173) was tagged v1.2.0 on
GitHub, but nodejs/package.json and CHANGELOG.md were never bumped for
it. Backfill a 1.2.0 CHANGELOG entry and bump this release to 1.2.1
instead of 1.1.18, so package.json/CHANGELOG stay ahead of the highest
existing tag rather than behind it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 02:07:28 -04:00
wmantly bb1b84b56d Release 1.1.18: fix bootstrap admin lockout, refresh screenshots
models/user_redis.js hardcoded the bootstrap anti-lockout admin
username to 'proxyadmin2', while migrations/permission_bootstrap.js
grants the global-admin permission to conf.auth.adminUsers[0]. An
operator who customized adminUsers away from the default ended up
with a bootstrapped account that had no admin permissions -- a
silent lockout. user_redis.js now derives the bootstrap username the
same way permission_bootstrap.js does.

Also corrected a secrets.js.example comment that claimed the
bootstrap password defaults to the username itself (it actually
generates and logs a random password), and refreshed all README
screenshots against the current UI, including a new load-balancing
screenshot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 02:04:26 -04:00
wmantly 4321826dc8 feat: Add multi-target load balancing support
Fixes #47.
- Added lua-resty-balancer to dependencies (Dockerfile & install.sh).
- Added 'targets' field to the Host model to hold additional targets.
- Updated the UI to allow inputting additional targets (IP:port).
- Updated targetinfo.lua to parse the additional targets and load balance between them and the primary target using resty.balancer.round_robin.
2026-07-21 00:48:43 -04:00
wmantly 7452ccd655 Bump version to 1.1.17; update CHANGELOG
Move the wildcard sibling-parent fix into its own 1.1.17 section (it is not
in the v1.1.16 tag) and bump nodejs/package.json + lockfile to 1.1.17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:27:48 -04:00
wmantly fcd97b12aa Fix attaching an existing sibling subdomain to a parent wildcard
Host.lookUpWildcardParent() walked all labels of the host down to its own
leaf and only inspected that leaf's "*" child, so it found a wildcard
nested under the host (the base-domain case, e.g. *.cool.mysite.com for
cool.mysite.com) but missed the common case where the wildcard is a
SIBLING of the host's leftmost label (e.g. *.nl.wgnode.com covering an
already-existing sso.nl.wgnode.com). The /wildcard-parent route then
returned nothing and the edit form's "Parent Wildcard" option stayed
greyed out, leaving no way to convert an existing auto-SSL host onto a
wildcard issued afterward.

Track the parent node during the walk and check the sibling "*" slot too.
The never-created-subdomain case is unchanged (plain lookUp()'s wildcard
fallback in the route still handles it). Sync the test mock and add
regression coverage for the sibling case (existing host, never-created
host via the route fallback, and a deeper-wildcard negative case).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:13:13 -04:00
wmantly 6ede072213 security: swap sanitizer to xss for Jest-compatible SSR output
- Replace isomorphic-dompurify with xss to avoid ESM-only jsdom/htmlparser2
  transitive deps that break the existing Jest test suite.
- Keep rendered docs HTML sanitized via xss() in routes/docs.js.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 23:02:11 -04:00
wmantly bdaba513a7 security: sanitize markdown output and harden unix socket permissions
- Add isomorphic-dompurify to sanitize rendered docs HTML
- Tighten SocketServerJson socket permissions from 777 to 660
- Keep package.json version at 1.1.16

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 22:41:47 -04:00
wmantly d1dd40d60a chore(release): public-release readiness fixes for 1.1.16
- Fix MIT LICENSE copyright placeholder
- Remove private flag and correct GitHub repository URL in package.json
- Bump version to 1.1.16
- Genericize committed config defaults (example.com/localhost)
- Harden global error handler against information leakage
- Generate random initial password for proxyadmin2 bootstrap account
- Correct docs to describe CONF_SECRETS instead of symlink behavior

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 22:02:27 -04:00
wmantly f8d620f4d3 Bump version to 1.1.15; update CHANGELOG
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 01:15:31 -04:00
wmantly a02ca4d3e7 Bump version to 1.1.14; update CHANGELOG
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 23:42:00 -04:00
wmantly 355a9d68e5 Bump @simpleworkjs/conf to 1.2.0, jq-repeat to 2.2.0
conf 1.2.0 adds CONF_SECRETS, an env var to point at the secrets file
directly -- use it in the Docker entrypoint instead of symlinking the
mounted file into /app/conf/secrets.js, so the app no longer needs
write access to its own conf/ directory to pick up mounted secrets.
jq-repeat 2.2.0 is a compatible feature release (sort(), replace(),
faster leading-edge update() timing); no call-site changes needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 23:35:24 -04:00
wmantly 426fa111ec Add plain-language concept docs; fix docs viewer rendering; link API tokens
- New docs/concepts-{hosts,dns,access,api-tokens}.md -- plain-language
  guides aimed at less technical readers, each linking onward to the
  existing system-design-level doc for anyone who wants that detail.
  Card help links (Proxy List, Add/Edit host, DNS Provider cards,
  Users/Permissions/Groups cards) now point here instead of straight at
  Installation/Architecture.
- The "New API Token" card had no help link at all -- added, pointing to
  the new API Tokens doc.
- Fixed the in-app docs viewer rendering every docs/*.md page with a
  garbled heading + stray <hr> at the top: Jekyll front matter (meant
  only for the GitHub Pages build) was never stripped before being
  handed to the markdown renderer.
- Fixed cross-doc links never resolving in-app, since this viewer serves
  docs at /docs/<slug> with no .html suffix: rewritten to the correct
  in-app URL, first by registered slug, falling back to the doc's real
  filename (the correct, working link form on the Jekyll/GitHub Pages
  build) -- same idea as the existing image-path fix, and lets one link
  written in a doc work on both targets.

Bumps to v1.1.13.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
2026-07-17 22:09:53 -04:00
wmantly 4f1fce367e Fix wildcard-parent edit greying and deprecated nginx http2 directive
- The edit form's "Parent Wildcard" option stayed greyed out even when a
  valid wildcard existed, since hostEditOpen() never ran the eligibility
  check (only the host field's keyup handler did, which setting .val()
  programmatically doesn't fire) -- and the check itself, GET
  /host/lookup/:item, had the same self-match bug as the recently-fixed
  Host.prototype.update() case: it resolves an already-existing host to
  its own record instead of a sibling wildcard. Added a dedicated
  /host/wildcard-parent/:item route combining lookUp() (handles a
  brand-new subdomain) with lookUpWildcardParent() (handles an
  already-existing host), and hostEditOpen() now actually runs it.

- Migrated ops/nginx_conf/autossl.conf's deprecated "listen ... http2"
  directive to the standalone "http2 on;" directive (nginx 1.25.1+).

Bumps to v1.1.12.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
2026-07-17 21:33:26 -04:00
wmantly a40da55993 Move help links from the global header onto each relevant card
The single header-wide help icon (added last release) pointed at a
per-page doc guess, but a page can have several cards covering different
topics. Removed it and added a small help icon directly to each card
that has real corresponding doc content, linking straight to that doc:
Proxy List + Add/Edit host modal (hosts.ejs), Add DNS Provider + Dynamic
A Records (dns.ejs), Add New User + User List (users.ejs), Add
Permission + Permissions (permissions.ejs), Add Group (groups.ejs).

Bumps to v1.1.11.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
2026-07-17 20:00:34 -04:00