```html

Multi-Domain Guest & Crew Portal Architecture: Routing, Auth, and CloudFront Function-Based Path Rewriting

This post documents the infrastructure and application changes required to support guest-facing charter booking pages across multiple domains while maintaining separate crew authentication, dynamic S3 routing, and CloudFront-based path normalization.

What Was Done

We built a unified guest and crew portal system that:

  • Serves guest pages from queenofsandiego.com/g/{slug} via CloudFront function-based path rewriting
  • Routes crew-facing pages through the ShipCaptainCrew (SCC) Lambda backend with role-based auth
  • Automatically syncs charter metadata to DynamoDB events with financial details stripped from crew visibility
  • Sends authenticated magic-link invites to crew via SES and the SCC event creation API
  • Stores guest pages as flat .html files in S3, normalized by CloudFront function

Technical Architecture

Guest Page Routing via CloudFront Function

Guest pages live at s3://queenofsandiego.com/g/ as flat HTML files. A CloudFront function intercepts requests to queenofsandiego.com/g/{slug} and rewrites them internally to fetch s3://queenofsandiego.com/g/{slug}.html.

CloudFront Distribution: Retrieved via AWS CLI to identify the distribution ID. The function reads the live code from the distribution's function association:

aws cloudfront list-distributions --query "Distributions[?DomainName=='d[HASH].cloudfront.net'].Id"

The CloudFront function code implements a path-rewriting strategy:

  • Intercepts GET /g/xhqgmdh requests
  • Rewrites to /g/xhqgmdh.html before S3 origin fetch
  • Preserves query strings and auth headers
  • Avoids 404s for directory-like paths

This convention eliminates the need for S3 index document routing or request/response Lambda@Edge logic, reducing cold-start latency and Lambda invocation costs.

Crew-Facing Pages via SCC Lambda

The crew portal is handled entirely by the SCC Lambda backend at /tmp/scc-lambda-src/lambda_function.py. Route definitions for crew pages live in the event routing block:

  • GET /g/{event_id} – Presign photo upload tokens for guests
  • Crew checklist pages served via role-based auth checks in the Lambda handler
  • Event details and crew assignments loaded from DynamoDB table Events

The Lambda validates all requests using a service key hash stored in environment variables. Auth headers are stripped by CloudFront at the API Gateway edge, so we implemented direct API Gateway endpoint access for server-to-server calls:

https://[API_GATEWAY_ID].execute-api.[REGION].amazonaws.com/[STAGE]/event

This bypasses CloudFront header stripping and allows credential passing for event creation and updates.

Data Sync: Calendar, Events, and Financial Privacy

When a charter is booked, three objects are created in sequence:

  1. JADA Internal Calendar Entry – Created via the calendar Lambda at /tmp/calendar-lambda/index.js with full financial details (captain fee, crew costs, port fees, net revenue). This is internal-only and never exposed to crew or guests.
  2. SCC Event in DynamoDB – Created via the SCC Lambda at the /event POST route. This record includes guest details, crew assignments, and timelines. Critically, the notes field originally contained Revenue and Captain fee values, which must be removed before crew access.
  3. Guest Page in S3 – Deployed to s3://queenofsandiego.com/g/{slug}.html with a friendly slug (e.g., boatsetter-may-30 instead of the booking ID). The page includes a time-aware photo upload widget that presigns S3 URLs via the SCC Lambda's handle_guest_presign() function.

To remove financial data from crew view, we perform a DynamoDB direct update after event creation:

aws dynamodb update-item \
  --table-name Events \
  --key '{"event_id":{"S":"[EVENT_ID]"}}' \
  --update-expression "REMOVE notes.Revenue, notes.#cf" \
  --expression-attribute-names '{"#cf":"Captain fee"}'

This ensures crew see only operational details (guest count, timing, setup requirements) without visibility into compensation or margins.

Infrastructure Changes

S3 Bucket Organization

Bucket: queenofsandiego.com

  • /g/ – Guest pages (flat .html files)
  • /photos/ – Guest-uploaded charter photos (private, presigned URLs only)
  • CloudFront origin: bucket policy allows only the CloudFront distribution principal

Guest pages are uploaded as:

aws s3 cp jada-guest-xhqgmdh.html s3://queenofsandiego.com/g/boatsetter-may-30.html

CloudFront cache invalidation is triggered immediately:

aws cloudfront create-invalidation \
  --distribution-id [DISTRIBUTION_ID] \
  --paths "/g/boatsetter-may-30.html"

DynamoDB Schema Updates

The SCC Lambda reads and writes to the Events table with the following key attributes:

  • event_id (PK) – UUID generated by Lambda
  • crew_ids (SS) – Set of crew member IDs for invitations
  • notes (M) – Operational data (date, time, guest count, notes); financial fields removed post-creation
  • photos_s3_prefix (S) – S3 path for guest photo uploads
  • created_at (N) – Unix timestamp

The update expressions preserve operational data while removing sensitive fields post-creation.

API Gateway Direct Access

CloudFront strips custom headers (including service authentication) before forwarding to the API Gateway origin. To support authenticated server-to-server calls, the SCC Lambda is accessible via direct API Gateway invocation:

POST https://[API_ID].execute-api.[REGION].amazonaws.com/prod/event
Header: X-Service-Key: [SERVICE_KEY]

The Lambda validates the service key against a hashed environment variable SERVICE_