wmantly fe9b7c168b Dockerize SSO Manager (all-in-one image) + GitHub Pages docs
All-in-one Dockerfile.openldap bundling the app + OpenLDAP + Redis in one
container, plus an idempotent bare-metal install.sh, and a Jekyll docs site
for GitHub Pages:
- Dockerfile.openldap (node:20-alpine; openldap + pw-sha2/ppolicy/memberof/
  refint; dumb-init PID 1; npm ci --omit=dev; tos.md copied to /).
- docker-entrypoint.sh: generate slapd.conf (mdb + overlays + TLS + indexes +
  access), self-signed LDAPS cert, seed directory tree + required groups,
  bundled redis, export app_* config, exec node.
- docker-compose.yml, .dockerignore, DEPLOYMENT.md, secrets.js.example.
- install.sh: idempotent Debian/Ubuntu bare-metal installer (Node 20.x,
  OpenLDAP, Redis, systemd unit) with flags + --dry-run/--skip-ldap/--skip-app.
- ops/ldif/: memberof/refint/tls/index/nodes/logging LDIFs.
- nodejs/conf/base.js: generic defaults (dc=example,dc=com / localhost /
  SSO Manager) so per-deployment values move to secrets.js or app_* env.
- nodejs/package.json: bump @simpleworkjs/conf to ^1.1.0 (app_* env overrides).
- nodejs/routes/index.js: /health endpoint for healthchecks.
- docs/: _config.yml + index/deployment/configuration/oauth/ldap pages
  (jekyll-theme-cayman) for GitHub Pages from /docs.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-11 17:03:16 -04:00
2026-07-01 11:10:09 -04:00
2026-07-11 00:54:49 -04:00
2020-05-03 18:22:51 -04:00
2026-07-01 23:09:44 -04:00
2026-07-01 11:10:09 -04:00

SSO manager

API docs

API docs

Server set up

The server requires:

  • NodeJS 13.x
  • LDAP server

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:

⚠️ 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=theta42,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=theta42,dc=com
olcPPolicyUseLockout: TRUE
olcPPolicyHashCleartext: FALSE
EOF

3. Create the policies container and default policy:

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

dn: cn=ppolicy,ou=policies,dc=theta42,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=theta42,dc=com" -W << 'EOF'
dn: cn=testuser,ou=people,dc=theta42,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
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%