Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79f1f62318 | |||
| d8717fd613 | |||
| f8a213a3bf | |||
| f90d319eeb | |||
| 27ab105325 | |||
| 5aaec1b18a | |||
| 8c7648781e | |||
| 46815e681c | |||
| a888624f38 | |||
| 191ef0a55f | |||
| 128083aee6 | |||
| f59f987115 | |||
| 71e8c09e8f | |||
| acc61a4c3d | |||
| 67f62276e7 | |||
| 6ef12408df | |||
| c3d232f7cc | |||
| 285cc4fbef | |||
| 5299556057 | |||
| cc03b3758c | |||
| 2d0496cfda | |||
| 48df638ddd | |||
| d6d2c7144a | |||
| 67374dc914 | |||
| a3b41c6775 | |||
| b25fb56a0d | |||
| a15002b588 | |||
| fe8133b21c |
+284
-1
@@ -10,6 +10,288 @@ for what changed inside the apps it composes.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.10.0] - 2026-07-27
|
||||
|
||||
### Fixed
|
||||
- **`bootstrap/bootstrap.js`'s jump-secrets.js template now points jump-host at `ldaps://sso-manager:636`**, not `ldap://sso-manager:389`. The plain-port URL combined with jump-host's `tlsOptions` made `ldapts` attempt implicit TLS against a port serving plaintext LDAP — slapd dropped every connection before any LDAP message parsed, so SSH password login failed for every account, with any password, indistinguishable from a wrong credential. Root-caused by standing up a local jump-host, editing its config, and calling `getUser`/`checkPassword` directly inside the container. **Existing deployments must edit `./config/jump-secrets.js` themselves** (this template only affects fresh bootstraps) — see theta42/theta-env#99. Companion defensive fix: [simpleworkjs/ldap v1.0.2](https://github.com/simpleworkjs/ldap/releases/tag/v1.0.2) now rejects this `ldap://` + `tlsOptions` combination outright.
|
||||
|
||||
### Bumped
|
||||
- jump-host -> [v1.7.0](https://github.com/theta42/jump-host/releases/tag/v1.7.0) — adds self-service API tokens (create/list/rotate/revoke from its dashboard); jump-host previously had none.
|
||||
|
||||
No `setup.sh` or compose change.
|
||||
|
||||
## [1.9.0] - 2026-07-27
|
||||
|
||||
### Bumped
|
||||
- sso-manager-node -> [v1.6.1](https://github.com/theta42/sso-manager-node/releases/tag/v1.6.1)
|
||||
- proxy -> [v1.5.1](https://github.com/theta42/proxy/releases/tag/v1.5.1)
|
||||
|
||||
Both apps had every native `alert()`/`confirm()` call removed, replaced by
|
||||
`@simpleworkjs/frontend`'s `app.messages.action`/`confirm`/`toast` (the
|
||||
same modules adopted in [1.8.0](#180---2026-07-27)). This was found live,
|
||||
mid browser-verification of that release: clicking sso-manager-node
|
||||
directory.ejs's "Rotate Client Secret" triggered a native `confirm()`,
|
||||
which blocks all further browser events on the page — a real hazard for
|
||||
anyone driving the app with browser automation, not just a cosmetic
|
||||
inconsistency. sso-manager-node also dropped `app.user.remove`/
|
||||
`app.oauthClient.remove` from `public/js/app.js` (dead code with a native
|
||||
`confirm()` guard and zero callers).
|
||||
|
||||
No `setup.sh`, compose, or config change.
|
||||
|
||||
## [1.8.0] - 2026-07-27
|
||||
|
||||
### Bumped
|
||||
- sso-manager-node -> [v1.6.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.6.0)
|
||||
- proxy -> [v1.5.0](https://github.com/theta42/proxy/releases/tag/v1.5.0)
|
||||
- jump-host -> [v1.6.0](https://github.com/theta42/jump-host/releases/tag/v1.6.0)
|
||||
|
||||
All three apps adopt the newly published `@simpleworkjs/frontend` package's
|
||||
`app.messages`, `app.modal`, and `app.validate` modules, replacing the
|
||||
vendored `app.util.actionMessage`/`actionConfirm`/`alert` in
|
||||
`public/lib/js/app-base.js` (byte-identical across all three apps) and the
|
||||
vendored `public/lib/js/val.js` (byte-identical in sso-manager-node and
|
||||
jump-host, and the same engine plus proxy-only DNS/hostname rules in proxy).
|
||||
Message content is now HTML-escaped — the ad hoc `app.util.alert()` this
|
||||
replaces had none — and `app.messages.action` falls back to a page-wide
|
||||
toast when there's no inline `.actionMessage` target on the page. proxy's
|
||||
`host`/`target`/`hostname` wildcard-DNS validation rules (mirroring
|
||||
`utils/hostname_validate.js`) move to its own `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`).
|
||||
jump-host doesn't currently use any `[validate]` attributes, so its `val.js`
|
||||
swap is dedup/future-proofing rather than a behavior change.
|
||||
|
||||
`app.api`/`app.auth`/`app.pubsub`/`app.socket` in each app's `app-base.js`
|
||||
are untouched: they're app-specific (a dual-mode callback/promise API with
|
||||
`auth-token` header injection) and not something the frontend package's
|
||||
generic `app.js` provides, so it isn't loaded.
|
||||
|
||||
No `setup.sh`, compose, or config change.
|
||||
|
||||
## [1.7.0] - 2026-07-27
|
||||
|
||||
### Bumped
|
||||
- sso-manager-node -> [v1.5.1](https://github.com/theta42/sso-manager-node/releases/tag/v1.5.1)
|
||||
- jump-host -> [v1.5.0](https://github.com/theta42/jump-host/releases/tag/v1.5.0)
|
||||
|
||||
Two production bugs fixed: `PUT /api/user/:uid` 500'd with an LDAP
|
||||
`ObjectClassViolationError` when setting `sshPublicKey` on any account
|
||||
predating the `ldapPublicKey` objectClass (notably the bootstrap admin) —
|
||||
and the exact same bug, in the shared `@simpleworkjs/ldap` package's
|
||||
`addSshKey`, was silently aborting SSH connections at jump-host's
|
||||
key-injection step for the same class of accounts. Both are fixed by
|
||||
ensuring the objectClass is present before writing the attribute. Also
|
||||
fixed: the Directory's "add resource" modal left the parent-Service
|
||||
dropdown blank when adding an OAuth Integration.
|
||||
|
||||
Jump-host's web dashboard also gained a "Hosts you can reach" list
|
||||
(admins see "All hosts") — previously it only showed usage metrics with
|
||||
no way to see your actual access from the browser.
|
||||
|
||||
No `setup.sh`, compose, or config change.
|
||||
|
||||
## [1.6.0] - 2026-07-26
|
||||
|
||||
### Bumped
|
||||
- jump-host -> [v1.4.0](https://github.com/theta42/jump-host/releases/tag/v1.4.0)
|
||||
|
||||
Jump-host gains **standalone mode**: it can now run with no LDAP directory and
|
||||
no SSO Manager at all, storing users and hosts itself via
|
||||
`@simpleworkjs/orm` (Sequelize; SQLite by default, any Sequelize-supported
|
||||
dialect). This is an app-internal capability, opt-in via
|
||||
`standalone.enabled` in jump-host's own config — the bundled theta-env stack
|
||||
is unaffected and continues to wire jump-host to the shared LDAP directory
|
||||
and SSO Manager as before. Two bugs were also fixed in jump-host's SSH
|
||||
server: an ephemeral listen port (`0`) was silently overridden back to the
|
||||
default, and session listeners could miss a client's immediate `exec`/`shell`
|
||||
request.
|
||||
|
||||
No `setup.sh`, compose, or config change on the theta-env side.
|
||||
|
||||
## [1.5.0] - 2026-07-26
|
||||
|
||||
### Bumped
|
||||
- sso-manager-node -> [v1.5.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.5.0)
|
||||
- proxy -> [v1.4.0](https://github.com/theta42/proxy/releases/tag/v1.4.0)
|
||||
- jump-host -> [v1.3.0](https://github.com/theta42/jump-host/releases/tag/v1.3.0)
|
||||
|
||||
This release finishes the UI half of the unification that 1.4.0 deferred: the
|
||||
three apps now share one front-end shell. `views/top.ejs`, `views/bottom.ejs`
|
||||
and `public/lib/js/app-base.js` are byte-identical across sso-manager-node,
|
||||
proxy and jump-host, and everything per-app moved into each repo's new
|
||||
`nodejs/utils/ui.js` (nav items and the groups that may see them, footer links,
|
||||
favicon, profile/logout targets, update-banner on/off). Nav gating is one model
|
||||
everywhere — the shell reveals `.group-required-<cn>` from `GET /api/user/me`,
|
||||
normalising sso's LDAP DNs and the OIDC clients' group CNs to the same shape,
|
||||
with the clients' `isAdmin` flag exposed as a synthetic `admin` group. jQuery is
|
||||
4.0.0 and EJS 3.1.10 in all three.
|
||||
|
||||
Five client-side bugs were fixed along the way, including two that broke real
|
||||
flows: `app.api.delete` ignored the callback that `formAJAX` passes (so
|
||||
DELETE-method forms — the proxy's host and DNS delete buttons — never refreshed),
|
||||
and the login page threw on every logged-out visit while revealing its card.
|
||||
|
||||
No `setup.sh`, compose or config change: this is app-internal UI work. Verified
|
||||
by driving a full stack of all three apps in a browser — every page renders
|
||||
console-clean, nav gating is correct per role, and the OIDC login round trip
|
||||
completes on both OIDC clients.
|
||||
|
||||
sso-manager-node 1.5.0:
|
||||
|
||||
### Changed
|
||||
- **Unified the front-end UI shell across the three theta42 apps.** `views/top.ejs`, `views/bottom.ejs` and `public/lib/js/app-base.js` are 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 new `nodejs/utils/ui.js`, exposed to every render as `ui` via `app.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.js` reveals `.group-required-<cn>` elements for each group the current user is in, read from `GET /api/user/me`. sso-manager-node reports LDAP DNs in `memberOf` and the OIDC clients report CNs in `groups`; both normalise to CNs client-side, and the clients' effective-rights `isAdmin` flag is exposed as a synthetic `admin` group — 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/me` is fetched once per page load and cached** (`app.auth.loadUser`). The nav, per-view `forceLogin` and every group-gated element read that one promise instead of issuing their own request.
|
||||
- `app.auth.isLoggedIn` is dual-mode: it returns a Promise **and** invokes an optional node-style callback, so the async and callback call styles both work against one shared `top.ejs`.
|
||||
- `app.auth.forceLogin` no longer uses `$.holdReady` (removed in jQuery 4). An unauthenticated user is redirected to `/login?redirect=<path>`; group requirements are still enforced, and `logOut` now only clears the session, leaving the destination to the caller (`ui.logoutRedirect`).
|
||||
- Dependency alignment across all three apps: `jquery` `^4.0.0` and `ejs` `^3.1.10`.
|
||||
|
||||
### Fixed
|
||||
- **`app.api.delete` dropped its callback when called by `formAJAX`.** `formAJAX` always passes the serialized form as the second argument, so a DELETE-method form's callback landed in the data slot and never ran. `delete` now accepts both `(url, callback)` and `(url, data, callback)`.
|
||||
- **`app.api.post`/`put` referenced an undefined `callback2`** and 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.
|
||||
- **`logInRedirect` on 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.
|
||||
|
||||
### Fixed (sso-manager-node)
|
||||
- `public/lib/js/val.js` shadowed `message` with `let` inside `validateField`, so a custom rule's return value never reached `validateMessage` and the caller always saw the generic length message. Resolved by adopting the shared validator, which also brings the `target`/`hostname` rules and the real password policy (>= 8 chars, and either 12+ or 3 of 4 character classes) to this app.
|
||||
- `public/js/app.js` used `$.isFunction`, removed in jQuery 4.
|
||||
|
||||
### Added (sso-manager-node)
|
||||
- `GET /api/user/me` now also reports `isAdmin` (membership in `app_sso_admin`), the single effective-rights flag the shared UI shell gates the update banner on. Group-level gating still reads `memberOf`.
|
||||
|
||||
### 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.js` blocks 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.
|
||||
|
||||
proxy 1.4.0:
|
||||
|
||||
### Changed
|
||||
- **Unified the front-end UI shell across the three theta42 apps.** `views/top.ejs`, `views/bottom.ejs` and `public/lib/js/app-base.js` are 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 new `nodejs/utils/ui.js`, exposed to every render as `ui` via `app.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.js` reveals `.group-required-<cn>` elements for each group the current user is in, read from `GET /api/user/me`. sso-manager-node reports LDAP DNs in `memberOf` and the OIDC clients report CNs in `groups`; both normalise to CNs client-side, and the clients' effective-rights `isAdmin` flag is exposed as a synthetic `admin` group — 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/me` is fetched once per page load and cached** (`app.auth.loadUser`). The nav, per-view `forceLogin` and every group-gated element read that one promise instead of issuing their own request.
|
||||
- `app.auth.isLoggedIn` is dual-mode: it returns a Promise **and** invokes an optional node-style callback, so the async and callback call styles both work against one shared `top.ejs`.
|
||||
- `app.auth.forceLogin` no longer uses `$.holdReady` (removed in jQuery 4). An unauthenticated user is redirected to `/login?redirect=<path>`; group requirements are still enforced, and `logOut` now only clears the session, leaving the destination to the caller (`ui.logoutRedirect`).
|
||||
- Dependency alignment across all three apps: `jquery` `^4.0.0` and `ejs` `^3.1.10`.
|
||||
|
||||
### Fixed
|
||||
- **`app.api.delete` dropped its callback when called by `formAJAX`.** `formAJAX` always passes the serialized form as the second argument, so a DELETE-method form's callback landed in the data slot and never ran. `delete` now accepts both `(url, callback)` and `(url, data, callback)`.
|
||||
- **`app.api.post`/`put` referenced an undefined `callback2`** and 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.
|
||||
- **`logInRedirect` on 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 }` in `public/css/styles.css`, the base rule the shared gating model reveals against.
|
||||
- Admin-only nav items lost their inline `display: none` in 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.js` blocks 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.
|
||||
|
||||
jump-host 1.3.0:
|
||||
|
||||
### Changed
|
||||
- **Unified the front-end UI shell across the three theta42 apps.** `views/top.ejs`, `views/bottom.ejs` and `public/lib/js/app-base.js` are 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 new `nodejs/utils/ui.js`, exposed to every render as `ui` via `app.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.js` reveals `.group-required-<cn>` elements for each group the current user is in, read from `GET /api/user/me`. sso-manager-node reports LDAP DNs in `memberOf` and the OIDC clients report CNs in `groups`; both normalise to CNs client-side, and the clients' effective-rights `isAdmin` flag is exposed as a synthetic `admin` group — 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/me` is fetched once per page load and cached** (`app.auth.loadUser`). The nav, per-view `forceLogin` and every group-gated element read that one promise instead of issuing their own request.
|
||||
- `app.auth.isLoggedIn` is dual-mode: it returns a Promise **and** invokes an optional node-style callback, so the async and callback call styles both work against one shared `top.ejs`.
|
||||
- `app.auth.forceLogin` no longer uses `$.holdReady` (removed in jQuery 4). An unauthenticated user is redirected to `/login?redirect=<path>`; group requirements are still enforced, and `logOut` now only clears the session, leaving the destination to the caller (`ui.logoutRedirect`).
|
||||
- Dependency alignment across all three apps: `jquery` `^4.0.0` and `ejs` `^3.1.10`.
|
||||
|
||||
### Fixed
|
||||
- **`app.api.delete` dropped its callback when called by `formAJAX`.** `formAJAX` always passes the serialized form as the second argument, so a DELETE-method form's callback landed in the data slot and never ran. `delete` now accepts both `(url, callback)` and `(url, data, callback)`.
|
||||
- **`app.api.post`/`put` referenced an undefined `callback2`** and 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.
|
||||
- **`logInRedirect` on 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 }` in `public/css/styles.css`, the base rule the shared gating model reveals against.
|
||||
- `#spa-shell` dropped its inline `margin-top`; `styles.css` already sets it and the shared shell adjusts it when a banner is shown.
|
||||
|
||||
### 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.js` blocks 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.4.0] - 2026-07-25
|
||||
|
||||
### Bumped
|
||||
- sso-manager-node -> [v1.4.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.4.0)
|
||||
- proxy -> [v1.3.0](https://github.com/theta42/proxy/releases/tag/v1.3.0)
|
||||
- jump-host -> [v1.2.0](https://github.com/theta42/jump-host/releases/tag/v1.2.0)
|
||||
|
||||
This release unifies the three theta42 apps onto shared `@simpleworkjs/*` packages
|
||||
(`oidc-client`, `directory-schema`, `ldap`, `app-stack` — published under the
|
||||
simpleworkjs org at 1.0.0), replacing each app's byte-identical forks of the same
|
||||
code so they share one codebase and API schema. It also fixes a security
|
||||
regression in the SSO directory discovery API (OAuth `client_secret_hash` leaked
|
||||
to every authenticated caller) and the envelope drift that broke jump-host
|
||||
bridging. The shared UI chrome (`top.ejs`/`bottom.ejs`, `app-base.js`, `val.js`)
|
||||
is intentionally **not** unified in this release — that work is deferred to a
|
||||
browser-verified session; see `UI_UNIFICATION_HANDOFF.md`. No `setup.sh` change:
|
||||
the new `@simpleworkjs/*` deps resolve from npm inside each app's image build
|
||||
(`npm ci` stays clean; no `file:`/`link:`).
|
||||
|
||||
sso-manager-node 1.4.0:
|
||||
|
||||
### Security
|
||||
- **The directory discovery API leaked OAuth `client_secret_hash` (and any secret-ish metadata key) to every authenticated caller.** `Resource` doesn't override `toJSON`, so the ORM serialized `metadata` wholesale — including the `client_secret_hash` stored on `kind:'oauth'` resources — across `GET /api/discovery/resources`, `/graph`, `/me`, `/resources/:slug`, and the directory-admin `GET /api/directory-admin/resources`. Every discovery read endpoint and the admin list now route through `projectResource`/`projectResources` from `@simpleworkjs/directory-schema`, which unconditionally strips secret keys (anything matching `/secret|password|privatekey/i`, including `client_secret_hash`) and, for non-directory-admins, reduces metadata to a public allowlist. Admins never receive `client_secret_hash` either.
|
||||
|
||||
### Fixed
|
||||
- **Directory discovery envelope drift.** `routes/discovery.js` (the `autoRouter(Resource)` mounted live at `app.js:87`) returned **bare arrays**, not the `{ results: [...] }` envelope the directory contract specifies — so jump-host's `data.results || []` collapsed every per-group query to `[]` and no user could bridge. Discovery is now served by explicit `/resources`, `/resources/:slug`, `/graph`, `/me` handlers that all return the `{ results }` envelope. The dead `routes/api_discovery.js` (mounted at `app.js:112`, *after* the 404 catcher) and its mount were removed.
|
||||
- `GET /api/discovery/resources?group=<cn>` now returns 200 with `{ results: [...] }` instead of 404.
|
||||
|
||||
### Added
|
||||
- `@simpleworkjs/directory-schema` — the directory contract: the `kind` enum, `Resource`/`ResourceEdge`/`ResourceGroup` field defs, the `{ results }` envelope, the security projection (`projectResource`/`projectResources`/`isDirectoryAdmin`), and the discovery client. `models/resource.js` imports the field defs; the discovery + directory-admin routes use the projection.
|
||||
- `@simpleworkjs/ldap` — `models/user_ldap.js` and `models/group_ldap.js` now take `escapeFilter`/`escapeDN` and `makeClient`/`withClient` from the shared package (via local wrappers that pass `conf`); sso keeps its rich `User.get`/`Group.get`/`User.login`/`User.addSSHkey` (posix/write-side stays app-local). sso's `makeClient` passes no `tlsOptions`, so cert validation is unchanged.
|
||||
- `@simpleworkjs/app-stack` — unified `build_info` (`{buildVersion, buildHash, buildYear}`) and the `static-modules` mounting helper. `utils/build_info.js` and the static-modules loop in `routes/index.js` use the shared helpers.
|
||||
- New `tests/discovery.test.js` (jest + supertest, runs under the docker harness): locks in the `{ results }` envelope on `/resources`, `/graph`, `/me`, `/resources/:slug`, the `?group=` 200-regression, and the no-`client_secret_hash`/no-secret-key guarantee for every caller.
|
||||
|
||||
### Changed
|
||||
- Dependency alignment: `ldapts` `^8.1.2` → `^8.1.8`. The new `@simpleworkjs/*` deps resolve from the npm registry (`^1.0.0`); no `file:`/`link:` entries in the lockfile, so `npm ci` is clean in docker builds.
|
||||
- `build_info` export shape changed from `{commit, version}` to `{buildVersion, buildHash, buildYear}` (the shared shape used by all three apps).
|
||||
|
||||
proxy 1.3.0:
|
||||
|
||||
### Added
|
||||
- `@simpleworkjs/oidc-client` — the OIDC client (session models, auth router, OIDC utils, safe-redirect, local-admin bootstrap). Deleted the local `utils/oidc.js`, `utils/safe_redirect.js`, `models/oidc_state.js`, `models/token.js`, `models/auth.js`, `routes/auth.js`; `models/index.js` wires the factory. The per-host SSO in `routes/host_auth.js` is unchanged but consumes the shared OIDC utils.
|
||||
- `@simpleworkjs/ldap` — the ldapts client + RFC 4515/4514 escaping.
|
||||
- `@simpleworkjs/app-stack` — unified `build_info` (`{buildVersion, buildHash, buildYear}`) and the `static-modules` mounting helper. `utils/build_info.js` and the static-modules loop in `routes/render.js` now use the shared helpers.
|
||||
|
||||
### Security
|
||||
- **LDAP filter injection in `User.get`.** The user lookup built its search filter by interpolating `data.username` raw 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 through `escapeFilter` from `@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`); no `file:`/`link:` entries in the lockfile, so `npm ci` is clean in docker builds.
|
||||
- `build_info` export shape changed from `{commit, version}` to `{buildVersion, buildHash, buildYear}` (the shared shape used by all three apps). The `/health` endpoint and footer now report `buildVersion`/`buildHash`.
|
||||
|
||||
jump-host 1.2.0:
|
||||
|
||||
### Added
|
||||
- `@simpleworkjs/oidc-client` — the OIDC client (session models, auth router, OIDC utils, safe-redirect, local-admin bootstrap). Deleted the local `utils/oidc.js`, `utils/safe_redirect.js`, `models/oidc_state.js`, `models/token.js`, `models/auth.js`, `routes/auth.js`; `models/index.js` wires the factory and the local-admin bootstrap.
|
||||
- `@simpleworkjs/directory-schema` — the sso↔jump-host directory contract. `utils/access.js` now fetches reachable hosts through the shared `createDirectoryClient` (`getResourcesByGroup`).
|
||||
- `@simpleworkjs/ldap` — `models/user_ldap.js` is now a thin wrapper over `createLdapClient`, preserving this app's loose TLS default (`rejectUnauthorized: false`) and the exact export shape.
|
||||
- `@simpleworkjs/app-stack` — unified `build_info` (`{buildVersion, buildHash, buildYear}`) and the `static-modules` mounting helper. `build_info` moved from `models/` to `utils/`; `routes/render.js` uses `mountStaticModules`.
|
||||
|
||||
### Fixed
|
||||
- **Directory envelope drift was silently treated as "no reachable hosts".** `utils/access.js` previously read `data.results || []`, so if the SSO directory ever returned a bare array (envelope drift) every per-group query collapsed to `[]` and no user could bridge. The shared client now validates the `{ results }` envelope on every call and treats an envelope violation as a failed group fetch rather than silently returning `[]`.
|
||||
|
||||
### Changed
|
||||
- Dependency alignment: `ldapts` `^8.1.2` → `^8.1.8`, `redis` `^4.7` → `^6.1.0` (the direct `redis` dep is unused — only `model-redis` is used, which already brings `redis` ^6.1.0). The new `@simpleworkjs/*` deps resolve from the npm registry (`^1.0.0`); no `file:`/`link:` entries in the lockfile, so `npm ci` is clean in docker builds.
|
||||
- `build_info` export shape changed from `{commit, version}` to `{buildVersion, buildHash, buildYear}` (the shared shape used by all three apps). The `/health` endpoint and footer now report `buildVersion`/`buildHash`.
|
||||
- `app-base.js` `forceLogin`/`logInRedirect` switched to the `?redirect=` query-param convention (matching the server-side `/login?redirect=` route).
|
||||
|
||||
## [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-jump` OAuth client and writes a full `oidc` block (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-OIDC `jump-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](https://github.com/theta42/sso-manager-node/releases/tag/v1.3.2)
|
||||
|
||||
sso-manager-node 1.3.2:
|
||||
|
||||
### Fixed
|
||||
- **OAuth client management API returned `client_id: undefined` on every GET**, which broke this stack's bootstrap: it lists the OAuth clients and rotates by the returned `client_id`, so it called `/api/oauth/client/undefined/rotate` and got a 500 — aborting `setup.sh` with `bootstrap failed` whenever `proxy-secrets.js` had no usable secret (e.g. a fresh/rotated deployment). The ORM's `toJSON()` was stripping the mapped `client_id`/`scopes`/… fields; `OAuthClient.get()` now emits them explicitly (and omits `client_secret_hash`). Unknown client ids now 404 instead of 500.
|
||||
|
||||
## [1.3.5] - 2026-07-23
|
||||
|
||||
### Added
|
||||
@@ -389,7 +671,8 @@ First tagged release. Establishes the `vX.Y.Z` tag convention going forward.
|
||||
- proxy -> [v1.1.0](https://github.com/theta42/proxy/releases/tag/v1.1.0)
|
||||
- sso-manager-node -> [v1.1.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.0)
|
||||
|
||||
[Unreleased]: https://github.com/theta42/theta-env/compare/v1.1.20...HEAD
|
||||
[Unreleased]: https://github.com/theta42/theta-env/compare/v1.4.0...HEAD
|
||||
[1.4.0]: https://github.com/theta42/theta-env/compare/v1.3.7...v1.4.0
|
||||
[1.1.17]: https://github.com/theta42/theta-env/compare/v1.1.16...v1.1.17
|
||||
[1.1.16]: https://github.com/theta42/theta-env/compare/v1.1.15...v1.1.16
|
||||
[1.1.15]: https://github.com/theta42/theta-env/compare/v1.1.14...v1.1.15
|
||||
|
||||
Vendored
+68
-14
@@ -229,14 +229,15 @@ async function listClients(token) {
|
||||
return (data && data.results) || [];
|
||||
}
|
||||
|
||||
async function createClient(token) {
|
||||
async function createClient(token, opts) {
|
||||
const o = opts || { name: CLIENT_NAME, description: 'theta-env proxy (auto-registered)', redirect_uris: [REDIRECT_URI] };
|
||||
const res = await fetch(`${SSO_INTERNAL}/api/oauth/client`, {
|
||||
method: 'POST',
|
||||
headers: { 'auth-token': token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: CLIENT_NAME,
|
||||
description: 'theta-env proxy (auto-registered)',
|
||||
redirect_uris: [REDIRECT_URI],
|
||||
name: o.name,
|
||||
description: o.description,
|
||||
redirect_uris: o.redirect_uris,
|
||||
scopes: ['openid', 'profile', 'email', 'groups'],
|
||||
allowed_groups: [],
|
||||
}),
|
||||
@@ -249,7 +250,7 @@ async function createClient(token) {
|
||||
const id = (data.results && data.results.client_id) || data.client_id;
|
||||
const secret = data.client_secret;
|
||||
if (!id || !secret) throw new Error(`create OAuth client returned no id/secret: ${JSON.stringify(data)}`);
|
||||
log(`Created OAuth client ${CLIENT_NAME} (${id})`);
|
||||
log(`Created OAuth client ${o.name} (${id})`);
|
||||
return { id, secret };
|
||||
}
|
||||
|
||||
@@ -487,6 +488,8 @@ const JUMP_ENABLED = /^(1|true|yes)$/i.test(process.env.CFG_JUMP_HOST_ENABLED ||
|
||||
const JUMP_HOST = process.env.CFG_JUMP_HOST || (DOMAIN ? `jump.${DOMAIN}` : '');
|
||||
const JUMP_SECRETS = '/config/jump-secrets.js';
|
||||
const JUMP_TOKEN_NAME = 'theta-jump-host';
|
||||
const JUMP_CLIENT_NAME = 'theta-jump';
|
||||
const JUMP_REDIRECT_URI = `https://${JUMP_HOST}/api/auth/oidc/callback`;
|
||||
|
||||
async function mintApiToken(token, name) {
|
||||
const res = await fetch(`${SSO_INTERNAL}/api/api-token`, {
|
||||
@@ -501,14 +504,19 @@ async function mintApiToken(token, name) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
function jumpFileHasToken() {
|
||||
// The generated file is "complete" only if it has BOTH a real directory API
|
||||
// token AND an OIDC client id — an existing file from the pre-OIDC layout (a
|
||||
// token but no oidc block) is regenerated so the web UI's SSO login works.
|
||||
function jumpFileComplete() {
|
||||
try {
|
||||
const src = fs.readFileSync(JUMP_SECRETS, 'utf8');
|
||||
return /apiToken:\s*['"]sso_[0-9a-f]{24}_[0-9a-f]{48}['"]/.test(src);
|
||||
const hasToken = /apiToken:\s*['"]sso_[0-9a-f]{24}_[0-9a-f]{48}['"]/.test(src);
|
||||
const hasOidc = /clientId:\s*['"][0-9a-f-]{8,}['"]/.test(src);
|
||||
return hasToken && hasOidc;
|
||||
} catch (_) { return false; }
|
||||
}
|
||||
|
||||
function writeJumpSecrets(apiToken) {
|
||||
function writeJumpSecrets(apiToken, oidc, localAdminPass) {
|
||||
const siteName = (sso.stack && sso.stack.siteName) || 'local';
|
||||
const ldapsHost = (sso.ldap && sso.ldap.ldapsHost) || SSO_HOST;
|
||||
const body = `'use strict';
|
||||
@@ -519,7 +527,16 @@ function writeJumpSecrets(apiToken) {
|
||||
module.exports = {
|
||||
\tname: ${JSON.stringify(sso.name || 'SSO Manager')},
|
||||
\tldap: {
|
||||
\t\turl: 'ldap://sso-manager:389',
|
||||
\t\t// ldaps:// (636), not ldap:// (389): @simpleworkjs/ldap's client always
|
||||
\t\t// sets tlsOptions (see jump-host's models/user_ldap.js), and ldapts
|
||||
\t\t// treats a non-empty tlsOptions as "use implicit TLS" regardless of the
|
||||
\t\t// URL scheme -- pointed at the plain port, that means it opens a raw TLS
|
||||
\t\t// handshake against a server expecting plaintext LDAP, which slapd just
|
||||
\t\t// drops (logged as "connection lost", no BIND ever attempted). This bit
|
||||
\t\t// jump-host silently: every SSH login failed with the generic
|
||||
\t\t// "Permission denied" for any password, because getUser()/checkPassword()
|
||||
\t\t// never even reached slapd.
|
||||
\t\turl: 'ldaps://sso-manager:636',
|
||||
\t\tbindDN: ${JSON.stringify(BIND_DN)},
|
||||
\t\tbindPassword: ${JSON.stringify(ADMIN_PASS)},
|
||||
\t\tuserBase: ${JSON.stringify(`ou=people,${BASE_DN}`)},
|
||||
@@ -537,7 +554,27 @@ module.exports = {
|
||||
\t\tkeyComment: ${JSON.stringify(`jump-host@${siteName}`)},
|
||||
\t},
|
||||
\tweb: { port: 3002 },
|
||||
\tauth: { adminGroups: ['app_sso_admin'] },
|
||||
\t// Web UI SSO login — the jump host's own OAuth client. tokenEndpoint /
|
||||
\t// userinfoEndpoint use the internal docker-net address (server-to-server);
|
||||
\t// authorizationEndpoint is the public SSO host (browser-facing).
|
||||
\toidc: {
|
||||
\t\tenabled: true,
|
||||
\t\tissuer: ${JSON.stringify(`https://${SSO_HOST}`)},
|
||||
\t\tauthorizationEndpoint: ${JSON.stringify(`https://${SSO_HOST}/oauth/authorize`)},
|
||||
\t\ttokenEndpoint: 'http://sso-manager:3001/oauth/token',
|
||||
\t\tuserinfoEndpoint: 'http://sso-manager:3001/oauth/userinfo',
|
||||
\t\tclientId: ${JSON.stringify(oidc.id)},
|
||||
\t\tclientSecret: ${JSON.stringify(oidc.secret)},
|
||||
\t\tredirectUri: ${JSON.stringify(JUMP_REDIRECT_URI)},
|
||||
\t\tscopes: ['openid', 'profile', 'email', 'groups'],
|
||||
\t\tgroupsClaim: 'groups',
|
||||
\t\tusernameClaim: 'preferred_username',
|
||||
\t},
|
||||
\tauth: {
|
||||
\t\tadminGroups: ['app_sso_admin'],
|
||||
\t\tadminUsers: ['jumpadmin'],
|
||||
\t\tlocalAdminPass: ${JSON.stringify(localAdminPass)},
|
||||
\t},
|
||||
\tredis: { prefix: 'jump_host_', redisConf: { url: 'redis://127.0.0.1:6379' } },
|
||||
\tstack: { ssoHost: ${JSON.stringify(SSO_HOST)}, jumpHost: ${JSON.stringify(JUMP_HOST)}, ldapsHost: ${JSON.stringify(ldapsHost)} },
|
||||
};
|
||||
@@ -546,13 +583,30 @@ module.exports = {
|
||||
}
|
||||
|
||||
async function provisionJumpHost(token) {
|
||||
if (jumpFileHasToken()) {
|
||||
log('Jump host: /config/jump-secrets.js already has an API token — keeping.');
|
||||
if (jumpFileComplete()) {
|
||||
log('Jump host: /config/jump-secrets.js already has API token + OIDC client — keeping.');
|
||||
return;
|
||||
}
|
||||
const apiToken = await mintApiToken(token, JUMP_TOKEN_NAME);
|
||||
writeJumpSecrets(apiToken);
|
||||
log('Jump host: wrote /config/jump-secrets.js (minted directory API token).');
|
||||
|
||||
// Mint (or reuse) the jump host's own OAuth client for web-UI SSO login.
|
||||
const clients = await listClients(token);
|
||||
let oidc = clients.find((c) => c.name === JUMP_CLIENT_NAME);
|
||||
if (oidc && oidc.client_id) {
|
||||
oidc = await rotateClient(token, oidc.client_id);
|
||||
oidc = { id: oidc.id, secret: oidc.secret };
|
||||
} else {
|
||||
oidc = await createClient(token, {
|
||||
name: JUMP_CLIENT_NAME,
|
||||
description: 'theta-env jump host web UI (auto-registered)',
|
||||
redirect_uris: [JUMP_REDIRECT_URI],
|
||||
});
|
||||
}
|
||||
|
||||
const localAdminPass = crypto.randomBytes(16).toString('hex');
|
||||
writeJumpSecrets(apiToken, oidc, localAdminPass);
|
||||
log(`Jump host: wrote /config/jump-secrets.js (API token + OAuth client ${oidc.id}).`);
|
||||
log(`Jump host: local admin 'jumpadmin' password: ${localAdminPass}`);
|
||||
}
|
||||
|
||||
(async function main() {
|
||||
|
||||
@@ -36,6 +36,12 @@ services:
|
||||
# setup.sh sets this from the host, where the submodule resolves
|
||||
# correctly (git -C sso-manager-node rev-parse --short HEAD).
|
||||
GIT_COMMIT: ${SSO_GIT_COMMIT:-}
|
||||
# Optional upstream HTTP(S) proxy for npm/apt during the build (NOT
|
||||
# the theta42 "proxy" app). Set CFG_HTTP_PROXY in setup.env; empty by
|
||||
# default, so this is a no-op unless configured.
|
||||
HTTP_PROXY: ${CFG_HTTP_PROXY:-}
|
||||
HTTPS_PROXY: ${CFG_HTTPS_PROXY:-}
|
||||
NO_PROXY: ${CFG_NO_PROXY:-}
|
||||
container_name: sso-manager
|
||||
restart: unless-stopped
|
||||
networks: [theta-net]
|
||||
@@ -58,6 +64,11 @@ services:
|
||||
- NODE_PORT=3001
|
||||
- LDAP_SERVER_ID=${LDAP_SERVER_ID:-}
|
||||
- LDAP_REPLICATION_HOSTS=${LDAP_REPLICATION_HOSTS:-}
|
||||
# Optional upstream HTTP(S) proxy for outbound calls (SMTP, etc.) at
|
||||
# runtime. See the build args above for the same setting during build.
|
||||
- HTTP_PROXY=${CFG_HTTP_PROXY:-}
|
||||
- HTTPS_PROXY=${CFG_HTTPS_PROXY:-}
|
||||
- NO_PROXY=${CFG_NO_PROXY:-}
|
||||
volumes:
|
||||
# Operator-edited SSO secrets (sso-secrets.js). Read-WRITE so the bootstrap
|
||||
# can write the generated OAuth client creds into proxy-secrets.js. The
|
||||
@@ -91,6 +102,11 @@ services:
|
||||
# setup.sh sets this from the host, where the submodule resolves
|
||||
# correctly (git -C proxy rev-parse --short HEAD).
|
||||
GIT_COMMIT: ${PROXY_GIT_COMMIT:-}
|
||||
# Optional upstream HTTP(S) proxy for npm/apt during the build. See
|
||||
# the sso-manager service above for details.
|
||||
HTTP_PROXY: ${CFG_HTTP_PROXY:-}
|
||||
HTTPS_PROXY: ${CFG_HTTPS_PROXY:-}
|
||||
NO_PROXY: ${CFG_NO_PROXY:-}
|
||||
container_name: proxy
|
||||
restart: unless-stopped
|
||||
networks: [theta-net]
|
||||
@@ -110,6 +126,11 @@ services:
|
||||
# not from env. NODE_ENV/NODE_PORT are process env the app reads directly.
|
||||
- NODE_ENV=production
|
||||
- NODE_PORT=3000
|
||||
# Optional upstream HTTP(S) proxy for outbound calls (ACME/Let's
|
||||
# Encrypt, DNS providers) at runtime.
|
||||
- HTTP_PROXY=${CFG_HTTP_PROXY:-}
|
||||
- HTTPS_PROXY=${CFG_HTTPS_PROXY:-}
|
||||
- NO_PROXY=${CFG_NO_PROXY:-}
|
||||
volumes:
|
||||
# Operator-edited proxy secrets (proxy-secrets.js). READ-ONLY — the proxy
|
||||
# only reads it; the sso-manager bootstrap writes the OAuth creds. The
|
||||
@@ -142,6 +163,11 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
GIT_COMMIT: ${JUMP_GIT_COMMIT:-}
|
||||
# Optional upstream HTTP(S) proxy for npm/apt during the build. See
|
||||
# the sso-manager service above for details.
|
||||
HTTP_PROXY: ${CFG_HTTP_PROXY:-}
|
||||
HTTPS_PROXY: ${CFG_HTTPS_PROXY:-}
|
||||
NO_PROXY: ${CFG_NO_PROXY:-}
|
||||
container_name: jump-host
|
||||
restart: unless-stopped
|
||||
networks: [theta-net]
|
||||
@@ -153,6 +179,11 @@ services:
|
||||
- "${JUMP_WEB_BIND:-0.0.0.0}:${JUMP_WEB_PORT:-3002}:3002" # web UI/API
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
# Optional upstream HTTP(S) proxy for outbound calls (the directory API
|
||||
# client) at runtime.
|
||||
- HTTP_PROXY=${CFG_HTTP_PROXY:-}
|
||||
- HTTPS_PROXY=${CFG_HTTPS_PROXY:-}
|
||||
- NO_PROXY=${CFG_NO_PROXY:-}
|
||||
volumes:
|
||||
- ./config:/config:ro # jump-secrets.js (written by ensure_config/bootstrap)
|
||||
- jump-data:/var/lib/jump-host # generated host keys persist here
|
||||
|
||||
@@ -31,7 +31,7 @@ fetches all three in one step; `git submodule update --remote` bumps them.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ your browser / apps / legacy LDAP clients │
|
||||
│ your browser / apps / direct LDAP clients │
|
||||
└───────────────┬──────────────────────────────┘
|
||||
│ https (:443) ldaps (:636)
|
||||
┌─────────▼─────────┐
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 394 KiB After Width: | Height: | Size: 310 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 141 KiB |
+11
-2
@@ -15,7 +15,9 @@ LDAP directory) and [Proxy](https://theta42.github.io/proxy/) (an
|
||||
OIDC-protected reverse proxy that can also look users up directly in LDAP) —
|
||||
and automates the fiddly part: registering the proxy as an OIDC client of the
|
||||
SSO and pointing it at the right LDAP directory, with hostnames and secrets
|
||||
generated from one `setup.env`.
|
||||
generated from one `setup.env`. An optional third component, the
|
||||
[Jump Host](https://theta42.github.io/jump-host/), adds directory-driven SSH
|
||||
access to your machines through one public entry point.
|
||||
|
||||
## Screenshots
|
||||
|
||||
@@ -23,6 +25,7 @@ The SSO Manager and the proxy it fronts, both stood up by one `./setup.sh` run:
|
||||
|
||||
<a href="images/sso-dashboard.png" target="_blank"><img src="images/sso-dashboard.png" alt="SSO Manager dashboard" width="49%"></a>
|
||||
<a href="images/proxy-hosts.png" target="_blank"><img src="images/proxy-hosts.png" alt="Proxy host list" width="49%"></a>
|
||||
<a href="images/jump-dashboard.png" target="_blank"><img src="images/jump-dashboard.png" alt="Jump Host dashboard" width="49%"></a>
|
||||
|
||||
*(click either screenshot to view full size)*
|
||||
|
||||
@@ -41,7 +44,11 @@ snapshots state before every rebuild.
|
||||
- **SSO Manager**, fronted by the proxy under TLS — manage users, groups,
|
||||
and OAuth clients.
|
||||
- **Proxy** — add the hosts you want to protect with OIDC login.
|
||||
- **LDAPS** for legacy apps that bind directly.
|
||||
- **LDAPS** for direct binds — Linux hosts (PAM/SSSD, sudo, SSH keys) and
|
||||
LDAP-native apps authenticate against the same directory.
|
||||
- **SSH Jump Host** *(optional)* — `ssh uid_-_host@jump.<domain>` (WinSCP-friendly)
|
||||
or an interactive picker; access is driven by directory group membership, with
|
||||
a web UI for audit + metrics. Enable with `CFG_JUMP_HOST_ENABLED=true`.
|
||||
- **Self-service API tokens** in both apps' UIs, for scripting/CI without a
|
||||
browser session.
|
||||
- **Multi-Site Support (Geo-Location Scaling)** — built-in support for N-Way Multi-Master LDAP replication across physical locations.
|
||||
@@ -67,3 +74,5 @@ architecture, and running each project standalone, see the
|
||||
provider + LDAP directory this stack runs.
|
||||
- **[Proxy](https://theta42.github.io/proxy/)** — the reverse proxy this
|
||||
stack runs in front of it.
|
||||
- **[Jump Host](https://theta42.github.io/jump-host/)** — the optional SSH jump
|
||||
host this stack can bring up (`CFG_JUMP_HOST_ENABLED=true`).
|
||||
|
||||
@@ -60,6 +60,9 @@ setups `CFG_DOMAIN` is the only value you set:
|
||||
| `CFG_ADMIN_UID` | `admin` | optional, defaults to `admin` |
|
||||
| `CFG_ADMIN_EMAIL` | `admin@<proxyHost>` | optional |
|
||||
| `CFG_BASE_DN` | `dc=lab,dc=local` | advanced: override the derived LDAP base DN |
|
||||
| `CFG_JUMP_HOST_ENABLED` | `true` | optional: bring up the [SSH jump host](https://theta42.github.io/jump-host/) (default off) |
|
||||
| `CFG_JUMP_HOST` | `jump.lab.local` | optional, defaults to `jump.<domain>` |
|
||||
| `JUMP_SSH_PORT` | `2222` | optional: host port for the jump host's SSH (never 22 by default) |
|
||||
|
||||
`setup.env` is used **only on the first run** to generate `./config/`; after
|
||||
that `./config/*.js` are operator-owned and `setup.env` is ignored. Secrets
|
||||
|
||||
+1
-1
Submodule jump-host updated: 6e6f42e891...b03fcefaae
+1
-1
Submodule proxy updated: 60dbfe5b9b...58204da3cb
@@ -49,6 +49,19 @@ CFG_DOMAIN=example.com
|
||||
# under an OU-style prefix). Leave unset to use the DN built from CFG_DOMAIN:
|
||||
#CFG_BASE_DN=dc=example,dc=com
|
||||
|
||||
# ── Optional outbound HTTP(S) proxy ──────────────────────────────────────────
|
||||
# For an isolated/offline/corporate-network test host that only reaches the
|
||||
# internet through an upstream HTTP proxy — NOT the theta42 "proxy" app.
|
||||
# Wired into every service's docker build (npm/apt) AND its running container
|
||||
# (SMTP, ACME/Let's Encrypt, DNS provider calls, the jump-host directory API
|
||||
# client). Leave unset to disable (the default); CFG_HTTPS_PROXY falls back to
|
||||
# CFG_HTTP_PROXY if unset, and CFG_NO_PROXY defaults to covering the stack's
|
||||
# own internal service names so container-to-container traffic never goes
|
||||
# through the proxy.
|
||||
#CFG_HTTP_PROXY=http://proxy.example.com:3128
|
||||
#CFG_HTTPS_PROXY=http://proxy.example.com:3128
|
||||
#CFG_NO_PROXY=localhost,127.0.0.1,sso-manager,proxy,jump-host
|
||||
|
||||
# Optional — sensible defaults if left blank:
|
||||
#CFG_ORG=SSO Manager # app display name + outbound email org
|
||||
#CFG_ADMIN_UID=admin # initial SSO admin username
|
||||
|
||||
@@ -165,6 +165,22 @@ export CFG_JUMP_HOST_ENABLED CFG_JUMP_HOST
|
||||
# When enabled, activate the compose profile so `up`/`ps` include the service.
|
||||
if [[ "$JUMP_ENABLED" == "1" ]]; then export COMPOSE_PROFILES="jump-host"; fi
|
||||
|
||||
# ── Optional outbound HTTP(S) proxy for docker build + the running containers ─
|
||||
# CFG_HTTP_PROXY / CFG_HTTPS_PROXY / CFG_NO_PROXY (from ./setup.env or the
|
||||
# environment) — NOT the theta42 "proxy" app; this is an upstream HTTP proxy
|
||||
# for reaching the internet (npm/apt during image builds, and SMTP/ACME/DNS
|
||||
# provider calls at runtime), useful on isolated/offline/corporate-network
|
||||
# test hosts. Off by default. docker-compose.yml passes these through as both
|
||||
# build args (Docker also recognizes them as predefined build ARGs) and
|
||||
# container environment on every service, so one setup.env entry covers the
|
||||
# whole stack.
|
||||
export CFG_HTTP_PROXY="${CFG_HTTP_PROXY:-}"
|
||||
export CFG_HTTPS_PROXY="${CFG_HTTPS_PROXY:-${CFG_HTTP_PROXY:-}}"
|
||||
export CFG_NO_PROXY="${CFG_NO_PROXY:-localhost,127.0.0.1,sso-manager,proxy,jump-host}"
|
||||
if [[ -n "$CFG_HTTP_PROXY" ]]; then
|
||||
info "Using HTTP proxy for docker build + containers: $CFG_HTTP_PROXY"
|
||||
fi
|
||||
|
||||
# ── 1. Update submodules to their latest release tag, verify build contexts ───
|
||||
# Submodules track release tags (vX.Y.Z), not the tip of master -- so
|
||||
# "update" means "move to the newest tag", not "move to the newest commit".
|
||||
|
||||
+1
-1
Submodule sso-manager-node updated: 3babf18fe4...18119d54aa
Reference in New Issue
Block a user