diff --git a/README.md b/README.md index 40ed0e6..0f42ed8 100755 --- a/README.md +++ b/README.md @@ -1,157 +1,218 @@ -# proxy +# Proxy -A simple reverse proxy and https termination using openresty/nginx with a managment API and GUI. +A reverse proxy and HTTPS termination service using OpenResty/nginx with a management API and web GUI. -## API docs -[API docs](api.md) +**Documentation:** [https://theta42.github.io/proxy/](https://theta42.github.io/proxy/) -## Server set up +## Features -The server requires: -* NodeJS 8.x -* inbound Internet access -* OpenResty -* redis -* lua rocks +- 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) +- 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 +- Unix socket-based host lookup for high-performance routing -This has been tested on ubuntu 16.04, but should work on any modern Linux -distro. -**Optional** Linux users for its user management, so this will -**ONLY** work on Linux, no macOS, BSD or Windows and require root. +## Requirements -The steps below are for a new ubuntu server, they should be mostly the same for -other distros, but the paths and availability of packages may vary. A dedicated -server is highly recommended (since it will make ever user a system user), a VPS -like Digital Ocean will do just fine. +- Node.js 18+ (tested with 18.x, 20.x, 22.x) +- OpenResty (nginx with Lua support) +- Redis +- Modern Linux distribution (tested on Ubuntu 20.04+, Debian 11+) +- Inbound internet access for Let's Encrypt validation +- Root access (required for user management features) -* Install openresty +## Quick Install - [OpenResty® Linux Packages](https://openresty.org/en/linux-packages.html) - -* These packages are needed for the PAM node package - - ```bash - apt install libpam0g-dev build-essential - ``` - -* Install redis - - ```bash - apt install redis-server - ``` - -* install lua plugin +An automated installer is available for modern Debian-based systems: ```bash -apt install luarocks -sudo luarocks install lua-resty-auto-ssl -sudo luarocks install lua-resty-socket -sudo luarocks install lua-socket -sudo luarocks install socket -sudo luarocks install luasocket -sudo luarocks install luasocket-unix -sudo luarocks install lua-cjson +wget -O - https://raw.githubusercontent.com/theta42/proxy/master/ops/install.sh | sudo bash ``` -* openresty config +This installer will: +- Install Node.js 20.x +- Install OpenResty and required dependencies +- Install and configure Redis +- Set up SSL fallback certificates +- Install Lua dependencies (lua-resty-auto-ssl, luasocket) +- Clone and install the proxy application +- Configure systemd service +- Start the proxy service -Set up fail back SSL certs +## Manual Installation +For manual installation or other distributions, see the detailed steps below. + +### System Dependencies + +**Ubuntu/Debian:** ```bash -mkdir /etc/ssl/ - -openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -subj '/CN=sni-support-required-for-valid-ssl' -keyout /etc/ssl/resty-auto-ssl-fallback.key -out /etc/ssl/resty-auto-ssl-fallback.crt - -openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -subj '/CN=sni-support-required-for-valid-ssl' -keyout /etc/ssl/resty-auto-ssl-fallback.key -out /etc/ssl/resty-auto-ssl-fallback.crt - -# openssl dhparam -out /etc/nginx/dhparam.pem 4096 # This takes a LONG time and is not needed. - +apt install libpam0g-dev build-essential redis-server luarocks -y ``` -Change the `/etc/openresty/nginx.conf to have this config` - -``` -#user nobody; -worker_processes 4; - -#error_log logs/error.log; -#error_log logs/error.log notice; -#error_log logs/error.log info; - -#pid logs/nginx.pid; - - -events { - worker_connections 1024; -} - - -http { - client_max_body_size 4g; - - - lua_shared_dict auto_ssl 100m; - lua_shared_dict auto_ssl_settings 64k; - - resolver 8.8.4.4 8.8.8.8; - - init_by_lua_block { - auto_ssl = (require "resty.auto-ssl").new() - auto_ssl:set("storage_adapter", "resty.auto-ssl.storage_adapters.redis") - auto_ssl:set("allow_domain", function(domain) - return true - end) - auto_ssl:init() - } - - init_worker_by_lua_block { - auto_ssl:init_worker() - } - - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - - server { - listen 127.0.0.1:8999; - - # Increase the body buffer size, to ensure the internal POSTs can always - # parse the full POST contents into memory. - client_body_buffer_size 128k; - client_max_body_size 128k; - - location / { - content_by_lua_block { - auto_ssl:hook_server() - } - } - } - - include mime.types; - default_type application/octet-stream; - - #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - # '$status $body_bytes_sent "$http_referer" ' - # '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - sendfile on; - #tcp_nopush on; - - #keepalive_timeout 0; - keepalive_timeout 65; - - #gzip on; - include sites-enabled/*; - -} - +**Node.js 20.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 +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 ``` -add the SSL config file `/etc/openresty/autossl.conf`, contents from here -https://github.com/theta42/t42-common/blob/master/templates/openresty/autossl.conf.erb +**OpenResty:** +```bash +wget -O - https://openresty.org/package/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/openresty.gpg +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list +apt update && apt install openresty -y +``` +**Lua Dependencies:** +```bash +luarocks install lua-resty-auto-ssl +luarocks install luasocket +``` -Add the proxy config `/etc/openresty/sites-enabled/000-proxy` contents from here -https://github.com/theta42/t42-common/blob/master/templates/openresty/010-proxy.conf.erb +### SSL Configuration + +Create fallback SSL certificates: +```bash +mkdir -p /etc/ssl/ +openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ + -subj '/CN=sni-support-required-for-valid-ssl' \ + -keyout /etc/ssl/resty-auto-ssl-fallback.key \ + -out /etc/ssl/resty-auto-ssl-fallback.crt +``` + +### OpenResty Configuration + +Configuration files are provided in `ops/nginx_conf/`: +- `nginx.conf` - Main nginx configuration +- `autossl.conf` - Auto-SSL configuration for Let's Encrypt HTTP-01 +- `proxy.conf` - Proxy server configuration with host lookup +- `targetinfo.lua` - Lua module for host lookup via Unix socket + +Copy these files to `/etc/openresty/`: +```bash +mkdir -p /etc/openresty/sites-enabled/ +cp ops/nginx_conf/nginx.conf /etc/openresty/nginx.conf +cp ops/nginx_conf/autossl.conf /etc/openresty/autossl.conf +cp ops/nginx_conf/proxy.conf /etc/openresty/sites-enabled/000-proxy +cp ops/nginx_conf/targetinfo.lua /usr/local/openresty/lualib/targetinfo.lua +``` + +### Application Setup + +Clone and install: +```bash +cd /var/www +git clone https://github.com/theta42/proxy.git +cd proxy/nodejs +npm install +``` + +Create systemd service: +```bash +cp ops/proxy.service /etc/systemd/system/proxy.service +systemctl daemon-reload +systemctl enable proxy.service +systemctl start proxy.service +``` + +## DNS Provider Configuration + +For wildcard SSL certificates, configure a DNS provider via the web UI or API: + +**Supported providers:** +- **CloudFlare** - Requires API token +- **DigitalOcean** - Requires API token +- **PorkBun** - Requires API key and secret API key + +Once configured, create a wildcard host (e.g., `*.example.com`) and the system will automatically request and manage the DNS-01 challenge certificate. + +## Architecture + +The system consists of three main components: + +1. **OpenResty/Nginx** - Frontend proxy with Lua-based routing + - Handles SSL termination via lua-resty-auto-ssl + - Queries Node.js backend via Unix socket for host routing + - Proxies requests to configured backend servers + +2. **Node.js API** - Backend management and control plane + - RESTful API for host/user/DNS management + - Wildcard SSL certificate orchestration + - Host lookup tree with wildcard matching + - User authentication and authorization + +3. **Redis** - Data store + - Host configurations + - User accounts and tokens + - SSL certificate storage + - Domain and DNS provider configurations + +## Host Lookup System + +The proxy supports sophisticated domain matching: +- **Exact match**: `example.com` matches only `example.com` +- **Single wildcard**: `*.example.com` matches `sub.example.com` but not `deep.sub.example.com` +- **Double wildcard**: `**.example.com` matches any depth (`sub.example.com`, `deep.sub.example.com`, etc.) +- **Mixed wildcards**: `api.*.example.com` matches `api.v1.example.com`, `api.v2.example.com`, etc. + +Priority: Exact match > Single wildcard > Double wildcard + +## Development + +**Running locally:** +```bash +cd nodejs +npm install +npm run dev # Runs with nodemon for auto-reload +``` + +**Running tests:** +```bash +npm test # Run all tests +npm run test:unit # Run unit tests only +npm run test:watch # Watch mode for development +``` + +Tests use Node.js built-in test runner (requires Node 18+). + +## API Documentation + +See [API Documentation](nodejs/api.md) for complete API reference. + +## Contributing + +Pull requests are welcome. The project uses GitHub Actions for CI/CD: +- Tests run automatically on all PRs +- All tests must pass before merging to master +- Tests run on Node.js 18.x, 20.x, and 22.x + +## License + +MIT - See LICENSE file for details. + +## Project Structure + +``` +proxy/ +├── nodejs/ # Node.js backend application +│ ├── bin/ # Entry point (www) +│ ├── models/ # Data models (Host, User, DNS providers) +│ ├── routes/ # API routes +│ ├── services/ # Background services (host lookup, scheduler) +│ ├── middleware/ # Express middleware +│ ├── utils/ # Utility functions +│ ├── public/ # Static web assets +│ ├── views/ # EJS templates +│ └── test/ # Test suite +├── ops/ # Operations and deployment +│ ├── nginx_conf/ # OpenResty configuration files +│ ├── install.sh # Automated installer +│ └── proxy.service # Systemd service definition +└── .github/workflows/ # CI/CD workflows +``` diff --git a/nodejs/api.md b/nodejs/api.md index 8c7df99..f354ec4 100755 --- a/nodejs/api.md +++ b/nodejs/api.md @@ -1,132 +1,542 @@ -## get host info +# API Documentation -**GET** `/api/hosts` +All API endpoints require authentication via the `auth-token` header unless otherwise noted. + +Base URL: `https://your-proxy-host.com/api` + +--- + +## Authentication + +### Login + +**POST** `/api/auth/login` + +Authenticate a user and receive an auth token. ```bash -curl -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" https://proxy-host.com/api/hostsmine.com +curl -H "Content-Type: application/json" \ + -X POST \ + -d '{"username": "myuser", "password": "mypassword"}' \ + https://proxy-host.com/api/auth/login ``` -* 200 {"host":"yours.com","results":{"ip":"127.0.0.1:4000","updated":"1518595297563","username":"test10","forceSSL": false, "targetSSL": true, "targetPort": "443"}} -* 404 {"name": "HostNotFound", "message": "Host does not exists"} +**Responses:** +- `200` `{"login": true, "token": "027d3964-7d81-4462-a6f9-2c1f9b40b4be", "message": "myuser logged in!"}` +- `401` `{"name": "LoginFailed", "message": "Invalid Credentials, login failed."}` +### Logout -## view all hosts +**ALL** `/api/auth/logout` -**GET** `/api/hosts` +Invalidate the current auth token. ```bash -curl -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" https://proxy-host.com/api/hosts +curl -H "auth-token: your-token-here" \ + -X POST \ + https://proxy-host.com/api/auth/logout ``` -* 200 {"hosts":["mine.com","mine2.com"]} +**Responses:** +- `200` `{"message": "Bye"}` +--- -## Add host +## Users -**POST** `/api/hosts` +All user endpoints require authentication. -Params -* **host** -- Required, The domain name for the new record. -* **ip** -- Required, The target IP or FQDN for the record. -* **targetSSL** -- If the remote IP target is SSL. Default is false and this is -not recommended. -* **targetPort** -- Required, TCP port for the remote server. Unless you know -otherwise, 80 for targetSSL false and 443 for true. -* **forceSSL** -- If requests should be forced to use SSL from the client to -the proxy. The default is false and this is HIGHLY recommended. -* ** +### List Users + +**GET** `/api/user` + +Get list of all users. ```bash -curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X POST -d '{"host": "test.vm42.com", "ip": "192.168.1.21", "targetSSL": false, "targetPort": "443", "forceSSL": true} https://proxy-host.com/api/hosts +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/user ``` -* 200 {"message":"Host yours.com added."} -* 409 {"name":"HostNameUsed", "message":"Host already exists"} -* 422 {"name":"ObjectValidateError","message":[{"key":"ip","message":"ip is required."}]} Missing or incorrect keys/values. Returns a list with a message per key error. +**Query Parameters:** +- `detail` - Include full user details (optional) -## Edit +**Responses:** +- `200` `{"results": ["user1", "user2"]}` +- `200` `{"results": [{"username": "user1", ...}, ...]}` (with `?detail=true`) -**PUT** `/api/hosts` +### Get Current User -Takes the same params as add, but none are required +**GET** `/api/user/me` -curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X POST -d '{"host": "test.vm42.com", "ip": "192.168.1.21", "targetSSL": false, "targetPort": "443", "forceSSL": true} https://proxy-host.com/api/hosts - -* 200 {"message":"Host yours.com updated."} -* 404 {"name": "HostNotFound", "message": "Host does not exists"} -* 409 {"name":"HostNameUsed", "message":"Host already exists"} -* 422 {"name":"ObjectValidateError","message":[{"key":"ip","message":"ip is required."}]} Missing or incorrect keys/values. Returns a list with a message per key error. - - -## delete host - -**DELETE** /`api/hosts/` +Get information about the currently authenticated user. ```bash -curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X DELETE https://proxy-host.com/api/hosts +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/user/me ``` -* 200 {"message":"Host yours.com deleted"} -* 404 {"name": "HostNotFound", "message": "Host does not exists"} +**Responses:** +- `200` `{"username": "myuser"}` +### Create User -## create invite token +**POST** `/api/user` -**post** `/users/invite` +Create a new user. ```bash -curl -H "Content-Type: application/json" -H "auth-token: 0b06eb2e-4ca4-4881-9a0f-b8df55431cd1" -X POST https://proxy-host.com/users/invite +curl -H "Content-Type: application/json" \ + -H "auth-token: your-token-here" \ + -X POST \ + -d '{"username": "newuser", "password": "newpassword"}' \ + https://proxy-host.com/api/user ``` -* 200 {"token":"5caf94d2-2c91-4010-8df7-968d10802b9d"} +**Responses:** +- `200` User created successfully +- `409` Username already exists +- `422` `{"name": "ObjectValidateError", "message": ...}` Validation error +### Delete User -## sing up +**DELETE** `/api/user/:username` -**post** `/auth/invite/` +Delete a user account. ```bash -curl -H "Content-Type: application/json" -X POST -d "{\"username\": \"test9\", \"password\": \"palm7\"}" https://proxy-host.com/auth/invite/b33d8819-ec64-4cf4-a6ec-77562d738fa4 - +curl -H "auth-token: your-token-here" \ + -X DELETE \ + https://proxy-host.com/api/user/olduser ``` -* 200 {"user":"test9","token":"af662d8b-3d44-4110-8ad9-047dc752d97f"} -* 400 {"message":"Missing fields"} -* 401 {"message":"Token not valid"} -* 409 {"message":"username taken"} +**Responses:** +- `200` `{"username": "olduser", "results": ...}` +- `404` User not found +### Change Password (Self) -## login +**PUT** `/api/user/password` -**post** `/auth/login` +Change the password for the currently authenticated user. ```bash -curl -H "Content-Type: application/json" -X POST -d '{"username": "test8", "password": "mypassword"}' https://proxy-host.com/auth/login +curl -H "Content-Type: application/json" \ + -H "auth-token: your-token-here" \ + -X PUT \ + -d '{"password": "newpassword"}' \ + https://proxy-host.com/api/user/password ``` -* 200 {"login":true,"token":"027d3964-7d81-4462-a6f9-2c1f9b40b4be"} -* 401 {"login":false} +**Responses:** +- `200` `{"results": ...}` Password changed successfully +### Change Password (Other User) -## verify SSH key +**PUT** `/api/user/password/:username` -**post** `/auth/verifykey` +Change the password for another user (admin function). ```bash -curl -H "Content-Type: application/json" -X POST -d "{\"key\":\"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDM9vboz5YGgESsrR2e4JOeP2qtmQo2S8BjI+Y/VxPQ6WbNFzAkXxDniHcnPCrhkeX36SKINvMjWnt4XOK2S+X+1tCoXJzqtcKKyK0gx8ijBxcWVPxsMWjMYTGSVSKiKnt6CyQzrbVGJMh3iAQ8Yv1JwH+6SAtMgT8it7iLyntNFJCesh4I/znEG58A5VBbdUle1Ztz9afjj1CZns17jk7KPm9ig5DmuvdvnMEfhFjfKv1Rp6S5nxacMoTP4tJNSEUh55IicoWk94ii5GwUVLYgyMmzdlA32TqVLFpU2yAvdA9WSnBaI/ZyktlfI7YAmK2wFBsagr9Pq1TcUAY6rZ/GTMjDxExgdYn/FxlufcuqeNJsJXs2A+0xDS/9mv/yGQzNZrL8DrVhY2OKKLoH4Q7enDbhSgEFmJUJMqPxuPEgLEvKfzcURSvIwRj1iCEw6S4dhdaLJl2RRBb1ZWBQbE5ogIbvAl7GFJUAhj3pqYJnd30VENv1MkK+IoCS7EEP0caqL9RNAId0Plud7q2XElHqzkYUE+z+Q/LvGgclXK1ZmZejNaMnV53wfhAevfwVyNGK9i5gbwc1P2lplIa5laXCcVWezqELEkTpdjp4AeKmMuCr8rY8EnLKIcKWEOsX5UumztCow6e1E55v3VeHvRZLpw4DZP7EE0Q8B/jPFWqbCw== wmantly@gmail.com\"}" https://proxy-host.com/auth/verifykey +curl -H "Content-Type: application/json" \ + -H "auth-token: your-token-here" \ + -X PUT \ + -d '{"password": "newpassword"}' \ + https://proxy-host.com/api/user/password/otheruser ``` -* 200 {"info":"4096 SHA256:dfdCYzt0atMBXVZTJzUxsu99IjXXFXpocSox5q+jOs8 wmantly@gmail.com (RSA)\n"} -* 400 {"message":"Key is not a public key file!"} +**Responses:** +- `200` `{"results": ...}` Password changed successfully +- `404` User not found +### Create Invite Token -## add ssh key to current user +**POST** `/api/user/invite` -**post** `/users/key` +Create an invitation token for new user registration. ```bash -curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X POST -d "{\"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAjWnt4XOK2S+X+1tCoXJzqtcKKyK0gx8ijBxcWVPxsMWjMYTGSVSKiKnt6CyQzrbVGJMh3iAQ8Yv1JwH+6SAtMgT8it7iLyntNFJCesh4I/znEG58A5VBbdUle1Ztz9afjj1CZns17jk7KPm9ig5DmuvdvnMEfhFjfKv1Rp6S5nxacMoTP4tJNSEUh55IicoWk94ii5GwUVLYgyMmzdlA32TqVLFpU2yAvdA9WSnBaI/ZyktlfI7YAmK2wFBsagr9Pq1TcUAY6rZ/GTMjDxExgdYn/FxlufcuqeNJsJXs2A+0xDS/9mv/yGQzNZrL8DrVhY2OKKLoH4Q7enDbhSgEFmJUJMqPxuPEgLEvKfzcURSvIwRj1iCEw6S4dhdaLJl2RRBb1ZWBQbE5ogIbvAl7GFJUAhj3pqYJnd30VENv1MkK+IoCS7EEP0caqL9RNAId0Plud7q2XElHqzkYUE+z+Q/LvGgclXK1ZmZejNaMnV53wfhAevfwVyNGK9i5gbwc1P2lplIa5laXCcVWezqELEkTpdjp4AeKmMuCr8rY8EnLKIcKWEOsX5UumztCow6e1E55v3VeHvRZLpw4DZP7EE0Q8B/jPFWqbCw== wmantly@gmail.co\"}" https://proxy-host.com/users/key +curl -H "auth-token: your-token-here" \ + -X POST \ + https://proxy-host.com/api/user/invite ``` -* 200 {"message":true} -* 400 {"message":"Bad SSH key"} +**Responses:** +- `200` `{"token": "5caf94d2-2c91-4010-8df7-968d10802b9d"}` + +### Add SSH Key + +**POST** `/api/user/key` + +Add an SSH public key to the current user's account. + +```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 +``` + +**Responses:** +- `200` `{"message": true}` Key added successfully +- `400` `{"message": "Bad SSH key"}` Invalid key format + +--- + +## Hosts + +Manage proxy host configurations. + +### List Hosts + +**GET** `/api/host` + +Get list of all configured hosts. + +```bash +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/host +``` + +**Query Parameters:** +- `detail` - Include full host details (optional) + +**Responses:** +- `200` `{"results": ["example.com", "*.wildcard.com"]}` +- `200` `{"results": [{"host": "example.com", "ip": "192.168.1.10", ...}, ...]}` (with `?detail=true`) + +### Get Host + +**GET** `/api/host/:host` + +Get configuration for a specific host. + +```bash +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/host/example.com +``` + +**Responses:** +- `200` `{"item": "example.com", "results": {"host": "example.com", "ip": "192.168.1.10", "targetPort": 8080, ...}}` +- `404` `{"name": "HostNotFound", "message": "Host does not exists"}` + +### Lookup Host + +**GET** `/api/host/lookup/:domain` + +Test the host lookup algorithm (supports wildcard matching). + +```bash +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/host/lookup/sub.example.com +``` + +**Responses:** +- `200` `{"string": "sub.example.com", "results": {"host": "*.example.com", ...}}` +- `200` `{"string": "sub.example.com", "results": null}` (no match) + +### Get Lookup Tree + +**GET** `/api/host/lookupobj` + +Get the internal lookup tree structure (for debugging). + +```bash +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/host/lookupobj +``` + +**Responses:** +- `200` `{"results": {"com": {"example": {...}}}}` + +### Create Host + +**POST** `/api/host` + +Add a new host configuration. + +**Parameters:** +- `host` (required) - Domain name (e.g., `example.com`, `*.example.com`) +- `ip` (required) - Target IP address or FQDN +- `targetPort` (required) - Target port number (1-65535) +- `forcessl` (optional) - Force HTTPS redirect (default: true) +- `targetssl` (optional) - Use HTTPS to backend (default: false) +- `challengeType` (optional) - For wildcards: `DNS-01-wildcard` or `wildcardChild` + +```bash +curl -H "Content-Type: application/json" \ + -H "auth-token: your-token-here" \ + -X POST \ + -d '{"host": "example.com", "ip": "192.168.1.10", "targetPort": 8080, "forcessl": true, "targetssl": false}' \ + https://proxy-host.com/api/host +``` + +**Responses:** +- `200` `{"message": "\"example.com\" added.", "host": "example.com", ...}` +- `409` `{"name": "HostNameUsed", "message": "Host already exists"}` +- `422` `{"name": "ObjectValidateError", "message": ...}` Validation error + +### Update Host + +**PUT** `/api/host/:host` + +Update an existing host configuration. + +**Parameters:** Same as Create Host (all optional) + +```bash +curl -H "Content-Type: application/json" \ + -H "auth-token: your-token-here" \ + -X PUT \ + -d '{"ip": "192.168.1.20", "targetPort": 9000}' \ + https://proxy-host.com/api/host/example.com +``` + +**Responses:** +- `200` `{"message": "\"example.com\" updated.", ...}` +- `404` `{"name": "HostNotFound", "message": "Host does not exists"}` +- `422` Validation error + +### Delete Host + +**DELETE** `/api/host/:host` + +Remove a host configuration. + +```bash +curl -H "auth-token: your-token-here" \ + -X DELETE \ + https://proxy-host.com/api/host/example.com +``` + +**Responses:** +- `200` `{"message": "example.com deleted", ...}` +- `404` `{"name": "HostNotFound", "message": "Host does not exists"}` + +### Renew Wildcard Certificate + +**PUT** `/api/host/:host/renew` + +Manually trigger wildcard certificate renewal. + +```bash +curl -H "auth-token: your-token-here" \ + -X PUT \ + https://proxy-host.com/api/host/*.example.com/renew +``` + +**Responses:** +- `200` `{"message": "Requesting wildcard cert for *.example.com"}` +- `404` Host not found + +--- + +## DNS Providers + +Manage DNS provider integrations for wildcard SSL certificates. + +### List DNS Providers + +**GET** `/api/dns` + +Get list of configured DNS providers. + +```bash +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/dns +``` + +**Query Parameters:** +- `detail` - Include full provider details (optional) + +**Responses:** +- `200` `{"results": ["provider-id-1", "provider-id-2"]}` + +### List Available Provider Types + +**OPTIONS** `/api/dns` + +Get list of supported DNS provider types and their configuration requirements. + +```bash +curl -H "auth-token: your-token-here" \ + -X OPTIONS \ + https://proxy-host.com/api/dns +``` + +**Responses:** +- `200` `{"results": [{"name": "CloudFlare", "fields": {...}}, {"name": "DigitalOcean", ...}, ...]}` + +### Create DNS Provider + +**POST** `/api/dns` + +Configure a new DNS provider. + +**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"}' \ + https://proxy-host.com/api/dns +``` + +**DigitalOcean:** +```bash +curl -H "Content-Type: application/json" \ + -H "auth-token: your-token-here" \ + -X POST \ + -d '{"name": "My DO", "dnsProvider": "DigitalOcean", "token": "your-api-token"}' \ + https://proxy-host.com/api/dns +``` + +**PorkBun:** +```bash +curl -H "Content-Type: application/json" \ + -H "auth-token: your-token-here" \ + -X POST \ + -d '{"name": "My PorkBun", "dnsProvider": "PorkBun", "apiKey": "pk_xxx", "secretApiKey": "sk_xxx"}' \ + https://proxy-host.com/api/dns +``` + +**Responses:** +- `200` `{"message": "\"provider-id\" added.", ...}` +- `422` Validation error or invalid API credentials + +### Get DNS Provider + +**GET** `/api/dns/:id` + +Get a specific DNS provider configuration. + +```bash +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/dns/provider-id +``` + +**Responses:** +- `200` `{"item": "provider-id", "results": {...}}` +- `404` Provider not found + +### Update DNS Provider + +**PUT** `/api/dns/:id` + +Update DNS provider configuration. + +```bash +curl -H "Content-Type: application/json" \ + -H "auth-token: your-token-here" \ + -X PUT \ + -d '{"name": "Updated Name"}' \ + https://proxy-host.com/api/dns/provider-id +``` + +**Responses:** +- `200` `{"message": "\"provider-id\" updated.", ...}` +- `404` Provider not found + +### Delete DNS Provider + +**DELETE** `/api/dns/:id` + +Remove a DNS provider and all associated domains. + +```bash +curl -H "auth-token: your-token-here" \ + -X DELETE \ + https://proxy-host.com/api/dns/provider-id +``` + +**Responses:** +- `200` `{"message": "provider-id deleted", ...}` +- `404` Provider not found + +### List Domains + +**GET** `/api/dns/domain` + +List all domains from all configured providers. + +```bash +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/dns/domain +``` + +**Query Parameters:** +- `detail` - Include full domain details (optional) + +**Responses:** +- `200` `{"results": ["example.com", "test.com"]}` + +### Get Domain + +**GET** `/api/dns/domain/:domain` + +Get details for a specific domain. + +```bash +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/dns/domain/example.com +``` + +**Responses:** +- `200` `{"results": [{"domain": "example.com", "zoneId": "...", ...}]}` +- `404` Domain not found + +### Refresh Domains + +**POST** `/api/dns/domain/refresh/:providerId` + +Refresh the domain list from a DNS provider's API. + +```bash +curl -H "auth-token: your-token-here" \ + -X POST \ + https://proxy-host.com/api/dns/domain/refresh/provider-id +``` + +**Responses:** +- `200` `{"results": ...}` Updated domain list +- `404` Provider not found + +--- + +## Certificates + +Retrieve SSL certificate information. + +### Get Certificate + +**GET** `/api/cert/:host` + +Get the SSL certificate for a host. + +```bash +curl -H "auth-token: your-token-here" \ + https://proxy-host.com/api/cert/example.com +``` + +**Responses:** +- `200` Certificate data including `cert_pem`, `fullchain_pem`, `privkey_pem`, expiry information +- `404` Certificate not found + +--- + +## Error Responses + +All endpoints may return the following error responses: + +- `401` `{"name": "LoginFailed", "message": "Invalid Credentials, login failed."}` - Authentication required or invalid +- `404` `{"name": "NotFound", "message": "..."}` - Resource not found +- `422` `{"name": "ObjectValidateError", "message": [...], "keys": [...]}` - Validation errors +- `500` Internal server error + +## Notes + +- All timestamps are in milliseconds since epoch +- The `auth-token` header is required for all authenticated endpoints +- 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 diff --git a/nodejs/package.json b/nodejs/package.json index e1235f8..b43d941 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -10,7 +10,14 @@ ], "scripts": { "start": "node ./bin/www", - "dev": "npx nodemon --ignore public/ ./bin/www" + "dev": "npx nodemon --ignore public/ ./bin/www", + "test": "node --test test/**/*.test.js", + "test:unit": "node --test test/unit/**/*.test.js", + "test:integration": "node --test test/integration/**/*.test.js", + "test:watch": "node --test --watch test/**/*.test.js" + }, + "engines": { + "node": ">=18.0.0" }, "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2",