Conversation
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.
There was a problem hiding this comment.
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.
Summary
GET /host/:id/?ip=:ipwhere:idispawneeormuncie, mapped to<id>.gingerlycoding.comgood <ip>,nochg <ip>,nohost,badauth,911Test plan
wrangler deployand set secrets (CF_API_TOKEN,CF_ZONE_ID,BASIC_AUTH_USERNAME,BASIC_AUTH_PASSWORD)curl -u user:pass "https://dyndns.gingerlycoding.com/host/pawnee?ip=1.2.3.4"