Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 289a9587d6 | |||
| 6ede072213 | |||
| bdaba513a7 | |||
| d1dd40d60a | |||
| c0e1aa666e | |||
| f8d620f4d3 | |||
| 899c4d91d6 | |||
| 0f268fdcae | |||
| da0ed0e2ad |
+22
-1
@@ -6,6 +6,26 @@ correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
||||||
|
- `ops/install.sh` now installs to `/opt/theta42/proxy` (was `/var/www/proxy`) and seeds `/etc/proxy/secrets.js` from `secrets.js.example` on first run (never overwritten on later runs), instead of requiring a manual `nodejs/conf/secrets.js` edit inside the repo checkout. `ops/proxy.service` sets `CONF_SECRETS=/etc/proxy/secrets.js` to match.
|
||||||
|
- `install.sh` now prints the version it's updating from/to (or "Already up to date") on every run, instead of updating silently.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `install.sh` could hang indefinitely on a fresh host if a base package pulled in `tzdata` as a new dependency — it prompted interactively for a timezone with no TTY attached. Set `DEBIAN_FRONTEND=noninteractive`.
|
||||||
|
|
||||||
## [1.1.14] - 2026-07-17
|
## [1.1.14] - 2026-07-17
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -103,7 +123,8 @@ First tagged release. Establishes the `vX.Y.Z` tag convention that the in-app up
|
|||||||
- Standalone backup script (`ops/backup.sh`) for deployments not using theta-env's orchestrator — snapshots Redis and `./config`, with retention.
|
- Standalone backup script (`ops/backup.sh`) for deployments not using theta-env's orchestrator — snapshots Redis and `./config`, with retention.
|
||||||
- Admin-only in-app banner that checks GitHub releases every 24h and surfaces available updates.
|
- Admin-only in-app banner that checks GitHub releases every 24h and surfaces available updates.
|
||||||
|
|
||||||
[Unreleased]: https://github.com/theta42/proxy/compare/v1.1.14...HEAD
|
[Unreleased]: https://github.com/theta42/proxy/compare/v1.1.15...HEAD
|
||||||
|
[1.1.15]: https://github.com/theta42/proxy/compare/v1.1.14...v1.1.15
|
||||||
[1.1.14]: https://github.com/theta42/proxy/compare/v1.1.13...v1.1.14
|
[1.1.14]: https://github.com/theta42/proxy/compare/v1.1.13...v1.1.14
|
||||||
[1.1.13]: https://github.com/theta42/proxy/compare/v1.1.12...v1.1.13
|
[1.1.13]: https://github.com/theta42/proxy/compare/v1.1.12...v1.1.13
|
||||||
[1.1.12]: https://github.com/theta42/proxy/compare/v1.1.11...v1.1.12
|
[1.1.12]: https://github.com/theta42/proxy/compare/v1.1.11...v1.1.12
|
||||||
|
|||||||
+20
-10
@@ -75,11 +75,12 @@ $EDITOR config/proxy-secrets.js # set oidc.clientId/clientSecret, ldap.bindP
|
|||||||
docker compose up -d --build
|
docker compose up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
`docker-entrypoint.sh` symlinks `/config/proxy-secrets.js` → `/app/conf/secrets.js`
|
`docker-entrypoint.sh` sets `CONF_SECRETS=/config/proxy-secrets.js` so
|
||||||
so `@simpleworkjs/conf` reads it. No `app_*` env is passed — `app_*` env would
|
`@simpleworkjs/conf` reads it directly. No `app_*` env is passed — `app_*` env
|
||||||
override the file (env beats secrets.js in `@simpleworkjs/conf`), so the file is
|
would override the file (env beats secrets.js in `@simpleworkjs/conf`), so the
|
||||||
kept authoritative. `RESOLVER` / `REAL_IP_FROM` / `NODE_ENV` / `NODE_PORT` are
|
file is kept authoritative. `RESOLVER` / `REAL_IP_FROM` / `NODE_ENV` /
|
||||||
OpenResty-runtime / process env, not `app_*` config, so they stay in the compose.
|
`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
|
> Running the unified `theta-env` stack? Its `setup.sh` generates
|
||||||
> `./config/proxy-secrets.js` (+ `./config/sso-secrets.js`) for you and
|
> `./config/proxy-secrets.js` (+ `./config/sso-secrets.js`) for you and
|
||||||
@@ -227,16 +228,25 @@ docker compose logs --tail=200 --since=10m proxy # recent context
|
|||||||
|
|
||||||
`ops/install.sh` is an idempotent installer: it installs Node.js 22.x, OpenResty
|
`ops/install.sh` is an idempotent installer: it installs Node.js 22.x, OpenResty
|
||||||
(from openresty.org), Lua modules (luarocks), Redis, force-syncs the repo to
|
(from openresty.org), Lua modules (luarocks), Redis, force-syncs the repo to
|
||||||
`/var/www/proxy`, symlinks the OpenResty + systemd config from the repo, and
|
`/opt/theta42/proxy`, symlinks the OpenResty + systemd config from the repo, and
|
||||||
starts `proxy.service`. Re-run it to update.
|
starts `proxy.service`. Re-run it to update — it prints the version you're
|
||||||
|
updating from and to (or "Already up to date" if there's nothing new).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget -O - https://raw.githubusercontent.com/theta42/proxy/master/ops/install.sh | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
or, if you already have the repo checked out:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./ops/install.sh
|
sudo ./ops/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Configuration is file-based: write `nodejs/conf/secrets.js` with the OIDC +
|
Configuration is file-based: on first run the installer seeds
|
||||||
LDAP values (see `nodejs/conf/base.js` for the shape), then
|
`/etc/proxy/secrets.js` from `secrets.js.example` (placeholders you must fill
|
||||||
`sudo systemctl restart proxy`.
|
in — OIDC + LDAP values, see `nodejs/conf/base.js` for the shape). Edit it,
|
||||||
|
then `sudo systemctl restart proxy`. Later runs never touch an existing
|
||||||
|
secrets file.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
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:
|
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:
|
||||||
|
|
||||||
|
|||||||
@@ -127,10 +127,15 @@ This installer will:
|
|||||||
- Install and configure Redis
|
- Install and configure Redis
|
||||||
- Set up SSL fallback certificates
|
- Set up SSL fallback certificates
|
||||||
- Install Lua dependencies (lua-resty-auto-ssl, luasocket)
|
- Install Lua dependencies (lua-resty-auto-ssl, luasocket)
|
||||||
- Clone and install the proxy application
|
- Clone/update the proxy application at `/opt/theta42/proxy`
|
||||||
|
- Seed `/etc/proxy/secrets.js` on first run (edit it, then re-run or `systemctl restart proxy`)
|
||||||
- Configure systemd service
|
- Configure systemd service
|
||||||
- Start the proxy service
|
- Start the proxy service
|
||||||
|
|
||||||
|
It's idempotent and safe to re-run — re-running it updates the app in place and
|
||||||
|
prints the version you're updating from and to (e.g. `Updated v1.1.13 ->
|
||||||
|
v1.1.14`), or `Already up to date` if there's nothing new.
|
||||||
|
|
||||||
## Logs (Docker)
|
## Logs (Docker)
|
||||||
|
|
||||||
The all-in-one image runs OpenResty in the foreground and the Node app in the
|
The all-in-one image runs OpenResty in the foreground and the Node app in the
|
||||||
@@ -224,15 +229,24 @@ cp ops/nginx_conf/targetinfo.lua /usr/local/openresty/lualib/targetinfo.lua
|
|||||||
|
|
||||||
Clone and install:
|
Clone and install:
|
||||||
```bash
|
```bash
|
||||||
cd /var/www
|
mkdir -p /opt/theta42
|
||||||
|
cd /opt/theta42
|
||||||
git clone https://github.com/theta42/proxy.git
|
git clone https://github.com/theta42/proxy.git
|
||||||
cd proxy/nodejs
|
cd proxy/nodejs
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Configure secrets:
|
||||||
|
```bash
|
||||||
|
mkdir -p /etc/proxy
|
||||||
|
cp ../secrets.js.example /etc/proxy/secrets.js
|
||||||
|
chmod 600 /etc/proxy/secrets.js
|
||||||
|
$EDITOR /etc/proxy/secrets.js
|
||||||
|
```
|
||||||
|
|
||||||
Create systemd service:
|
Create systemd service:
|
||||||
```bash
|
```bash
|
||||||
cp ops/proxy.service /etc/systemd/system/proxy.service
|
cp ../ops/proxy.service /etc/systemd/system/proxy.service
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable proxy.service
|
systemctl enable proxy.service
|
||||||
systemctl start proxy.service
|
systemctl start proxy.service
|
||||||
|
|||||||
+5
-5
@@ -39,11 +39,11 @@ which deep-merges, in order:
|
|||||||
3. `conf/secrets.js` (gitignored)
|
3. `conf/secrets.js` (gitignored)
|
||||||
4. **`app_*` environment variables** — the highest-precedence layer
|
4. **`app_*` environment variables** — the highest-precedence layer
|
||||||
|
|
||||||
The bundled `docker-compose.yml` mount `./config/proxy-secrets.js` at `/config`,
|
The bundled `docker-compose.yml` mounts `./config/proxy-secrets.js` at `/config`,
|
||||||
and `docker-entrypoint.sh` symlinks it into `/app/conf/secrets.js` so the app
|
and `docker-entrypoint.sh` sets `CONF_SECRETS=/config/proxy-secrets.js` so the
|
||||||
reads the OIDC + LDAP + auth wiring from the file. **No `app_*` env is passed** —
|
app reads the OIDC + LDAP + auth wiring from the file. **No `app_*` env is
|
||||||
`app_*` env beats `secrets.js`, so the file is authoritative only if the matching
|
passed** — `app_*` env beats `secrets.js`, so the file is authoritative only if
|
||||||
`app_*` env is absent. See `secrets.js.example` for the shape.
|
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
|
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
|
name splits on **double-underscore** (`__`) into a nested path. Values are
|
||||||
|
|||||||
+16
-3
@@ -146,7 +146,8 @@ openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \
|
|||||||
Clone the repository and copy configuration files:
|
Clone the repository and copy configuration files:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /var/www
|
mkdir -p /opt/theta42
|
||||||
|
cd /opt/theta42
|
||||||
git clone https://github.com/theta42/proxy.git
|
git clone https://github.com/theta42/proxy.git
|
||||||
cd proxy
|
cd proxy
|
||||||
|
|
||||||
@@ -161,14 +162,26 @@ cp ops/nginx_conf/targetinfo.lua /usr/local/openresty/lualib/targetinfo.lua
|
|||||||
### Step 7: Install Application
|
### Step 7: Install Application
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /var/www/proxy/nodejs
|
cd /opt/theta42/proxy/nodejs
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Step 7b: Configure Secrets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p /etc/proxy
|
||||||
|
cp /opt/theta42/proxy/secrets.js.example /etc/proxy/secrets.js
|
||||||
|
chmod 600 /etc/proxy/secrets.js
|
||||||
|
$EDITOR /etc/proxy/secrets.js # set oidc.clientId/clientSecret, ldap.bindPassword, ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`@simpleworkjs/conf` reads this file via the `CONF_SECRETS` env var, which the
|
||||||
|
systemd unit below sets to `/etc/proxy/secrets.js`.
|
||||||
|
|
||||||
### Step 8: Configure Systemd Service
|
### Step 8: Configure Systemd Service
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp /var/www/proxy/ops/proxy.service /etc/systemd/system/proxy.service
|
cp /opt/theta42/proxy/ops/proxy.service /etc/systemd/system/proxy.service
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable proxy.service
|
systemctl enable proxy.service
|
||||||
systemctl start proxy.service
|
systemctl start proxy.service
|
||||||
|
|||||||
+12
-6
@@ -100,15 +100,21 @@ app.use(async function(req, res, next) {
|
|||||||
|
|
||||||
// Error handler. This is where `next()` will go on error
|
// Error handler. This is where `next()` will go on error
|
||||||
app.use(async function(err, req, res, next) {
|
app.use(async function(err, req, res, next) {
|
||||||
try{
|
try{
|
||||||
console.error(err.status || res.status, err.name, req.method, req.url);
|
const status = err.status || 500;
|
||||||
|
console.error(status, err.name, req.method, req.url);
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
console.error(err.stack);
|
if (err.stack) console.error(err.stack);
|
||||||
console.error('=========================================');
|
console.error('=========================================');
|
||||||
|
|
||||||
res.status(err.status || 500);
|
res.status(status);
|
||||||
res.json({name: err.name, message: err.message, keys: err.keys});
|
// Only expose safe, non-internal fields to the client.
|
||||||
|
const body = { name: err.name, message: err.message };
|
||||||
|
res.json(body);
|
||||||
}catch(error){
|
}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
@@ -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
|
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
|
userModel: 'redis', // pam, redis, ldap
|
||||||
ldap: {
|
ldap: {
|
||||||
url: 'ldap://192.168.1.55:389',
|
url: 'ldap://localhost',
|
||||||
bindDN: 'cn=ldapclient service,ou=people,dc=theta42,dc=com',
|
bindDN: 'cn=ldapclient service,ou=people,dc=example,dc=com',
|
||||||
bindPassword: '__IN SRECREST FILE__',
|
bindPassword: '__IN SRECREST FILE__',
|
||||||
searchBase: 'ou=people,dc=theta42,dc=com',
|
searchBase: 'ou=people,dc=example,dc=com',
|
||||||
userFilter: '(objectClass=inetOrgPerson)',
|
userFilter: '(objectClass=inetOrgPerson)',
|
||||||
userNameAttribute: 'uid'
|
userNameAttribute: 'uid'
|
||||||
},
|
},
|
||||||
@@ -29,11 +29,11 @@ module.exports = {
|
|||||||
// redirectUri MUST be registered on the SSO client and match exactly.
|
// redirectUri MUST be registered on the SSO client and match exactly.
|
||||||
oidc: {
|
oidc: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
issuer: 'https://sso.theta42.com',
|
issuer: 'https://sso.example.com',
|
||||||
authorizationEndpoint: 'https://sso.theta42.com/oauth/authorize',
|
authorizationEndpoint: 'https://sso.example.com/oauth/authorize',
|
||||||
tokenEndpoint: 'https://sso.theta42.com/oauth/token',
|
tokenEndpoint: 'https://sso.example.com/oauth/token',
|
||||||
userinfoEndpoint: 'https://sso.theta42.com/oauth/userinfo',
|
userinfoEndpoint: 'https://sso.example.com/oauth/userinfo',
|
||||||
endSessionEndpoint: 'https://sso.theta42.com/oauth/logout',
|
endSessionEndpoint: 'https://sso.example.com/oauth/logout',
|
||||||
clientId: '__SET_ME__',
|
clientId: '__SET_ME__',
|
||||||
// Where the SSO sends the user back. Must be an absolute URL reachable
|
// Where the SSO sends the user back. Must be an absolute URL reachable
|
||||||
// by the browser and registered on the SSO client.
|
// by the browser and registered on the SSO client.
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
userModel: 'redis', // pam, redis, ldap
|
userModel: 'redis', // pam, redis, ldap
|
||||||
ldap: {
|
ldap: {
|
||||||
url: 'ldap://192.168.1.55:389',
|
url: 'ldap://localhost',
|
||||||
bindDN: 'cn=ldapclient service,ou=people,dc=theta42,dc=com',
|
bindDN: 'cn=ldapclient service,ou=people,dc=example,dc=com',
|
||||||
bindPassword: '__IN SRECREST FILE__',
|
bindPassword: '__IN SRECREST FILE__',
|
||||||
searchBase: 'ou=people,dc=theta42,dc=com',
|
searchBase: 'ou=people,dc=example,dc=com',
|
||||||
userFilter: '(objectClass=inetOrgPerson)',
|
userFilter: '(objectClass=inetOrgPerson)',
|
||||||
userNameAttribute: 'uid'
|
userNameAttribute: 'uid'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -90,10 +90,19 @@ User.register();
|
|||||||
var defaultUser = 'proxyadmin2'
|
var defaultUser = 'proxyadmin2'
|
||||||
// Optional: an orchestrator (e.g. theta-env's setup.sh) can set
|
// Optional: an orchestrator (e.g. theta-env's setup.sh) can set
|
||||||
// auth.localAdminPass in proxy-secrets.js to a generated password so this
|
// auth.localAdminPass in proxy-secrets.js to a generated password so this
|
||||||
// bootstrap account isn't left at the well-known default (username ==
|
// bootstrap account isn't left at a well-known default. Only used on first
|
||||||
// password == "proxyadmin2"). Only used on first creation -- once the
|
// creation -- once the account exists this is never read again, so it's
|
||||||
// account exists this is never read again, so it's safe to leave set.
|
// safe to leave set. If unset, a random password is generated and printed
|
||||||
var defaultPass = (conf.auth && conf.auth.localAdminPass) || defaultUser;
|
// 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{
|
try{
|
||||||
let user = await User.get(defaultUser);
|
let user = await User.get(defaultUser);
|
||||||
}catch(error){
|
}catch(error){
|
||||||
@@ -103,7 +112,7 @@ User.register();
|
|||||||
password: defaultPass,
|
password: defaultPass,
|
||||||
created_by: defaultUser
|
created_by: defaultUser
|
||||||
});
|
});
|
||||||
console.log(defaultUser, 'created', user);
|
console.log(defaultUser, 'created');
|
||||||
}catch(error){
|
}catch(error){
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+34
-5
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "proxy-api",
|
"name": "proxy-api",
|
||||||
"version": "1.1.14",
|
"version": "1.1.16",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "proxy-api",
|
"name": "proxy-api",
|
||||||
"version": "1.1.14",
|
"version": "1.1.16",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^7.3.0",
|
"@fortawesome/fontawesome-free": "^7.3.0",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@simpleworkjs/conf": "^1.1.0",
|
"@simpleworkjs/conf": "^1.2.0",
|
||||||
"acme-client": "^5.4.0",
|
"acme-client": "^5.4.0",
|
||||||
"axios": "^1.13.5",
|
"axios": "^1.13.5",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"express-rate-limit": "^8.5.2",
|
"express-rate-limit": "^8.5.2",
|
||||||
"extend": "^3.0.2",
|
"extend": "^3.0.2",
|
||||||
"jq-repeat": "^2.1.0",
|
"jq-repeat": "^2.2.0",
|
||||||
"jquery": "^4.0.0",
|
"jquery": "^4.0.0",
|
||||||
"ldapts": "^8.1.8",
|
"ldapts": "^8.1.8",
|
||||||
"linux-sys-user": "^1.2.0",
|
"linux-sys-user": "^1.2.0",
|
||||||
@@ -32,7 +32,8 @@
|
|||||||
"p2psub": "^0.2.0",
|
"p2psub": "^0.2.0",
|
||||||
"redis": "^6.1.0",
|
"redis": "^6.1.0",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
"tld-extract": "^2.1.0"
|
"tld-extract": "^2.1.0",
|
||||||
|
"xss": "^1.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.11"
|
"nodemon": "^3.1.11"
|
||||||
@@ -611,6 +612,12 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/compressible": {
|
||||||
"version": "2.0.18",
|
"version": "2.0.18",
|
||||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||||
@@ -722,6 +729,12 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
@@ -2251,6 +2264,22 @@
|
|||||||
"optional": true
|
"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
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "proxy-api",
|
"name": "proxy-api",
|
||||||
"version": "1.1.14",
|
"version": "1.1.16",
|
||||||
"private": true,
|
|
||||||
"author": [
|
"author": [
|
||||||
{
|
{
|
||||||
"name": "William Mantly",
|
"name": "William Mantly",
|
||||||
@@ -43,12 +42,13 @@
|
|||||||
"p2psub": "^0.2.0",
|
"p2psub": "^0.2.0",
|
||||||
"redis": "^6.1.0",
|
"redis": "^6.1.0",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
"tld-extract": "^2.1.0"
|
"tld-extract": "^2.1.0",
|
||||||
|
"xss": "^1.0.15"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.theta42.com/wmantly/proxy.git"
|
"url": "https://github.com/theta42/proxy.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.11"
|
"nodemon": "^3.1.11"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const path = require('path');
|
|||||||
const router = require('express').Router();
|
const router = require('express').Router();
|
||||||
const {rateLimit} = require('express-rate-limit');
|
const {rateLimit} = require('express-rate-limit');
|
||||||
const {marked} = require('marked');
|
const {marked} = require('marked');
|
||||||
|
const xss = require('xss');
|
||||||
const conf = require('@simpleworkjs/conf');
|
const conf = require('@simpleworkjs/conf');
|
||||||
const buildInfo = require('../utils/build_info');
|
const buildInfo = require('../utils/build_info');
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ router.get('/:slug', function(req, res, next) {
|
|||||||
docs: docList,
|
docs: docList,
|
||||||
currentSlug: req.params.slug,
|
currentSlug: req.params.slug,
|
||||||
docTitle: doc.title,
|
docTitle: doc.title,
|
||||||
docHtml: fixDocLinks(fixImagePaths(marked(content))),
|
docHtml: xss(fixDocLinks(fixImagePaths(marked(content)))),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ class SocketServerJson {
|
|||||||
this.onClientClose = new CallbackQueue(args.onClientClose);
|
this.onClientClose = new CallbackQueue(args.onClientClose);
|
||||||
this.onClientError = new CallbackQueue(args.onClientError);
|
this.onClientError = new CallbackQueue(args.onClientError);
|
||||||
|
|
||||||
// Set socket file permissions after listening
|
// Set socket file permissions after listening. 660 (owner + group read/write)
|
||||||
// 777 is acceptable here for single-use container environments
|
// is the safest default; the Docker image runs both processes as root, and
|
||||||
// Wrapped in try-catch as chmod may fail in test/restricted environments
|
// 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(() => {
|
this.onListen.push(() => {
|
||||||
try {
|
try {
|
||||||
fs.chmodSync(this.socketFile, '777');
|
fs.chmodSync(this.socketFile, '660');
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
// Chmod may fail in test environments or certain filesystems
|
// Chmod may fail in test environments or certain filesystems
|
||||||
// Socket will still work with default permissions
|
// Socket will still work with default permissions
|
||||||
|
|||||||
+48
-3
@@ -9,19 +9,29 @@
|
|||||||
# update is just "sync the repo + reload" -- the files under /etc always track
|
# update is just "sync the repo + reload" -- the files under /etc always track
|
||||||
# the repo, so there is nothing to re-copy.
|
# the repo, so there is nothing to re-copy.
|
||||||
#
|
#
|
||||||
|
# Secrets live at $SECRETS_FILE (/etc/proxy/secrets.js by default), outside the
|
||||||
|
# repo checkout so they survive the hard reset below. First run seeds it from
|
||||||
|
# secrets.js.example (placeholders you must fill in); later runs never touch
|
||||||
|
# an existing file.
|
||||||
|
#
|
||||||
# Intended to be driven by CI/CD with no human writes on prod: the checkout is
|
# Intended to be driven by CI/CD with no human writes on prod: the checkout is
|
||||||
# hard-reset to origin/$BRANCH on every run, so the box deterministically mirrors
|
# hard-reset to origin/$BRANCH on every run, so the box deterministically mirrors
|
||||||
# the repo (any drift on the box is discarded).
|
# the repo (any drift on the box is discarded).
|
||||||
#
|
#
|
||||||
# Usage: sudo ./install.sh (override with REPO_URL=, REPO_DIR=, BRANCH=)
|
# Usage: sudo ./install.sh (override with REPO_URL=, REPO_DIR=, BRANCH=,
|
||||||
|
# SECRETS_FILE=)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# Never block on an interactive git credential prompt in CI.
|
# Never block on an interactive git credential prompt in CI.
|
||||||
export GIT_TERMINAL_PROMPT=0
|
export GIT_TERMINAL_PROMPT=0
|
||||||
|
# Never block on an interactive debconf prompt (e.g. tzdata, pulled in as a
|
||||||
|
# dependency on a box that's never configured it).
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
REPO_URL="${REPO_URL:-https://github.com/theta42/proxy.git}"
|
REPO_URL="${REPO_URL:-https://github.com/theta42/proxy.git}"
|
||||||
REPO_DIR="${REPO_DIR:-/var/www/proxy}"
|
REPO_DIR="${REPO_DIR:-/opt/theta42/proxy}"
|
||||||
BRANCH="${BRANCH:-master}"
|
BRANCH="${BRANCH:-master}"
|
||||||
NODE_MAJOR=22
|
NODE_MAJOR=22
|
||||||
|
SECRETS_FILE="${SECRETS_FILE:-/etc/proxy/secrets.js}"
|
||||||
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
echo "This script must be run as root (try: sudo $0)" >&2
|
echo "This script must be run as root (try: sudo $0)" >&2
|
||||||
@@ -34,6 +44,19 @@ link(){
|
|||||||
echo "linked $2 -> $1"
|
echo "linked $2 -> $1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Read the "version" field out of a package.json without depending on Node
|
||||||
|
# being installed yet (this runs before the Node.js install step below).
|
||||||
|
pkg_version(){
|
||||||
|
sed -n 's/^[[:space:]]*"version":[[:space:]]*"\([^"]*\)".*/\1/p' "$1" | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Installed version before this run touches anything, for the upgrade banner
|
||||||
|
# at the end. Empty on a fresh install (no prior checkout).
|
||||||
|
CURRENT_VERSION=""
|
||||||
|
if [ -f "$REPO_DIR/nodejs/package.json" ]; then
|
||||||
|
CURRENT_VERSION="$(pkg_version "$REPO_DIR/nodejs/package.json")"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "==> Base packages"
|
echo "==> Base packages"
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
@@ -134,6 +157,20 @@ else
|
|||||||
git clone --branch "$BRANCH" "$REPO_URL" "$REPO_DIR"
|
git clone --branch "$BRANCH" "$REPO_URL" "$REPO_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
NEW_VERSION="$(pkg_version "$REPO_DIR/nodejs/package.json")"
|
||||||
|
|
||||||
|
echo "==> Secrets file at ${SECRETS_FILE}"
|
||||||
|
install -d -m 0750 "$(dirname "$SECRETS_FILE")"
|
||||||
|
if [ ! -f "$SECRETS_FILE" ]; then
|
||||||
|
cp "$REPO_DIR/secrets.js.example" "$SECRETS_FILE"
|
||||||
|
chmod 600 "$SECRETS_FILE"
|
||||||
|
echo " seeded ${SECRETS_FILE} from secrets.js.example -- EDIT IT before the proxy will work:"
|
||||||
|
echo " \$EDITOR ${SECRETS_FILE}"
|
||||||
|
echo " then re-run this script (or: sudo systemctl restart proxy)"
|
||||||
|
else
|
||||||
|
echo " ${SECRETS_FILE} already exists, leaving it untouched"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "==> Symlink config from the repo"
|
echo "==> Symlink config from the repo"
|
||||||
install -d /etc/openresty/sites-enabled /var/log/nginx
|
install -d /etc/openresty/sites-enabled /var/log/nginx
|
||||||
link "$REPO_DIR/ops/nginx_conf/nginx.conf" /etc/openresty/nginx.conf
|
link "$REPO_DIR/ops/nginx_conf/nginx.conf" /etc/openresty/nginx.conf
|
||||||
@@ -162,4 +199,12 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> Done. Update later with: sudo BRANCH=${BRANCH} $0"
|
echo "==> Done."
|
||||||
|
if [ -z "$CURRENT_VERSION" ]; then
|
||||||
|
echo " Installed v${NEW_VERSION}."
|
||||||
|
elif [ "$CURRENT_VERSION" = "$NEW_VERSION" ]; then
|
||||||
|
echo " Already up to date (v${NEW_VERSION})."
|
||||||
|
else
|
||||||
|
echo " Updated v${CURRENT_VERSION} -> v${NEW_VERSION}."
|
||||||
|
fi
|
||||||
|
echo " Update later with: sudo BRANCH=${BRANCH} $0"
|
||||||
|
|||||||
+3
-2
@@ -8,9 +8,10 @@ Type=simple
|
|||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=1
|
RestartSec=1
|
||||||
User=root
|
User=root
|
||||||
WorkingDirectory=/var/www/proxy/nodejs
|
WorkingDirectory=/opt/theta42/proxy/nodejs
|
||||||
Environment="NODE_ENV=production"
|
Environment="NODE_ENV=production"
|
||||||
ExecStart=/usr/bin/env node /var/www/proxy/nodejs/bin/www
|
Environment="CONF_SECRETS=/etc/proxy/secrets.js"
|
||||||
|
ExecStart=/usr/bin/env node /opt/theta42/proxy/nodejs/bin/www
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
+7
-4
@@ -6,13 +6,16 @@
|
|||||||
// direct LDAP client for user lookups. This file supplies that wiring.
|
// direct LDAP client for user lookups. This file supplies that wiring.
|
||||||
//
|
//
|
||||||
// Docker / unified stack: place at ./config/proxy-secrets.js and bind-mount
|
// Docker / unified stack: place at ./config/proxy-secrets.js and bind-mount
|
||||||
// ./config at /config (see docker-compose.yml); docker-entrypoint.sh symlinks
|
// ./config at /config (see docker-compose.yml); docker-entrypoint.sh points the
|
||||||
// it into /app/conf/secrets.js so @simpleworkjs/conf reads it. No app_* env
|
// CONF_SECRETS env var at it so @simpleworkjs/conf reads it. No app_* env
|
||||||
// should be passed — app_* env beats this file in @simpleworkjs/conf, so the
|
// should be passed — app_* env beats this file in @simpleworkjs/conf, so the
|
||||||
// file is authoritative only if the matching app_* env is absent.
|
// file is authoritative only if the matching app_* env is absent.
|
||||||
//
|
//
|
||||||
// Bare-metal: copy to nodejs/conf/secrets.js and fill in your values. Values
|
// Bare-metal: ops/install.sh seeds this file at /etc/proxy/secrets.js on first
|
||||||
// here override conf/base.js and win over <environment>.js.
|
// run (with placeholders for the values it can't guess) and points the
|
||||||
|
// systemd unit's CONF_SECRETS env var at it. Fill in your values, then
|
||||||
|
// `sudo systemctl restart proxy`. Values here override conf/base.js and win
|
||||||
|
// over <environment>.js.
|
||||||
//
|
//
|
||||||
// Only the keys the app reads are listed below. The `stack` key is read by the
|
// Only the keys the app reads are listed below. The `stack` key is read by the
|
||||||
// theta-env orchestrator (setup.sh) and ignored by the app.
|
// theta-env orchestrator (setup.sh) and ignored by the app.
|
||||||
|
|||||||
Reference in New Issue
Block a user