- app_super_admin (cross-app, also recognized by sso-manager-node/proxy)
and a new app_jump_admin group are added to conf.auth: super admins are
full admins here same as app_sso_admin; jump admins get audit page/data
access without other admin rights (isJumpAdmin/requireJumpAdmin in
middleware/auth.js, wired into routes/api.js's audit-data gate and the
/audit page's client-side forceLogin -- previously the page shell
rendered for any logged-in user, only the data was gated).
- Dashboard: moved the stat boxes and Top hosts/Top users cards to the
Audit page (audit is now the admin-facing metrics home; dashboard stays
focused on "hosts I can reach"). Renamed "All hosts" to "My hosts".
- Host list now shows Last connection/Last failed connection columns and
highlights rows green (live session, from session_registry) or yellow
(most recent attempt failed) -- backed by new per-host last-success/
last-fail timestamps in models/metrics.js, populated by ssh_server.js
(which now attributes grammar/TUI connect failures to the resolved host
when one was found, not just aggregate counters) and surfaced through
GET /api/user/hosts (routes/user.js).
- Dashboard, Sessions, and Audit pages now wrap their content in
<div class="container mt-4">, matching sso-manager-node/proxy's width
instead of rendering full-bleed inside the fluid shell.
- Audit's nav entry now carries groups: ['admin'] (utils/ui.js), reusing
the existing synthetic-admin-group nav-gating convention -- the API
route was already server-side admin-gated, this closes the last gap by
hiding the nav link/page for non-admins too.
- app-base.js (byte-identical across the 3 apps): added
app.util.revealItem() and the --sw-content-offset sticky-positioning
variable, carried over from the same round of changes in
sso-manager-node/proxy. Not yet called anywhere in this app -- no
sticky/reveal use case here yet -- but keeps the shared file in sync.
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>
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>