Cloudflare Workers Implementation

**Referenced Files in This Document** - [worker.js](file://worker.js) - [wrangler.jsonc](file://wrangler.jsonc) - [package.json](file://package.json) - [README.md](file://README.md) - [cloudflare-pages.toml](file://cloudflare-pages.toml) - [netlify.toml](file://netlify.toml) - [src/alliance-login.njk](file://src/alliance-login.njk) - [src/alliance-members.njk](file://src/alliance-members.njk)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion
  10. Appendices

Introduction

This document explains the Cloudflare Workers implementation of the member authentication system for the Invest Australia Alliance platform. It covers the worker.js architecture, route handlers for member authentication and CMS OAuth, KV namespace configuration, environment variables, CORS setup, and static asset delivery via the ASSETS binding. It also provides practical deployment steps, environment configuration, troubleshooting tips, performance considerations, and monitoring approaches tailored to Cloudflare Workers.

Project Structure

The project is a hybrid static site built with Eleventy and served via Cloudflare Workers. The Worker intercepts specific routes for authentication and API integrations, while delegating all other requests to the static assets stored in the ASSETS binding.

graph TB
subgraph "Static Site (_site)"
A["HTML Templates<br/>Eleventy-generated"]
B["Admin CMS (Sveltia)<br/>_site/admin/"]
C["Search Index (Pagefind)<br/>_site/pagefind/"]
end
subgraph "Cloudflare Worker"
W["worker.js<br/>Export default fetch handler"]
R1["/alliance/members/*<br/>GET"]
R2["/alliance/login/<br/>POST"]
R3["/alliance/verify/<br/>GET"]
R4["/alliance/logout/<br/>GET"]
R5["/api/polling.json<br/>GET"]
R6["/api/auth/*<br/>GET/OPTIONS"]
R7["/api/auth/callback<br/>GET"]
end
subgraph "External Services"
KV1["KV: MEMBER_EMAILS"]
KV2["KV: MAGIC_TOKENS"]
RES["Resend API"]
GH["GitHub OAuth"]
GS["Google Sheets API"]
end
A --> W
B --> W
C --> W
W --> R1
W --> R2
W --> R3
W --> R4
W --> R5
W --> R6
W --> R7
R1 --> KV1
R2 --> KV1
R2 --> KV2
R2 --> RES
R3 --> KV2
R5 --> GS
R6 --> GH
R7 --> GH

Diagram sources

  • [worker.js:297-321](file://worker.js#L297-L321)
  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
  • [README.md:638-652](file://README.md#L638-L652)

Section sources

  • [worker.js:1-321](file://worker.js#L1-L321)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
  • [package.json:1-32](file://package.json#L1-L32)
  • [cloudflare-pages.toml:1-17](file://cloudflare-pages.toml#L1-L17)

Core Components

  • Export default fetch handler: The Worker exposes a single fetch handler that inspects the incoming request path and method to decide whether to handle the route or delegate to static assets.
  • Route handlers:
    • GET /alliance/members/*: Validates session cookie and serves protected content or redirects to login with a next parameter.
    • POST /alliance/login/: Issues a magic link to approved members and sends an email via Resend.
    • GET /alliance/verify/: Validates the one-time token, deletes it, and sets a session cookie.
    • GET /alliance/logout/: Clears the session cookie and redirects to the login page.
    • GET /api/polling.json: Returns cached polling data from Google Sheets.
    • GET /api/auth/* and /api/auth/callback: Legacy GitHub OAuth for CMS (OPTIONS preflight).
  • Crypto helpers: HMAC-based session token creation and verification with constant-time comparison.
  • Cookie utilities: Parsing cookies and constructing secure Set-Cookie headers.
  • KV helpers: Early exit when KV namespaces are not configured.

Section sources

  • [worker.js:297-321](file://worker.js#L297-L321)
  • [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:230-276](file://worker.js#L230-L276)
  • [worker.js:179-227](file://worker.js#L179-L227)
  • [worker.js:19-58](file://worker.js#L19-L58)

Architecture Overview

The Worker acts as a router and orchestrator:

  • Authentication: Uses MEMBER_EMAILS to validate membership and MAGIC_TOKENS to store one-time tokens with TTL.
  • Session management: Creates signed session tokens with HMAC-SHA-256 and stores them in HttpOnly cookies.
  • Static delivery: Delegates all unmatched routes to env.ASSETS.fetch for efficient CDN caching.
  • Integrations: Sends magic links via Resend, authenticates CMS via GitHub OAuth, and fetches polling data from Google Sheets.
sequenceDiagram
participant U as "User Agent"
participant W as "Worker (fetch)"
participant KV as "KV Namespaces"
participant R as "Resend API"
participant S as "Static Assets (ASSETS)"
U->>W : "POST /alliance/login/"
W->>KV : "Lookup MEMBER_EMAILS"
alt "Approved email"
W->>KV : "Put MAGIC_TOKENS with TTL"
W->>R : "Send magic link email"
else "Not approved or invalid"
W-->>U : "Redirect with 'sent=1'"
end
W-->>U : "Redirect to /alliance/login/?sent=1"
U->>W : "GET /alliance/verify/?token=..."
W->>KV : "Get MAGIC_TOKENS"
alt "Valid token"
W->>KV : "Delete token"
W-->>U : "302 to /alliance/members/ with session cookie"
else "Invalid/expired"
W-->>U : "302 to /alliance/login/?error=expired"
end
U->>W : "GET /alliance/members/*"
W->>W : "Parse cookie and verify HMAC signature"
alt "Valid session"
W->>S : "Fetch static content"
S-->>U : "200 OK"
else "No or invalid session"
W-->>U : "302 to /alliance/login/?next=/alliance/members/..."
end

Diagram sources

  • [worker.js:94-147](file://worker.js#L94-L147)
  • [worker.js:149-177](file://worker.js#L149-L177)
  • [worker.js:77-91](file://worker.js#L77-L91)
  • [worker.js:297-321](file://worker.js#L297-L321)

Detailed Component Analysis

Export Default Fetch Handler and Request Routing

  • The export default fetch handler inspects the pathname and method to dispatch to the appropriate route handler.
  • Unhandled routes are passed to env.ASSETS.fetch for static delivery.
  • CORS preflight is handled for the legacy CMS OAuth endpoints.
flowchart TD
Start(["Incoming Request"]) --> Parse["Parse URL and method"]
Parse --> Route{"Route Match?"}
Route --> |/alliance/members/*| Members["handleMembersRoute"]
Route --> |/alliance/login/ POST| Login["handleLoginPost"]
Route --> |/alliance/verify/ GET| Verify["handleVerify"]
Route --> |/alliance/logout/ GET| Logout["handleLogout"]
Route --> |/api/polling.json GET| Polling["handlePollingApi"]
Route --> |OPTIONS| Cors["handleCorsPrelight"]
Route --> |/api/auth/*| OAuthStart["handleOAuthStart"]
Route --> |/api/auth/callback| OAuthCb["handleOAuthCallback"]
Route --> |Else| Assets["env.ASSETS.fetch(request)"]
Members --> Assets
Login --> Assets
Verify --> Assets
Logout --> Assets
Polling --> Assets
Cors --> Assets
OAuthStart --> Assets
OAuthCb --> Assets

Diagram sources

  • [worker.js:297-321](file://worker.js#L297-L321)

Section sources

  • [worker.js:297-321](file://worker.js#L297-L321)

Member Authentication Flow

  • Session cookie name and duration are defined constants.
  • Session tokens are signed payloads containing email and expiry, verified via HMAC.
  • Cookie attributes include HttpOnly, Secure, SameSite=Lax, and Max-Age aligned with session duration.
sequenceDiagram
participant U as "User Agent"
participant W as "Worker"
participant KV as "KV : MAGIC_TOKENS"
participant S as "Static Assets"
U->>W : "GET /alliance/verify/?token=hex"
W->>KV : "Get token : <hex>"
alt "Token exists"
W->>KV : "Delete token : <hex>"
W-->>U : "302 to /alliance/members/ with Set-Cookie"
else "Token missing/expired"
W-->>U : "302 to /alliance/login/?error=expired"
end
U->>W : "GET /alliance/members/*"
W->>W : "Parse and verify session cookie"
alt "Valid"
W->>S : "Fetch static content"
S-->>U : "200 OK"
else "Invalid"
W-->>U : "302 to /alliance/login/?next=/alliance/members/..."
end

Diagram sources

  • [worker.js:149-177](file://worker.js#L149-L177)
  • [worker.js:77-91](file://worker.js#L77-L91)

Section sources

  • [worker.js:12-14](file://worker.js#L12-L14)
  • [worker.js:32-58](file://worker.js#L32-L58)
  • [worker.js:163-171](file://worker.js#L163-L171)

Login Route Handler (Magic Link)

  • Validates form submission and applies a basic bot trap using a hidden field.
  • Normalizes and validates the email address.
  • Checks membership against MEMBER_EMAILS and, if approved, generates a random token, stores it in MAGIC_TOKENS with TTL, and sends an email via Resend.
  • Always responds with a “check your email” message to avoid member enumeration.
flowchart TD
Start(["POST /alliance/login/"]) --> FormData["Parse form data"]
FormData --> Honeypot{"_gotcha empty?"}
Honeypot --> |No| RedirectSent["Redirect to /alliance/login/?sent=1"]
Honeypot --> |Yes| EmailValid{"Valid email?"}
EmailValid --> |No| RedirectEmail["Redirect to /alliance/login/?error=email"]
EmailValid --> |Yes| CheckMember["Lookup MEMBER_EMAILS"]
CheckMember --> Approved{"Approved?"}
Approved --> |No| ShowEmail["Show 'check your email'"]
Approved --> |Yes| GenToken["Generate random token"]
GenToken --> StoreKV["Put token:<hex> -> email (TTL)"]
StoreKV --> SendMail["POST to Resend API"]
SendMail --> ShowEmail
ShowEmail --> End(["Redirect to /alliance/login/?sent=1"])

Diagram sources

  • [worker.js:94-147](file://worker.js#L94-L147)

Section sources

  • [worker.js:94-147](file://worker.js#L94-L147)

Verification Route Handler

  • Extracts token from query parameters and retrieves the associated email from MAGIC_TOKENS.
  • Deletes the token immediately to enforce one-time use.
  • Constructs a signed session token and sets a secure cookie, then redirects to the members area.
flowchart TD
Start(["GET /alliance/verify/"]) --> GetToken["Extract token param"]
GetToken --> TokenExists{"Token present?"}
TokenExists --> |No| RedirectInvalid["Redirect to /alliance/login/?error=invalid"]
TokenExists --> |Yes| LookupKV["Get token:<hex> from MAGIC_TOKENS"]
LookupKV --> Found{"Email found?"}
Found --> |No| RedirectExpired["Redirect to /alliance/login/?error=expired"]
Found --> |Yes| DeleteKV["Delete token:<hex>"]
DeleteKV --> CreateSession["Create session token (HMAC-signed)"]
CreateSession --> SetCookie["Set secure session cookie"]
SetCookie --> RedirectMembers["Redirect to /alliance/members/"]

Diagram sources

  • [worker.js:149-177](file://worker.js#L149-L177)

Section sources

  • [worker.js:149-177](file://worker.js#L149-L177)

Logout Route Handler

  • Clears the session cookie by setting Max-Age=0 and redirects to the login page.

Section sources

  • [worker.js:279-295](file://worker.js#L279-L295)

GitHub OAuth for CMS (Legacy)

  • OPTIONS preflight allows cross-origin requests from the CMS origin.
  • GET /api/auth starts the OAuth flow by redirecting to GitHub with client credentials.
  • GET /api/auth/callback exchanges the code for an access token and posts it back to the opener window for the CMS.
sequenceDiagram
participant CMS as "Sveltia CMS"
participant W as "Worker"
participant GH as "GitHub OAuth"
CMS->>W : "OPTIONS /api/auth/*"
W-->>CMS : "Allow Origin, Methods, Headers"
CMS->>W : "GET /api/auth/*"
W->>GH : "Authorize URL with client_id"
GH-->>CMS : "302 to GitHub"
CMS->>W : "GET /api/auth/callback?code=..."
W->>GH : "Exchange code for access_token"
GH-->>W : "access_token"
W-->>CMS : "HTML with script posting token to opener"

Diagram sources

  • [worker.js:179-227](file://worker.js#L179-L227)

Section sources

  • [worker.js:183-191](file://worker.js#L183-L191)
  • [worker.js:193-227](file://worker.js#L193-L227)

Live Polling API

  • Returns polling data from Google Sheets with a public cache policy.
  • Requires GOOGLE_SHEETS_ID and GOOGLE_SHEETS_API_KEY secrets.
  • Supports federal and SA ranges based on query parameter.

Section sources

  • [worker.js:230-276](file://worker.js#L230-L276)

Frontend Templates

  • Login template renders a form that posts to /alliance/login/, includes a bot trap field, and shows success/error messages based on query parameters.
  • Members template displays protected content and a logout link.

Section sources

  • [src/alliance-login.njk:21-72](file://src/alliance-login.njk#L21-L72)
  • [src/alliance-members.njk:14](file://src/alliance-members.njk#L14)

Dependency Analysis

  • Internal dependencies:
    • Crypto helpers depend on WebCrypto for HMAC signing and verification.
    • Cookie parsing depends on request headers.
    • Route handlers depend on KV namespaces and external APIs.
  • External dependencies:
    • MEMBER_EMAILS and MAGIC_TOKENS KV namespaces.
    • Resend API for sending magic link emails.
    • GitHub OAuth endpoints for CMS authentication.
    • Google Sheets API for polling data.
  • Static assets:
    • ASSETS binding serves the entire generated site.
graph LR
W["worker.js"] --> C["Crypto helpers"]
W --> K["Cookies"]
W --> M["/alliance/members/*"]
W --> L["/alliance/login/"]
W --> V["/alliance/verify/"]
W --> O["/alliance/logout/"]
W --> P["/api/polling.json"]
W --> G["/api/auth/*"]
W --> GC["/api/auth/callback"]
M --> KV1["MEMBER_EMAILS"]
L --> KV1
L --> KV2["MAGIC_TOKENS"]
V --> KV2
L --> RES["Resend API"]
P --> GS["Google Sheets API"]
G --> GH["GitHub OAuth"]
GC --> GH
W --> AS["ASSETS (static)"]

Diagram sources

  • [worker.js:19-58](file://worker.js#L19-L58)
  • [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:230-276](file://worker.js#L230-L276)
  • [worker.js:179-227](file://worker.js#L179-L227)
  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)

Section sources

  • [worker.js:19-58](file://worker.js#L19-L58)
  • [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:230-276](file://worker.js#L230-L276)
  • [worker.js:179-227](file://worker.js#L179-L227)
  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)

Performance Considerations

  • Cold start optimization:
    • Keep the Worker minimal and avoid heavy initialization in fetch.
    • Use KV reads for membership checks and token validation; these are fast and cached by Cloudflare.
    • Avoid synchronous network calls during cold start; the current design defers external calls until needed.
  • Static delivery:
    • ASSETS binding serves static content efficiently; ensure cache headers are appropriate for your content.
  • KV usage:
    • MAGIC_LINK_TTL is set to 15 minutes; ensure tokens are deleted after use to prevent reuse.
    • MEMBER_EMAILS keys use a simple prefix; keep the list manageable for KV performance.
  • Email delivery:
    • Resend API calls are asynchronous; consider retry/backoff if needed and monitor delivery failures.
  • Monitoring:
    • Enable Worker observability in wrangler.jsonc to capture logs and metrics.
    • Use Cloudflare Analytics and Logs to track route hit rates, KV operations, and external API latency.

[No sources needed since this section provides general guidance]

Troubleshooting Guide

  • KV namespaces not configured:
    • The Worker returns a 503 response when MEMBER_EMAILS or MAGIC_TOKENS are missing. Configure namespaces and bind them in wrangler.jsonc.
  • Missing secrets:
    • SESSION_SECRET and RESEND_API_KEY are required for authentication and email delivery. Set them via wrangler secret put.
  • CORS issues for CMS OAuth:
    • OPTIONS preflight must allow GET, POST, OPTIONS and Content-Type from the CMS origin. Verify the Access-Control-Allow-Origin header.
  • Static assets not loading:
    • Ensure ASSETS binding points to the correct directory and that the build output is uploaded by wrangler deploy.
  • Email delivery failures:
    • Check Resend API key validity and verify the from address and recipient email.
  • Session cookie not persisting:
    • Confirm cookie attributes (HttpOnly, Secure, SameSite) and that the domain/path match the site’s configuration.
  • Preview deployments:
    • Use wrangler dev for local preview and confirm that the build pipeline produces the _site directory.

Section sources

  • [worker.js:70-75](file://worker.js#L70-L75)
  • [worker.js:183-191](file://worker.js#L183-L191)
  • [wrangler.jsonc:28-34](file://wrangler.jsonc#L28-L34)
  • [README.md:523-548](file://README.md#L523-L548)

Conclusion

The Cloudflare Workers implementation provides a secure, efficient member authentication system integrated with static site delivery. By leveraging KV namespaces for membership and one-time tokens, HMAC-signed session cookies, and external services for email and data, the Worker offers a robust foundation for the Alliance’s members portal. Proper configuration of secrets, KV namespaces, and CORS ensures smooth operation, while observability and monitoring enable ongoing reliability.

[No sources needed since this section summarizes without analyzing specific files]

Appendices

Environment Variables and Secrets

  • SESSION_SECRET: Random 32+ byte string for HMAC signing of session tokens.
  • RESEND_API_KEY: API key for Resend to send magic link emails.
  • GOOGLE_SHEETS_ID and GOOGLE_SHEETS_API_KEY: Required for the polling API.
  • GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET: Required for legacy CMS OAuth.
  • ASSETS binding: Points to the generated static site directory.

Section sources

  • [worker.js:4-6](file://worker.js#L4-L6)
  • [worker.js:233-246](file://worker.js#L233-L246)
  • [worker.js:193-210](file://worker.js#L193-L210)
  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
  • [wrangler.jsonc:28-34](file://wrangler.jsonc#L28-L34)

KV Namespace Configuration

  • MEMBER_EMAILS:
    • Purpose: Store approved member emails.
    • Key format: member:
    • Value: "1"
  • MAGIC_TOKENS:
    • Purpose: Store one-time login tokens.
    • Key format: token:
    • Value: email
    • TTL: 900 seconds (15 minutes)

Section sources

  • [worker.js:8-10](file://worker.js#L8-L10)
  • [worker.js:116-120](file://worker.js#L116-L120)
  • [worker.js:158](file://worker.js#L158)

CORS Configuration for GitHub OAuth

  • Access-Control-Allow-Origin: https://acestrategies.au
  • Access-Control-Allow-Methods: GET, POST, OPTIONS
  • Access-Control-Allow-Headers: Content-Type
  • OPTIONS preflight is handled for /api/auth/* routes.

Section sources

  • [worker.js:183-191](file://worker.js#L183-L191)
  • [worker.js:314](file://worker.js#L314)

Static Content Delivery via ASSETS Binding

  • ASSETS binding is configured to serve the _site directory.
  • All unmatched routes are delegated to env.ASSETS.fetch(request).

Section sources

  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
  • [worker.js:318](file://worker.js#L318)

Deployment and Environment Setup

  • Build: npm run build generates the static site and search index.
  • Deploy: npm run deploy uploads assets and deploys the Worker.
  • First-time setup includes creating KV namespaces, setting secrets, and configuring domains.

Section sources

  • [package.json:5-12](file://package.json#L5-L12)
  • [README.md:523-548](file://README.md#L523-L548)
  • [README.md:556-562](file://README.md#L556-L562)

Route Reference

  • /alliance/members/* GET: Session check; serve or redirect to login.
  • /alliance/logout/ GET: Clear cookie; redirect to login.
  • /alliance/verify/ GET: Validate magic token; issue session cookie.
  • /alliance/login/ POST: Validate email; send magic link via Resend.
  • /api/polling.json GET: SA polling data from Google Sheets (5-min cache).
  • /api/auth/* GET: GitHub OAuth start (legacy CMS).
  • /api/auth/callback GET: GitHub OAuth callback (legacy CMS).
  • OPTIONS *: CORS preflight for CMS auth.
  • /*: Static assets from ASSETS.

Section sources

  • [README.md:638-652](file://README.md#L638-L652)
  • [worker.js:297-321](file://worker.js#L297-L321)