```html

Multi-Domain Guest Page Architecture: CloudFront Path Rewriting and Cross-Service Lambda Coordination

This post documents the technical decisions and infrastructure changes made to consolidate guest-facing charter booking pages across multiple domains while maintaining separate backend service ownership and avoiding credential exposure in client-side code.

Problem Statement

A charter booking system needed to:

  • Generate guest-facing pages at predictable, short URLs (/g/XHQGMDH)
  • Host these pages on a public-facing domain (queenofsandiego.com) separate from the operational crew platform (sailjada.com)
  • Coordinate between two independent Lambda-based services (ShipCaptainCrew and JADA Internal Calendar) without exposing service credentials to the browser
  • Handle authenticated API calls from a static HTML page in S3 without storing secrets client-side

Solution Architecture

Domain and Storage Strategy

Guest pages were deployed to queenofsandiego.com CloudFront distribution (not the crew platform at sailjada.com). This separation serves multiple purposes:

  • Security boundary: Public guest pages are isolated from crew-only content
  • CDN efficiency: Separate distributions allow different cache policies (guest pages cache longer; crew pages refresh frequently)
  • Operational clarity: Guest URLs never expose internal operational domain names to customers

Pages are stored as flat .html files in the S3 bucket backing queenofsandiego.com, following the CloudFront function convention that rewrites requests to /g/XHQGMDH to /g/XHQGMDH.html.

Path Rewriting via CloudFront Functions

CloudFront Functions (not Lambda@Edge) handle the URL rewrite for guest pages. The live function code at the queenofsandiego.com distribution performs this transformation:

Request: /g/XHQGMDH
↓ (CloudFront Function rewrites)
S3 lookup: /g/XHQGMDH.html

This approach avoids Lambda@Edge overhead while maintaining clean guest-facing URLs. CloudFront Functions execute in under 1ms at edge locations with no cold start.

Authentication Without Client-Side Secrets

The guest page itself is public HTML/CSS/JavaScript. But it needs to:

  • Call ShipCaptainCrew Lambda to fetch crew details and presigned photo upload URLs
  • Potentially submit confirmations back to operational systems

Rather than embed service credentials in the HTML, the architecture uses:

  • API Gateway endpoints: ShipCaptainCrew Lambda is exposed via API Gateway at a dedicated endpoint (not through CloudFront, which strips custom headers)
  • Service-to-service auth: The backend Lambda that generates the guest page includes an embedded session token or uses time-bound signed requests
  • CORS configuration: API Gateway allows requests from queenofsandiego.com origins only

This way, the guest page calls an API Gateway endpoint directly (which preserves auth headers), not a CloudFront distribution (which strips non-standard headers like X-Service-Key).

Infrastructure Components

S3 Buckets

  • sailjada.com — Crew platform and operational backend assets
  • queenofsandiego.com (public) — Guest-facing pages, stored as /g/*.html files

CloudFront Distributions

  • sailjada.com distribution: Origin is the S3 bucket; serves crew dashboard and admin panels
  • queenofsandiego.com distribution: Origin is its S3 bucket; includes a CloudFront Function that rewrites /g/[ID] to /g/[ID].html

Lambda Functions

  • ShipCaptainCrew Lambda: Exposed via API Gateway (not CloudFront) to allow custom auth headers; handles crew data, event creation, and photo presign operations
  • JADA Calendar Lambda: Backend-only; called from update_dashboard.py or directly from ShipCaptainCrew; creates calendar entries with dashboard token auth
  • Guest page generator (TBD in deployment): Dynamically builds guest page HTML, embeds session tokens, uploads to queenofsandiego.com S3 bucket

Key Technical Decisions

Why Not Use CloudFront for ShipCaptainCrew API Calls?

CloudFront strips non-standard HTTP headers for security. The ShipCaptainCrew Lambda expects an X-Service-Key or similar auth header. Routing guest pages through CloudFront for API calls would lose this header. Solution: expose the Lambda directly via API Gateway, which preserves all headers. Guest pages call https://api.example.com/g/presign instead of going through CloudFront.

Why Flat .html Files Over /index.html Convention?

CloudFront Functions are cheaper and faster than Lambda@Edge, but they have a 1KB context limit and limited string manipulation. The simplest convention that works: store each guest page as /g/XHQGMDH.html and rewrite /g/XHQGMDH/g/XHQGMDH.html`. This avoids directory traversal and keeps S3 listings flat.

Why Separate queenofsandiego.com from sailjada.com?

Public guest pages should never reveal the crew platform domain. If a guest inspects network traffic or HTML source, they shouldn't learn that internal operations run on sailjada.com. This is a defense-in-depth practice. Additionally, separate CloudFront distributions allow independent cache invalidation and SSL certificate management.

Deployment Workflow

  1. Generate guest page HTML: Backend logic creates an .html file with event details, crew list, and JavaScript for API calls.
  2. Upload to S3: aws s3 cp /tmp/jada-guest-XHQGMDH.html s3://queenofsandiego.com/g/XHQGMDH.html
  3. Invalidate CloudFront cache: aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/g/XHQGMDH"
  4. Verify live: Request https://queenofsandiego.com/g/XHQGMDH and confirm CloudFront Function rewrites to .html

Security Considerations

  • No credentials in HTML: Guest pages contain only public event metadata and JavaScript that calls API Gateway with request-time auth (e.g.,