Utility Endpoints
**Referenced Files in This Document** - [worker.js](file://worker.js) - [wrangler.jsonc](file://wrangler.jsonc) - [package.json](file://package.json) - [feed.njk](file://src/feed.njk) - [alliance-login.njk](file://src/alliance-login.njk) - [alliance-members.njk](file://src/alliance-members.njk) - [README.md](file://README.md)Table of Contents
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
Introduction
This document explains the Cloudflare Worker utility endpoints and helper functions that power the Invest Australia Alliance member portal. It covers:
- Member route validation under /alliance/members/* for session checking
- CORS preflight handling for cross-origin requests
- RSS feed generation via the feed.njk template
- KV namespace requirements for member management and token storage
- Cryptographic functions for HMAC signing, session token creation, and verification
- Asset proxy functionality for serving static content
- Error handling patterns and environment variable requirements
- Deployment configuration in wrangler.jsonc and troubleshooting for common deployment issues
Project Structure
The Cloudflare Worker integrates with an Eleventy-generated static site. The worker intercepts specific routes to enforce authentication, handle OAuth, and expose APIs, while delegating all other requests to the static asset handler.
graph TB
subgraph "Static Site (_site)"
ELEVENTY["Eleventy build output"]
end
subgraph "Cloudflare Worker"
WORKER["worker.js"]
ROUTES["Routes: /alliance/*, /api/*"]
ASSETS["ASSETS binding"]
end
subgraph "External Services"
KV1["KV: MEMBER_EMAILS"]
KV2["KV: MAGIC_TOKENS"]
RESEND["Resend API"]
SHEETS["Google Sheets API"]
end
ELEVENTY --> ASSETS
ASSETS --> WORKER
ROUTES --> WORKER
WORKER --> KV1
WORKER --> KV2
WORKER --> RESEND
WORKER --> SHEETS
Diagram sources
- [worker.js:297-320](file://worker.js#L297-L320)
- [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
Section sources
- [worker.js:297-320](file://worker.js#L297-L320)
- [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
Core Components
- Member route validation: GET /alliance/members/* validates a session cookie and serves protected pages.
- Magic-link login: POST /alliance/login/ issues a one-time token stored in KV and emails it via Resend.
- Token verification: GET /alliance/verify/ validates the token, deletes it, and issues a session cookie.
- Logout: GET /alliance/logout/ clears the session cookie.
- CORS preflight: OPTIONS /api/auth responds with allowed methods/headers for cross-origin requests.
- GitHub OAuth: /api/auth starts OAuth; /api/auth/callback exchanges code for a token.
- Polling API: GET /api/polling.json returns live polling data from Google Sheets.
- Asset proxy: All other requests are served via env.ASSETS.fetch.
Section sources
- [worker.js:77-91](file://worker.js#L77-L91)
- [worker.js:94-147](file://worker.js#L94-L147)
- [worker.js:149-177](file://worker.js#L149-L177)
- [worker.js:279-295](file://worker.js#L279-L295)
- [worker.js:183-191](file://worker.js#L183-L191)
- [worker.js:193-227](file://worker.js#L193-L227)
- [worker.js:233-276](file://worker.js#L233-L276)
- [worker.js:301-320](file://worker.js#L301-L320)
Architecture Overview
The worker’s fetch handler dispatches requests to specialized handlers. Authentication and API routes are handled in the worker; all other requests are proxied to the static asset handler bound as ASSETS.
sequenceDiagram
participant Client as "Browser"
participant Worker as "worker.js"
participant Assets as "ASSETS"
participant KV1 as "MEMBER_EMAILS"
participant KV2 as "MAGIC_TOKENS"
participant Resend as "Resend API"
participant Sheets as "Google Sheets"
Client->>Worker : Request
alt Protected member route
Worker->>Worker : Read cookie
Worker->>Worker : Verify session token
alt Valid session
Worker->>Assets : Fetch static page
Assets-->>Client : HTML/CSS/JS
else No/invalid session
Worker-->>Client : Redirect to /alliance/login/?next=...
end
else Login POST
Worker->>KV1 : Lookup member : <email>
alt Approved
Worker->>Worker : Generate token
Worker->>KV2 : Put token : <hex>, TTL=900s
Worker->>Resend : Send magic link email
end
Worker-->>Client : Redirect to /alliance/login/?sent=1
else Verify GET
Worker->>KV2 : Get token : <hex>
alt Found
Worker->>KV2 : Delete token : <hex>
Worker->>Worker : Create session token
Worker-->>Client : 302 to /alliance/members/ with Set-Cookie
else Not found/expired
Worker-->>Client : Redirect to /alliance/login/?error=expired
end
else OAuth
Worker-->>Client : Redirect to GitHub OAuth
Client->>Worker : Callback with code
Worker->>Worker : Exchange code for token
Worker-->>Client : HTML with token posted to opener
else Polling API
Worker->>Sheets : Fetch spreadsheet values
Sheets-->>Worker : JSON data
Worker-->>Client : JSON with CORS headers
else Other routes
Worker->>Assets : Fetch static asset
Assets-->>Client : Asset bytes
end
Diagram sources
- [worker.js:77-91](file://worker.js#L77-L91)
- [worker.js:94-147](file://worker.js#L94-L147)
- [worker.js:149-177](file://worker.js#L149-L177)
- [worker.js:183-227](file://worker.js#L183-L227)
- [worker.js:233-276](file://worker.js#L233-L276)
- [worker.js:301-320](file://worker.js#L301-L320)
Detailed Component Analysis
Member Route Validation (/alliance/members/*)
Purpose: Enforce session-based access to member-only pages. Reads the session cookie, verifies it, and either serves the asset or redirects to the login page with the original path preserved.
Key behaviors:
- Validates presence and validity of ace_member_session cookie
- Uses verifySessionToken to check signature and expiry
- Redirects to /alliance/login/?next={original path} if invalid
- Delegates to env.ASSETS.fetch when authorized
flowchart TD
Start(["GET /alliance/members/*"]) --> ReadCookie["Read ace_member_session"]
ReadCookie --> HasCookie{"Cookie present?"}
HasCookie --> |No| RedirectLogin["Redirect to /alliance/login/?next=..."]
HasCookie --> |Yes| Verify["verifySessionToken(payload.signature)"]
Verify --> Valid{"Valid and not expired?"}
Valid --> |Yes| Serve["env.ASSETS.fetch(request)"]
Valid --> |No| RedirectLogin
RedirectLogin --> End(["Done"])
Serve --> End
Diagram sources
- [worker.js:81-91](file://worker.js#L81-L91)
- [worker.js:39-58](file://worker.js#L39-L58)
Section sources
- [worker.js:77-91](file://worker.js#L77-L91)
- [worker.js:39-58](file://worker.js#L39-L58)
Magic-Link Login (/alliance/login/)
Purpose: Issue a one-time login link to approved members. Validates email, checks membership in KV, generates a random token, stores it in KV with TTL, and emails the link.
Key behaviors:
- POST form submission with email and hidden honeypot field
- Rejects bot submissions via honeypot
- Normalizes and validates email format
- Checks MEMBER_EMAILS for "member:
" - Generates 32-byte hex token, stores in MAGIC_TOKENS with TTL
- Sends HTML email via Resend with a link to /alliance/verify/?token=
- Always returns a “check your email” message to prevent enumeration
sequenceDiagram
participant Client as "Browser"
participant Worker as "worker.js"
participant KV1 as "MEMBER_EMAILS"
participant KV2 as "MAGIC_TOKENS"
participant Resend as "Resend API"
Client->>Worker : POST /alliance/login/ {email, _gotcha}
Worker->>Worker : Validate and normalize email
Worker->>KV1 : Get member : <email>
alt Approved
Worker->>Worker : Generate random 32-byte hex token
Worker->>KV2 : Put token : <hex>, TTL=900s
Worker->>Resend : Send magic link email
end
Worker-->>Client : Redirect to /alliance/login/?sent=1
Diagram sources
- [worker.js:97-147](file://worker.js#L97-L147)
- [worker.js:116-144](file://worker.js#L116-L144)
Section sources
- [worker.js:94-147](file://worker.js#L94-L147)
Token Verification and Session Issuance (/alliance/verify/)
Purpose: Validate the one-time token, delete it, create a session token, and set a secure cookie.
Key behaviors:
- Extracts token from query param
- Retrieves email from MAGIC_TOKENS
- Deletes the token immediately to prevent reuse
- Creates a session token with payload email|expiry signed by HMAC-SHA256
- Sets HttpOnly, Secure, SameSite=Lax, 30-day Max-Age cookie
- Redirects to /alliance/members/
sequenceDiagram
participant Client as "Browser"
participant Worker as "worker.js"
participant KV2 as "MAGIC_TOKENS"
Client->>Worker : GET /alliance/verify/?token=<hex>
Worker->>KV2 : Get token : <hex>
alt Found
Worker->>KV2 : Delete token : <hex>
Worker->>Worker : Create session token (payload|signature)
Worker-->>Client : 302 to /alliance/members/ with Set-Cookie
else Not found/expired
Worker-->>Client : Redirect to /alliance/login/?error=expired
end
Diagram sources
- [worker.js:153-177](file://worker.js#L153-L177)
- [worker.js:32-58](file://worker.js#L32-L58)
Section sources
- [worker.js:149-177](file://worker.js#L149-L177)
- [worker.js:32-58](file://worker.js#L32-L58)
Logout (/alliance/logout/)
Purpose: Clear the session cookie and redirect to the login page.
Key behaviors:
- Sets ace_member_session with Max-Age=0 to expire immediately
- Redirects to /alliance/login/
Section sources
- [worker.js:279-295](file://worker.js#L279-L295)
CORS Preflight Handling
Purpose: Allow cross-origin requests to /api/auth by responding to OPTIONS with appropriate headers.
Key behaviors:
- Responds to OPTIONS with Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers
- Used for Sveltia CMS OAuth integration
Section sources
- [worker.js:183-191](file://worker.js#L183-L191)
GitHub OAuth for Sveltia CMS
Purpose: Provide a simplified OAuth flow for CMS access via a hosted proxy.
Key behaviors:
- OPTIONS: Preflight response for cross-origin requests
- GET /api/auth: Redirects to GitHub OAuth authorize endpoint
- GET /api/auth/callback: Exchanges code for token and posts it to opener window
Section sources
- [worker.js:183-227](file://worker.js#L183-L227)
Polling API (/api/polling.json)
Purpose: Serve live polling data from Google Sheets with CORS and caching headers.
Key behaviors:
- Requires GOOGLE_SHEETS_ID and GOOGLE_SHEETS_API_KEY
- Supports state=federal or state=sa to select ranges
- Returns JSON with computed fields and cache headers
- Returns 503 if misconfigured, 500 on fetch errors
Section sources
- [worker.js:233-276](file://worker.js#L233-L276)
Asset Proxy (env.ASSETS.fetch)
Purpose: Serve static assets generated by Eleventy. All routes not explicitly handled by the worker are passed through to the ASSETS binding.
Key behaviors:
- env.ASSETS.fetch(request) serves the built site from _site/
- Configured in wrangler.jsonc under assets.directory and assets.binding
Section sources
- [worker.js:318](file://worker.js#L318)
- [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
RSS Feed Generation (feed.njk)
Purpose: Generate Atom feed at /feed.xml from the news collection.
Key behaviors:
- Uses Eleventy front matter to set permalink: /feed.xml
- Iterates over collections.news to produce entries
- Outputs Atom XML with title, subtitle, link, author, and entries
Section sources
- [feed.njk:1-27](file://src/feed.njk#L1-L27)
Dependency Analysis
- Internal dependencies:
- Crypto helpers (hmacSign, createSessionToken, verifySessionToken) used by member routes
- Cookie parsing helper (getCookie) used by member route
- Redirect helper (redirect) used across routes
- KV helpers (env.MEMBER_EMAILS, env.MAGIC_TOKENS) used by member routes
- External services:
- Resend API for sending magic link emails
- Google Sheets API for polling data
- External bindings:
- ASSETS binding for static asset proxy
- KV namespaces MEMBER_EMAILS and MAGIC_TOKENS
- Secrets: SESSION_SECRET, RESEND_API_KEY, GOOGLE_SHEETS_ID, GOOGLE_SHEETS_API_KEY, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
graph LR
Worker["worker.js"] --> Crypto["Crypto helpers"]
Worker --> Cookies["Cookie parsing"]
Worker --> Redirect["Redirect helper"]
Worker --> KV1["MEMBER_EMAILS"]
Worker --> KV2["MAGIC_TOKENS"]
Worker --> Resend["Resend API"]
Worker --> Sheets["Google Sheets API"]
Worker --> Assets["ASSETS"]
Diagram sources
- [worker.js:20-58](file://worker.js#L20-L58)
- [worker.js:60-68](file://worker.js#L60-L68)
- [worker.js:97-147](file://worker.js#L97-L147)
- [worker.js:153-177](file://worker.js#L153-L177)
- [worker.js:233-276](file://worker.js#L233-L276)
- [worker.js:318](file://worker.js#L318)
Section sources
- [worker.js:20-58](file://worker.js#L20-L58)
- [worker.js:60-68](file://worker.js#L60-L68)
- [worker.js:97-147](file://worker.js#L97-L147)
- [worker.js:153-177](file://worker.js#L153-L177)
- [worker.js:233-276](file://worker.js#L233-L276)
- [worker.js:318](file://worker.js#L318)
Performance Considerations
- KV latency: Member and token lookups are O(1) but still involve network latency. Keep KV keys minimal and leverage TTLs to avoid stale reads.
- Crypto operations: HMAC signing and session token verification are CPU-bound; keep payloads small.
- Email delivery: Resend API adds network overhead; ensure retry/backoff is handled by the external provider.
- Caching: Polling API sets cache headers to reduce load; consider increasing stale-while-revalidate for infrequent updates.
- Asset serving: env.ASSETS.fetch is optimized for Cloudflare; avoid unnecessary transformations in the worker.
Troubleshooting Guide
Missing KV Namespaces
Symptoms:
- 503 responses from member routes indicating KV namespaces not configured.
Resolution:
- Create MEMBER_EMAILS and MAGIC_TOKENS namespaces and bind them in wrangler.jsonc.
- Ensure the kv_namespaces section is populated with the correct IDs.
Section sources
- [worker.js:70-75](file://worker.js#L70-L75)
- [wrangler.jsonc:17-26](file://wrangler.jsonc#L17-L26)
Missing Secrets
Symptoms:
- Authentication failures or inability to send emails.
- Misconfigured polling API responses.
Resolution:
- Set SESSION_SECRET, RESEND_API_KEY, GOOGLE_SHEETS_ID, GOOGLE_SHEETS_API_KEY, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET via wrangler secret put.
- Verify secret names match those referenced in the worker.
Section sources
- [worker.js:4-10](file://worker.js#L4-L10)
- [README.md:497-511](file://README.md#L497-L511)
CORS Issues for /api/auth
Symptoms:
- Cross-origin requests blocked or failing preflight.
Resolution:
- Ensure OPTIONS requests to /api/auth return Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers.
Section sources
- [worker.js:183-191](file://worker.js#L183-L191)
Email Delivery Failures
Symptoms:
- Login attempts succeed but no email is received.
Resolution:
- Confirm RESEND_API_KEY is set and valid.
- Verify the email template and recipient address formatting.
- Check Resend dashboard for delivery logs.
Section sources
- [worker.js:123-144](file://worker.js#L123-L144)
Polling Data Not Updating
Symptoms:
- /api/polling.json returns cached or empty data.
Resolution:
- Verify GOOGLE_SHEETS_ID and GOOGLE_SHEETS_API_KEY are set.
- Confirm the spreadsheet ID and range names are correct.
- Check network connectivity and rate limits.
Section sources
- [worker.js:244-276](file://worker.js#L244-L276)
Deployment Issues
Common issues and resolutions:
- Node version mismatch: Ensure Node.js meets project requirements.
- Wrangler version: Use a compatible version as specified in devDependencies.
- Build pipeline: Run npm run build before deploying to ensure _site is up to date.
- Local preview: Use npm run preview to simulate production behavior.
Section sources
- [package.json:22-30](file://package.json#L22-L30)
- [README.md:48-76](file://README.md#L48-L76)
Conclusion
The Cloudflare Worker provides a focused set of utility endpoints for member authentication, OAuth, and API access, while delegating static asset serving to the ASSETS binding. Proper configuration of KV namespaces and secrets is essential for secure operation. The included CORS handling and RSS feed generation demonstrate practical patterns for cross-origin requests and content syndication.