26 KiB
26 KiB
v1.13.2
- chore: Update CI pipeline integration
Changelog
All notable changes to this project are documented here. Format loosely
follows Keep a Changelog; versions
correspond to git tags (vX.Y.Z) and nodejs/package.json's version.
Unreleased
[1.13.1] - 2026-08-01
Fixed
- Bumped
@simpleworkjs/bao-confto 1.0.1 so standalone/no-OpenBao boots don't crash. bao-conf 1.0.0'sinit()threw whenVAULT_TOKENwas unset, which — combined withbin/www's.catch(() => process.exit(1))— made the proxy exit at boot in any deployment without an OpenBao sidecar (standalone Docker, bare metal). 1.0.1 makesinit()fail-soft on a missing token (warn- continue from
CONF_SECRETS), matching the documented contract. The theta-env stack is unaffected (it always sets a scopedVAULT_TOKEN).
- continue from
[1.13.0] - 2026-08-01
Changed
- Secrets now load from OpenBao at boot via
@simpleworkjs/bao-conf, which
deep-merges
secret/proxy/confover the file-loaded config. The proxy authenticates to OpenBao with a scopedVAULT_TOKEN(policyproxy— read-only on its own path), never the root token. Because the OIDCclientSecretis captured at require time insidecreateOidcClient(duringrequire('../models'), whichrequire('../app')triggers transitively),bin/wwwnow defersrequire('../app')until afterbao-conf.init()resolves. Fail-soft: if OpenBao is unreachable, boot continues fromCONF_SECRETS. Theconfig/proxy-secrets.jsfile is now an operator-edit seed artifact (gitignored); OpenBao is authoritative. See theta-env's Secrets docs. - Bumped package version to track the release tag.
[1.12.1] - 2026-08-01
Changed
- Bumped
body-parser2.2.2 → 2.3.0 (Dependabot #175). - Bumped
ejsandbrace-expansion(Dependabot #179, security maintenance).
[1.12.0] - 2026-08-01
Fixed
- Changed UNIX socket permission in
unix_socket_json.jsto666so OpenResty Nginx workers running asnobodycan resolve targets properly.
[1.9.0] - 2026-07-30
Added
app_super_admincross-app group recognized as a global admin (conf.auth.adminGroups), same group also recognized by sso-manager-node and jump-host, and byldap-client's SSSD access filter (SSH login on every host).
Changed
- 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.modaldialog, matching the hosts.ejs convention. - Let's Encrypt ACME account key now defaults to the already-persisted
/datavolume instead of a CWD-relative path (./le_key.cert->/app/le_key.certin the container), which was lost on every image rebuild.
[1.8.0] - 2026-07-28
Added
- Users backed by SSO/OIDC login are now marked "External (SSO)" and read-only: their password-change control is hidden, and
PUT /password/:usernamerejects with 403 server-side. Deletion stays allowed. Redis user-backend only. app.util.revealItem()(sharedapp-base.js): scrolls a just-added/-edited element into view and flashes its background. Wired into the Users/Permissions create flows.
Changed
- All pages now wrap their content in a standard-width container, matching sso-manager-node instead of rendering full-bleed.
- Users and Permissions pages converted from bare
<table>s to the card-grid convention already used on the Groups page. @simpleworkjs/frontendbumped to^0.2.7.
[1.7.0] - 2026-07-28
Added
- API tokens: "+ New Token" modal button (replacing the always-visible inline create-form card) and a new Edit modal — continues the cross-app API-token UI unification started in jump-host. The Edit modal's footer shows real created-by/on data; the
PUT /api-token/:idroute already fully supported editing, so no backend change was needed.
Fixed
- Creating an API token didn't show the "save this secret now" reveal modal — the create flow called
app.modal.close()immediately beforeapp.modal.open()(to show the secret) in the same tick; sinceapp.modalis a singleton, that collided with Bootstrap's hide-transition guard and the reveal modal silently never appeared.
[1.6.0] - 2026-07-28
Added
- Host modal: footer with created/updated-by/on metadata, linkable
/hosts/{host}URL — migrated onto the sharedapp.modalcomponent (@simpleworkjs/frontend0.2.6), continuing the entity-modal standardization started in sso-manager-node. Kept the existing 6-tab layout (General/TLS & Wildcard/Traffic/Headers/Access/Authentication) unchanged.
Fixed
- The Let's-Encrypt challenge-type/wildcard-matching visibility logic could stop reacting to the hostname field after the first Add/Edit — its
keyuphandler was bound once directly against a selector captured at page load; now delegated so it survives the modal being rebuilt on every open. - The SSO allow-list autocomplete (users/groups) could go empty starting on the second Add/Edit — its backing
<datalist>s are rebuilt fresh on every modal open now, so the suggestions are refetched each time too.
[1.5.3] - 2026-07-28
Fixed
formAJAX's loading indicator showed literal HTML, not a spinner — same fix as sso-manager-node's companion release. Replaced with plain text.
[1.5.2] - 2026-07-28
Added
- Regression test: a static check across all views/client-side scripts fails CI if any native
alert()/confirm()/prompt()call appears — these block all further browser events on the page and were fully removed in 1.5.1.
[1.5.1] - 2026-07-27
Fixed
- Removed native
confirm()calls inrevokeToken/rotateToken(profile.ejs), replacing them withapp.messages.confirm. Nativeconfirm()blocks all further browser events on the page (discovered live, mid browser-verification of sso-manager-node's equivalent flow, where it froze the tab).
[1.5.0] - 2026-07-27
Changed
- Adopted
@simpleworkjs/frontend'sapp.messages,app.modal, andapp.validatemodules, replacing the vendoredapp.util.actionMessage/actionConfirminpublic/lib/js/app-base.jsand the vendoredpublic/lib/js/val.js. Message content is now HTML-escaped, andapp.messages.actionfalls back to a page-wide toast when there's no inline.actionMessagetarget. proxy'shost/target/hostnamewildcard-DNS validation rules (mirroringutils/hostname_validate.js) moved topublic/js/app.js, registered via$.validateSettings, since they're proxy-specific and don't belong in the shared package's generic rule set.app.api/app.auth/app.pubsub/app.socketare untouched.
[1.4.0] - 2026-07-26
Changed
- Unified the front-end UI shell across the three theta42 apps.
views/top.ejs,views/bottom.ejsandpublic/lib/js/app-base.jsare now byte-identical in sso-manager-node, proxy and jump-host, so the apps look and behave the same and a shell change lands in one edit per repo instead of three divergent ones. Everything that differs between the apps moved into a newnodejs/utils/ui.js, exposed to every render asuiviaapp.locals: nav items and the groups that may see them, footer repo/license/docs/Terms links, favicon, the profile and post-logout targets, and whether the update banner exists at all. - One nav-gating model everywhere.
app-base.jsreveals.group-required-<cn>elements for each group the current user is in, read fromGET /api/user/me. sso-manager-node reports LDAP DNs inmemberOfand the OIDC clients report CNs ingroups; both normalise to CNs client-side, and the clients' effective-rightsisAdminflag is exposed as a syntheticadmingroup — so one gating model covers a group-based provider and boolean-admin clients without either app learning the other's response shape. GET /api/user/meis fetched once per page load and cached (app.auth.loadUser). The nav, per-viewforceLoginand every group-gated element read that one promise instead of issuing their own request.app.auth.isLoggedInis dual-mode: it returns a Promise and invokes an optional node-style callback, so the async and callback call styles both work against one sharedtop.ejs.app.auth.forceLoginno longer uses$.holdReady(removed in jQuery 4). An unauthenticated user is redirected to/login?redirect=<path>; group requirements are still enforced, andlogOutnow only clears the session, leaving the destination to the caller (ui.logoutRedirect).- Dependency alignment across all three apps:
jquery^4.0.0andejs^3.1.10.
Fixed
app.api.deletedropped its callback when called byformAJAX.formAJAXalways passes the serialized form as the second argument, so a DELETE-method form's callback landed in the data slot and never ran.deletenow accepts both(url, callback)and(url, data, callback).app.api.post/putreferenced an undefinedcallback2and threw when handed a non-function callback. Both are now dual-mode Promise/callback.- The login page's "reveal the card once we know you're logged out" branch threw (
Cannot read properties of null) whenever the logged-in check answered before the parser reached that element — which it always did without a stored token. It now runs on DOM ready. logInRedirecton the legacy/login/<path>form kept only the path. The OIDC provider routes an unauthenticated authorization request through/login/oauth/authorize?client_id=…&state=…; dropping the query there loses the entire authorization request. The suffix form now preserves its query string.
Added
.group-required { display: none }inpublic/css/styles.css, the base rule the shared gating model reveals against.- Admin-only nav items lost their inline
display: nonein favour of that class, and the brand link points at/instead of#.
Verified
- Browser-verified against a full theta-env stack (sso-manager + proxy + jump-host): every top-level page renders with a clean console; nav gating is correct for admin and non-admin;
forceLogin's onboarding and group gates fire;val.jsblocks a weak password and accepts a strong one through a real form submit; the DELETE-method forms work; and the OIDC login round trip (authorize with PKCE -> login -> consent -> callback -> token fragment) completes on both OIDC clients.
[1.3.0] - 2026-07-25
Added
- Adopted the shared
@simpleworkjs/*packages published under the simpleworkjs org, replacing this app's byte-identical forks of the same code so the theta42 apps share one codebase and API schema:@simpleworkjs/oidc-client— the OIDC client (session models, auth router, OIDC utils, safe-redirect, local-admin bootstrap). Deleted the localutils/oidc.js,utils/safe_redirect.js,models/oidc_state.js,models/token.js,models/auth.js, androutes/auth.js;models/index.jsnow wires the factory. The per-host SSO inroutes/host_auth.jsis unchanged but consumes the shared OIDC utils.@simpleworkjs/ldap— the ldapts client + RFC 4515/4514 escaping.@simpleworkjs/app-stack— unifiedbuild_info({buildVersion, buildHash, buildYear}) and thestatic-modulesmounting helper.utils/build_info.jsand the static-modules loop inroutes/render.jsnow use the shared helpers.
Security
- LDAP filter injection in
User.get. The user lookup built its search filter by interpolatingdata.usernameraw into(&(objectClass=inetOrgPerson)(uid=<username>)). A username containing*,(,),\, or NUL could widen or alter the filter (e.g.*→ match-all). The filter value is now passed throughescapeFilterfrom@simpleworkjs/ldap(RFC 4515 escaping).
Changed
- Dependency alignment:
model-redis^1.5→^1.6.0,ldapts^8.1.2→^8.1.8. The four new@simpleworkjs/*deps resolve from the npm registry (^1.0.0); nofile:/link:entries in the lockfile, sonpm ciis clean in docker builds. build_infoexport shape changed from{commit, version}to{buildVersion, buildHash, buildYear}(the shared shape used by all three apps). The/healthendpoint and footer now reportbuildVersion/buildHash.
[1.2.2] - 2026-07-21
Fixed
- Multi-target load balancing (added in 1.2.0) crashed every request to a load-balanced host:
ops/nginx_conf/targetinfo.luarequired a nonexistentresty.balancer.round_robinmodule. Thelua-resty-balancerrock installed by the Dockerfile/install.shdoesn't provide that path — it providesresty.roundrobin(constructed asroundrobin:new(nodes), not:new()+:reinit(nodes)). Fixedtargetinfo.luato use the real module and API; verified end-to-end that requests now round-robin across targets with no Lua errors.
[1.2.1] - 2026-07-21
Fixed
- The bootstrap anti-lockout admin account was always created as
proxyadmin2regardless ofconf.auth.adminUsers, whilemigrations/permission_bootstrap.jsgrants the global-admin permission toconf.auth.adminUsers[0]. If an operator customizedadminUsersaway from the default, the bootstrapped account and the permissioned account were two different (non-matching) usernames, so the anti-lockout account ended up with no admin access.models/user_redis.jsnow derives the bootstrap username fromconf.auth.adminUsers[0](falling back toproxyadmin2), matchingpermission_bootstrap.js. - Corrected a
secrets.js.examplecomment that claimed the bootstrap admin's password "defaults to the username itself" — it actually generates a random password printed to the container log on first boot.
Changed
- Refreshed all README screenshots (hosts, per-host SSO auth, per-host basic auth) against the current UI, and added a new load-balancing screenshot for the multi-target feature.
[1.2.0] - 2026-07-21
Added
- Multi-target load balancing: hosts can now specify additional backend targets (
IP:port, one per line) alongside the primary target; the proxy distributes requests across all of them round-robin vialua-resty-balancer. Fixes #47.
1.1.17 - 2026-07-20
Fixed
- An existing single-label subdomain host (e.g.
sso.nl.wgnode.com) could not be attached to a wildcard cert added later (e.g.*.nl.wgnode.com):Host.lookUpWildcardParent()only checked the wildcard-as-child position (the wildcard's own base domain) and missed the far more common wildcard-as-sibling case, so the edit form's "Parent Wildcard" option stayed permanently greyed out. It now checks both positions, and a regression test covers the sibling case.
1.1.16 - 2026-07-18
Changed
- Public-release packaging: removed
"private": truefromnodejs/package.json, corrected the repository URL tohttps://github.com/theta42/proxy.git, and fixed the MITLICENSEcopyright line. - Genericized committed defaults in
conf/base.jsandconf/development.js: LDAP now defaults toldap://localhostwithdc=example,dc=com, and OIDC endpoints default tohttps://sso.example.cominstead of internal theta42 infrastructure. - The bootstrap
proxyadmin2account now gets a random, one-time password whenauth.localAdminPassis unset, instead of the well-known defaultproxyadmin2. The password is printed to the log on first creation and can be made deterministic by settingauth.localAdminPassin the secrets file.
Security
- Sanitized rendered docs HTML via
xssinroutes/docs.jsso malicious markdown cannot inject scripts or other dangerous markup into the in-app docs viewer. - The Unix socket JSON-RPC socket is now created with mode
660instead of world-writable777.
Fixed
- The global error handler no longer leaks
err.keys, stack traces, or other internal details in JSON responses; onlynameandmessageare returned to clients. DEPLOYMENT.mdanddocs/docker.mdnow correctly describe theCONF_SECRETSenv-var mechanism instead of the old symlink behavior.
1.1.15 - 2026-07-18
Changed
ops/install.shnow installs to/opt/theta42/proxy(was/var/www/proxy) and seeds/etc/proxy/secrets.jsfromsecrets.js.exampleon first run (never overwritten on later runs), instead of requiring a manualnodejs/conf/secrets.jsedit inside the repo checkout.ops/proxy.servicesetsCONF_SECRETS=/etc/proxy/secrets.jsto match.install.shnow prints the version it's updating from/to (or "Already up to date") on every run, instead of updating silently.
Fixed
install.shcould hang indefinitely on a fresh host if a base package pulled intzdataas a new dependency — it prompted interactively for a timezone with no TTY attached. SetDEBIAN_FRONTEND=noninteractive.
1.1.14 - 2026-07-17
Changed
- Bumped
@simpleworkjs/confto 1.2.0 andjq-repeatto 2.2.0. The Docker entrypoint now sets the newCONF_SECRETSenv var to point directly at a mountedproxy-secrets.jsinstead of symlinking it into/app/conf/secrets.js— the app no longer needs write access to its ownconf/directory to pick up mounted secrets.
1.1.13 - 2026-07-17
Added
- Four new plain-language docs aimed at less technical readers, replacing the system-design-level Architecture/Installation docs as the target of most card help links: Hosts & HTTPS, DNS Providers, Users, Groups & Permissions, and API Tokens. Each links onward to the deeper technical reference for readers who want it; the technical docs link back the other way too. The personal-access-token card (previously missed entirely) now has a help link.
Fixed
- The in-app docs viewer rendered every
docs/*.mdpage with a garbled heading and a stray horizontal rule at the top — Jekyll front matter (meant only for the GitHub Pages build) was never stripped before being handed to the markdown renderer. Also fixed: cross-doc links never resolved in-app, since this viewer serves docs at/docs/<slug>with no.htmlsuffix — they're now rewritten to the correct in-app URL (by registered slug, falling back to the doc's real filename), the same way image paths already were.
1.1.12 - 2026-07-17
Fixed
- The host edit form's "Parent Wildcard" option stayed greyed out even when a valid wildcard actually existed for that host, so an already-created host could never be switched onto one from the edit modal (only brand-new hosts, via the field's
keyuphandler, ever saw it become available). The underlying/host/lookup/:itemcheck also had the same self-match issue as the recently-fixed backend bug: it resolved an already-existing host to its own record instead of a sibling wildcard. Added a dedicated/host/wildcard-parent/:itemendpoint that checks both directions, and the edit form now actually runs the check when it opens. - Fixed an nginx startup warning:
the "listen ... http2" directive is deprecated, use the "http2" directive instead. Migrated to the standalonehttp2 on;directive (nginx 1.25.1+).
1.1.11 - 2026-07-17
Changed
- Moved the help (❓) link out of the global header and onto each relevant card individually (Proxy List, Add/Edit host, Add DNS Provider, Dynamic A Records, Add New User, User List, Add Permission, Permissions, Add Group) — each now deep-links straight to the doc that actually covers it, instead of one generic header icon.
1.1.10 - 2026-07-17
Added
- A help icon (❓) in the top-right header now deep-links to the doc most relevant to the current page (falls back to the docs index elsewhere).
- The in-app docs viewer (
/docs) is now searchable — a simple line-substring search over the same local doc set, no new dependency, still works with no internet access.
1.1.9 - 2026-07-17
Added
- The host list now shows who created each host, and when.
- Plain (non-wildcard) hosts can now be renamed after creation — the hostname field is no longer permanently locked. Wildcard hosts, wildcard children, and auto-created subdomain cache entries stay locked, since other records reference them by name.
- More inline help text on the host create/edit form (Target SSL, wildcard matching behavior).
Fixed
- The host create/edit modal's tabs could overflow awkwardly on narrow (mobile) screens — they now scroll horizontally instead.
- Fixed a bug in the vendored
model-redislibrary's record-rename path: renaming a record's primary key while anotheralways-type field (e.g.updated_on) is defined earlier in the schema left a stray, incomplete hash behind under the old key, making that name permanently unavailable for reuse. Worked around inHost.prototype.update().
1.1.8 - 2026-07-17
Fixed
- Couldn't attach an existing host to a parent wildcard. The host edit form's "Parent Wildcard" option submitted correctly, but
Host.prototype.update()had nochallengeTypehandling at all (onlyHost.create()did) — selecting it and saving silently did nothing. Added the same wildcard-parent lookup toupdate(). - Couldn't register a wildcard's own base domain as a host. A wildcard cert's
altNamesalready cover both the base domain and*.base domain, but the lookup tree stores the wildcard one level below its base domain, and a lookup for the bare base domain landed on that empty parent node and found nothing — even though the already-issued cert covers it.buildLookUpObj()now also stamps the parent node so this resolves correctly, without re-issuing or duplicating the cert.
Both required a corrected lookup: attaching an existing host (which already has its own tree leaf) needed a new Host.lookUpWildcardParent() that checks the sibling wildcard slot instead of resolving to the host's own record.
1.1.7 - 2026-07-16
Changed
- Redesigned the GitHub Pages docs site to match the app's own look (dark navbar/footer, Bootstrap 5, Font Awesome) instead of the generic
jekyll-theme-caymantheme, added a real cross-page nav, SEO (jekyll-seo-tag+jekyll-sitemap, per-page descriptions, OG/Twitter tags, sitemap.xml, robots.txt), and mobile-responsive layout.
1.1.6 - 2026-07-16
Fixed
- Hosts admin UI's Authentication tab radios (Off / Basic / SSO) had no shared
name, so clicking one didn't uncheck the others -- multiple options could appear selected at once. Addedname="auth_mode"to restore standard exclusive radio-group behavior.
1.1.5 - 2026-07-16
Fixed
- Bumped
jq-repeat2.0.1 -> 2.1.0. Fixed real breakage:users.ejs/groups.ejs/permissions.ejscalled the removed$.scope.X.__setPut(fn)/__setTake(fn)setter-method API; insert/remove row hooks are now set via direct property assignment ($.scope.X.__put = fn), matching 2.1.0's API.
1.1.4 - 2026-07-16
Added
- White-label:
<title>, the navbar brand text, and the nav logo image were hardcoded "Proxy - Theta 42"/"Dynamic Proxy". Now driven by newconf.name/conf.logokeys (defaults unchanged). Footer attribution (copyright,theta42.comlink, GitHub/license links) and favicon are left as-is. Closes #45.
1.1.3 - 2026-07-16
Added
CHANGELOG.md(this file), backfilled from the release notes for every tag so far and served in-app at/docs/changelog. Closes theta-env#43.
1.1.2 - 2026-07-16
Fixed
- Air-gap:
DynamicRecord.refreshAll()called the public-IP resolvers (api.ipify.org,icanhazip.com,ifconfig.me) every 4h on a timer regardless of whether any dynamic DNS records were configured — the one background network call in the repo not actually gated by feature use. Now skips the lookup entirely when there's nothing to refresh. - Removed the stray, unauthenticated
GET /testpage (a leftover jq-repeat demo) that loaded jQuery + Mustache from external CDNs. - Removed a dead IE<9-only
html5shimscript tag pointing at a domain that no longer resolves.
Added
- In-app documentation:
GET /docsandGET /docs/:slugrender this project's own README, DEPLOYMENT,api.md, anddocs/*.mdserver-side — readable from the running app with no dependency on GitHub Pages, which requires internet access to view. Public, no auth, rate-limited.
1.1.1 - 2026-07-16
Fixed
- DuckDNS provider: adding a DuckDNS provider no longer pushes this host's public IP to the domain's live A/AAAA record as a side effect of token validation. Validation now writes a fixed marker to the TXT record instead, leaving routing untouched. (#142)
1.1.0 - 2026-07-16
First tagged release. Establishes the vX.Y.Z tag convention that the in-app update-check banner polls against going forward.
Added
- Standalone backup script (
ops/backup.sh) for deployments not using theta-env's orchestrator — snapshots Redis and./config, with retention. - Admin-only in-app banner that checks GitHub releases every 24h and surfaces available updates.