Documentation cleanup for public release (#123)

Prepares the docs for the public release announcement: removes obsolete/dead
material, fixes drift between the API reference and the actual routes, and
standardizes on the default GitHub Pages URL.

- Remove Vagrant entirely: delete Vagrantfile, docs/dev_setup.md, and stale
  vagrant references in .gitignore/.dockerignore; rewrite openresty/README.md
  to describe the actual (currently unused) directory and point to
  ops/nginx_conf/ for the real OpenResty config.
- Delete docs/Update 4.11.md (personal scratch changelog) and drop both its
  and dev_setup.md's references from docs/README.md's Legacy Documentation
  section.
- Remove checkmark emoji from docs/contributing.md's PR Requirements list.
- Bring the auth model docs up to date with the code: document
  GET /api/auth/oidc/start + /callback, the /api/permission and /api/group
  RBAC routers, the /api/dns/dynamic/* sub-API, and /api/api-token (self
  -service PATs) in both nodejs/api.md and docs/api.md; add the missing
  "Clear Host Cache" section; drop the invite-token/SSH-key endpoints that no
  longer exist in nodejs/routes/user.js; note admin-only routes. Mention
  OIDC/LDAP/RBAC as core features in README.md.
- Keep nodejs/api.md and docs/api.md fully in sync (same body, differing only
  in Jekyll front matter / relative links) instead of letting them drift.
- Fix Node.js version references (20.x -> 22.x) in README.md and
  docs/installation.md to match ops/install.sh and the Dockerfile.
- Note that the manual nginx-conf/systemd install steps in README.md and
  docs/installation.md won't auto-track repo changes the way install.sh's
  symlink approach does, and recommend install.sh.
- Update the stale test/unit file lists in docs/contributing.md and
  nodejs/test/README.md to match the actual directory contents.
- Add npm run test:integration to README.md's Running Tests section.
- Add nodejs/conf/, nodejs/controller/, and nodejs/migrations/ to the project
  structure diagrams in README.md, docs/architecture.md, and
  docs/contributing.md.
- Standardize "CloudFlare" -> "Cloudflare" everywhere to match the actual API
  value in nodejs/models/dns_provider.js.
- Add the missing app_auth__adminGroups row to DEPLOYMENT.md's app_* table.
- Delete docs/CNAME (custom domain) so GitHub Pages serves from the default
  https://theta42.github.io/proxy/, matching docs/README.md.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 23:20:59 -04:00
committed by GitHub
parent 3df7d8c5cb
commit 5139fbb79a
17 changed files with 943 additions and 382 deletions
+1 -2
View File
@@ -7,13 +7,12 @@ nodejs/node_modules/
nodejs/test/
nodejs/npm-debug.log*
# Bare-metal installer + chef/vagrant ops not needed in the image.
# Bare-metal installer + chef ops not needed in the image.
ops/install.sh
ops/cert.sh
ops/cookbooks/
ops/roles/
ops/proxy.service
Vagrantfile
# Docs site (served via GitHub Pages, not from the image).
docs/
-1
View File
@@ -58,7 +58,6 @@ typings/
# dotenv environment variables file
.env
.vagrant
*~
*#
.#*
+4
View File
@@ -39,8 +39,12 @@ are `JSON.parse`-coerced when possible and kept as raw strings otherwise.
| `app_ldap__tlsOptions__rejectUnauthorized` | `conf.ldap.tlsOptions.rejectUnauthorized` | `false` for self-signed LDAPS |
| `app_ldap__tlsOptions__ca` | `conf.ldap.tlsOptions.ca` | path to a CA cert for strict trust |
| `app_auth__adminUsers` | `conf.auth.adminUsers` | local anti-lockout admin (uid) |
| `app_auth__adminGroups` | `conf.auth.adminGroups` | SSO/LDAP groups that are global admin (JSON array) |
| `app_redis__prefix` | `conf.redis.prefix` | default `proxy_` |
See [`docs/docker.md`](docs/docker.md) for a shorter, container-focused version
of this reference.
> **Requires `@simpleworkjs/conf` >= 1.1.0.** The Docker image will not honor
> `app_*` env vars on 1.0.0. The lock is already on `^1.1.0`; if you regenerate it:
> ```bash
+23 -6
View File
@@ -8,12 +8,17 @@ A reverse proxy and HTTPS termination service using OpenResty/nginx with a manag
- Automated HTTPS/SSL certificate management via Let's Encrypt
- Support for HTTP-01 (auto-ssl) and DNS-01 (wildcard) ACME challenges
- Multiple DNS provider integrations (CloudFlare, DigitalOcean, PorkBun)
- Multiple DNS provider integrations (Cloudflare, DigitalOcean, PorkBun)
- Wildcard SSL certificate support with automatic renewal
- Dynamic host routing with wildcard domain matching (*, **)
- Web-based management interface
- RESTful API for automation
- User authentication and management
- **OIDC login** — the proxy is an OpenID Connect client of an external SSO
(e.g. [`theta42/sso-manager-node`](https://github.com/theta42/sso-manager-node))
- **Direct LDAP lookups**, independent of the OIDC flow
- **Role-based access control (RBAC)** — global admins, local groups, and
per-domain permissions (viewer/manager) via `/api/permission` and `/api/group`
- Self-service API tokens (PATs) for scripting/CI without a browser session
- Unix socket-based host lookup for high-performance routing
## Requirements
@@ -34,7 +39,7 @@ wget -O - https://raw.githubusercontent.com/theta42/proxy/master/ops/install.sh
```
This installer will:
- Install Node.js 20.x
- Install Node.js 22.x
- Install OpenResty and required dependencies
- Install and configure Redis
- Set up SSL fallback certificates
@@ -68,6 +73,14 @@ docker compose logs --tail=200 --since=10m proxy
For manual installation or other distributions, see the detailed steps below.
> **Recommended path:** `ops/install.sh` is idempotent and safe to re-run — it
> symlinks the OpenResty/systemd config from the repo checkout, so updates
> stay in sync automatically. The manual steps below copy those same files
> instead of symlinking them, so they will **not** auto-track future changes
> to `ops/nginx_conf/` or `ops/proxy.service` — you'd need to re-copy them
> yourself after every update. Use the manual path only if `install.sh`
> doesn't fit your distribution.
### System Dependencies
**Ubuntu/Debian:**
@@ -75,10 +88,10 @@ For manual installation or other distributions, see the detailed steps below.
apt install libpam0g-dev build-essential redis-server luarocks -y
```
**Node.js 20.x:**
**Node.js 22.x:**
```bash
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=20
NODE_MAJOR=22
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
apt update && apt install nodejs -y
```
@@ -147,7 +160,7 @@ systemctl start proxy.service
For wildcard SSL certificates, configure a DNS provider via the web UI or API:
**Supported providers:**
- **CloudFlare** - Requires API token
- **Cloudflare** - Requires API token
- **DigitalOcean** - Requires API token
- **PorkBun** - Requires API key and secret API key
@@ -197,6 +210,7 @@ npm run dev # Runs with nodemon for auto-reload
```bash
npm test # Run all tests
npm run test:unit # Run unit tests only
npm run test:integration # Run integration tests only
npm run test:watch # Watch mode for development
```
@@ -223,6 +237,9 @@ MIT - See LICENSE file for details.
proxy/
├── nodejs/ # Node.js backend application
│ ├── bin/ # Entry point (www)
│ ├── conf/ # Configuration (base.js, environment overlays, secrets.js)
│ ├── controller/ # App-level wiring (pubsub, startup)
│ ├── migrations/ # One-off Redis data migration scripts
│ ├── models/ # Data models (Host, User, DNS providers)
│ ├── routes/ # API routes
│ ├── services/ # Background services (host lookup, scheduler)
Vendored
-142
View File
@@ -1,142 +0,0 @@
require 'json'
begin
secrets = JSON.parse(File.read('secrets.json'))
puts 'Loading secrets file'
rescue
secrets = {}
puts 'Secrets file not found'
end
class ::Hash
def deep_merge(second)
second.each do |key, value|
if value.class == Hash and self[key.to_sym]
self[key.to_sym].deep_merge(value)
else
self[key.to_sym] = value
end
end
return self
end
end
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
config.vm.box = "ubuntu/jammy64"
config.vm.synced_folder '.', '/vagrant' # The vagrant dir just stopped automounting
# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# NOTE: This will enable public access to the opened port
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 443, host: 8443
config.vm.network "forwarded_port", guest: 3000, host: 8300
# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
config.vm.provider 'virtualbox' do |vb|
# Customize the amount of memory on the VM:
vb.memory = '1024'
vb.cpus = "2"
# vb.default_nic_type = "virtio"
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
end
#
# View the documentation for the provider you are using for more
# information on available options.
# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
config.vm.provision "shell", inline: <<~SHELL
apt-get update
if ! apt list ruby-dev | grep installed; then
# apt-add-repository ppa:brightbox/ruby-ng -y
sudo apt-get install -y build-essential resolvconf ruby-full gem
gem install chef -v 17.10.0
fi
if ! which berks >/dev/null; then
gem install ruby-shadow berkshelf --no-document
ln -s /opt/chef/embedded/bin/berks /usr/local/bin/berks
fi
cd /vagrant
cd /vagrant/ops/cookbooks
rm -rf vendor
rm -rf $HOME/.berksfile
if [ -f ".Berksfile.lock" ]; then
berks update
else
berks install
fi
berks vendor vendor
SHELL
config.vm.provision 'chef_solo' do |chef|
chef.arguments = "--chef-license accept"
# chef.version = '15.7.31' # version 14.12.9 fails to run
chef.cookbooks_path = [
'ops/cookbooks/',
'ops/cookbooks/vendor/'
]
chef.roles_path ='ops/roles'
chef.add_role('common')
chef.json = {
'working-dir': '/vagrant',
'app': {
'name': 't42-proxy',
'run_user': 'root',
'domain': 'proxy.local',
},
'python': {
# 'working-dir': 'django',
'version': '2.7'
},
'nodejs': {
'working-dir': 'nodejs',
'port': '3000',
'install_version': 18,
'exec_file': 'bin/www',
'service': true,
},
'redis':{
'unix': {
'perm': '777'
}
},
'web':{
'admin_email': 'admin2342@example.com',
'do_ssl': true,
't42-proxy': true
},
}.deep_merge(secrets);
end
config.vm.provision "shell", inline: <<~SHELL
cd /vagrant/openresty
shopt -s extglob
rm -frv !("README.md")
cp -a "/usr/local/openresty/nginx/conf/." /vagrant/openresty
rm -rf /usr/local/openresty/nginx/conf/
ln -s /vagrant/openresty/ /usr/local/openresty/nginx/conf
SHELL
end
-1
View File
@@ -1 +0,0 @@
proxy.projects.theta42.com
-5
View File
@@ -37,8 +37,3 @@ The site uses the Cayman theme (`jekyll-theme-cayman`). Configuration is in `_co
2. Commit and push to master branch
3. GitHub Pages automatically rebuilds (may take 1-2 minutes)
4. Changes visible at https://theta42.github.io/proxy/
## Legacy Documentation
- `dev_setup.md` - Old development setup notes (kept for reference)
- `Update 4.11.md` - Old update notes (kept for reference)
-46
View File
@@ -1,46 +0,0 @@
# Update 4.11
## Changes
The API routes have changed a bit and I will update the documentation soon.
All the endpoints now have key validations and return standardized errors.
Any 500 class error should be reported as an issue.
End points for working with user have been added.
A GUI as need added and can be accessed from root of port 3000,
`http://server.ip:3000/`
## How to get it
* Navigate to where the project is installed. This should be done as root.
* Pull the newest version from master
```bash
git pull origin master
```
* Move to the NodeJS folder
```bash
cd nodejs
```
* Update and install the NPM packages
```bash
npm update
npm install
```
* Run the migration script for the redis Hosts.
```bash
node migrations/host_1.js
```
* Restart the proxy service or container.
* Enoy the GUI!
+433 -26
View File
@@ -7,7 +7,26 @@ title: API Reference
[← Back to Home](index.html)
All API endpoints require authentication via the `auth-token` header unless otherwise noted.
All API endpoints require authentication unless otherwise noted. Three
authentication methods are supported:
- **`auth-token` header** — a browser-session token from `POST /api/auth/login`
or the OIDC flow (below).
- **`Authorization: Bearer <token>` header** — a self-service API token (PAT,
see [API Tokens](#api-tokens)), for scripts/CI without a browser session.
- **OIDC (browser)** — if the proxy is configured as an OIDC client of an SSO
(`app_oidc__*` / `conf.oidc`, see [DEPLOYMENT.md](https://github.com/theta42/proxy/blob/master/DEPLOYMENT.md)),
users can log in via `GET /api/auth/oidc/start` instead of posting a
username/password.
The proxy can also be configured as a **direct LDAP client** (`app_ldap__*` /
`conf.ldap`) for looking up/validating users, independent of the OIDC flow —
see DEPLOYMENT.md for the full configuration reference.
Authenticated requests also carry **RBAC** (role-based access control):
global admins can manage everything; other users are scoped to `viewer` or
`manager` rights on specific domains via [Permissions](#permissions) and
[Groups](#groups).
Base URL: `https://your-proxy-host.com/api`
@@ -47,17 +66,167 @@ curl -H "auth-token: your-token-here" \
**Responses:**
- `200` `{"message": "Bye"}`
### OIDC Login (start)
**GET** `/api/auth/oidc/start`
Begin the OIDC authorization-code flow: creates a PKCE + state challenge and
redirects the browser to the configured SSO's authorize endpoint. Only
available when `conf.oidc.enabled` is true.
**Query Parameters:**
- `redirect` - Internal path to return to after login (optional; sanitized to same-origin)
```bash
curl -i "https://proxy-host.com/api/auth/oidc/start?redirect=/hosts"
```
**Responses:**
- `302` Redirect to the SSO's authorization endpoint
- `404` `{"name": "OidcDisabled", "message": "OIDC login is not enabled."}`
### OIDC Callback
**GET** `/api/auth/oidc/callback`
Redirect target for the SSO after login. Validates the one-time `state`,
exchanges the authorization `code` for tokens, reads identity from the
userinfo endpoint, establishes a session, and redirects the browser back to
the login page with the app's own `auth-token` in a URL fragment.
**Query Parameters:**
- `code` (required) - Authorization code from the SSO
- `state` (required) - State value from the `start` step
```bash
# Not called directly — the SSO redirects the browser here after login.
```
**Responses:**
- `302` Redirect to `/login#token=...&redirect=...`
- `400` `{"name": "OidcCallbackInvalid", "message": "Missing code or state."}` or expired/unknown state
---
## API Tokens
Self-service personal access tokens (PATs) for scripting/CI without a browser
session. Every endpoint is owner-scoped: a user only sees/manages tokens they
created. Mounted at `/api/api-token`.
### List API Tokens
**GET** `/api/api-token`
List the current user's API tokens.
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/api-token
```
**Responses:**
- `200` `{"results": [{"id": "...", "name": "ci", ...}, ...]}`
### Create API Token
**POST** `/api/api-token`
Create a new API token. The raw token string is only returned once, at
creation.
**Parameters:**
- `name` (required) - Display name
- `description` (optional)
- `expires_in_days` (optional) - `0` or omitted means no expiry
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"name": "ci", "expires_in_days": 90}' \
https://proxy-host.com/api/api-token
```
**Responses:**
- `200` `{"results": {...}, "token": "prx_<id>_<secret>", "message": "API token 'ci' created. Save it now — it will not be shown again."}`
### Get API Token
**GET** `/api/api-token/:id`
Get a token's metadata (not the raw secret, which is never stored/returned again).
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/api-token/<id>
```
**Responses:**
- `200` `{"results": {...}}`
- `403` Not your token
### Update API Token
**PUT** `/api/api-token/:id`
Update a token's name/description/expiry.
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X PUT \
-d '{"name": "ci-updated"}' \
https://proxy-host.com/api/api-token/<id>
```
**Responses:**
- `200` `{"results": {...}, "message": "API token 'ci-updated' updated."}`
### Delete (Revoke) API Token
**DELETE** `/api/api-token/:id`
Revoke a token immediately.
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/api-token/<id>
```
**Responses:**
- `200` `{"id": "<id>", "message": "API token 'ci' revoked."}`
### Rotate API Token
**POST** `/api/api-token/:id/rotate`
Issue a new secret for an existing token (same id, new raw value shown once).
```bash
curl -H "auth-token: your-token-here" \
-X POST \
https://proxy-host.com/api/api-token/<id>/rotate
```
**Responses:**
- `200` `{"token": "prx_<id>_<new-secret>", "message": "API token 'ci' rotated. Save it — it will not be shown again."}`
---
## Users
All user endpoints require authentication.
All user endpoints require authentication. `GET /me` and `PUT /password`
(self-service) work for any authenticated user; everything else (listing,
creating, deleting users, resetting another user's password) requires global
admin.
### List Users
**GET** `/api/user`
Get list of all users.
Get list of all users. Admin only.
```bash
curl -H "auth-token: your-token-here" \
@@ -70,12 +239,14 @@ curl -H "auth-token: your-token-here" \
**Responses:**
- `200` `{"results": ["user1", "user2"]}`
- `200` `{"results": [{"username": "user1", ...}, ...]}` (with `?detail=true`)
- `403` Not an admin
### Get Current User
**GET** `/api/user/me`
Get information about the currently authenticated user.
Get the currently authenticated user's identity and effective RBAC rights
(drives the web UI's nav/button gating).
```bash
curl -H "auth-token: your-token-here" \
@@ -83,13 +254,13 @@ curl -H "auth-token: your-token-here" \
```
**Responses:**
- `200` `{"username": "myuser"}`
- `200` `{"username": "myuser", "groups": [...], "localGroups": [...], "externalGroups": [...], "isAdmin": false, "global": null, "domains": {...}}`
### Create User
**POST** `/api/user`
Create a new user.
Create a new local user. Admin only.
```bash
curl -H "Content-Type: application/json" \
@@ -101,14 +272,15 @@ curl -H "Content-Type: application/json" \
**Responses:**
- `200` User created successfully
- `403` Not an admin
- `409` Username already exists
- `422` `{"name": "ObjectValidateError", "message": ...}` Validation error
- `422` `{"name": "ObjectValidateError", "message": ...}` Validation error (also returned for weak passwords)
### Delete User
**DELETE** `/api/user/:username`
Delete a user account.
Delete a user account. Admin only.
```bash
curl -H "auth-token: your-token-here" \
@@ -118,6 +290,7 @@ curl -H "auth-token: your-token-here" \
**Responses:**
- `200` `{"username": "olduser", "results": ...}`
- `403` Not an admin
- `404` User not found
### Change Password (Self)
@@ -136,12 +309,13 @@ curl -H "Content-Type: application/json" \
**Responses:**
- `200` `{"results": ...}` Password changed successfully
- `422` Weak password rejected by the password policy
### Change Password (Other User)
**PUT** `/api/user/password/:username`
Change the password for another user (admin function).
Change the password for another user. Admin only.
```bash
curl -H "Content-Type: application/json" \
@@ -153,40 +327,163 @@ curl -H "Content-Type: application/json" \
**Responses:**
- `200` `{"results": ...}` Password changed successfully
- `403` Not an admin
- `404` User not found
### Create Invite Token
---
**POST** `/api/user/invite`
## Permissions
Create an invitation token for new user registration.
RBAC: grants a `viewer` or `manager` role to a user or group, either globally
or scoped to one domain. Global-admin-only. Mounted at `/api/permission`.
### List Permissions
**GET** `/api/permission`
```bash
curl -H "auth-token: your-token-here" \
-X POST \
https://proxy-host.com/api/user/invite
https://proxy-host.com/api/permission
```
**Responses:**
- `200` `{"token": "5caf94d2-2c91-4010-8df7-968d10802b9d"}`
- `200` `{"results": [{"id": "...", "subjectType": "user", "subject": "alice", "role": "manager", "scope": "domain", "domain": "example.com", ...}, ...]}`
### Add SSH Key
### List Permission Subjects
**POST** `/api/user/key`
**GET** `/api/permission/subjects`
Add an SSH public key to the current user's account.
Autocomplete source for the "Subject" field: known usernames plus known group
names (local groups, groups already used in permissions, and groups from
`conf.auth.adminGroups` / `conf.auth.groupRoleMap`).
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/permission/subjects
```
**Responses:**
- `200` `{"users": ["alice", "bob"], "groups": ["ops", "sre"]}`
### Create Permission
**POST** `/api/permission`
Grant a role to a subject.
**Parameters:**
- `subjectType` (required) - `user` or `group`
- `subject` (required) - username or group name
- `role` (required) - `viewer` or `manager`
- `scope` (required) - `global` or `domain`
- `domain` (required if `scope` is `domain`)
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"key": "ssh-rsa AAAAB3..."}' \
https://proxy-host.com/api/user/key
-d '{"subjectType": "user", "subject": "alice", "role": "manager", "scope": "domain", "domain": "example.com"}' \
https://proxy-host.com/api/permission
```
**Responses:**
- `200` `{"message": true}` Key added successfully
- `400` `{"message": "Bad SSH key"}` Invalid key format
- `200` `{"message": "Granted manager to user \"alice\" on example.com.", ...}`
- `422` Validation error
### Delete Permission
**DELETE** `/api/permission/:id`
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/permission/<id>
```
**Responses:**
- `200` `{"message": "Permission <id> removed."}`
---
## Groups
Local groups (independent of any SSO/LDAP groups) used as subjects for
permission grants. Global-admin-only. Mounted at `/api/group`.
### List Groups
**GET** `/api/group`
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/group
```
**Responses:**
- `200` `{"results": [{"name": "ops", "members": ["alice", "bob"], ...}, ...]}`
### Create Group
**POST** `/api/group`
**Parameters:**
- `name` (required)
- `members` (optional) - array of usernames
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"name": "ops", "members": ["alice"]}' \
https://proxy-host.com/api/group
```
**Responses:**
- `200` `{"message": "Group \"ops\" created.", ...}`
### Delete Group
**DELETE** `/api/group/:name`
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/group/ops
```
**Responses:**
- `200` `{"message": "Group \"ops\" removed."}`
### Add Group Member
**POST** `/api/group/:name/members`
**Parameters:**
- `username` (required)
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"username": "bob"}' \
https://proxy-host.com/api/group/ops/members
```
**Responses:**
- `200` `{"message": "Added \"bob\" to \"ops\".", ...}`
### Remove Group Member
**DELETE** `/api/group/:name/members/:username`
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/group/ops/members/bob
```
**Responses:**
- `200` `{"message": "Removed \"bob\" from \"ops\".", ...}`
---
@@ -320,6 +617,24 @@ curl -H "auth-token: your-token-here" \
- `200` `{"message": "example.com deleted", ...}`
- `404` `{"name": "HostNotFound", "message": "Host does not exists"}`
### Clear Host Cache
**DELETE** `/api/host/cache`
Remove all cached wildcard-subdomain host lookups. Cache entries are created on
demand when a wildcard host serves a subdomain; clearing them forces the next
request for each subdomain to be resolved fresh through the lookup tree.
Admin only.
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/host/cache
```
**Responses:**
- `200` `{"message": "Cleared 3 cached hosts.", "count": 3}`
### Renew Wildcard Certificate
**PUT** `/api/host/:host/renew`
@@ -372,7 +687,7 @@ curl -H "auth-token: your-token-here" \
```
**Responses:**
- `200` `{"results": [{"name": "CloudFlare", "fields": {...}}, {"name": "DigitalOcean", ...}, ...]}`
- `200` `{"results": [{"name": "Cloudflare", "fields": {...}}, {"name": "DigitalOcean", ...}, ...]}`
### Create DNS Provider
@@ -380,12 +695,12 @@ curl -H "auth-token: your-token-here" \
Configure a new DNS provider.
**CloudFlare:**
**Cloudflare:**
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"name": "My CloudFlare", "dnsProvider": "Cloudflare", "token": "your-api-token"}' \
-d '{"name": "My Cloudflare", "dnsProvider": "Cloudflare", "token": "your-api-token"}' \
https://proxy-host.com/api/dns
```
@@ -508,6 +823,97 @@ curl -H "auth-token: your-token-here" \
- `200` `{"results": ...}` Updated domain list
- `404` Provider not found
### Dynamic DNS
A-records kept automatically pointed at this box's public (WAN) IP. All
`/api/dns/dynamic*` routes are viewer/manager scoped to the record's domain
(via [Permissions](#permissions)), not admin-only like the rest of `/api/dns`.
#### Get Current Public IP
**GET** `/api/dns/dynamic/ip`
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/dns/dynamic/ip
```
**Responses:**
- `200` `{"ip": "203.0.113.5"}`
#### List Dynamic Records
**GET** `/api/dns/dynamic`
Lists records the caller may view (their own/granted domains, or all for admins).
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/dns/dynamic
```
**Responses:**
- `200` `{"results": [{"id": "...", "domain": "example.com", "name": "home", "last_status": "ok", ...}, ...]}`
#### Create Dynamic Record
**POST** `/api/dns/dynamic`
Requires `manager` rights on the target domain. Applies the record immediately
against the current public IP (best-effort — failures are recorded in
`last_status` and retried by the scheduler).
**Parameters:**
- `domain` (required)
- `name` (required) - sub-label, or `@` for the apex
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"domain": "example.com", "name": "home"}' \
https://proxy-host.com/api/dns/dynamic
```
**Responses:**
- `200` `{"message": "\"home.example.com\" added.", ...}`
- `403` Missing `manager` rights on the domain
- `422` Validation error
#### Refresh Dynamic Record
**POST** `/api/dns/dynamic/:id/refresh`
Force an immediate refresh of one record against the current public IP.
Requires `manager` rights on the record's domain.
```bash
curl -H "auth-token: your-token-here" \
-X POST \
https://proxy-host.com/api/dns/dynamic/<id>/refresh
```
**Responses:**
- `200` `{"message": "Refreshed \"home.example.com\".", "result": {...}}`
- `403` Missing `manager` rights on the domain
#### Delete Dynamic Record
**DELETE** `/api/dns/dynamic/:id`
Stop managing a record. Requires `manager` rights on the record's domain.
Leaves the provider's A record in place at its last value.
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/dns/dynamic/<id>
```
**Responses:**
- `200` `{"message": "home.example.com removed.", ...}`
- `403` Missing `manager` rights on the domain
---
## Certificates
@@ -543,7 +949,8 @@ All endpoints may return the following error responses:
## Notes
- All timestamps are in milliseconds since epoch
- The `auth-token` header is required for all authenticated endpoints
- Authenticated endpoints accept either the `auth-token` header (browser
session / OIDC login) or an `Authorization: Bearer <token>` API token
- Host names support wildcards: `*` (single level) and `**` (multi-level)
- DNS providers are validated on creation - invalid API credentials will be rejected
- Wildcard certificates are automatically renewed 30 days before expiration
+8 -2
View File
@@ -42,7 +42,7 @@ The proxy system consists of three main components working together to provide h
┌──────────────────────┐
│ DNS Providers │
│ - CloudFlare │
│ - Cloudflare │
│ - DigitalOcean │
│ - PorkBun │
│ (DNS-01 challenges) │
@@ -84,6 +84,9 @@ The proxy system consists of three main components working together to provide h
```
nodejs/
├── bin/www # Application entry point
├── conf/ # Configuration (base.js, environment overlays, secrets.js)
├── controller/ # App-level wiring (pubsub, startup)
├── migrations/ # One-off Redis data migration scripts
├── models/ # Data models
│ ├── host.js # Host configuration and lookup
│ ├── auth.js # Authentication logic
@@ -93,7 +96,10 @@ nodejs/
│ ├── host.js # Host CRUD operations
│ ├── dns.js # DNS provider management
│ ├── user.js # User management
── auth.js # Authentication
── auth.js # Authentication (login + OIDC)
│ ├── permission.js # RBAC permission management
│ ├── group.js # Local group management
│ └── api_token.js # Self-service API (PAT) tokens
├── services/ # Background services
│ ├── host_lookup.js # Unix socket server
│ └── host_scheduler.js # Cert renewal scheduler
+21 -8
View File
@@ -71,9 +71,19 @@ npm run test:watch
```
test/
├── unit/ # Unit tests for isolated components
│ ├── basicauth.test.js
│ ├── callback_queue.test.js
│ ├── dynamic_record.test.js
│ ├── host_features.test.js
│ ├── host_lookup.test.js
── unix_socket.test.js
── hostname_validate.test.js
│ ├── host_sso.test.js
│ ├── oidc.test.js
│ ├── password_policy.test.js
│ ├── roles.test.js
│ ├── safe_redirect.test.js
│ ├── unix_socket.test.js
│ └── wildcard_matchany.test.js
├── integration/ # Integration tests
│ └── dns_provider.test.js
└── helpers/ # Test utilities
@@ -171,6 +181,9 @@ Understanding the codebase:
```
nodejs/
├── conf/ # Configuration (base.js, environment overlays, secrets.js)
├── controller/ # App-level wiring (pubsub, startup)
├── migrations/ # One-off Redis data migration scripts
├── models/ # Data models (Host, User, DNS providers)
├── routes/ # API route handlers
├── services/ # Background services (lookup, scheduler)
@@ -232,12 +245,12 @@ nodejs/
### PR Requirements
- All tests must pass (CI/CD runs automatically)
- Tests run on Node.js 18.x, 20.x, and 22.x
- No merge conflicts with `master`
- Code follows project conventions
- New features include tests
- Documentation updated if needed
- All tests must pass (CI/CD runs automatically)
- Tests run on Node.js 18.x, 20.x, and 22.x
- No merge conflicts with `master`
- Code follows project conventions
- New features include tests
- Documentation updated if needed
### CI/CD Process
@@ -305,7 +318,7 @@ class Host extends Table {
### Adding API Endpoints
1. **Add route** in appropriate file (`routes/`)
2. **Update API documentation** (`nodejs/api.md`)
2. **Update API documentation** (`nodejs/api.md` and `docs/api.md` — keep them in sync)
3. **Test the endpoint** manually and add integration tests if needed
## Getting Help
-100
View File
@@ -1,100 +0,0 @@
# Development environment setup
This project used vagrant for as standard development environment. This should
easy setting things up and reduce environment related errors.
## What you need
There are 3 things you will need to get before your local environment is up and
running.
### Git
This should already be installed on your system. If you are using Windows,
install [git bash for windows](https://git-scm.com/download/win)
### VirtualBox
Virtual box will be used to create a manged Linux VM on your computer. Please
install version 5.9 as version 6 is not supported.
### Vagrant
Vagrant is used to managed the local virtual environment and provision the VM.
**MAKE SURE GIT VIRTUAL BOX ARE INSTALLED FIRST!!!** At install time, vagrant
will integrate with them.
## Usage
Once you have everything installed and the projected cloned on your local
computer, open a terminal( Git Bash for windows users ) and move to the root of
the project.
### Chef secrets
This project like many other used secret API tokens that we do want tracked in
the git repo. in the root of the project, create a file called 'secrets.json'
and populate like so:
```json
{
"django": {
"github": {
"id": "<ID>",
"secret": "<SECRET>",
"token": "<TOKEN>"
}
}
}
```
### Basic vagrant usage and commands
We will interact with the project using vagrant. The work flow is `vagrant up`
creates a VM for your project and runs the provisioner to set everything up.
This command should be ran after the project is cloned or when you sit down to
start development. This command may take some time to complete depending on how
complex the project is. Once the VM is set up, you may now interact with it.
Vagrant will forward ports from the project to the user localhost address. For
example with this project, port 80 from the container will be mapped to
localhost:8000 and you will be able to access there. If you make a change to any
provisioning chef recipes, `vagrant provision` will need to ran. This will
run the chef provisioner on the VM making any changes needed.
Because we use the `secrets.json` file to store untracked configuration, all
vagrant command need to be ran where in the root of the project, where
`secrets.json` lives.
### `vagrant up`
Will start the local VM, creating it if needed. This command should always be
ran when a development session is started.
### `vagrant provision`
Will run the chef-solo provisioner. This will need to be ran anytime the chef
recipes or roles are changed.
### `vagrant status`
Will show you the status of local manged VM
### `vagrant halt`
Will shutdown the local VM. This should be done when you are finished working on
the project so you dont have a VM running in the background eating CPU/RAM and
battery. `vagrant up` can be used later to turn the VM back on.
### `vagrant destroy`
This will shutdown the VM and delete it. This command is useful if you have
messed up the VM and want to start from scratch. Or if you are done with the
project and want to free space from the computer.
### `vagrant ssh`
This will bring you into the local VM as the vagrant user. The vagrant user has
sudo. Use this only for debugging! **DO NOT INSTALL OR CHANGE THE STATE OF
PROJECT OR VM FROM HERE!!!!** that will break the concept of provisioning make
chef useless. Also make installation and configuration changes with chef.
+1 -1
View File
@@ -11,7 +11,7 @@ A reverse proxy and HTTPS termination service using OpenResty/nginx with a manag
- **Automated HTTPS/SSL** - Let's Encrypt integration with HTTP-01 and DNS-01 challenges
- **Wildcard SSL Certificates** - Support for wildcard domains with automatic renewal
- **Multiple DNS Providers** - CloudFlare, DigitalOcean, PorkBun integrations
- **Multiple DNS Providers** - Cloudflare, DigitalOcean, PorkBun integrations
- **Advanced Routing** - Sophisticated wildcard domain matching (*, **)
- **RESTful API** - Full programmatic control
- **Web Interface** - User-friendly management GUI
+13 -4
View File
@@ -16,7 +16,7 @@ wget -O - https://raw.githubusercontent.com/theta42/proxy/master/ops/install.sh
```
This automated installer will:
- Install Node.js 20.x
- Install Node.js 22.x
- Install OpenResty and required dependencies
- Install and configure Redis
- Set up SSL fallback certificates
@@ -27,6 +27,15 @@ This automated installer will:
## Manual Installation
> **Recommended path:** `ops/install.sh` (above) is idempotent and safe to
> re-run — it symlinks the OpenResty/systemd config from the repo checkout,
> so future updates stay in sync automatically (`git pull` + re-run). The
> manual steps below *copy* those same files instead of symlinking them, so
> they will **not** auto-track later changes to `ops/nginx_conf/` or
> `ops/proxy.service` — you'd need to re-copy them by hand after every
> update. Prefer the manual path only if `install.sh` doesn't fit your
> distribution.
### System Requirements
- Modern Linux distribution (Ubuntu 20.04+, Debian 11+, or equivalent)
@@ -41,13 +50,13 @@ This automated installer will:
apt install libpam0g-dev build-essential redis-server luarocks -y
```
### Step 2: Install Node.js 20.x
### Step 2: Install Node.js 22.x
```bash
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | \
sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=20
NODE_MAJOR=22
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | \
sudo tee /etc/apt/sources.list.d/nodesource.list
@@ -56,7 +65,7 @@ apt update && apt install nodejs -y
Verify installation:
```bash
node --version # Should show v20.x.x
node --version # Should show v22.x.x
npm --version
```
+416 -26
View File
@@ -1,6 +1,25 @@
# API Documentation
All API endpoints require authentication via the `auth-token` header unless otherwise noted.
All API endpoints require authentication unless otherwise noted. Three
authentication methods are supported:
- **`auth-token` header** — a browser-session token from `POST /api/auth/login`
or the OIDC flow (below).
- **`Authorization: Bearer <token>` header** — a self-service API token (PAT,
see [API Tokens](#api-tokens)), for scripts/CI without a browser session.
- **OIDC (browser)** — if the proxy is configured as an OIDC client of an SSO
(`app_oidc__*` / `conf.oidc`, see [DEPLOYMENT.md](../DEPLOYMENT.md)),
users can log in via `GET /api/auth/oidc/start` instead of posting a
username/password.
The proxy can also be configured as a **direct LDAP client** (`app_ldap__*` /
`conf.ldap`) for looking up/validating users, independent of the OIDC flow —
see DEPLOYMENT.md for the full configuration reference.
Authenticated requests also carry **RBAC** (role-based access control):
global admins can manage everything; other users are scoped to `viewer` or
`manager` rights on specific domains via [Permissions](#permissions) and
[Groups](#groups).
Base URL: `https://your-proxy-host.com/api`
@@ -40,17 +59,167 @@ curl -H "auth-token: your-token-here" \
**Responses:**
- `200` `{"message": "Bye"}`
### OIDC Login (start)
**GET** `/api/auth/oidc/start`
Begin the OIDC authorization-code flow: creates a PKCE + state challenge and
redirects the browser to the configured SSO's authorize endpoint. Only
available when `conf.oidc.enabled` is true.
**Query Parameters:**
- `redirect` - Internal path to return to after login (optional; sanitized to same-origin)
```bash
curl -i "https://proxy-host.com/api/auth/oidc/start?redirect=/hosts"
```
**Responses:**
- `302` Redirect to the SSO's authorization endpoint
- `404` `{"name": "OidcDisabled", "message": "OIDC login is not enabled."}`
### OIDC Callback
**GET** `/api/auth/oidc/callback`
Redirect target for the SSO after login. Validates the one-time `state`,
exchanges the authorization `code` for tokens, reads identity from the
userinfo endpoint, establishes a session, and redirects the browser back to
the login page with the app's own `auth-token` in a URL fragment.
**Query Parameters:**
- `code` (required) - Authorization code from the SSO
- `state` (required) - State value from the `start` step
```bash
# Not called directly — the SSO redirects the browser here after login.
```
**Responses:**
- `302` Redirect to `/login#token=...&redirect=...`
- `400` `{"name": "OidcCallbackInvalid", "message": "Missing code or state."}` or expired/unknown state
---
## API Tokens
Self-service personal access tokens (PATs) for scripting/CI without a browser
session. Every endpoint is owner-scoped: a user only sees/manages tokens they
created. Mounted at `/api/api-token`.
### List API Tokens
**GET** `/api/api-token`
List the current user's API tokens.
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/api-token
```
**Responses:**
- `200` `{"results": [{"id": "...", "name": "ci", ...}, ...]}`
### Create API Token
**POST** `/api/api-token`
Create a new API token. The raw token string is only returned once, at
creation.
**Parameters:**
- `name` (required) - Display name
- `description` (optional)
- `expires_in_days` (optional) - `0` or omitted means no expiry
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"name": "ci", "expires_in_days": 90}' \
https://proxy-host.com/api/api-token
```
**Responses:**
- `200` `{"results": {...}, "token": "prx_<id>_<secret>", "message": "API token 'ci' created. Save it now — it will not be shown again."}`
### Get API Token
**GET** `/api/api-token/:id`
Get a token's metadata (not the raw secret, which is never stored/returned again).
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/api-token/<id>
```
**Responses:**
- `200` `{"results": {...}}`
- `403` Not your token
### Update API Token
**PUT** `/api/api-token/:id`
Update a token's name/description/expiry.
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X PUT \
-d '{"name": "ci-updated"}' \
https://proxy-host.com/api/api-token/<id>
```
**Responses:**
- `200` `{"results": {...}, "message": "API token 'ci-updated' updated."}`
### Delete (Revoke) API Token
**DELETE** `/api/api-token/:id`
Revoke a token immediately.
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/api-token/<id>
```
**Responses:**
- `200` `{"id": "<id>", "message": "API token 'ci' revoked."}`
### Rotate API Token
**POST** `/api/api-token/:id/rotate`
Issue a new secret for an existing token (same id, new raw value shown once).
```bash
curl -H "auth-token: your-token-here" \
-X POST \
https://proxy-host.com/api/api-token/<id>/rotate
```
**Responses:**
- `200` `{"token": "prx_<id>_<new-secret>", "message": "API token 'ci' rotated. Save it — it will not be shown again."}`
---
## Users
All user endpoints require authentication.
All user endpoints require authentication. `GET /me` and `PUT /password`
(self-service) work for any authenticated user; everything else (listing,
creating, deleting users, resetting another user's password) requires global
admin.
### List Users
**GET** `/api/user`
Get list of all users.
Get list of all users. Admin only.
```bash
curl -H "auth-token: your-token-here" \
@@ -63,12 +232,14 @@ curl -H "auth-token: your-token-here" \
**Responses:**
- `200` `{"results": ["user1", "user2"]}`
- `200` `{"results": [{"username": "user1", ...}, ...]}` (with `?detail=true`)
- `403` Not an admin
### Get Current User
**GET** `/api/user/me`
Get information about the currently authenticated user.
Get the currently authenticated user's identity and effective RBAC rights
(drives the web UI's nav/button gating).
```bash
curl -H "auth-token: your-token-here" \
@@ -76,13 +247,13 @@ curl -H "auth-token: your-token-here" \
```
**Responses:**
- `200` `{"username": "myuser"}`
- `200` `{"username": "myuser", "groups": [...], "localGroups": [...], "externalGroups": [...], "isAdmin": false, "global": null, "domains": {...}}`
### Create User
**POST** `/api/user`
Create a new user.
Create a new local user. Admin only.
```bash
curl -H "Content-Type: application/json" \
@@ -94,14 +265,15 @@ curl -H "Content-Type: application/json" \
**Responses:**
- `200` User created successfully
- `403` Not an admin
- `409` Username already exists
- `422` `{"name": "ObjectValidateError", "message": ...}` Validation error
- `422` `{"name": "ObjectValidateError", "message": ...}` Validation error (also returned for weak passwords)
### Delete User
**DELETE** `/api/user/:username`
Delete a user account.
Delete a user account. Admin only.
```bash
curl -H "auth-token: your-token-here" \
@@ -111,6 +283,7 @@ curl -H "auth-token: your-token-here" \
**Responses:**
- `200` `{"username": "olduser", "results": ...}`
- `403` Not an admin
- `404` User not found
### Change Password (Self)
@@ -129,12 +302,13 @@ curl -H "Content-Type: application/json" \
**Responses:**
- `200` `{"results": ...}` Password changed successfully
- `422` Weak password rejected by the password policy
### Change Password (Other User)
**PUT** `/api/user/password/:username`
Change the password for another user (admin function).
Change the password for another user. Admin only.
```bash
curl -H "Content-Type: application/json" \
@@ -146,40 +320,163 @@ curl -H "Content-Type: application/json" \
**Responses:**
- `200` `{"results": ...}` Password changed successfully
- `403` Not an admin
- `404` User not found
### Create Invite Token
---
**POST** `/api/user/invite`
## Permissions
Create an invitation token for new user registration.
RBAC: grants a `viewer` or `manager` role to a user or group, either globally
or scoped to one domain. Global-admin-only. Mounted at `/api/permission`.
### List Permissions
**GET** `/api/permission`
```bash
curl -H "auth-token: your-token-here" \
-X POST \
https://proxy-host.com/api/user/invite
https://proxy-host.com/api/permission
```
**Responses:**
- `200` `{"token": "5caf94d2-2c91-4010-8df7-968d10802b9d"}`
- `200` `{"results": [{"id": "...", "subjectType": "user", "subject": "alice", "role": "manager", "scope": "domain", "domain": "example.com", ...}, ...]}`
### Add SSH Key
### List Permission Subjects
**POST** `/api/user/key`
**GET** `/api/permission/subjects`
Add an SSH public key to the current user's account.
Autocomplete source for the "Subject" field: known usernames plus known group
names (local groups, groups already used in permissions, and groups from
`conf.auth.adminGroups` / `conf.auth.groupRoleMap`).
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/permission/subjects
```
**Responses:**
- `200` `{"users": ["alice", "bob"], "groups": ["ops", "sre"]}`
### Create Permission
**POST** `/api/permission`
Grant a role to a subject.
**Parameters:**
- `subjectType` (required) - `user` or `group`
- `subject` (required) - username or group name
- `role` (required) - `viewer` or `manager`
- `scope` (required) - `global` or `domain`
- `domain` (required if `scope` is `domain`)
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"key": "ssh-rsa AAAAB3..."}' \
https://proxy-host.com/api/user/key
-d '{"subjectType": "user", "subject": "alice", "role": "manager", "scope": "domain", "domain": "example.com"}' \
https://proxy-host.com/api/permission
```
**Responses:**
- `200` `{"message": true}` Key added successfully
- `400` `{"message": "Bad SSH key"}` Invalid key format
- `200` `{"message": "Granted manager to user \"alice\" on example.com.", ...}`
- `422` Validation error
### Delete Permission
**DELETE** `/api/permission/:id`
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/permission/<id>
```
**Responses:**
- `200` `{"message": "Permission <id> removed."}`
---
## Groups
Local groups (independent of any SSO/LDAP groups) used as subjects for
permission grants. Global-admin-only. Mounted at `/api/group`.
### List Groups
**GET** `/api/group`
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/group
```
**Responses:**
- `200` `{"results": [{"name": "ops", "members": ["alice", "bob"], ...}, ...]}`
### Create Group
**POST** `/api/group`
**Parameters:**
- `name` (required)
- `members` (optional) - array of usernames
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"name": "ops", "members": ["alice"]}' \
https://proxy-host.com/api/group
```
**Responses:**
- `200` `{"message": "Group \"ops\" created.", ...}`
### Delete Group
**DELETE** `/api/group/:name`
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/group/ops
```
**Responses:**
- `200` `{"message": "Group \"ops\" removed."}`
### Add Group Member
**POST** `/api/group/:name/members`
**Parameters:**
- `username` (required)
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"username": "bob"}' \
https://proxy-host.com/api/group/ops/members
```
**Responses:**
- `200` `{"message": "Added \"bob\" to \"ops\".", ...}`
### Remove Group Member
**DELETE** `/api/group/:name/members/:username`
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/group/ops/members/bob
```
**Responses:**
- `200` `{"message": "Removed \"bob\" from \"ops\".", ...}`
---
@@ -320,6 +617,7 @@ curl -H "auth-token: your-token-here" \
Remove all cached wildcard-subdomain host lookups. Cache entries are created on
demand when a wildcard host serves a subdomain; clearing them forces the next
request for each subdomain to be resolved fresh through the lookup tree.
Admin only.
```bash
curl -H "auth-token: your-token-here" \
@@ -382,7 +680,7 @@ curl -H "auth-token: your-token-here" \
```
**Responses:**
- `200` `{"results": [{"name": "CloudFlare", "fields": {...}}, {"name": "DigitalOcean", ...}, ...]}`
- `200` `{"results": [{"name": "Cloudflare", "fields": {...}}, {"name": "DigitalOcean", ...}, ...]}`
### Create DNS Provider
@@ -390,12 +688,12 @@ curl -H "auth-token: your-token-here" \
Configure a new DNS provider.
**CloudFlare:**
**Cloudflare:**
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"name": "My CloudFlare", "dnsProvider": "Cloudflare", "token": "your-api-token"}' \
-d '{"name": "My Cloudflare", "dnsProvider": "Cloudflare", "token": "your-api-token"}' \
https://proxy-host.com/api/dns
```
@@ -518,6 +816,97 @@ curl -H "auth-token: your-token-here" \
- `200` `{"results": ...}` Updated domain list
- `404` Provider not found
### Dynamic DNS
A-records kept automatically pointed at this box's public (WAN) IP. All
`/api/dns/dynamic*` routes are viewer/manager scoped to the record's domain
(via [Permissions](#permissions)), not admin-only like the rest of `/api/dns`.
#### Get Current Public IP
**GET** `/api/dns/dynamic/ip`
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/dns/dynamic/ip
```
**Responses:**
- `200` `{"ip": "203.0.113.5"}`
#### List Dynamic Records
**GET** `/api/dns/dynamic`
Lists records the caller may view (their own/granted domains, or all for admins).
```bash
curl -H "auth-token: your-token-here" \
https://proxy-host.com/api/dns/dynamic
```
**Responses:**
- `200` `{"results": [{"id": "...", "domain": "example.com", "name": "home", "last_status": "ok", ...}, ...]}`
#### Create Dynamic Record
**POST** `/api/dns/dynamic`
Requires `manager` rights on the target domain. Applies the record immediately
against the current public IP (best-effort — failures are recorded in
`last_status` and retried by the scheduler).
**Parameters:**
- `domain` (required)
- `name` (required) - sub-label, or `@` for the apex
```bash
curl -H "Content-Type: application/json" \
-H "auth-token: your-token-here" \
-X POST \
-d '{"domain": "example.com", "name": "home"}' \
https://proxy-host.com/api/dns/dynamic
```
**Responses:**
- `200` `{"message": "\"home.example.com\" added.", ...}`
- `403` Missing `manager` rights on the domain
- `422` Validation error
#### Refresh Dynamic Record
**POST** `/api/dns/dynamic/:id/refresh`
Force an immediate refresh of one record against the current public IP.
Requires `manager` rights on the record's domain.
```bash
curl -H "auth-token: your-token-here" \
-X POST \
https://proxy-host.com/api/dns/dynamic/<id>/refresh
```
**Responses:**
- `200` `{"message": "Refreshed \"home.example.com\".", "result": {...}}`
- `403` Missing `manager` rights on the domain
#### Delete Dynamic Record
**DELETE** `/api/dns/dynamic/:id`
Stop managing a record. Requires `manager` rights on the record's domain.
Leaves the provider's A record in place at its last value.
```bash
curl -H "auth-token: your-token-here" \
-X DELETE \
https://proxy-host.com/api/dns/dynamic/<id>
```
**Responses:**
- `200` `{"message": "home.example.com removed.", ...}`
- `403` Missing `manager` rights on the domain
---
## Certificates
@@ -553,7 +942,8 @@ All endpoints may return the following error responses:
## Notes
- All timestamps are in milliseconds since epoch
- The `auth-token` header is required for all authenticated endpoints
- Authenticated endpoints accept either the `auth-token` header (browser
session / OIDC login) or an `Authorization: Bearer <token>` API token
- Host names support wildcards: `*` (single level) and `**` (multi-level)
- DNS providers are validated on creation - invalid API credentials will be rejected
- Wildcard certificates are automatically renewed 30 days before expiration
+12 -2
View File
@@ -23,9 +23,19 @@ npm run test:watch
```
test/
├── unit/ # Unit tests for isolated components
│ ├── basicauth.test.js
│ ├── callback_queue.test.js
│ ├── dynamic_record.test.js
│ ├── host_features.test.js
│ ├── host_lookup.test.js
── unix_socket.test.js
── hostname_validate.test.js
│ ├── host_sso.test.js
│ ├── oidc.test.js
│ ├── password_policy.test.js
│ ├── roles.test.js
│ ├── safe_redirect.test.js
│ ├── unix_socket.test.js
│ └── wildcard_matchany.test.js
├── integration/ # Integration tests for complex interactions
│ └── dns_provider.test.js
└── helpers/ # Test utilities and contracts
@@ -58,7 +68,7 @@ test/
**dns_provider.test.js**
- DNS provider contract compliance
- All existing providers (CloudFlare, DigitalOcean, PorkBun)
- All existing providers (Cloudflare, DigitalOcean, PorkBun)
- Method signatures
- Key mapping
- Type validation
+8 -7
View File
@@ -1,9 +1,10 @@
The folder will hold a link to the openresty conf file. Nothing in here will be
tracked, and edits are only valid on you local VM. The contents of this folder
is also reset during each provision run. Be mind full of that when making
changes here.
This directory is currently unused.
To apply any changes made in this folder, run the following command from outside
the VM:
The real OpenResty/nginx configuration for this project lives in
[`ops/nginx_conf/`](../ops/nginx_conf/) (`nginx.conf`, `autossl.conf`,
`proxy.conf`, `targetinfo.lua`, `hostfeatures.lua`). `ops/install.sh` symlinks
those files into place on a bare-metal host, and the Docker image copies them
in at build time — see [`DEPLOYMENT.md`](../DEPLOYMENT.md) for details.
`vagrant ssh -c "sudo service openresty restart"`
If you're looking for how to change the nginx/Lua configuration, edit the
files under `ops/nginx_conf/` instead of this directory.