Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
31 KiB
Changelog
All notable changes to this project are documented here. Format loosely
follows Keep a Changelog; versions
correspond to git tags (vX.Y.Z). Entries here cover theta-env's own
orchestration code; see each submodule's own CHANGELOG.md
(proxy,
sso-manager-node)
for what changed inside the apps it composes.
Unreleased
[1.3.7] - 2026-07-23
Added
- The bootstrap now provisions the jump host's web-UI SSO login when the jump host is enabled: it mints a dedicated
theta-jumpOAuth client and writes a fulloidcblock (endpoints, client id/secret, callback) plus a generated local anti-lockout admin password into./config/jump-secrets.js. Matches how the proxy's OIDC client is provisioned. An existing pre-OIDCjump-secrets.js(API token but no OIDC client) is regenerated so upgraders get SSO login. Requires jump-host ≥ v1.1.0.
[1.3.6] - 2026-07-23
Bumped
- sso-manager-node -> v1.3.2
sso-manager-node 1.3.2:
Fixed
- OAuth client management API returned
client_id: undefinedon every GET, which broke this stack's bootstrap: it lists the OAuth clients and rotates by the returnedclient_id, so it called/api/oauth/client/undefined/rotateand got a 500 — abortingsetup.shwithbootstrap failedwheneverproxy-secrets.jshad no usable secret (e.g. a fresh/rotated deployment). The ORM'stoJSON()was stripping the mappedclient_id/scopes/… fields;OAuthClient.get()now emits them explicitly (and omitsclient_secret_hash). Unknown client ids now 404 instead of 500.
[1.3.5] - 2026-07-23
Added
- Optional SSH jump host (theta42/jump-host) as a third, opt-in submodule. Enable with
CFG_JUMP_HOST_ENABLED=trueinsetup.env: setup.sh clones/tag-tracks the submodule and builds it behind thejump-hostcompose profile, the bootstrap mints a directory API token and writes./config/jump-secrets.js(LDAP admin bind so it can inject users'sshPublicKey), the jump host is registered as a proxy Host (its web UI) and seeded as a directory service. Users thenssh uid_-_host@jump.<domain>(WinSCP-friendly) orssh uid@jump.<domain>for a TUI host picker; the web UI on :3002 shows audit + metrics. Off by default — existing installs are unaffected.
[1.3.4] - 2026-07-23
Bumped
- sso-manager-node -> v1.3.1
sso-manager-node 1.3.1:
Added
- The Directory documentation (
docs/directory.md) is now surfaced: registered in-app at/docs/directory("Directory & Inventory"), help-linked from the Directory page header, and linked from the docs-site index. Extended with the shared slug conventions (site_<name>,host_<hostname>— as used by ldap-client and the theta-env seed), the automatic-registration story (theta-env stack seeding, ldap-client Linux host enrollment), and the API surface (admin at/api/directory-admin, read-only graph at/api/discovery).
Changed
- Direct LDAP binds are described as first-class, not "legacy", across README, DEPLOYMENT.md, docs, and the Dockerfile: Linux hosts are a primary consumer of the directory (PAM/SSSD login, LDAP-backed
sudoviasudoRole, SSH public keys via openssh-lpk) — exactly what the custom schemas exist for.
theta-env own changes
Added
CFG_SITE_NAMEinsetup.env(right belowCFG_DOMAIN, defaultlocal): names the SSO directory site the stack registers itself under — slugsite_<name>, matching theparentSlugconvention ldap-client-joined Linux hosts use, so they land under the same site.- The directory seed now collects real host facts on the machine (hostname, IP, MAC of the default-route interface, OS pretty-name, kernel — same collection as
ldap-client/index.sh) and registers the stack host ashost_<hostname>with that metadata, plus fills in each service's internal port and git repo (sso-manager3001,proxy3000,openldap389/636,openresty443). Existing resources from the earlier seed layout (stack-host, domain-slug site) are adopted in place — seed metadata only fills fields the operator hasn't set, never overwrites. - The bootstrap now seeds the SSO directory with the stack's own resources: a site (from the configured domain), a "Stack host", and the SSO Manager + Proxy services (with their public URLs in metadata), linking the proxy's auto-registered OAuth client under its service. Also seeds the two non-obvious services the stack runs: the OpenLDAP directory (advertising the
ldaps://endpoint Linux hosts and LDAP-native apps bind to, honoringldap.ldapsHost) and the OpenResty edge (the 80/443 data plane every hostname flows through, with a wildcardhttps://*.<domain>address). The Directory page is populated out of the box instead of starting empty. Idempotent — resources whose slug already exists are operator-owned and never touched, and a seed failure only warns (never fails a bring-up, e.g. against an older sso-manager image without/api/directory).
[1.3.3] - 2026-07-23
Bumped
- sso-manager-node -> v1.3.0 (from v1.1.18; includes the intermediate v1.2.1 release)
sso-manager-node 1.3.0:
Added
- OAuth client management API at
/api/oauth/client(groupapp_sso_oauth_admin): list, create, update, delete, and rotate-secret for OAuth clients, backed by the Resource model. Accepts form-style string inputs (newline-separatedredirect_uris/allowed_groups, space-separatedscopes). - Dockerized test suite:
docker-compose -f docker-compose.test.yml up --buildspins up OpenLDAP + Redis + a test-runner that seeds the test user and runs the full jest suite (174 tests) against them.tests/globalSetup.jshonorsREDIS_URL.
Fixed
- Completed the model-redis →
@simpleworkjs/ormport that shipped half-finished in 1.2.1:OtpToken.issue/verifycalled nonexistentfind()/listDetail()— every OTP login 500'd.- Impersonation create/revoke called nonexistent
ImpersonationToken.listDetail()— both endpoints 500'd. OAuthClientreadis_validfrom the Resource model, which has no such column — every client evaluated as disabled and all/oauth/authorizerequests were rejected with 400. Client validity now lives inmetadata(absent = valid).OAuthClient.adddidn't set the required-uniqueResource.slug; clients now get a slug derived from the client name.GET /api/token/:name/:tokenreturned{results: null}with 200 for unknown tokens (ormget()returns null instead of throwing); now 404s.
User.loginreturns a clean 401 instead of crashing when neitheruidnorusernameis supplied.- Depend on published
@simpleworkjs/orm^0.2.8 andmodel-redis^1.6.0 instead of a localfile:link that brokenpm ciin docker builds.
Changed
- Removed the Mobile Phone field from the user create/edit form.
sso-manager-node 1.2.1:
Added
- Actionable Metrics: New real-time metrics tracking for failed logins, top IPs, and service usage per user.
- LDAP Monitor: Background service to parse OpenLDAP binds over port 389 and track metrics for legacy apps.
- UI Updates: Executive dashboard now displays actionable metrics cards instead of raw logs. User profiles show individual service usage stats to admins.
- Directory Management: Integrated site/host/service abstractions into directory UI and allowed associating OAuth apps directly to services.
[1.3.2] - 2026-07-21
Bumped
- proxy -> v1.2.2
proxy:
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 actually installed providesresty.roundrobininstead, with a different constructor API. Fixedtargetinfo.luato use the real module — verified end-to-end that requests now round-robin across targets with no Lua errors.
[1.3.1] - 2026-07-21
Bumped
proxy:
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.
sso-manager-node:
Added
- N-Way Multi-Master LDAP replication:
LDAP_SERVER_ID+LDAP_REPLICATION_HOSTSconfiguresyncreplpeers in the bundled OpenLDAP, and a new/sitespage (nav: Sites) shows each configured peer's LDAP URL and live reachability. - A
locationproperty on users, editable from the profile and user-edit forms.
Fixed
/sites(added above) 500'd on every load:views/sites.ejsincluded nonexistent partialsheader/footerinstead of this app's actualtop/bottom. Fixed to match every other view.
Changed
- Refreshed all README screenshots (dashboard, users, groups, OAuth apps) against the current UI, and added a new Sites & Replication screenshot.
theta-env own changes
- Refreshed
docs/images/sso-dashboard.pnganddocs/images/proxy-hosts.pngto match the submodules' updated screenshots.
[1.1.20] - 2026-07-20
Bumped
- proxy -> v1.1.17
proxy:
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.19] - 2026-07-18
Bumped
- sso-manager-node -> v1.1.17
sso-manager-node:
Added
conf.ldap.ldapsHostandconf.ldap.ldapsPortconfig options for advertising a separate, internal-only LDAPS hostname on the/integrationspage. Falls back to the public OAuth issuer host when unset.- Contextual help panel on
/integrations→ LDAP explaining why LDAPS needs a hostname, why port 636 should not be forwarded publicly, and the recommended internal-DNS / Docker-internal alternatives. - Tests for the
/integrationsroute's LDAPS URL derivation andldapsHostoverride.
Changed
nodejs/package.json/package-lock.jsonversion bumped to1.1.17.routes/index.jsnow derives the displayed LDAPS URL fromconf.ldap.ldapsHost/ldapsPortwith fallback to the OAuth issuer host.docs/configuration.md,docs/ldap.md,DEPLOYMENT.md, andsecrets.js.exampledocument the newldapsHost/ldapsPortoptions and recommended network layouts.
theta-env own changes
setup.env.exampleadds optionalCFG_LDAPS_HOSTfor the internal LDAPS hostname.setup.shpassesCFG_LDAPS_HOSTinto the generated./config/sso-secrets.jsasldap.ldapsHost.config.example/sso-secrets.js.exampledocumentsldap.ldapsHost/ldap.ldapsPort..env.exampleaddsLDAPS_HOSTfor legacy.envmigrations.docker-compose.ymlcomments warn against forwarding 636 to the public internet.README.mdexplains theCFG_LDAPS_HOSTrecommendation in the port-forwarding section.
[1.1.18] - 2026-07-18
Bumped
proxy:
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 config defaults in
conf/base.jsandconf/development.js(example.com/localhostinstead of theta42 infrastructure). - The bootstrap
proxyadmin2account now gets a random, one-time password whenauth.localAdminPassis unset, instead of the well-known default.
Security
- Sanitized rendered docs HTML with
xssinroutes/docs.js. - 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 internal details in JSON responses. DEPLOYMENT.mdanddocs/docker.mdnow correctly describe theCONF_SECRETSenv-var mechanism.
sso-manager-node:
Security
- Hardened LDAP filter and DN construction against injection in
models/group_ldap.jsandmodels/user_ldap.js. - Replaced
Math.random()-based token/UUID/OTP generation withcrypto.randomUUID()/crypto.randomInt()inmodels/token.js,models/oauth_code.js, andmodels/oauth_client.js. - Refused startup when
oauth.jwtSecretis missing or placeholder. - Sanitized rendered docs/Terms-of-Service HTML with
xssto block malicious markdown output. - Removed full-object
console.logof new-user data and reduced login error logging toname/messageonly.
Changed
- Public-release packaging: removed
"private": truefromnodejs/package.jsonand bumped version to1.1.16.
Fixed
models/email.js: fixed from-address template rendering bug.
theta-env own changes
CHANGELOG.mdnow embeds the full app-level release notes for each submodule bump, not just links..env.exampleno longer ships realistic-looking default passwords; values are clearly placeholders.config.example/*.js.examplecomments now describe the actualCONF_SECRETSenv-var loading mechanism.setup.shsummary no longer prints generated passwords to stdout; it points to./config/*.js.bootstrap/bootstrap.jsfails hard instead of falling back to weak default passwords when config is missing.
1.1.17 - 2026-07-18
Bumped
Both apps' bare-metal install.sh now installs to /opt/theta42/<app> and seeds /etc/<app>/secrets.js on first run, matching a wget -O - .../install.sh | sudo bash one-line install for both (previously proxy-only); re-running it prints the version it's updating from/to. sso-manager-node's installer was rewritten from a flag-driven, copy-based script into the same idempotent git-clone pattern proxy already used, and now bootstraps OpenLDAP itself on first run instead of requiring the repo to already be checked out locally. None of this affects the Docker/unified-stack deployment this repo orchestrates — bare-metal-only.
1.1.16 - 2026-07-18
Bumped
Both: bumped @simpleworkjs/conf to 1.2.0 and jq-repeat to 2.2.0.
Changed
./config/sso-secrets.jsand./config/proxy-secrets.jsare now loaded via each app'sCONF_SECRETSenv var (set by the entrypoint) instead of being symlinked into/app/conf/secrets.js— neither container needs write access to its ownconf/directory anymore. No change to the config file format or bind mounts; existing./config/directories keep working as-is.
1.1.15 - 2026-07-17
Bumped
proxy:
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+).
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.
sso-manager-node:
Added
- Three new plain-language docs aimed at less technical readers, replacing the schema-level LDAP/OAuth/API docs as the target of most card help links: Accounts, Groups & Managers, Connecting Apps (SSO), 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) now links to its own doc.
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 (ldap.html,index.html, etc.) 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, the same way image paths already were. - The new concept docs' cross-links (
concepts-accounts.htmletc.) are the correct, working URL on the Jekyll/GitHub Pages build (where the page's URL is its filename stem) but didn't resolve in the in-app docs viewer, which serves docs at a separate short slug (/docs/accounts). The in-app renderer now also resolves a doc's real filename as a fallback, so one link written in a doc works on both targets.
1.1.14 - 2026-07-17
Bumped
Both: moved the help (❓) link out of the global header and onto each relevant card individually, so it deep-links straight to the doc that actually covers that card instead of one generic per-page guess.
1.1.13 - 2026-07-17
Bumped
Both: added a help icon (❓) in the top-right header that deep-links to the doc most relevant to the current page, and made the in-app docs viewer (/docs) searchable (a simple line-substring search over the local doc set, no new dependency, still works with no internet access).
1.1.12 - 2026-07-17
Bumped
- proxy -> v1.1.9
proxy:
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.11 - 2026-07-17
Bumped
- proxy -> v1.1.8
proxy:
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.
1.1.10 - 2026-07-17
Bumped
- sso-manager-node -> v1.1.9
sso-manager-node:
Added
- Every account's personal Unix group (its primary GID holder) can now have supplementary members managed from the account's profile page ("Members of
<uid>'s group", admin-only) — e.g. to share write access to files owned by that group. Uses the standardmemberUidattribute (RFC 2307posixGroup).
1.1.9 - 2026-07-17
Bumped
- sso-manager-node -> v1.1.8
sso-manager-node:
Added
- Group membership is now editable directly from a user's profile page ("My groups" -- add via a group-name picker, remove with a button per row), instead of only from each group's own card on the Groups page. Admin-only, using the existing per-group member add/remove endpoints.
Fixed
- The Edit Profile form's Mobile Phone field had a stray
validate=":9"making it effectively required (submission was blocked with "Please fix the form errors" if left blank) -- it was always meant to be optional, matching the "Add user" form. Removed. - A service account's profile always showed
Name: Service Account-- every service account has the same literal filler given/last name (a schema-satisfying placeholder, not meant to be shown), making them indistinguishable by name. The Name line is now hidden for service accounts. - The Users page's Service Accounts tab, and a freshly-created service account's own profile, could appear empty/not-a-service-account for up to 5 minutes right after creation. Creating a user caches it via
User.get()before the route handler marks it as a service account (group membership), so the cached copy hadisServiceAccountstuck wrong until the cache TTL expired. Now cleared and re-fetched immediately after marking. - A user belonging to exactly one LDAP group had their
memberOfattribute returned as a bare string instead of a one-element array (ldapts's normal behavior for single-valued attributes) -- client-side permission checks (for(let group of user.memberOf)) would then iterate the DN character-by-character instead of once, causing pages gated on that group (e.g. Groups) to incorrectly show "You do not have permission to be here." NormalizedmemberOfto always be an array, same fix already applied tomanager.
1.1.8 - 2026-07-17
Bumped
- sso-manager-node -> v1.1.7
sso-manager-node:
Changed
- Service accounts unified to one kind. Removed the LDAP bind-only service account type (the Integrations → LDAP "Service Accounts" card, and its
/api/service-accountroutes) -- every service account is now a real Unix/POSIX account with a UID, created from the new Users → Service Accounts tab. Email and password are both optional for service accounts; a blank password means nouserPasswordis set at all (the account simply can't bind). - Added a
managerfield to every account. Multi-valued (a list of usernames), defaults to whoever created the account (the admin who added it, or whoever sent the invite), and reassignable from the account's Edit form. Anyone listed as a manager can edit that account -- same fields an admin can (mobile, description, SSH key, date of birth, home directory, login shell, manager list) -- without needingapp_sso_admin. homeDirectoryandloginShellare now editable from the Edit Profile form (previously view-only).
1.1.7 - 2026-07-16
Bumped
Both: redesigned the GitHub Pages docs site to match each app's own look (dark navbar/footer, Bootstrap 5, Font Awesome) instead of the generic jekyll-theme-cayman theme, added a real cross-page nav, SEO (jekyll-seo-tag + jekyll-sitemap), and mobile-responsive layout. theta-env's own docs site got the same treatment in this release too (see below).
Changed
- theta-env's own docs site redesigned the same way -- dark navbar/footer using the shared theta42 logo (this repo has no app UI of its own), cross-page nav, SEO, mobile-responsive.
docs/index.md's "More docs" section removed (redundant with the new nav). - Added
docs/_siteto.gitignore(missing entirely before).
1.1.6 - 2026-07-16
Bumped
- proxy -> v1.1.6
proxy: Hosts admin UI's Authentication tab radios (Off / Basic / SSO) had no shared name, so clicking one didn't uncheck the others. Added name="auth_mode" to restore standard exclusive radio-group behavior.
1.1.5 - 2026-07-16
Bumped
Both: bumped jq-repeat 2.0.1 -> 2.1.0. proxy fixed real breakage from the removed __setPut/__setTake API (insert/remove row hooks in the admin UI); sso-manager-node fixed a stale-data flash in the edit-profile flow caused by update()'s new throttling.
1.1.4 - 2026-07-16
Added
- White-label support in both proxy and sso-manager-node --
<title>, navbar brand, and logo are now conf-driven (conf.name/conf.logo) instead of hardcoded. Closes proxy#45 and sso-manager-node#6.
Fixed
- sso-manager-node: the bundled default LDAP ppolicy had
pwdLockout: FALSE, silently making "deactivate user" not actually block login. Fixed, with a drift-correction path for already-deployed instances.
Bumped
1.1.3 - 2026-07-16
Added
CHANGELOG.md(this file). Closes #43.
Bumped
1.1.2 - 2026-07-16
Changed
docs/index.md(the published site's home page) never linked toarchitecture.md,quickstart.md, orstandalone.md— added a "More docs" section so they're reachable from the site instead of only by direct URL.
Bumped
1.1.1 - 2026-07-16
Changed
setup.shnow pinsproxyandsso-manager-nodeto their latest release tag (vX.Y.Z) instead of the tip ofmaster. A rebuild now always lands on a tagged, versioned release of each app rather than whatever was most recently merged upstream.
Bumped
1.1.0 - 2026-07-16
First tagged release. Establishes the vX.Y.Z tag convention going forward.
Added
setup.shnow reports which submodules actually moved to a newer commit during an update, instead of updating silently.