Member Authentication Portal

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

Update Summary

Changes Made

  • Enhanced login flow reliability with improved rate limiting mechanism
  • Added comprehensive error handling for email delivery failures
  • Implemented honeypot field protection against automated bots
  • Improved frontend error messaging and user experience
  • Strengthened security measures with better input validation

Table of Contents

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

Introduction

This document explains the Cloudflare Workers-based member authentication system for the Invest Australia Alliance (IAA) portal. It covers the magic link authentication flow, token generation and storage in KV namespaces, session management, security measures, and the Cloudflare Worker routes. The system has been enhanced with improved login flow reliability, rate limiting, and better error handling to provide a more robust and secure authentication experience.

Project Structure

The member portal is implemented as a Cloudflare Worker that intercepts specific routes and serves static assets for the rest. The site is generated by Eleventy and deployed via Wrangler to Cloudflare Workers.

graph TB
subgraph "Static Site (Eleventy)"
ELE["Generated _site/"]
TPL_LOGIN["Template: alliance-login.njk"]
TPL_MEMBERS["Template: alliance-members.njk"]
end
subgraph "Cloudflare Worker"
WRK["worker.js"]
KV_ME["KV: MEMBER_EMAILS"]
KV_MT["KV: MAGIC_TOKENS"]
SECRET["Secrets: SESSION_SECRET, RESEND_API_KEY, GOOGLE_*"]
RATE_LIMIT["Rate Limiting: LOGIN_RATE_WINDOW, LOGIN_RATE_MAX"]
HONEYPOT["Honeypot Protection: _gotcha field"]
END_USER["End User Experience: Enhanced Error Handling"]
end
subgraph "External Services"
RES["Resend API"]
SHEETS["Google Sheets API"]
end
ELE --> WRK
TPL_LOGIN --> ELE
TPL_MEMBERS --> ELE
WRK --> KV_ME
WRK --> KV_MT
WRK --> RES
WRK --> SHEETS
SECRET --> WRK
RATE_LIMIT --> WRK
HONEYPOT --> WRK
END_USER --> WRK

Diagram sources

  • [worker.js:1-359](file://worker.js#L1-L359)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
  • [src/alliance-login.njk:1-131](file://src/alliance-login.njk#L1-L131)
  • [src/alliance-members.njk:1-71](file://src/alliance-members.njk#L1-L71)

Section sources

  • [README.md:80-156](file://README.md#L80-L156)
  • [package.json:1-32](file://package.json#L1-L32)

Core Components

  • Cloudflare Worker entrypoint and routing logic with enhanced error handling
  • HMAC-based session tokens and cookie issuance
  • KV namespaces for approved members and one-time magic tokens with rate limiting
  • Frontend templates for login and members portal with improved user experience
  • Comprehensive security measures including rate limiting, honeypot protection, and input validation
  • Secrets for session signing and external integrations

Key implementation references:

  • Worker routes and handlers: [worker.js:339-359](file://worker.js#L339-L359)
  • Session creation and verification: [worker.js:20-60](file://worker.js#L20-L60)
  • Login route (magic link issuance) with rate limiting: [worker.js:99-177](file://worker.js#L99-L177)
  • Verification route (cookie issuance): [worker.js:183-207](file://worker.js#L183-L207)
  • Logout route with CSRF protection: [worker.js:312-333](file://worker.js#L312-L333)
  • Rate limiting implementation: [worker.js:102-114](file://worker.js#L102-L114)
  • Honeypot protection: [worker.js:116-126](file://worker.js#L116-L126)
  • KV bindings and secrets: [wrangler.jsonc:17-34](file://wrangler.jsonc#L17-L34)
  • Frontend login page with enhanced error handling: [src/alliance-login.njk:1-131](file://src/alliance-login.njk#L1-L131)
  • Frontend members portal: [src/alliance-members.njk:1-71](file://src/alliance-members.njk#L1-L71)

Section sources

  • [worker.js:1-359](file://worker.js#L1-L359)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
  • [src/alliance-login.njk:1-131](file://src/alliance-login.njk#L1-L131)
  • [src/alliance-members.njk:1-71](file://src/alliance-members.njk#L1-L71)

Architecture Overview

The system enforces authentication for member-only routes by checking a signed session cookie. If missing or invalid, users are redirected to a login page where they submit an email. The enhanced flow includes rate limiting to prevent abuse, honeypot protection against bots, and comprehensive error handling. If the email is approved, a one-time token is generated and stored in KV, and a magic link email is sent. Clicking the link validates the token, deletes it from KV, and issues a long-lived session cookie.

sequenceDiagram
participant U as "User Browser"
participant W as "Cloudflare Worker"
participant KV as "KV Namespaces"
participant R as "Resend API"
U->>W : GET /alliance/members/*
W->>W : Read ace_member_session cookie
alt Cookie present and valid
W-->>U : Serve members page (assets)
else No/invalid cookie
W-->>U : 302 Redirect to /alliance/login/?next=...
end
U->>W : POST /alliance/login/ (email)
Note over W : Rate limiting check
W->>KV : Check rate limit for IP
alt Rate exceeded
W-->>U : Redirect with error=send
else Within limit
Note over W : Honeypot validation
W->>W : Validate email format and honeypot
alt Valid and approved
W->>W : Generate random 32-byte token (hex)
W->>KV : Put MAGIC_TOKENS "token : <hex>" -> email (TTL 900s)
W->>R : Send magic link email
alt Email send failed
W->>KV : Delete token and return error
else Email sent successfully
W-->>U : Redirect to login with status
end
end
end
W-->>U : 302 Redirect to /alliance/login/?sent=1
U->>W : GET /alliance/verify/?token=<hex>
W->>KV : Get MAGIC_TOKENS "token : <hex>"
alt Found
W->>KV : Delete "token : <hex>"
W->>W : Create session token (HMAC-SHA256)
W-->>U : 302 Redirect to /alliance/members/ with session cookie
else Not found/expired
W-->>U : 302 Redirect to /alliance/login/?error=expired
end

Diagram sources

  • [worker.js:78-93](file://worker.js#L78-L93)
  • [worker.js:99-177](file://worker.js#L99-L177)
  • [worker.js:183-207](file://worker.js#L183-L207)

Detailed Component Analysis

Enhanced Magic Link Authentication Flow

  • Session cookie name: ace_member_session
  • Session duration: 30 days
  • Magic link TTL: 15 minutes
  • Token storage: KV MAGIC_TOKENS with key pattern "token:"
  • Approved member storage: KV MEMBER_EMAILS with key pattern "member:"
  • Rate limiting: 5 POST requests per minute per IP address
  • Honeypot protection: Hidden form field to detect automated submissions
flowchart TD
Start(["User accesses /alliance/members/*"]) --> CheckCookie["Read ace_member_session"]
CheckCookie --> HasCookie{"Cookie present and valid?"}
HasCookie --> |Yes| Serve["Serve members page"]
HasCookie --> |No| RedirectLogin["Redirect to /alliance/login/?next=..."]
RedirectLogin --> SubmitEmail["POST /alliance/login/ with email"]
SubmitEmail --> RateLimit["Check rate limit (5 per minute per IP)"]
RateLimit --> WithinLimit{"Within limit?"}
WithinLimit --> |No| ShowError["Show rate limit exceeded message"]
WithinLimit --> |Yes| ValidateHoneypot["Validate honeypot field"]
ValidateHoneypot --> ValidSubmission{"Valid submission?"}
ValidSubmission --> |No| ShowSent["Show 'check your email' message"]
ValidSubmission --> |Yes| ValidateEmail["Validate email format"]
ValidateEmail --> IsApproved{"Member approved?"}
IsApproved --> |No| ShowSent
IsApproved --> |Yes| GenToken["Generate 32-byte hex token"]
GenToken --> StoreToken["Store in MAGIC_TOKENS with TTL 900s"]
StoreToken --> SendEmail["Send magic link via Resend"]
SendEmail --> EmailSuccess{"Email sent successfully?"}
EmailSuccess --> |No| DeleteToken["Delete token and show error"]
EmailSuccess --> |Yes| ShowSent
ShowSent --> ClickLink["User clicks magic link"]
ClickLink --> VerifyToken["Lookup token in MAGIC_TOKENS"]
VerifyToken --> Found{"Token found?"}
Found --> |No| Expired["Redirect to login with error=expired"]
Found --> |Yes| DeleteToken["Delete token from MAGIC_TOKENS"]
DeleteToken --> CreateSession["Create session token (payload|signature)"]
CreateSession --> IssueCookie["Issue HttpOnly; Secure; SameSite=Lax; Max-Age=2592000"]
IssueCookie --> RedirectMembers["Redirect to /alliance/members/"]

Diagram sources

  • [worker.js:78-93](file://worker.js#L78-L93)
  • [worker.js:99-177](file://worker.js#L99-L177)
  • [worker.js:183-207](file://worker.js#L183-L207)

Section sources

  • [worker.js:12-16](file://worker.js#L12-L16)
  • [worker.js:8-10](file://worker.js#L8-L10)
  • [worker.js:78-93](file://worker.js#L78-L93)
  • [worker.js:99-177](file://worker.js#L99-L177)
  • [worker.js:183-207](file://worker.js#L183-L207)

Session Management and Security

  • Session token payload: base64("email|expiry")
  • Signature: HMAC-SHA256 with SESSION_SECRET
  • Cookie flags: HttpOnly, Secure, SameSite=Lax, Max-Age=2592000 (30 days)
  • Timing attack protection: constant-time signature comparison
  • CSRF mitigation: SameSite=Lax and Secure flags
  • Rate limiting: 5 login attempts per minute per IP address
  • Input validation: Comprehensive email format checking
  • Honeypot protection: Hidden form field to detect bots
classDiagram
class SessionToken {
+string payload
+string signature
+verify(secret) bool
}
class RateLimiter {
+string ip
+number count
+number window
+checkLimit() bool
}
class HoneypotValidator {
+string honeypotValue
+validate() bool
}
class HMAC_SHA256 {
+sign(data, key) string
+compare(a, b) bool
}
SessionToken --> HMAC_SHA256 : "uses"
RateLimiter --> KV : "uses"
HoneypotValidator --> Form : "validates"

Diagram sources

  • [worker.js:20-60](file://worker.js#L20-L60)
  • [worker.js:102-114](file://worker.js#L102-L114)
  • [worker.js:116-126](file://worker.js#L116-L126)

Section sources

  • [worker.js:20-60](file://worker.js#L20-L60)
  • [worker.js:102-114](file://worker.js#L102-L114)
  • [worker.js:116-126](file://worker.js#L116-L126)
  • [worker.js:164-171](file://worker.js#L164-L171)

Cloudflare Worker Routes

  • /alliance/members/* — GET: session check; serve or redirect
  • /alliance/logout/ — GET: clear session cookie with CSRF protection
  • /alliance/verify/ — GET: validate token, issue session cookie
  • /alliance/login/ — POST: validate email, apply rate limiting, send magic link
  • /api/polling.json — GET: live polling data (non-auth)
  • /api/auth/* — legacy GitHub OAuth (non-auth)
sequenceDiagram
participant U as "User"
participant W as "Worker"
participant KV as "KV"
participant R as "Resend"
U->>W : GET /alliance/members/*
W-->>U : Serve or redirect
U->>W : POST /alliance/login/
Note over W : Rate limiting and validation
W->>KV : Check rate limit
W->>KV : Check MEMBER_EMAILS
alt Approved and within rate limit
W->>KV : Put MAGIC_TOKENS
W->>R : Send email
alt Email send failed
W->>KV : Delete token
W-->>U : Redirect with error=send
else Not approved or rate exceeded
W-->>U : Redirect with appropriate error
end
U->>W : GET /alliance/verify/?token
W->>KV : Get token
alt Valid
W->>KV : Delete token
W-->>U : Redirect to members with session cookie
else Invalid
W-->>U : Redirect to login with error
end
U->>W : GET /alliance/logout/
Note over W : CSRF protection check
W-->>U : Redirect to login with expired cookie

Diagram sources

  • [worker.js:339-359](file://worker.js#L339-L359)
  • [worker.js:78-93](file://worker.js#L78-L93)
  • [worker.js:99-177](file://worker.js#L99-L177)
  • [worker.js:183-207](file://worker.js#L183-L207)
  • [worker.js:312-333](file://worker.js#L312-L333)

Section sources

  • [worker.js:339-359](file://worker.js#L339-L359)
  • [README.md:638-652](file://README.md#L638-L652)

KV Namespace Configuration

  • MEMBER_EMAILS: stores approved member emails with keys "member:"
  • MAGIC_TOKENS: stores one-time tokens with keys "token:" and TTL 900 seconds
  • Rate limiting counters: stored with keys "rate:login:" and TTL 60 seconds
erDiagram
MEMBER_EMAILS {
string key PK
string value
}
MAGIC_TOKENS {
string key PK
string value
int expirationTtl
}
RATE_LIMIT_COUNTERS {
string key PK
string value
int expirationTtl
}

Diagram sources

  • [worker.js:8-10](file://worker.js#L8-L10)
  • [worker.js:105](file://worker.js#L105)
  • [wrangler.jsonc:17-26](file://wrangler.jsonc#L17-L26)

Section sources

  • [worker.js:8-10](file://worker.js#L8-L10)
  • [worker.js:105](file://worker.js#L105)
  • [wrangler.jsonc:17-26](file://wrangler.jsonc#L17-L26)

Frontend Templates

  • Login page: renders a form to submit email; shows "check your email" after submission; displays enhanced error messages for invalid/expired links, rate limiting, and email sending failures; includes honeypot field for bot protection
  • Members portal: displays dashboard tiles and resources; includes a sign-out link

Section sources

  • [src/alliance-login.njk:1-131](file://src/alliance-login.njk#L1-L131)
  • [src/alliance-members.njk:1-71](file://src/alliance-members.njk#L1-L71)
  • [src/_data/allianceMembers.json:1-40](file://src/_data/allianceMembers.json#L1-L40)

Enhanced Security Features

Rate Limiting Implementation

The system now includes sophisticated rate limiting to prevent abuse and automated attacks:

  • Maximum 5 login attempts per minute per IP address
  • Uses MAGIC_TOKENS KV namespace as a shared counter store
  • Graceful degradation: if rate limiting fails, the system continues to operate (fail-open principle)
  • Rate limit counters automatically expire after 60 seconds

Honeypot Protection

Automated bot submissions are detected and blocked:

  • Hidden form field named "_gotcha" with zero visibility styling
  • If the field contains any value, the submission is treated as spam
  • Prevents automated scraping and login attempts
  • Maintains user experience by not affecting legitimate users

Enhanced Error Handling

Improved error handling throughout the authentication flow:

  • Comprehensive error messages for different failure scenarios
  • Graceful handling of email delivery failures
  • Proper cleanup of temporary tokens on errors
  • User-friendly error presentation on the login page

CSRF Protection

Enhanced logout functionality includes CSRF protection:

  • Requires POST method or same-site requests for logout
  • Prevents one-click logout attacks
  • Maintains security while preserving usability

Section sources

  • [worker.js:102-114](file://worker.js#L102-L114)
  • [worker.js:116-126](file://worker.js#L116-L126)
  • [worker.js:164-171](file://worker.js#L164-L171)
  • [worker.js:312-333](file://worker.js#L312-L333)

Dependency Analysis

  • worker.js depends on:
    • KV namespaces MEMBER_EMAILS and MAGIC_TOKENS
    • Secrets SESSION_SECRET and RESEND_API_KEY
    • ASSETS binding for static file serving
  • Frontend templates depend on:
    • Static assets served by the Worker
    • Paths aligned with Worker routes (/alliance/login/, /alliance/verify/, /alliance/logout/)
    • Enhanced error handling and user experience features
graph LR
WRK["worker.js"] --> KV1["MEMBER_EMAILS"]
WRK --> KV2["MAGIC_TOKENS"]
WRK --> SESS["SESSION_SECRET"]
WRK --> RES["RESEND_API_KEY"]
WRK --> ASSETS["ASSETS binding"]
WRK --> RATE["Rate Limiting"]
WRK --> HONEYPOT["Honeypot Protection"]
LOGIN["alliance-login.njk"] --> WRK
MEMBERS["alliance-members.njk"] --> WRK

Diagram sources

  • [worker.js:1-359](file://worker.js#L1-L359)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
  • [src/alliance-login.njk:1-131](file://src/alliance-login.njk#L1-L131)
  • [src/alliance-members.njk:1-71](file://src/alliance-members.njk#L1-L71)

Section sources

  • [worker.js:1-359](file://worker.js#L1-L359)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)

Performance Considerations

  • KV latency: KV operations are fast but add network latency; keep token TTL short (15 minutes) and session cookie long-lived (30 days) to balance UX and security.
  • Email delivery: Resend API latency affects user experience; ensure reliable connectivity and consider retry strategies if needed.
  • Rate limiting: Minimal performance impact with efficient KV-based counting mechanism.
  • Static asset delivery: All non-auth routes are served via ASSETS; ensure minimal Worker overhead for static content.
  • Caching: The polling API endpoint caches responses; leverage this to reduce external API calls.

Troubleshooting Guide

Common issues and resolutions:

  • KV namespaces not configured
    • Symptom: 503 response indicating KV not configured
    • Resolution: Create MEMBER_EMAILS and MAGIC_TOKENS namespaces and bind them in wrangler.jsonc; set SESSION_SECRET and RESEND_API_KEY
    • References: [worker.js:72-77](file://worker.js#L72-L77), [wrangler.jsonc:17-34](file://wrangler.jsonc#L17-L34)
  • Email not received
    • Symptom: User sees "check your email" but receives nothing
    • Resolution: Confirm email is approved; verify RESEND_API_KEY; check Resend deliverability; ensure the user checks spam; check rate limiting status
    • References: [worker.js:134-177](file://worker.js#L134-L177), [README.md:497-511](file://README.md#L497-L511)
  • Magic link expired or invalid
    • Symptom: Redirected to login with error=expired or error=invalid
    • Resolution: Request a new magic link; ensure token was not reused; confirm MAGIC_TOKENS TTL and deletion
    • References: [worker.js:188](file://worker.js#L188), [worker.js:190](file://worker.js#L190)
  • Session cookie not set
    • Symptom: Redirect loop to login
    • Resolution: Check SameSite/Lax and Secure flags; ensure HTTPS; verify HMAC signature validity
    • References: [worker.js:194-201](file://worker.js#L194-L201), [worker.js:41-60](file://worker.js#L41-L60)
  • Logout does not work
    • Symptom: User remains logged in
    • Resolution: Ensure /alliance/logout/ is accessed; verify cookie deletion and redirect; check CSRF protection
    • References: [worker.js:312-333](file://worker.js#L312-L333)
  • Rate limit exceeded
    • Symptom: User receives "error=send" message after multiple login attempts
    • Resolution: Wait for the 60-second rate limit window to expire; check if the user is being rate limited legitimately
    • References: [worker.js:107-114](file://worker.js#L107-L114)
  • Bot detection false positive
    • Symptom: Legitimate users blocked by honeypot protection
    • Resolution: Ensure the hidden _gotcha field is properly implemented in forms; check browser support for CSS positioning
    • References: [worker.js:125](file://worker.js#L125), [src/alliance-login.njk:22](file://src/alliance-login.njk#L22)

Section sources

  • [worker.js:72-77](file://worker.js#L72-L77)
  • [worker.js:134-177](file://worker.js#L134-L177)
  • [worker.js:188](file://worker.js#L188)
  • [worker.js:194-201](file://worker.js#L194-L201)
  • [worker.js:312-333](file://worker.js#L312-L333)
  • [worker.js:107-114](file://worker.js#L107-L114)
  • [worker.js:125](file://worker.js#L125)

Conclusion

The member authentication system uses a robust magic link flow with strong security defaults: approved member lists in KV, one-time tokens with short TTLs, HMAC-signed session cookies, and conservative cookie flags. The enhanced system now includes improved login flow reliability through rate limiting, comprehensive error handling for email delivery failures, and honeypot protection against automated bots. Administrators can manage members via KV commands and monitor authentication via standard logging and analytics. The system integrates seamlessly with the static site and external services while maintaining a low-overhead Cloudflare Worker runtime.

Appendices

Administrator Commands

  • Add a member:
    • wrangler kv:key put --binding MEMBER_EMAILS "member:user@example.com" "1"
  • Remove a member:
    • wrangler kv:key delete --binding MEMBER_EMAILS "member:user@example.com"
  • List members:
    • wrangler kv:key list --binding MEMBER_EMAILS

References:

  • [README.md:465-476](file://README.md#L465-L476)

Security Best Practices

  • Use long, random SESSION_SECRET values (recommended length: 32+ bytes)
  • Store secrets via wrangler secret put; never commit to version control
  • Monitor KV usage and token churn; consider periodic cleanup of old tokens
  • Ensure HTTPS and Secure cookies for production deployments
  • Validate email format server-side and reject bot submissions via the honeypot field
  • Implement rate limiting for all external API calls
  • Regularly audit authentication logs for suspicious activity

References:

  • [README.md:497-511](file://README.md#L497-L511)
  • [worker.js:102-114](file://worker.js#L102-L114)
  • [worker.js:116-126](file://worker.js#L116-L126)

Integration Notes

  • Static site generation via Eleventy; Worker serves as router and API gateway
  • Cloudflare Pages configuration is deprecated; deployment is handled by Workers
  • The polling API endpoint is exposed at /api/polling.json and cached for 5 minutes
  • Enhanced error handling provides better user experience across all authentication flows

References:

  • [README.md:556-586](file://README.md#L556-L586)
  • [cloudflare-pages.toml:1-17](file://cloudflare-pages.toml#L1-L17)