formAJAX always passes the serialized form as the second argument, so a
DELETE-method form (the host/DNS delete buttons) landed its callback in
the data slot and never ran.
The login page's "reveal the card once we know you're logged out" branch
touched an element further down the same page, which threw when
isLoggedIn answered before the parser got there (it always did without a
stored token). It now runs on DOM ready.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
views/top.ejs, views/bottom.ejs and public/lib/js/app-base.js are now
byte-identical across sso-manager-node, proxy and jump-host. Everything
per-app moved into utils/ui.js, exposed to every render as `ui` via
app.locals (nav items + their group gates, footer repo/docs/ToS links,
favicon, profile/logout targets, update-banner on/off + label).
Client framework changes:
- One gating model everywhere: app-base.js reveals .group-required-<cn>
for each of the current user user/me groups. sso-manager-node sends LDAP
DNs in memberOf, the OIDC clients send CNs in groups; both normalise to
CNs, and the clients isAdmin flag becomes a synthetic `admin` group, so
proxy nav-admin items are now group-required-admin.
- user/me is fetched once per page load and cached (app.auth.loadUser);
nav, forceLogin and group-required elements all read that one promise.
- isLoggedIn is dual-mode (Promise + node-style callback), so the async
and callback call styles both work from one shared top.ejs.
- forceLogin no longer uses $.holdReady (removed in jQuery 4): it redirects
to /login?redirect=<path>, and still enforces required groups.
- logOut only clears the session; the caller decides where to go next.
- post/put/delete are dual-mode Promise/callback, which also removes the
undefined `callback2` reference that threw on a non-function callback.
Dependencies: jquery ^4.0.0 and ejs ^3.1.10 in all three apps.
jump-host specifics:
- .group-required base rule added to styles.css (no gated nav items yet).
- #spa-shell drops its inline margin-top; styles.css already sets it, and
the shared shell adjusts it when a banner is shown.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rewire onto the shared @simpleworkjs/oidc-client, /directory-schema, /ldap, and
/app-stack packages (deleting the byte-identical local forks). utils/access.js
now fetches reachable hosts through the shared directory client, which
validates the {results} envelope and treats envelope drift as a failed group
rather than silently returning []. models/user_ldap.js is a thin wrapper over
createLdapClient (loose TLS default preserved). build_info moves to utils/ with
the shared {buildVersion,buildHash,buildYear} shape. Align ldapts ^8.1.8 and
redis ^6.1.0. Lockfile regenerated from the registry (no file:/link:).
Co-Authored-By: Claude <noreply@anthropic.com>
The web management UI was a bespoke minimal theme with LDAP-bind login.
Rebuild it to match the SSO Manager and Proxy — same stack, same look/feel,
same auth model. The SSH bridge, audit, metrics, and access logic are
unchanged; this is purely the web layer.
Frontend (mirrors proxy/sso):
- Express + EJS with the shared top.ejs/bottom.ejs shell, Bootstrap 5,
jQuery, jq-repeat, FontAwesome, Socket.IO, and the shared app-base.js /
val.js client framework (copied verbatim). Vendor libs served from
node_modules via /static-modules; app assets via /static.
- Dashboard / Sessions / Audit pages render in the common look/feel,
loading data through the authenticated /api/* endpoints.
Auth (mirrors proxy):
- OIDC against the SSO (utils/oidc.js + routes/auth.js + models/oidc_state)
plus a local anti-lockout admin (models/user_redis.js, bootstrapped from
auth.adminUsers[0] / auth.localAdminPass). AuthToken sessions carry the
group snapshot; middleware gates the data API on adminGroups or the local
admin. New config: oidc{} + auth.adminUsers/localAdminPass.
- /api/user/me drives the client login state; "Log in with SSO" hidden when
oidc.enabled is false.
Verified end to end: local admin login -> token -> /api/user/me isAdmin,
metrics/sessions/audit 200 with token / 401 without / 401 bad password;
static + page shells serve; 26 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
model-redis opens a client when models/index is required, so with no
redis the integration test saw ECONNREFUSED as a post-test unhandled
rejection. Provide redis:7 at 127.0.0.1:6379 in CI (mirrors how
sso-manager-node tests run).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
node's own '**' glob expansion for --test worked on local node 22.23 but
not in CI (node 20/22 runners) — 'Could not find test/**/*.test.js'. Use
plain single-star globs the shell expands instead; test files are exactly
one level under test/unit and test/integration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirrors the proxy/sso-manager docs-site layout (Jekyll + Bootstrap
default layout, theta42 assets). Documents the username grammar, the TUI
picker, SFTP/WinSCP, how auth/access/key-injection/bridging work, and the
three install paths + the required sshPublicKey write-ACL.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
node --test --test-force-exit needs Node 20.14+/22, so the matrix is
[20.x, 22.x] and engines is bumped to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An SSH jump host that authenticates users against the shared LDAP
directory, authorizes them from the SSO Manager's inventory graph, and
bridges them to downstream hosts — auditing everything.
- Username-grammar routing (uid_-_target@jump) + interactive TUI picker
- Inbound LDAP auth (publickey / password with off|local|all policy)
- Directory-driven access (LDAP groups x /api/discovery/resources?group=)
- Per-user key injection into sshPublicKey, connects downstream as the user
- Shell / exec / SFTP-subsystem bridging (WinSCP works)
- Web UI + HTTP API (:3002) for audit + metrics; LDAP-admin gated
- Packaged like proxy: ops/install.sh + systemd, all-in-one Docker, compose
- Tests: 23 unit + 3 integration (node --test), all green
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>