Skip to content

feat: IPv6 end-to-end, OpenAPI 1.4.0 nested schema, frontend dual-stack - #19

Merged
jjesse merged 3 commits into
feature/openapi-ipv6-private-ipfrom
copilot/featureopenapi-ipv6-private-ip-again
Aug 1, 2026
Merged

jjesse merged 3 commits into
feature/openapi-ipv6-private-ipfrom
copilot/featureopenapi-ipv6-private-ip-again

Conversation

Copilot AI commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Completes the remaining IPv6 + schema + frontend + docs work on the feature/openapi-ipv6-private-ip branch. utils/ip.js already had full dual-stack support; this wires it through the rest of the stack.

server.js

  • lookupIp(): Replaced blanket if (cidr.includes(':')) continue with a family-aware skip — IPv6 CIDRs now match IPv6 queries; IPv4 CIDRs match IPv4 queries:
    const range = parseCidr(cidr);
    const queryIsV6 = ip.includes(':');
    if ((range.family === 'ipv6') !== queryIsV6) continue;
  • getIpGeolocation(): Expanded private-IP block to cover ::1, IPv4 link-local (169.254.x.x), and IPv6 unique-local/link-local (fc00::/7, fe80::/10)
  • /api/lookup route: Added explicit typeof string guard on query params — prevents CodeQL type-confusion alert and rejects repeated param arrays
  • JSDoc @param updated from "IPv4" to "IPv4 or IPv6" throughout

openapi.yaml (v1.4.0)

  • Removed format: ipv4 from ip / sourceIp params
  • LookupFound now references new DatacenterInfo schema (name, city, country, latitude, longitude, ipRanges[]); LookupNotFound returns datacenter: null, matchedRange: null
  • TraceResult uses results[] with totalResults / foundResults; hops carry nested datacenter object
  • /api/zdx/userpath 200 response updated to match actual { success, data: { user, device, application, probe, timestamp, hops[] } } shape

public/app.js

  • validateIp: dual-stack — IPv4 octet-range check + IPv6 structural check (rejects multiple ::)
  • Error messages: "IPv4 or IPv6" everywhere
  • showSuccess: reads data.datacenter.name/city, data.continent, data.matchedRange from nested shape
  • handleTraceSubmit: normalises data.results → hops display shape when server returns new schema
  • ipInput sanitizer: allows [0-9a-fA-F.:] instead of digits-and-dots only

tests/unit/ip.test.js

  • Added ipv6ToBigInt, isValidIpv4, isValidIpv6, getIpFamily to imports
  • Fixed isValidIp test that expected false for IPv6 (now correctly expects true)
  • New suites: ipv6ToBigInt, isValidIpv4, isValidIpv6, getIpFamily, parseCidr IPv6 (2001:db8::/32, 2605:4300:e800::/40), isIpInRange IPv6

Docs

  • README.md: IPv6 noted in features; /api/lookup response example updated to nested datacenter shape; TRUST_PROXY added to env var table; troubleshooting says "IPv4 or IPv6"
  • README_PYTHON.md: New section documenting zdx_oneapi_geopath.py OneAPI env vars and usage
  • CHANGELOG.md: Unreleased entries for IPv6, OpenAPI 1.4.0, private-IP fix, frontend nested-datacenter fix
  • TODO.md: Marked done — IPv6 support, OpenAPI LookupFound schema, TRUST_PROXY docs, requirements.txt, OneAPI docs, private-IP prefix list, bare except
Original prompt

Work on branch feature/openapi-ipv6-private-ip. utils/ip.js already has full dual-stack support. Complete the remaining end-to-end IPv6 + schema + frontend + docs work so this branch can be PR'd to master.

1) server.js — three surgical edits

A. In lookupIp(), replace:

          if (cidr.includes(':')) continue;

          const range = parseCidr(cidr);

with:

          const range = parseCidr(cidr);
          // Skip ranges whose family does not match the query IP
          const queryIsV6 = ip.includes(':');
          if ((range.family === 'ipv6') !== queryIsV6) continue;

B. In getIpGeolocation(), replace the private-IP block with:

  if (ip === '127.0.0.1' || ip === 'localhost' || ip === '::1') {
    return null;
  }
  // IPv4 RFC 1918 + link-local
  if (ip.startsWith('192.168.') || ip.startsWith('10.') || ip.startsWith('169.254.')) {
    return null;
  }
  // RFC 1918: 172.16.0.0/12 (second octet 16-31 only)
  if (ip.startsWith('172.')) {
    const parts = ip.split('.');
    const secondOctet = parseInt(parts[1], 10);
    if (secondOctet >= 16 && secondOctet <= 31) {
      return null;
    }
  }
  // IPv6 unique-local (fc00::/7) and link-local (fe80::/10)
  const lower = ip.toLowerCase();
  if (lower.startsWith('fc') || lower.startsWith('fd') || lower.startsWith('fe80:')) {
    return null;
  }

Also update the comment above isValidIp check from 'IPv4' to 'IP', and JSDoc @PARAM for lookupIp/getIpGeolocation/getClientIp from IPv4-only to IPv4 or IPv6.

2) openapi.yaml — full replace to v1.4.0

  • info.version: 1.4.0; description mentions IPv4 and IPv6
  • /api/lookup params ip and sourceIp: no format:ipv4; description says IPv4 or IPv6
  • LookupFound uses nested datacenter object via DatacenterInfo schema: { name, city, country, latitude (number|null), longitude (number|null), ipRanges: string[] } plus top-level matchedRange, continent, optional client* and distance* fields
  • LookupNotFound: success true, datacenter null, matchedRange null
  • TraceResult: results[] (not hops), totalResults, foundResults, totalDistance, totalDistanceMiles; each hop has hop, ip, datacenter object|null, matchedRange, city, country, latitude, longitude, distanceFromPrevious
  • /api/zdx/userpath 200 response: { success, data: { user, device, application, probe, timestamp, hops[] } }

3) public/app.js

  • Replace ipv4-only validateIp with dual-stack (IPv4 octet check + IPv6 structural check allowing hex/colons; reject multiple ::)
  • Error messages: 'IPv4 or IPv6' not 'IPv4'
  • showSuccess: read data.datacenter.name/city, data.continent or datacenter.country, data.matchedRange or datacenter.ipRanges[0], lat/lng from datacenter object
  • handleTraceSubmit: if data.results and not data.hops, map results to hops shape (found, datacenter name, city, country, lat/lng, distanceFromPrevious) and set totalHops/foundHops from totalResults/foundResults
  • ipInput sanitizer: allow 0-9a-fA-F.: not only digits and dots

4) tests/unit/ip.test.js

Expand to import and test ipv6ToBigInt, isValidIpv4, isValidIpv6, getIpFamily; add parseCidr IPv6 tests for 2001:db8::/32 and 2605:4300:e800::/40; isIpInRange IPv6; isValidIp accepts both families. Keep all existing IPv4 tests.

5) Documentation

  • TODO.md: mark done the OpenAPI LookupFound, TRUST_PROXY docs, requirements.txt, OneAPI docs, private-IP prefix list, bare except, IPv6 support items
  • CHANGELOG.md [Unreleased]: IPv6 support, OpenAPI 1.4.0 nested schema, private-IP fix, frontend nested-datacenter fix
  • README.md: IPv6 in features; nested datacenter in lookup response example; TRUST_PROXY in config; troubleshooting says IPv4 or IPv6
  • README_PYTHON.md: short section for zdx_oneapi_geopath.py OneAPI env vars and usage
  • Ensure requirements.txt exists with zscaler-sdk-python and requests

Do not change unrelated files. Match existing code style.

Copilot AI changed the title [WIP] Complete end-to-end IPv6 and schema implementation feat: IPv6 end-to-end, OpenAPI 1.4.0 nested schema, frontend dual-stack Aug 1, 2026
Copilot AI requested a review from jjesse August 1, 2026 03:56
@jjesse
jjesse marked this pull request as ready for review August 1, 2026 04:05
Copilot AI review requested due to automatic review settings August 1, 2026 04:05
@jjesse
jjesse merged commit b3b414f into feature/openapi-ipv6-private-ip Aug 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown

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 completes end-to-end IPv6 support and aligns the API contract + frontend with the backend’s nested datacenter response shape, including OpenAPI 1.4.0 updates and supporting docs/tests.

Changes:

  • Wire IPv6 through lookup + geolocation filtering and update frontend validation/mapping for dual-stack responses.
  • Update openapi.yaml to v1.4.0 with nested DatacenterInfo and TraceResult.results[] shape (plus ZDX userpath response shape).
  • Expand unit tests for IPv6 parsing/range checks and refresh docs/changelog to reflect the new schema and IPv6 support.

Reviewed changes

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

Show a summary per file
File Description
server.js Dual-stack lookup/geolocation updates; stricter query-param type guarding.
openapi.yaml Bump to 1.4.0 and document nested datacenter + trace results schema updates.
public/app.js Dual-stack IP validation, nested datacenter rendering, and trace results[] normalization.
tests/unit/ip.test.js Add IPv6-focused unit coverage for parsing, validation, and range checks.
README.md Document IPv6 support and updated /api/lookup nested response example; add TRUST_PROXY.
README_PYTHON.md Document OneAPI variant usage and environment variables.
CHANGELOG.md Record IPv6 + OpenAPI 1.4.0 + frontend/schema fixes in Unreleased.
TODO.md Mark IPv6/schema/docs items as completed.

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

Comment thread server.js
Comment thread public/app.js
Comment thread public/app.js
Comment thread openapi.yaml
Comment thread openapi.yaml
Comment thread openapi.yaml
Comment thread CHANGELOG.md
@jjesse

jjesse commented Aug 1, 2026

Copy link
Copy Markdown
Owner

@copilot Fix the code for all comments in this review thread.

When a review comment includes a suggested change, apply the suggestion exactly.

Do not make changes beyond what is described in the linked review thread.

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.

3 participants