wmantly 158109de59 Documentation cleanup for public release (#38)
* docs: cleanup for public release (fix stale/wrong API docs, LICENSE, versions)

Documentation cleanup ahead of the public release announcement. Fixes a set
of confirmed issues from a prior audit:

- LICENSE: fill in MIT template placeholders (theta42, 2026).
- README.md: fix broken API docs link (api.md -> API.md), correct required
  Node.js version (13.x -> 20.x), add the missing app_sso_invite group to
  the LDAP groups table, scrub hardcoded dc=theta42,dc=com to the generic
  dc=example,dc=com used elsewhere, add a "Recommended: Docker or
  install.sh" section pointing to DEPLOYMENT.md/docs before the manual
  OpenLDAP walkthrough, and drop an emoji from a warning callout.
- nodejs/api.md: deleted — it was a stale/legacy doc with wrong routes,
  wrong request bodies, and endpoints that are dead/commented-out code.
  The root API.md is the accurate, current reference; README now links
  there directly.
- API.md: add the missing app_sso_invite permission group, fix the
  documented invite response to match the real {token, link, mail_sent}
  payload, document the previously-undocumented GET/PUT/DELETE
  /api/user/invite endpoints, add the real allowed_groups field to the
  OAuth client management examples, and document POST /api/oauth/authorize
  (the endpoint that actually issues the code after consent).
- nodejs/routes/auth.js + API.md: fix "emaill address" typo in the
  password-reset response message (source and docs kept in sync).
- DEPLOYMENT.md: fix the top-level summary to mention Redis, matching
  docs/deployment.md and the entrypoint behavior it already documents.

Flagged, not changed: tos.md reads like a personal home-lab acceptable-use
policy (Emby/Gitea/Proxmox/Discord/Signal, first-person "the admin") rather
than generic OSS docs. Left in place pending a manual decision to
genericize, relocate, or remove it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: genericize tos.md template, track runtime-editable terms in #39

Removes operator-specific references (Emby, Gitea, Proxmox, Discord,
Signal, first-person "the admin") so the shipped tos.md reads as a
neutral starting template rather than one operator's internal policy.
Actual runtime editability (admin/legal editing terms without a code
change) is tracked in issue #39, not implemented here.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 23:21:27 -04:00
2026-07-11 00:54:49 -04:00

SSO manager

API docs

API docs

Server set up

The supported, tested deployment paths are Docker (an all-in-one image bundling the app + OpenLDAP + Redis) and the install.sh bare-metal installer for Debian/Ubuntu. Most users should start there:

  • Deployment Guide — Docker + bare metal, configuration layers, backups, troubleshooting.
  • docs/index.md — documentation home (also published as a GitHub Pages site), with links to configuration, LDAP, and OAuth/OIDC docs.

Manual / advanced: raw OpenLDAP setup

The rest of this section walks through configuring OpenLDAP by hand. This is the manual/advanced path — useful if you're pointing the app at an existing LDAP server, or want to understand exactly what install.sh and the Docker entrypoint automate for you. Most deployments should use Docker or install.sh above instead.

The server requires:

  • NodeJS 20.x
  • LDAP server

Setting up the whole stack (Docker) or want the secrets-file layout? See DEPLOYMENT.md — your domain is entered once, as the LDAP base DN (stack.ldapBaseDn); the LDAP DNs (bindDN/userBase/groupBase) and oauth.issuer all derive from it and must stay consistent. Running the unified theta-env stack, setup.sh fills those in for you from setup.env.

OpenLDAP configuration

Password hashing (required)

Passwords are stored using {SSHA512} (salted SHA-512). The pw-sha2 module must be loaded in slapd before creating or resetting any passwords.

ldapadd -Y EXTERNAL -H ldapi:/// << 'EOF'
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: pw-sha2
EOF

Verify the module is working:

slappasswd -h {SSHA512} -s testpassword

Existing {MD5} password hashes continue to work after the module is loaded — users are migrated to SSHA512 the next time they change their password.

Account locking (required for active/inactive toggle)

User activation and deactivation uses the OpenLDAP ppolicy overlay. When a user is marked inactive, pwdAccountLockedTime is set on their entry, which causes all LDAP binds to fail — including logins to Emby, Gitea, and any other LDAP-backed service.

The easy way: run ops/ldap-setup.sh on the LDAP server. It is idempotent, auto-detects the correct user database, applies everything below (pw-sha2, ppolicy module/overlay/schema, custom schema, policy entry, SSO groups) and verifies ppolicy is active at the end:

sudo ./ops/ldap-setup.sh -p <admin-password>

If the app returns 503 OpenLDAP ppolicy overlay is not configured on PUT /api/user/<uid>/active, run this script — it means the overlay is not attached to the database holding your users. The manual steps below are equivalent and kept for reference.

1. Load the ppolicy module:

ldapadd -Y EXTERNAL -H ldapi:/// << 'EOF'
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: ppolicy
EOF

2. Add the overlay to your user database:

Warning: The database index below ({1}mdb) is not the same on every install. Confirm yours first — the overlay must go on the database whose olcSuffix is your base DN, or account locking silently won't apply to your users:

ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b cn=config \
  '(&(objectClass=olcDatabaseConfig)(olcSuffix=dc=example,dc=com))' dn
ldapadd -Y EXTERNAL -H ldapi:/// << 'EOF'
dn: olcOverlay=ppolicy,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcPPolicyConfig
olcOverlay: ppolicy
olcPPolicyDefault: cn=ppolicy,ou=policies,dc=example,dc=com
olcPPolicyUseLockout: TRUE
olcPPolicyHashCleartext: FALSE
EOF

3. Create the policies container and default policy:

ldapadd -x -D "cn=admin,dc=example,dc=com" -W << 'EOF'
dn: ou=policies,dc=example,dc=com
objectClass: organizationalUnit
ou: policies

dn: cn=ppolicy,ou=policies,dc=example,dc=com
objectClass: top
objectClass: organizationalRole
objectClass: pwdPolicy
cn: ppolicy
pwdAttribute: 2.5.4.35
pwdLockout: FALSE
pwdMustChange: FALSE
pwdAllowUserChange: TRUE
EOF

Verify by locking a test account and confirming bind fails:

ldapmodify -x -D "cn=admin,dc=example,dc=com" -W << 'EOF'
dn: cn=testuser,ou=people,dc=example,dc=com
changetype: modify
replace: pwdAccountLockedTime
pwdAccountLockedTime: 000001010000Z
EOF

Custom schema (required for date of birth)

User accounts store a dateOfBirth field (ISO 8601 YYYY-MM-DD) for age verification. This requires a custom attribute type and auxiliary objectClass to be loaded into the OpenLDAP schema before any accounts are created.

Load the schema:

ldapadd -Y EXTERNAL -H ldapi:/// << 'EOF'
dn: cn=theta42,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: theta42
olcAttributeTypes: ( 1.3.6.1.4.1.99999.1.1
  NAME 'dateOfBirth'
  DESC 'Date of birth in ISO 8601 format YYYY-MM-DD'
  EQUALITY caseExactMatch
  SUBSTR caseExactSubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE )
olcObjectClasses: ( 1.3.6.1.4.1.99999.2.1
  NAME 'theta42Person'
  DESC 'Theta42 SSO extended person attributes'
  AUXILIARY
  MAY ( dateOfBirth ) )
EOF

Verify the schema loaded:

ldapsearch -Y EXTERNAL -H ldapi:/// -b "cn=theta42,cn=schema,cn=config" olcAttributeTypes olcObjectClasses

Note: The OID prefix 1.3.6.1.4.1.99999 is used for internal/private schemas. If this deployment is ever connected to a federated directory, register a proper PEN at https://www.iana.org/assignments/enterprise-numbers and update the OIDs.

Required LDAP groups

Group Purpose
app_sso_admin Full admin access: manage users, groups, OAuth clients
app_sso_oauth_admin Manage OAuth clients only
app_sso_invite Invitation management

Logs (Docker)

The all-in-one image runs the Node app and slapd (OpenLDAP) in one container, both writing to the container's stdout/stderr, so docker compose logs is the primary view (slapd runs with -d 0, so LDAP output is there too).

docker compose logs -f sso-manager          # app + slapd (stdout/stderr)
# or, by container name:
docker logs -f sso-manager

docker compose logs --tail=200 --since=10m sso-manager   # recent context

# Query the directory directly to confirm LDAP is healthy
docker compose exec sso-manager ldapsearch -x -H ldap://localhost:389 \
  -D "cn=admin,$LDAP_BASE_DN" -W -b "$LDAP_BASE_DN"

See DEPLOYMENT.mdTroubleshooting for LDAP-specific errors.

S
Description
LDAP GUI and API manager for use with SSO.
Readme MIT 58 MiB
Languages
JavaScript 63.6%
EJS 31%
Shell 5.2%
CSS 0.2%