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:
+1
-2
@@ -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/
|
||||
|
||||
@@ -58,7 +58,6 @@ typings/
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
.vagrant
|
||||
*~
|
||||
*#
|
||||
.#*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 +0,0 @@
|
||||
proxy.projects.theta42.com
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user