Merge OAuth Apps + LDAP Info into one tabbed page; add Service Accounts

- OAuth Apps and LDAP Info are both "how do other apps/hosts plug into
  this SSO" concerns -- merged into a single /integrations page with
  tabs, replacing the two separate nav items with one. /oauth-clients
  and /ldap-info 301-redirect there for compat.
- Add a Service Accounts section under the LDAP tab: bind-only LDAP
  identities (organizationalRole + simpleSecurityObject, no
  posixAccount) for apps/hosts, as opposed to real people. Create,
  rotate password, and delete, all from the UI -- previously the only
  such account (theta-env's bootstrap-created cn=ldapclient) was
  invisible to the Users page entirely (filtered out by
  conf.ldap.userFilter) and had no GUI way to see or rotate it; the new
  ServiceAccount model uses the exact same objectClasses bootstrap.js
  already creates cn=ldapclient with, so it recognizes and manages that
  account too, not just ones created through this UI.
- The ldap-client bash snippet now points at "create one under Service
  Accounts above" instead of a bare textual example.

Verified against a real LDAP server (not just the dev sandbox's usual
unreachable one): created a service account, confirmed it binds
successfully with the generated password, rotated its password, and
deleted it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 19:57:32 -04:00
parent babbec98c0
commit 0b701dfc6f
8 changed files with 816 additions and 558 deletions
+10 -10
View File
@@ -77,15 +77,11 @@ router.get('/login', async function(req, res, next) {
res.render('login', {...values, redirect: req.query.redirect});
});
router.get('/oauth-clients', function(req, res, next) {
const issuer = ((conf.oauth && conf.oauth.issuer) || `${req.protocol}://${req.get('host')}`).replace(/\/$/, '');
res.render('oauth_clients', {...values, issuer, discoveryUrl: `${issuer}/.well-known/openid-configuration`});
});
// Everything a 3rd-party app or the ldap-client host script needs to bind
// this directory, derived from the running config + request host rather than
// hardcoded in a doc -- so it's always right for *this* deployment.
router.get('/ldap-info', function(req, res, next) {
// OAuth client management and LDAP connection info, merged into one page
// (tabs) -- both are "how do other apps/hosts plug into this SSO" concerns.
// LDAP values are derived from the running config + request host rather than
// hardcoded in a doc, so they're always right for *this* deployment.
router.get('/integrations', function(req, res, next) {
const issuer = ((conf.oauth && conf.oauth.issuer) || `${req.protocol}://${req.get('host')}`).replace(/\/$/, '');
const ldapHost = issuer.replace(/^https?:\/\//, '').replace(/:\d+$/, '');
@@ -96,8 +92,10 @@ router.get('/ldap-info', function(req, res, next) {
// -> dc=example,dc=com).
const baseDn = userBase.replace(/^ou=[^,]+,/i, '');
res.render('ldap_info', {
res.render('integrations', {
...values,
issuer,
discoveryUrl: `${issuer}/.well-known/openid-configuration`,
ldapHost,
ldapsUrl: `ldaps://${ldapHost}:636`,
baseDn,
@@ -109,6 +107,8 @@ router.get('/ldap-info', function(req, res, next) {
ssoUrl: issuer,
});
});
router.get('/oauth-clients', (req, res) => res.redirect(301, '/integrations'));
router.get('/ldap-info', (req, res) => res.redirect(301, '/integrations'));
// API Tokens is now a section on the Profile page (own profile only).
router.get('/api-tokens', (req, res) => res.redirect(301, '/'));