chore(release): public-release readiness fixes for 1.1.16

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
2026-07-18 23:14:36 -04:00
committed by GitHub
12 changed files with 106 additions and 47 deletions
+11
View File
@@ -6,6 +6,17 @@ correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
## [Unreleased]
## [1.1.16] - 2026-07-18
### 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 defaults in `conf/base.js` and `conf/development.js`: LDAP now defaults to `ldap://localhost` with `dc=example,dc=com`, and OIDC endpoints default to `https://sso.example.com` instead of internal theta42 infrastructure.
- The bootstrap `proxyadmin2` account now gets a random, one-time password when `auth.localAdminPass` is unset, instead of the well-known default `proxyadmin2`. The password is printed to the log on first creation and can be made deterministic by setting `auth.localAdminPass` in the secrets file.
### Fixed
- The global error handler no longer leaks `err.keys`, stack traces, or other internal details in JSON responses; only `name` and `message` are returned to clients.
- `DEPLOYMENT.md` and `docs/docker.md` now correctly describe the `CONF_SECRETS` env-var mechanism instead of the old symlink behavior.
## [1.1.15] - 2026-07-18
### Changed
+6 -5
View File
@@ -75,11 +75,12 @@ $EDITOR config/proxy-secrets.js # set oidc.clientId/clientSecret, ldap.bindP
docker compose up -d --build
```
`docker-entrypoint.sh` symlinks `/config/proxy-secrets.js` `/app/conf/secrets.js`
so `@simpleworkjs/conf` reads it. No `app_*` env is passed — `app_*` env would
override the file (env beats secrets.js in `@simpleworkjs/conf`), so the file is
kept authoritative. `RESOLVER` / `REAL_IP_FROM` / `NODE_ENV` / `NODE_PORT` are
OpenResty-runtime / process env, not `app_*` config, so they stay in the compose.
`docker-entrypoint.sh` sets `CONF_SECRETS=/config/proxy-secrets.js` so
`@simpleworkjs/conf` reads it directly. No `app_*` env is passed — `app_*` env
would override the file (env beats secrets.js in `@simpleworkjs/conf`), so the
file is kept authoritative. `RESOLVER` / `REAL_IP_FROM` / `NODE_ENV` /
`NODE_PORT` are OpenResty-runtime / process env, not `app_*` config, so they
stay in the compose.
> Running the unified `theta-env` stack? Its `setup.sh` generates
> `./config/proxy-secrets.js` (+ `./config/sso-secrets.js`) for you and
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) <year> <copyright holders>
Copyright (c) 2026 theta42
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+5 -5
View File
@@ -39,11 +39,11 @@ which deep-merges, in order:
3. `conf/secrets.js` (gitignored)
4. **`app_*` environment variables** — the highest-precedence layer
The bundled `docker-compose.yml` mount `./config/proxy-secrets.js` at `/config`,
and `docker-entrypoint.sh` symlinks it into `/app/conf/secrets.js` so the app
reads the OIDC + LDAP + auth wiring from the file. **No `app_*` env is passed**
`app_*` env beats `secrets.js`, so the file is authoritative only if the matching
`app_*` env is absent. See `secrets.js.example` for the shape.
The bundled `docker-compose.yml` mounts `./config/proxy-secrets.js` at `/config`,
and `docker-entrypoint.sh` sets `CONF_SECRETS=/config/proxy-secrets.js` so the
app reads the OIDC + LDAP + auth wiring from the file. **No `app_*` env is
passed** — `app_*` env beats `secrets.js`, so the file is authoritative only if
the matching `app_*` env is absent. See `secrets.js.example` for the shape.
Any env var starting with `app_` overrides the merged config; the rest of the
name splits on **double-underscore** (`__`) into a nested path. Values are
+12 -6
View File
@@ -100,15 +100,21 @@ app.use(async function(req, res, next) {
// Error handler. This is where `next()` will go on error
app.use(async function(err, req, res, next) {
try{
console.error(err.status || res.status, err.name, req.method, req.url);
try{
const status = err.status || 500;
console.error(status, err.name, req.method, req.url);
console.error(err.message);
console.error(err.stack);
if (err.stack) console.error(err.stack);
console.error('=========================================');
res.status(err.status || 500);
res.json({name: err.name, message: err.message, keys: err.keys});
res.status(status);
// Only expose safe, non-internal fields to the client.
const body = { name: err.name, message: err.message };
res.json(body);
}catch(error){
console.log('error in the catch all error fn....', error);
console.error('error in the catch-all error handler', error);
if (!res.headersSent) {
res.status(500).json({ name: 'Error', message: 'Internal server error' });
}
}
});
+8 -8
View File
@@ -6,10 +6,10 @@ module.exports = {
logo: "/static/img/theta42.svg", // shown in the nav; point at your own file under public/ (or an absolute URL) to white-label
userModel: 'redis', // pam, redis, ldap
ldap: {
url: 'ldap://192.168.1.55:389',
bindDN: 'cn=ldapclient service,ou=people,dc=theta42,dc=com',
url: 'ldap://localhost',
bindDN: 'cn=ldapclient service,ou=people,dc=example,dc=com',
bindPassword: '__IN SRECREST FILE__',
searchBase: 'ou=people,dc=theta42,dc=com',
searchBase: 'ou=people,dc=example,dc=com',
userFilter: '(objectClass=inetOrgPerson)',
userNameAttribute: 'uid'
},
@@ -29,11 +29,11 @@ module.exports = {
// redirectUri MUST be registered on the SSO client and match exactly.
oidc: {
enabled: true,
issuer: 'https://sso.theta42.com',
authorizationEndpoint: 'https://sso.theta42.com/oauth/authorize',
tokenEndpoint: 'https://sso.theta42.com/oauth/token',
userinfoEndpoint: 'https://sso.theta42.com/oauth/userinfo',
endSessionEndpoint: 'https://sso.theta42.com/oauth/logout',
issuer: 'https://sso.example.com',
authorizationEndpoint: 'https://sso.example.com/oauth/authorize',
tokenEndpoint: 'https://sso.example.com/oauth/token',
userinfoEndpoint: 'https://sso.example.com/oauth/userinfo',
endSessionEndpoint: 'https://sso.example.com/oauth/logout',
clientId: '__SET_ME__',
// Where the SSO sends the user back. Must be an absolute URL reachable
// by the browser and registered on the SSO client.
+3 -3
View File
@@ -4,10 +4,10 @@
module.exports = {
userModel: 'redis', // pam, redis, ldap
ldap: {
url: 'ldap://192.168.1.55:389',
bindDN: 'cn=ldapclient service,ou=people,dc=theta42,dc=com',
url: 'ldap://localhost',
bindDN: 'cn=ldapclient service,ou=people,dc=example,dc=com',
bindPassword: '__IN SRECREST FILE__',
searchBase: 'ou=people,dc=theta42,dc=com',
searchBase: 'ou=people,dc=example,dc=com',
userFilter: '(objectClass=inetOrgPerson)',
userNameAttribute: 'uid'
},
+14 -5
View File
@@ -90,10 +90,19 @@ User.register();
var defaultUser = 'proxyadmin2'
// Optional: an orchestrator (e.g. theta-env's setup.sh) can set
// auth.localAdminPass in proxy-secrets.js to a generated password so this
// bootstrap account isn't left at the well-known default (username ==
// password == "proxyadmin2"). Only used on first creation -- once the
// account exists this is never read again, so it's safe to leave set.
var defaultPass = (conf.auth && conf.auth.localAdminPass) || defaultUser;
// bootstrap account isn't left at a well-known default. Only used on first
// creation -- once the account exists this is never read again, so it's
// safe to leave set. If unset, a random password is generated and printed
// once; save it from the log or set auth.localAdminPass explicitly.
var defaultPass = (conf.auth && conf.auth.localAdminPass);
if (!defaultPass) {
defaultPass = crypto.randomBytes(16).toString('hex');
console.warn(`====================================================================`);
console.warn(`Bootstrap admin "${defaultUser}" created with random password:`);
console.warn(`${defaultPass}`);
console.warn(`Set auth.localAdminPass in your secrets file to make this deterministic.`);
console.warn(`====================================================================`);
}
try{
let user = await User.get(defaultUser);
}catch(error){
@@ -103,7 +112,7 @@ User.register();
password: defaultPass,
created_by: defaultUser
});
console.log(defaultUser, 'created', user);
console.log(defaultUser, 'created');
}catch(error){
console.error(error)
}
+34 -5
View File
@@ -1,17 +1,17 @@
{
"name": "proxy-api",
"version": "1.1.15",
"version": "1.1.16",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "proxy-api",
"version": "1.1.15",
"version": "1.1.16",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-free": "^7.3.0",
"@popperjs/core": "^2.11.8",
"@simpleworkjs/conf": "^1.1.0",
"@simpleworkjs/conf": "^1.2.0",
"acme-client": "^5.4.0",
"axios": "^1.13.5",
"bcrypt": "^6.0.0",
@@ -21,7 +21,7 @@
"express": "^5.2.1",
"express-rate-limit": "^8.5.2",
"extend": "^3.0.2",
"jq-repeat": "^2.1.0",
"jq-repeat": "^2.2.0",
"jquery": "^4.0.0",
"ldapts": "^8.1.8",
"linux-sys-user": "^1.2.0",
@@ -32,7 +32,8 @@
"p2psub": "^0.2.0",
"redis": "^6.1.0",
"socket.io": "^4.8.3",
"tld-extract": "^2.1.0"
"tld-extract": "^2.1.0",
"xss": "^1.0.15"
},
"devDependencies": {
"nodemon": "^3.1.11"
@@ -611,6 +612,12 @@
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
@@ -722,6 +729,12 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==",
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -2251,6 +2264,22 @@
"optional": true
}
}
},
"node_modules/xss": {
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz",
"integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==",
"license": "MIT",
"dependencies": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
},
"bin": {
"xss": "bin/xss"
},
"engines": {
"node": ">= 0.10.0"
}
}
}
}
+4 -4
View File
@@ -1,7 +1,6 @@
{
"name": "proxy-api",
"version": "1.1.15",
"private": true,
"version": "1.1.16",
"author": [
{
"name": "William Mantly",
@@ -43,12 +42,13 @@
"p2psub": "^0.2.0",
"redis": "^6.1.0",
"socket.io": "^4.8.3",
"tld-extract": "^2.1.0"
"tld-extract": "^2.1.0",
"xss": "^1.0.15"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://git.theta42.com/wmantly/proxy.git"
"url": "https://github.com/theta42/proxy.git"
},
"devDependencies": {
"nodemon": "^3.1.11"
+2 -1
View File
@@ -5,6 +5,7 @@ const path = require('path');
const router = require('express').Router();
const {rateLimit} = require('express-rate-limit');
const {marked} = require('marked');
const xss = require('xss');
const conf = require('@simpleworkjs/conf');
const buildInfo = require('../utils/build_info');
@@ -140,7 +141,7 @@ router.get('/:slug', function(req, res, next) {
docs: docList,
currentSlug: req.params.slug,
docTitle: doc.title,
docHtml: fixDocLinks(fixImagePaths(marked(content))),
docHtml: xss(fixDocLinks(fixImagePaths(marked(content)))),
});
} catch (error) {
next(error);
+6 -4
View File
@@ -27,12 +27,14 @@ class SocketServerJson {
this.onClientClose = new CallbackQueue(args.onClientClose);
this.onClientError = new CallbackQueue(args.onClientError);
// Set socket file permissions after listening
// 777 is acceptable here for single-use container environments
// Wrapped in try-catch as chmod may fail in test/restricted environments
// Set socket file permissions after listening. 660 (owner + group read/write)
// is the safest default; the Docker image runs both processes as root, and
// bare-metal operators should ensure the proxy service and openresty share a
// group when running as separate users. Wrapped in try-catch as chmod may
// fail in test/restricted environments.
this.onListen.push(() => {
try {
fs.chmodSync(this.socketFile, '777');
fs.chmodSync(this.socketFile, '660');
} catch(err) {
// Chmod may fail in test environments or certain filesystems
// Socket will still work with default permissions