Closes the end-user half of the directory and adds nested LDAP groups.
The directory could describe the lab but could not tell anyone what they had
or how to reach it, and several of the paths meant to do so were silently
returning nothing:
- GET /api/discovery/me resolved groups from req.user.groups, which does not
exist (req.user carries memberOf), so it returned only isPublic resources
for every human caller -- "My Services" was blank for everyone. The same
read made isDirectoryAdmin() false for real admins.
- The portal's "Discover More Services" called the admin-gated endpoint and
swallowed the 403, so it never rendered for non-admins at all.
- Services reported no address, because /me had reimplemented getMyAccess
without its parent-walking resolution.
Adds the catalog at /, self-service access requests, and admin access
visibility (per-resource counts, and the reverse "what can this user reach").
Nested groups come in two halves. groupOfNames.member already accepts a group
DN, so nesting needs no schema -- what it needs is resolution, which no
released OpenLDAP performs. The all-in-one image therefore builds slapd from a
pinned master commit for the nestgroup overlay, and the app computes the
closure itself when pointed at a server without it. Both paths are covered.
member-values is deliberately left out of nestgroup-flags: it expands `member`
when reading a group, which destroys the distinction between "listed here" and
"reachable through a nested group" and is not recoverable afterwards.
Full suite green in both resolution modes: 215 passed, 2 skipped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Retires the static #secretModal/#editModal elements in favor of the
shared app.modal singleton, matching the pattern already shipped in
directory.ejs, proxy, and jump-host this round. Converts the
always-visible create-form card into a "+ New Token" button + modal,
switches badge classes from bg-* to text-bg-*, and replaces the
checkmark-flash copy feedback (broken by FontAwesome's <i>-to-<svg>
replacement) with toast-based copyFieldValue.
Native confirm() dialogs block browser automation entirely (discovered
via a frozen tab while browser-testing the app.messages/app.modal
adoption), and native alert()/confirm() are visually inconsistent with
the rest of the UI. Replaced every call site with
app.messages.action/confirm/toast:
- directory.ejs: rotateSecret/deleteResource confirms and all inline
save/add/remove-group/edge error alerts now target #resourceModal's
actionMessage (or, for deleteResource — called from the outer table
row, not the modal — the page's own card).
- impersonate_modal.ejs, onboarding.ejs: no local .actionMessage target
exists on these pages, so their alerts became page-wide toasts.
- executive.ejs: two alerts in sendNotification's validation now use the
existing $compose target; saveTos's alert now reuses the function's
own msgEl inline-message element instead of introducing a second
mechanism.
- users.ejs, profile.ejs, proxy's profile.ejs: toggleActive's alert
(no row context available at the call site) became a toast;
revokeInvite/revokeToken/rotateToken use the row/card element already
in scope.
- app.js: removed app.user.remove and app.oauthClient.remove, which
contained native confirm() guards and had zero callers anywhere in the
app — dead code, deleted rather than converted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the vendored app.util.actionMessage/actionConfirm/alert (the
latter added ad hoc to fix "app.util.alert is not a function") with the
published @simpleworkjs/frontend package: app.messages.action/confirm,
app.modal.open, and app.validate.js (which also replaces the identical
vendored val.js). Gains real HTML-escaping on message content and a toast
fallback when there's no inline .actionMessage target, neither of which
the vendored code had.
app.api/app.auth/app.pubsub/app.socket in app-base.js are untouched —
they're app-specific (dual-mode callback/promise API, auth-token header
injection) and not something the generic frontend package's app.js
provides, so it isn't loaded here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- New docs/concepts-{accounts,oauth-apps,api-tokens}.md -- plain-language
guides aimed at less technical readers, each linking onward to the
existing schema/protocol-level doc for anyone who wants that detail.
Card help links (Users, Groups, OAuth cards, My groups, Members of
<uid>'s group) now point here instead of straight at the technical
docs; the LDAP-protocol-wiring cards (raw connection details for
connecting a 3rd-party app) stay pointed at the technical ldap.md,
since that's genuinely the right depth for that task.
- The "New API Token" card had no help link at all -- added, pointing to
the new API Tokens doc.
- Fixed the in-app docs viewer rendering every docs/*.md page with a
garbled heading + stray <hr> 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 .html suffix -- rewritten
to the correct in-app URL, same idea as the existing image-path fix.
Bumps to v1.1.12.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
The single header-wide help icon (added last release) pointed at a
per-page doc guess, but a page can have several cards covering different
topics (e.g. Integrations has both OAuth and LDAP cards). Removed it and
added a small help icon directly to each card that has real corresponding
doc content, linking straight to that doc -- Invite User/Add new
user/User List/Service Accounts (users.ejs), group cards (groups.ejs),
OAuth Apps + LDAP connection cards (integrations.ejs), My groups/Members
of <uid>'s group/New API Token (profile.ejs).
Bumps to v1.1.11.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
Every account gets a personal posixGroup at creation (its primary GID
holder) but there was no way to manage its memberUid list -- add
add/remove endpoints and a profile-page UI (admin-only), reusing the
userSelect widget already built for the manager field.
Bumps to v1.1.9.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
- Edit form's Mobile Phone field was effectively required (stray validate
attribute) -- removed.
- Service account profiles always showed the literal filler name "Service
Account" -- hidden now, since it's not meaningful. Required computing
isServiceAccount in User.get(), not just listDetail().
- Fresh service accounts could look uncategorized (missing from the
Service Accounts tab, wrong isServiceAccount) for up to 5 minutes after
creation, due to a cache-staleness race in the create route -- the user
gets cached via User.get() before the route marks it as a service
account. Cleared and re-fetched after marking.
- memberOf came back as a bare string instead of a one-element array for
users in exactly one group, causing client-side permission checks to
iterate character-by-character and incorrectly deny access -- normalized
alongside the existing manager normalization.
- Added editable group membership on the profile page ("My groups"),
admin-only, using the existing per-group member endpoints.
Bumps to v1.1.8.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
Removes the LDAP bind-only service account type in favor of a single
Unix/POSIX account model, surfaced in a new Users > Service Accounts tab.
Adds a multi-valued `manager` field to every account (defaults to the
creator, editable, and grants edit rights on the accounts a person manages
without needing app_sso_admin). homeDirectory and loginShell are now
editable from the profile edit form.
Bumps to v1.1.7.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
jq-repeat 2.1.0 (release notes: https://github.com/wmantly/jq-repeat/releases/tag/v2.1.0)
brings real fixes (throttled-update race conditions, sorted-list
reverse() leaking elements, nested-scope isolation) and a few
behavior changes. Audited every usage in this repo against the
changelog before upgrading:
- push()/unshift() now return the new array length -- every call
site in this repo is a bare statement, none consume the return
value. No risk.
- __setPut/__setTake, jr-order-reverse, nested jq-repeat templates:
not used anywhere in this repo (unlike proxy's companion PR, which
needed the __setPut/__setTake fix).
Real risk found and fixed: update() is now trailing-edge throttled
(~50ms) even on the first call, not just rapid subsequent ones.
profile.ejs's editUser()/editUserSeccess() call $.scope.editProfile
.update()/renderProfile() (which itself calls update()) and
immediately slideDown() the same element -- with the old synchronous
behavior the form was already populated by then; with throttling it
could briefly show stale/empty data. Deferred both slideUp/slideDown
pairs by 60ms (past the throttle window), per the library's own
migration guidance. Verified live (real bundled image + Playwright,
logged in as admin): the edit form's fields show real data, not
empty/stale, when checked right as the slide-open completes.
It linked to fixed personal infrastructure (718it.biz, vm42.us,
git.theta42.com) with an admin's name in one label — not appropriate for
what should be a generic, publicly-releasable template. Flagged in
theta42/sso-manager-node#45, tracked as #46; resolves that issue.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Replace the separate Profile/API Tokens nav items with a single link
showing the logged-in user's name, pointing at their own profile.
- Merge admin.ejs + notifications.ejs into a new dashboard.ejs page.
/admin and /notifications now 301-redirect to /dashboard.
- Fold the API Tokens page into profile.ejs as a self-service-only
section, gated on isOwnProfile so it never appears when an admin
views another user's profile via /users/:uid. /api-tokens 301s to /.
- Fix: the section must not carry class="row" — app-base.js runs a
page-wide $('div.row').fadeIn() on every page load that would reveal
it regardless of the isOwnProfile check, since it fires before this
page's own gating logic.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>