docker-compose.test.yml spins up the all-in-one OpenLDAP image, a
standalone Redis, and a test-runner that seeds the test user and runs
jest against them. globalSetup honors REDIS_URL; tests/setup.js
initializes the ORM and flushes test Redis keys before the run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Use published @simpleworkjs/orm ^0.2.8 (fixes redis adapter write path)
and model-redis ^1.6.0 instead of a local file: link that broke docker
npm ci with a misleading "no lockfile" error.
- OtpToken.issue/verify: replace nonexistent find()/listDetail() with
list({where}).
- routes/auth.js: ImpersonationToken.listDetail() -> list({where}).
- routes/token.js: drop listDetail() call; 404 on missing token instead
of returning {results: null} with 200 (orm get() returns null, does
not throw like model-redis Table.get did).
- OAuthClient: Resource has no is_valid column, so every client read as
disabled and all /oauth/authorize requests 400'd — validity now lives
in metadata (absent = valid). Also generate a unique slug on create
(Resource.slug is required+unique) and use Resource.get() for lookup.
- User.login: 401 cleanly when neither uid nor username is supplied.
- models/index.js: log ORM init and surface init failures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Sites & Replication page (added in the prior multi-master LDAP
release) 500'd on every load: views/sites.ejs included nonexistent
partials 'header'/'footer' instead of this app's actual 'top'/'bottom'.
Fixed to match every other view.
Also refreshed all README screenshots against the current UI and added
a new Sites & Replication screenshot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add conf.ldap.ldapsHost / conf.ldap.ldapsPort so the /integrations page
can advertise an internal-only LDAPS hostname separate from the public
OAuth issuer. This avoids forcing admins to port-forward 636 publicly.
- routes/index.js derives LDAPS URL from ldapsHost/ldapsPort with issuer fallback
- integrations.ejs adds a contextual help panel explaining TLS hostname
validation, the public-issuer default, and recommended internal-DNS /
Docker-internal alternatives
- conf/base.js, secrets.js.example, DEPLOYMENT.md, docs/configuration.md,
and docs/ldap.md document and expose the new options
- Add tests/integrations.test.js for default and custom ldapsHost behavior
- Bump version to 1.1.17
Co-authored-by: Claude <noreply@anthropic.com>
- Replace isomorphic-dompurify with xss to avoid ESM-only transitive
dependencies (jsdom/htmlparser2) that break the existing Jest test suite.
- Sanitize rendered docs and Terms-of-Service HTML via xss() in routes/docs.js
and routes/index.js.
- Remove full-object new-user logging from models/user_ldap.js and reduce
login-path error output to error.name/error.message only.
Co-Authored-By: Claude <noreply@anthropic.com>
- Add isomorphic-dompurify to sanitize rendered docs HTML and Terms of Service
- Remove addLdapUser full-object logging that included password hashes
- Log only error name/message on auth/login failures instead of full LDAP error objects
Co-Authored-By: Claude <noreply@anthropic.com>
routes/oauth.js now validates jwtSecret at module load time, so CI must
provide a non-placeholder value for the test runner.
Co-Authored-By: Claude <noreply@anthropic.com>
Security:
- Escape user-supplied values in LDAP filters and DNs (group_ldap.js, user_ldap.js)
- Replace Math.random() token/UUID/OTP generation with crypto.randomUUID / crypto.randomInt
- Refuse startup when oauth.jwtSecret is missing or placeholder
Fixes:
- Correct from-address template rendering in email.js
Packaging:
- Remove private flag and bump version to 1.1.16
Co-Authored-By: Claude <noreply@anthropic.com>
Replaces the old flag-driven, copy-based installer with an idempotent
git-clone-and-symlink installer matching theta42/proxy's ops/install.sh
pattern, so `wget -O - .../install.sh | sudo bash` works the same way
for both apps:
- Installs to /opt/theta42/sso-manager (was /opt/sso-manager, and the
repo had to already be checked out locally -- now it clones itself).
- First run only: bootstraps OpenLDAP (modules, overlays, schema,
directory tree, SSO groups -- ops/ldap-setup.sh) with a generated
admin password + JWT secret, and seeds /etc/sso-manager/secrets.js
(was /opt/sso-manager/conf/secrets.js, hand-filled from CLI flags).
Later runs never touch LDAP or the secrets file again.
- ops/systemd/sso-manager.service now points at the new install path
and sets CONF_SECRETS=/etc/sso-manager/secrets.js (requires
@simpleworkjs/conf >= 1.2.0, already the pinned version) instead of
the app needing a config file inside the repo checkout.
- Prints the version it's updating from/to (or "Already up to date")
on every run, instead of updating silently.
Two real bugs found and fixed while testing this end-to-end in a clean
container:
- The debconf `slapd/domain` value was computed as
`${LDAP_BASE_DN#dc=}` ("example,dc=com" for "dc=example,dc=com")
instead of a proper dotted domain -- slapd's postinst hangs
indefinitely on a malformed domain instead of failing cleanly.
Fixed to derive it the same way the secrets file already did
("example.com").
- ops/ldap-setup.sh's ppolicy-overlay checks used an LDAP substring
filter, `(olcOverlay=*ppolicy*)`, against an attribute that doesn't
support substring matching -- it silently matched nothing even when
the overlay was correctly configured (stored as "{0}ppolicy"),
so the final verification always reported failure and `set -e`
aborted the installer after LDAP was set up but before the app was.
Fixed to filter on `(objectClass=olcOverlayConfig)` and let the
existing DN-based grep narrow it down, matching the pattern already
used by every other check in that script.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
conf 1.2.0 adds CONF_SECRETS, an env var to point at the secrets file
directly -- use it in the Docker entrypoint instead of symlinking the
mounted file into /app/conf/secrets.js, so the app no longer needs
write access to its own conf/ directory to pick up mounted secrets.
jq-repeat 2.2.0 is a compatible feature release (sort(), replace(),
faster leading-edge update() timing); no call-site changes needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A repeated Edit-tool bump had overwritten the previous top version
heading instead of inserting a new one above it, silently merging
v1.1.10's release notes into v1.1.11's section with the v1.1.10
heading missing entirely. The underlying content was still present,
just missing its own "## [1.1.10]" header -- restored.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The new concept docs (and their "See also" reciprocal links) reference
each other by real filename -- "concepts-accounts.html" -- which is the
correct, working URL on the Jekyll/GitHub Pages build (a page's URL there
IS its filename stem), but doesn't match this viewer's own short slugs
(DOCS keys, e.g. "accounts" -> /docs/accounts), so fixDocLinks() left
those links unrewritten and 404ing in-app.
Rather than rewrite the docs to two different link forms depending on
target, resolve by filename as a fallback when the slug lookup misses --
one link written in a doc now works correctly on both targets.
Bumps to v1.1.13.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
- 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
- A ? icon in the top-right header deep-links to the doc most relevant to
the current page (client-side path mapping, same pattern already used
for top-nav active-link highlighting -- no server-side "current section"
local exists to key off of instead). Falls back to the docs index.
- GET /docs/search does a plain line-substring search over the existing
allowlisted doc set. No new dependency, stays usable with no internet
access.
Bumps to v1.1.10.
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