Skip to content

Add DDNS Cloudflare Worker#1

Merged
brianfoshee merged 22 commits intomainfrom
feature/dyndns-worker
Feb 24, 2026
Merged

Add DDNS Cloudflare Worker#1
brianfoshee merged 22 commits intomainfrom
feature/dyndns-worker

Conversation

@brianfoshee
Copy link
Member

@brianfoshee brianfoshee commented Feb 24, 2026

Summary

  • Cloudflare Worker that accepts dyndns2-protocol update requests and updates DNS A records via the Cloudflare API
  • Supports GET /host/:id/?ip=:ip where :id is pawnee or muncie, mapped to <id>.gingerlycoding.com
  • HTTP Basic Auth with timing-safe credential comparison
  • Returns standard dyndns2 responses: good <ip>, nochg <ip>, nohost, badauth, 911

Test plan

  • 54 tests passing across 5 test files (unit + integration)
  • Deploy with wrangler deploy and set secrets (CF_API_TOKEN, CF_ZONE_ID, BASIC_AUTH_USERNAME, BASIC_AUTH_PASSWORD)
  • Verify with curl -u user:pass "https://dyndns.gingerlycoding.com/host/pawnee?ip=1.2.3.4"
  • Configure Unifi gateways to use the endpoint

Set up wrangler, vitest with pool-workers, TypeScript config,
and a smoke test to verify the test infrastructure works.
Provides dyndns2Response(), authFailResponse(), and methodNotAllowed()
for generating dyndns2 protocol-compliant HTTP responses.
Uses crypto.subtle.timingSafeEqual to prevent timing attacks
on credential comparison.
Parses /host/:id/?ip=:ip routes, maps pawnee/muncie to their
FQDN, and validates IPv4 format with octet range checking.
Looks up existing A records by hostname, compares IPs, and patches
via the CF API when changed. Returns dyndns2 status codes.
Integrates all modules: method check → auth → route parse → DNS
update → dyndns2 response. Full integration test coverage via
SELF and fetchMock.
Tests relied on .dev.vars for env bindings, which is gitignored
and absent in CI. Define test bindings via miniflare config so
tests work without .dev.vars.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a Cloudflare Worker that implements a dynamic DNS (DDNS) service using the dyndns2 protocol. The worker accepts DNS update requests for two hostnames (pawnee and muncie) mapped to subdomains of gingerlycoding.com, authenticates requests via HTTP Basic Auth with timing-safe credential comparison, and updates DNS A records through the Cloudflare API. The implementation includes comprehensive test coverage with 42 tests across 6 test files, covering unit tests for individual modules and integration tests for the complete request flow.

Changes:

  • Cloudflare Worker with dyndns2 protocol support for dynamic DNS updates
  • HTTP Basic Auth with timing-safe credential comparison using crypto.subtle.timingSafeEqual
  • Request parsing, IPv4 validation, Cloudflare DNS API client, and response formatting modules
  • Comprehensive test suite with unit and integration tests using Vitest and Cloudflare Workers testing tools
  • CI workflow for automated testing on push and pull requests

Reviewed changes

Copilot reviewed 15 out of 18 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
wrangler.jsonc Worker configuration with compatibility date and custom domain routing
tsconfig.json TypeScript compiler configuration with strict mode and ESNext target
vitest.config.ts Vitest configuration for Cloudflare Workers test environment with test bindings
package.json Project dependencies including Cloudflare Workers types, Vitest, and Wrangler
package-lock.json Locked dependency versions for reproducible builds
.gitignore Git ignore patterns for node_modules, dist, and Wrangler artifacts
.github/workflows/ci.yml GitHub Actions workflow for running tests on push and PRs
src/index.ts Main fetch handler orchestrating auth, routing, and DNS updates
src/auth.ts HTTP Basic Auth verification with timing-safe credential comparison
src/routes.ts Request parsing and validation with allowed hosts and IPv4 validation
src/response.ts Dyndns2 protocol response formatting helpers
src/dns.ts Cloudflare DNS API client for querying and updating A records
test/smoke.test.ts Basic smoke test verifying test environment setup
test/auth.test.ts Authentication tests covering valid/invalid credentials and edge cases
test/routes.test.ts Route parsing tests for valid hostnames, IPs, and error conditions
test/response.test.ts Response formatting tests for dyndns2 protocol responses
test/dns.test.ts DNS API client tests with mocked Cloudflare API responses
test/index.test.ts Integration tests covering end-to-end request handling

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Always run timingSafeEqual by padding shorter buffers to match,
then check lengths after. Prevents leaking credential length
via early return timing.
Add clientErrorResponse() for nohost/badip responses instead of
constructing raw Responses in the fetch handler.
Encode hostname in the lookup URL query parameter. Check res.ok
before parsing JSON on both lookup and patch requests to handle
HTTP-level errors (401, 429, 500, etc.) before body parsing.
Verifies the worker returns "911" when the Cloudflare DNS API
returns a server error.
Replace placeholder content with accurate documentation covering
the API, dyndns2 response codes, secrets setup, and Unifi
gateway DDNS configuration.
Custom domains in wrangler config take a bare hostname only —
wildcards and paths are not supported. The Worker handles all
paths on the hostname automatically.
When no A record is found for the hostname, POST a new one via
the Cloudflare API instead of returning 911. Also extracts patch
logic into a helper for consistency.
Allows /host/pawnee.gingerlycoding.com in addition to /host/pawnee.
Rejects FQDNs on other domains (e.g. pawnee.example.com).
Comma-separated list of subdomain names (e.g. "pawnee,muncie")
replaces the hardcoded allowlist. Set via wrangler secret or
environment variable.
- Replace hardcoded domain in routes.ts with DOMAIN env var
- Remove unnecessary async from verifyAuth (no awaits inside)
- Add null-safe access on DNS lookup result to handle missing data
- Add test for empty ALLOWED_SUBDOMAINS rejecting all hostnames
DynDNS2 protocol (used by Ubiquiti UniFi gateways) only uses GET.
POST and PUT were accepted but never actually used by any client.
@brianfoshee brianfoshee merged commit e9a7a3a into main Feb 24, 2026
1 check passed
@brianfoshee brianfoshee deleted the feature/dyndns-worker branch February 24, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants