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
.htmlfiles 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/xhqgmdhrequests - Rewrites to
/g/xhqgmdh.htmlbefore 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:
- JADA Internal Calendar Entry – Created via the calendar Lambda at
/tmp/calendar-lambda/index.jswith full financial details (captain fee, crew costs, port fees, net revenue). This is internal-only and never exposed to crew or guests. - SCC Event in DynamoDB – Created via the SCC Lambda at the
/eventPOST route. This record includes guest details, crew assignments, and timelines. Critically, thenotesfield originally containedRevenueandCaptain feevalues, which must be removed before crew access. - Guest Page in S3 – Deployed to
s3://queenofsandiego.com/g/{slug}.htmlwith a friendly slug (e.g.,boatsetter-may-30instead of the booking ID). The page includes a time-aware photo upload widget that presigns S3 URLs via the SCC Lambda'shandle_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.htmlfiles)/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 Lambdacrew_ids(SS) – Set of crew member IDs for invitationsnotes(M) – Operational data (date, time, guest count, notes); financial fields removed post-creationphotos_s3_prefix(S) – S3 path for guest photo uploadscreated_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_