Compare commits

...

7 Commits

Author SHA1 Message Date
wmantly 3b0f8f1f9a chore: bump sso-manager-node submodule to v1.1.17 (#72)
Picks up the configurable LDAPS hostname feature and docs.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-19 01:14:32 -04:00
wmantly 28016376ad feat: propagate CFG_LDAPS_HOST through setup and document LDAPS networking (#71)
Pass optional CFG_LDAPS_HOST from setup.env through setup.sh into the
generated ./config/sso-secrets.js as ldap.ldapsHost. This lets operators
advertise an internal-only LDAPS hostname (e.g. ldap.internal.example.com
or sso-manager) on the SSO /integrations page instead of the public
OAuth issuer, avoiding a public 636 port forward.

- setup.env.example: add CFG_LDAPS_HOST
- setup.sh: read/forward CFG_LDAPS_HOST into sso-secrets.js
- config.example/sso-secrets.js.example: document ldapsHost/ldapsPort
- .env.example: add LDAPS_HOST for legacy .env migrations
- docker-compose.yml: comment warning against public 636 forwarding
- README.md: explain CFG_LDAPS_HOST recommendation
- CHANGELOG.md + bump version to 1.1.19

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-19 01:14:03 -04:00
wmantly daa48dd154 chore(release): public-release readiness fixes for 1.1.18
🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-18 23:22:35 -04:00
wmantly a226ff9d00 chore: pin proxy and sso-manager-node submodules to v1.1.16
- proxy: 289a9587d62facf67876efc85472f01576e1d6d2
- sso-manager-node: 5a8030fd7d95edf83618b74ae05d790a62ccf940

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 23:22:13 -04:00
wmantly 65a4c1d839 fix: remove unused password variables from setup.sh summary
- After the setup summary stopped printing generated passwords,
  ADMIN_PASS and PROXY_LOCAL_ADMIN_PASS were assigned but never used,
  causing shellcheck SC2034 warnings in CI. Drop them from the summary.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 23:14:08 -04:00
wmantly 005c66d3f4 docs: update changelog for v1.1.18 submodule notes
- Add XSS/PII-logging security notes for proxy and sso-manager-node v1.1.16.
- Fix comparison links to point to v1.1.18/v1.1.16.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 23:08:32 -04:00
wmantly e46768bb68 chore(release): public-release readiness fixes for 1.1.18
- CHANGELOG.md now embeds full app-level release notes for submodule bumps.
- .env.example uses explicit CHANGE-ME placeholders instead of realistic-looking defaults.
- config.example comments describe the actual CONF_SECRETS mechanism.
- setup.sh summary no longer prints generated passwords to stdout.
- bootstrap.js fails hard instead of falling back to weak default passwords.

Note: submodule pins will be updated to v1.1.16 after the app PRs merge.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 22:11:13 -04:00
11 changed files with 123 additions and 22 deletions
+13 -4
View File
@@ -14,7 +14,8 @@
LDAP_BASE_DN=dc=example,dc=com
# DNS domain (dc=foo,dc=bar -> foo.bar). Leave blank to derive from LDAP_BASE_DN.
LDAP_DOMAIN=
LDAP_ADMIN_PASS=change-me-ldap-admin-password
# LDAP admin password. MUST be changed. Leave blank and setup.sh will generate one.
LDAP_ADMIN_PASS=CHANGE-ME
ORG_NAME="My Org"
# ── Public hostnames (REQUIRED) ───────────────────────────────────────────────
@@ -30,13 +31,15 @@ PROXY_HOST=proxy.example.com
# app_sso_oauth_admin, and logs in as them to register the proxy OAuth client.
# Re-running setup.sh resets this password to BOOTSTRAP_ADMIN_PASS.
BOOTSTRAP_ADMIN_UID=admin
BOOTSTRAP_ADMIN_PASS=change-me-admin-password
# First admin password. MUST be changed. Leave blank and setup.sh will generate one.
BOOTSTRAP_ADMIN_PASS=CHANGE-ME
BOOTSTRAP_ADMIN_EMAIL=admin@example.com
# ── Proxy LDAP service account (created by the bootstrap) ────────────────────
# The proxy binds to LDAP as cn=ldapclient,ou=people,<base> with this password.
# Re-running setup.sh resets it to LDAP_SERVICE_PASS.
LDAP_SERVICE_PASS=change-me-ldap-service-password
# LDAP service-account password. MUST be changed. Leave blank and setup.sh will generate one.
LDAP_SERVICE_PASS=CHANGE-ME
# ── OAuth JWT secret (REQUIRED — persist it) ────────────────────────────────
# Signs the SSO's access/refresh tokens. Generate with: openssl rand -hex 32
@@ -73,4 +76,10 @@ MGMT_BIND=0.0.0.0
# Defaults to LDAP_DOMAIN. Set to the hostname the proxy connects via
# (sso-manager inside the docker net uses the service name, which is in the
# cert's SAN, so the default is usually fine).
LDAP_CERT_CN=
LDAP_CERT_CN=
# ── Optional: LDAPS hostname shown on the SSO /integrations page ────────────────
# Leave blank to derive from the public SSO host (SSO_HOST). Set an internal-only
# name like 'ldap.internal.example.com' or 'sso-manager' so direct-LDAP clients
# don't need a public 636 port forward. See docs/ldap.md for network layouts.
LDAPS_HOST=
+69 -1
View File
@@ -10,6 +10,74 @@ for what changed inside the apps it composes.
## [Unreleased]
## [1.1.19] - 2026-07-18
### Bumped
- sso-manager-node -> [v1.1.17](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.17)
sso-manager-node:
### Added
- `conf.ldap.ldapsHost` and `conf.ldap.ldapsPort` config options for advertising a separate, internal-only LDAPS hostname on the `/integrations` page. Falls back to the public OAuth issuer host when unset.
- Contextual help panel on `/integrations` → LDAP explaining why LDAPS needs a hostname, why port 636 should not be forwarded publicly, and the recommended internal-DNS / Docker-internal alternatives.
- Tests for the `/integrations` route's LDAPS URL derivation and `ldapsHost` override.
### Changed
- `nodejs/package.json` / `package-lock.json` version bumped to `1.1.17`.
- `routes/index.js` now derives the displayed LDAPS URL from `conf.ldap.ldapsHost`/`ldapsPort` with fallback to the OAuth issuer host.
- `docs/configuration.md`, `docs/ldap.md`, `DEPLOYMENT.md`, and `secrets.js.example` document the new `ldapsHost`/`ldapsPort` options and recommended network layouts.
### theta-env own changes
- `setup.env.example` adds optional `CFG_LDAPS_HOST` for the internal LDAPS hostname.
- `setup.sh` passes `CFG_LDAPS_HOST` into the generated `./config/sso-secrets.js` as `ldap.ldapsHost`.
- `config.example/sso-secrets.js.example` documents `ldap.ldapsHost` / `ldap.ldapsPort`.
- `.env.example` adds `LDAPS_HOST` for legacy `.env` migrations.
- `docker-compose.yml` comments warn against forwarding 636 to the public internet.
- `README.md` explains the `CFG_LDAPS_HOST` recommendation in the port-forwarding section.
## [1.1.18] - 2026-07-18
### Bumped
- proxy -> [v1.1.16](https://github.com/theta42/proxy/releases/tag/v1.1.16)
- sso-manager-node -> [v1.1.16](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.16)
proxy:
### Changed
- Public-release packaging: removed `"private": true` from `nodejs/package.json`, corrected the repository URL to `https://github.com/theta42/proxy.git`, and fixed the MIT `LICENSE` copyright line.
- Genericized committed config defaults in `conf/base.js` and `conf/development.js` (`example.com` / `localhost` instead of theta42 infrastructure).
- The bootstrap `proxyadmin2` account now gets a random, one-time password when `auth.localAdminPass` is unset, instead of the well-known default.
### Security
- Sanitized rendered docs HTML with `xss` in `routes/docs.js`.
- The Unix socket JSON-RPC socket is now created with mode `660` instead of world-writable `777`.
### Fixed
- The global error handler no longer leaks `err.keys`, stack traces, or internal details in JSON responses.
- `DEPLOYMENT.md` and `docs/docker.md` now correctly describe the `CONF_SECRETS` env-var mechanism.
sso-manager-node:
### Security
- Hardened LDAP filter and DN construction against injection in `models/group_ldap.js` and `models/user_ldap.js`.
- Replaced `Math.random()`-based token/UUID/OTP generation with `crypto.randomUUID()` / `crypto.randomInt()` in `models/token.js`, `models/oauth_code.js`, and `models/oauth_client.js`.
- Refused startup when `oauth.jwtSecret` is missing or placeholder.
- Sanitized rendered docs/Terms-of-Service HTML with `xss` to block malicious markdown output.
- Removed full-object `console.log` of new-user data and reduced login error logging to `name`/`message` only.
### Changed
- Public-release packaging: removed `"private": true` from `nodejs/package.json` and bumped version to `1.1.16`.
### Fixed
- `models/email.js`: fixed from-address template rendering bug.
### theta-env own changes
- `CHANGELOG.md` now embeds the full app-level release notes for each submodule bump, not just links.
- `.env.example` no longer ships realistic-looking default passwords; values are clearly placeholders.
- `config.example/*.js.example` comments now describe the actual `CONF_SECRETS` env-var loading mechanism.
- `setup.sh` summary no longer prints generated passwords to stdout; it points to `./config/*.js`.
- `bootstrap/bootstrap.js` fails hard instead of falling back to weak default passwords when config is missing.
## [1.1.17] - 2026-07-18
### Bumped
@@ -214,7 +282,7 @@ First tagged release. Establishes the `vX.Y.Z` tag convention going forward.
- proxy -> [v1.1.0](https://github.com/theta42/proxy/releases/tag/v1.1.0)
- sso-manager-node -> [v1.1.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.0)
[Unreleased]: https://github.com/theta42/theta-env/compare/v1.1.17...HEAD
[Unreleased]: https://github.com/theta42/theta-env/compare/v1.1.18...HEAD
[1.1.17]: https://github.com/theta42/theta-env/compare/v1.1.16...v1.1.17
[1.1.16]: https://github.com/theta42/theta-env/compare/v1.1.15...v1.1.16
[1.1.15]: https://github.com/theta42/theta-env/compare/v1.1.14...v1.1.15
+4
View File
@@ -120,6 +120,10 @@ Optional extra ports (only if you need them):
- **636** (LDAPS) — only if a legacy app on another machine binds to LDAP
directly over the network. The proxy itself reaches LDAP over the internal
Docker network, so you do **not** need to expose 636 for the stack to work.
**Do not forward 636 to the public internet.** If you need LAN clients to bind
LDAP, set `CFG_LDAPS_HOST=ldap.internal.example.com` (or `sso-manager` for
same-host Docker clients) in `setup.env` and use an internal DNS record / cert
SAN. The default shows the public SSO hostname, which implies a public route.
### 4. Docker + Docker Compose
+11 -4
View File
@@ -44,8 +44,15 @@ const fs = require('fs');
const sso = require('/config/sso-secrets.js');
const proxy = require('/config/proxy-secrets.js');
const BASE_DN = (sso.stack && sso.stack.ldapBaseDn) || 'dc=example,dc=com';
const ADMIN_PASS = (sso.ldap && sso.ldap.bindPassword) || 'admin';
function requireConf(value, name) {
if (value === undefined || value === null || value === '' || value === 'CHANGE-ME') {
throw new Error(`${name} is not configured in /config/sso-secrets.js`);
}
return value;
}
const BASE_DN = requireConf((sso.stack && sso.stack.ldapBaseDn), 'stack.ldapBaseDn');
const ADMIN_PASS = requireConf((sso.ldap && sso.ldap.bindPassword), 'ldap.bindPassword');
const BIND_DN = `cn=admin,${BASE_DN}`;
const LDAP_URL = 'ldap://localhost:389';
@@ -53,9 +60,9 @@ const ADMIN_UID = (sso.bootstrap && sso.bootstrap.adminUid) || 'admin';
// The first admin *user's* password (cn=<uid>,ou=people,<base>). Distinct from
// ADMIN_PASS above, which is the LDAP *root* (cn=admin,<base>) bind password —
// two different accounts, two different secrets.
const ADMIN_USER_PASS = (sso.bootstrap && sso.bootstrap.adminPass) || 'admin';
const ADMIN_USER_PASS = requireConf((sso.bootstrap && sso.bootstrap.adminPass), 'bootstrap.adminPass');
const ADMIN_EMAIL = (sso.bootstrap && sso.bootstrap.adminEmail) || '';
const SVC_PASS = sso.serviceAccountPass || 'service';
const SVC_PASS = requireConf(sso.serviceAccountPass, 'serviceAccountPass');
const SSO_HOST = (sso.stack && sso.stack.ssoHost) || 'sso.example.com';
const PROXY_HOST = (sso.stack && sso.stack.proxyHost) || 'proxy.example.com';
+2 -2
View File
@@ -5,8 +5,8 @@
// bootstrap writes the OAuth client clientId/clientSecret back into it; this
// file documents the shape for manual editing / reference.
//
// The proxy app reads this via @simpleworkjs/conf (docker-entrypoint.sh
// symlinks it to /app/conf/secrets.js). Never commit ./config/.
// The proxy app reads this via @simpleworkjs/conf (docker-entrypoint.sh sets
// CONF_SECRETS to point at it). Never commit ./config/.
module.exports = {
oidc: {
+5 -2
View File
@@ -4,8 +4,8 @@
// `./setup.sh` generates ./config/sso-secrets.js for you on first run; this file
// documents the shape for manual editing / reference.
//
// The SSO app reads this via @simpleworkjs/conf (docker-entrypoint.sh symlinks
// it to /app/conf/secrets.js). The app ignores the extra stack/bootstrap/
// The SSO app reads this via @simpleworkjs/conf (docker-entrypoint.sh sets
// CONF_SECRETS to point at it). The app ignores the extra stack/bootstrap/
// serviceAccountPass keys (read by the orchestrator). Back this up off-host —
// it holds all SSO secrets. Never commit ./config/.
@@ -17,6 +17,9 @@ module.exports = {
bindPassword: 'CHANGE-ME', // slapd root + app bind password
userBase: 'ou=people,dc=example,dc=com',
groupBase: 'ou=groups,dc=example,dc=com',
// ldapsHost: 'ldap.internal.example.com', // optional: internal-only hostname
// shown on /integrations for direct LDAPS binds. Empty -> derive from issuer.
// ldapsPort: 636,
},
smtp: { // optional; leave host '' to skip
host: '', port: 587, secure: false,
+2
View File
@@ -46,6 +46,8 @@ services:
- "${SSO_BIND:-0.0.0.0}:${SSO_PORT:-3001}:3001"
# LDAPS for EXTERNAL direct-LDAP clients (legacy apps). The proxy itself
# reaches LDAPS over theta-net (sso-manager:636) without this host mapping.
# Prefer an internal-only hostname (set CFG_LDAPS_HOST in setup.env / ldapsHost
# in sso-secrets.js) and do NOT forward 636 to the public internet.
- "${LDAPS_PORT:-636}:636"
# Plain LDAP (389) is NOT mapped — direct-LDAP clients should use LDAPS.
environment:
+1 -1
Submodule proxy updated: c0e1aa666e...289a9587d6
+6
View File
@@ -36,6 +36,12 @@ CFG_DOMAIN=example.com
#CFG_ADMIN_UID=admin # initial SSO admin username
#CFG_ADMIN_EMAIL=admin@proxy.example.com # defaults to admin@<proxyHost>
#CFG_LDAP_CERT_CN= # LDAP TLS cert CN; empty -> defaults to the domain
#
# Hostname advertised on the SSO /integrations page for direct LDAPS binds.
# Leave blank to derive it from the public SSO host (same as oauth.issuer).
# Recommended: set an internal-only name like 'ldap.internal.example.com' or
# 'sso-manager' so clients don't need a public 636 port forward. See docs.
#CFG_LDAPS_HOST=
# Optional SMTP (outbound email from the SSO app). Leave blank to disable:
#CFG_SMTP_HOST=smtp.example.com
+9 -7
View File
@@ -240,6 +240,8 @@ module.exports = {
bindPassword: $(js_str "$CFG_LDAP_ADMIN_PASS"),
userBase: $(js_str "ou=people,${dn}"),
groupBase: $(js_str "ou=groups,${dn}"),
ldapsHost: $(js_str "${CFG_LDAPS_HOST:-}"),
ldapsPort: 636,
},
smtp: {
host: $(js_str "${CFG_SMTP_HOST:-}"),
@@ -355,6 +357,7 @@ ensure_config() {
CFG_ADMIN_UID="${CFG_ADMIN_UID:-}"
CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-}"
CFG_LDAP_CERT_CN="${CFG_LDAP_CERT_CN:-}"
CFG_LDAPS_HOST="${CFG_LDAPS_HOST:-}"
CFG_CLIENT_ID="${CFG_CLIENT_ID:-}"
CFG_CLIENT_SECRET="${CFG_CLIENT_SECRET:-}"
CFG_LDAP_ADMIN_PASS="${CFG_LDAP_ADMIN_PASS:-}"
@@ -383,6 +386,8 @@ ensure_config() {
CFG_ADMIN_PASS="${BOOTSTRAP_ADMIN_PASS:-$CFG_ADMIN_PASS}"
CFG_SVC_PASS="${LDAP_SERVICE_PASS:-$CFG_SVC_PASS}"
CFG_LDAP_CERT_CN="${LDAP_CERT_CN:-$CFG_LDAP_CERT_CN}"
# .env has no legacy LDAPS_HOST key; this stays as set in setup.env/env.
CFG_LDAPS_HOST="${CFG_LDAPS_HOST:-}"
CFG_SMTP_HOST="${SMTP_HOST:-${CFG_SMTP_HOST:-}}"
CFG_SMTP_PORT="${SMTP_PORT:-${CFG_SMTP_PORT:-}}"
CFG_SMTP_USER="${SMTP_USER:-${CFG_SMTP_USER:-}}"
@@ -416,6 +421,7 @@ ensure_config() {
CFG_ADMIN_UID="${CFG_ADMIN_UID:-admin}"
CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-admin@$CFG_PROXY_HOST}"
CFG_LDAP_CERT_CN="${CFG_LDAP_CERT_CN:-}"
CFG_LDAPS_HOST="${CFG_LDAPS_HOST:-}"
CFG_CLIENT_ID="${CFG_CLIENT_ID:-}"
CFG_CLIENT_SECRET="${CFG_CLIENT_SECRET:-}"
# Random secrets (generated fresh unless sourced/migrated above). These do
@@ -610,8 +616,6 @@ read_config_kv() {
LDAP_BASE_DN: (c.stack && c.stack.ldapBaseDn) || "",
ORG_NAME: c.name || "",
ADMIN_UID: (c.bootstrap && c.bootstrap.adminUid) || "",
ADMIN_PASS: (c.bootstrap && c.bootstrap.adminPass) || "",
PROXY_LOCAL_ADMIN_PASS: (p.auth && p.auth.localAdminPass) || "",
};
for (const k in o) console.log(k + "=" + (o[k] == null ? "" : o[k]));
' 2>/dev/null
@@ -621,8 +625,6 @@ cfgval() { echo "$CFG_OUT" | grep -m1 "^$1=" | cut -d= -f2-; }
SSO_HOST="$(cfgval SSO_HOST)"
PROXY_HOST="$(cfgval PROXY_HOST)"
ADMIN_UID="$(cfgval ADMIN_UID)"
ADMIN_PASS="$(cfgval ADMIN_PASS)"
PROXY_LOCAL_ADMIN_PASS="$(cfgval PROXY_LOCAL_ADMIN_PASS)"
info "Stack config:"
info " SSO host: https://${SSO_HOST}"
@@ -717,13 +719,13 @@ echo " first-run fallback: http://127.0.0.1:${SSO_PORT:-300
echo " Proxy mgmt UI: https://${PROXY_HOST}"
echo " first-run fallback: http://127.0.0.1:${MGMT_PORT:-3000}"
echo
echo " First admin login:"
echo " First admin login credentials are in ./config/sso-secrets.js:"
echo " user: ${ADMIN_UID}"
echo " pass: ${ADMIN_PASS}"
echo " pass: bootstrap.adminPass"
echo
echo " Proxy local admin (anti-lockout fallback if the SSO is unreachable):"
echo " user: proxyadmin2"
echo " pass: ${PROXY_LOCAL_ADMIN_PASS}"
echo " pass: auth.localAdminPass in ./config/proxy-secrets.js"
echo " (only shown when the account is first created; edit ./config/proxy-secrets.js"
echo " or use the proxy UI to change it afterward)"
echo