Administration Tools and Management
**Referenced Files in This Document** - [worker.js](file://worker.js) - [wrangler.jsonc](file://wrangler.jsonc) - [cloudflare-pages.toml](file://cloudflare-pages.toml) - [netlify.toml](file://netlify.toml) - [src/admin/index.html](file://src/admin/index.html) - [src/admin/config.yml](file://src/admin/config.yml) - [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) - [src/_data/servicePolling.json](file://src/_data/servicePolling.json) - [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
- Appendices
Introduction
This document describes the administration tools and member management capabilities of the Invest Australia Alliance website. It covers:
- Member approval and access control using the MEMBER_EMAILS KV namespace
- Administrative commands for adding/removing members
- Magic token lifecycle, generation, TTL, and cleanup
- Integrations with external services (Resend for email delivery, GitHub OAuth for CMS access, Google Sheets for live polling)
- Monitoring and logging approaches for authentication activity
- Backup and recovery procedures for KV data and environment secrets
- Practical workflows for onboarding, bulk management, and troubleshooting
- Security best practices and access control for administrators
Project Structure
The project is a static site built with 11ty and served via Cloudflare Workers Assets. Authentication and CMS OAuth are handled by a Cloudflare Worker. The admin interface is powered by Sveltia CMS.
graph TB
subgraph "Static Site"
TPL1["src/alliance-login.njk"]
TPL2["src/alliance-members.njk"]
DATA1["src/_data/allianceMembers.json"]
DATA2["src/_data/servicePolling.json"]
ADMIN_HTML["src/admin/index.html"]
ADMIN_CFG["src/admin/config.yml"]
end
subgraph "Worker Runtime"
WRK["worker.js"]
WCFG["wrangler.jsonc"]
end
subgraph "External Services"
KV1["KV: MEMBER_EMAILS"]
KV2["KV: MAGIC_TOKENS"]
RES["Resend API"]
GH["GitHub OAuth"]
GS["Google Sheets API"]
end
TPL1 --> WRK
TPL2 --> WRK
DATA1 --> TPL2
DATA2 --> TPL2
ADMIN_HTML --> ADMIN_CFG
WRK --> KV1
WRK --> KV2
WRK --> RES
WRK --> GH
WRK --> GS
WCFG -. binds .-> WRK
Diagram sources
- [worker.js:1-321](file://worker.js#L1-L321)
- [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
- [src/admin/index.html:1-12](file://src/admin/index.html#L1-L12)
- [src/admin/config.yml:1-774](file://src/admin/config.yml#L1-L774)
- [src/alliance-login.njk:1-73](file://src/alliance-login.njk#L1-L73)
- [src/alliance-members.njk:1-58](file://src/alliance-members.njk#L1-L58)
- [src/_data/allianceMembers.json:1-40](file://src/_data/allianceMembers.json#L1-L40)
- [src/_data/servicePolling.json:1-39](file://src/_data/servicePolling.json#L1-L39)
Section sources
- [worker.js:1-321](file://worker.js#L1-L321)
- [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
- [cloudflare-pages.toml:1-17](file://cloudflare-pages.toml#L1-L17)
- [netlify.toml:1-26](file://netlify.toml#L1-L26)
Core Components
- Cloudflare Worker: Implements member authentication, session management, magic link issuance, logout, GitHub OAuth for CMS, and Google Sheets polling API.
- MEMBER_EMAILS KV namespace: Stores approved member records with keys formatted as member:
. - MAGIC_TOKENS KV namespace: Stores one-time tokens with keys token:
, TTL set to 900 seconds (15 minutes). - Sveltia CMS Admin: Configured via YAML to manage content collections and media.
- Static Templates: Nunjucks templates for login and members portal pages.
Key responsibilities:
- Enforce access to /alliance/members/* via session cookie verification
- Issue magic links only for approved emails
- Verify magic links once and expire/delete them
- Provide CORS preflight and OAuth callback for CMS
- Serve live polling data from Google Sheets with caching headers
Section sources
- [worker.js:1-321](file://worker.js#L1-L321)
- [src/admin/config.yml:1-774](file://src/admin/config.yml#L1-L774)
- [src/alliance-login.njk:1-73](file://src/alliance-login.njk#L1-L73)
- [src/alliance-members.njk:1-58](file://src/alliance-members.njk#L1-L58)
Architecture Overview
The authentication and admin architecture combines static rendering with serverless logic in a Cloudflare Worker.
sequenceDiagram
participant U as "User Browser"
participant L as "Login Template<br/>alliance-login.njk"
participant W as "Cloudflare Worker<br/>worker.js"
participant ME as "KV : MEMBER_EMAILS"
participant MT as "KV : MAGIC_TOKENS"
participant R as "Resend API"
U->>L : GET /alliance/login/
U->>W : POST /alliance/login/ (email, _gotcha)
W->>ME : get("member : <email>")
alt Approved
W->>MT : put("token : <hex>", email, TTL=900)
W->>R : send magic link email
else Not approved
W-->>U : redirect /alliance/login/?sent=1 (same response)
end
W-->>U : redirect /alliance/login/?sent=1
Diagram sources
- [worker.js:97-147](file://worker.js#L97-L147)
- [src/alliance-login.njk:21-34](file://src/alliance-login.njk#L21-L34)
sequenceDiagram
participant U as "User Browser"
participant W as "Cloudflare Worker"
participant MT as "KV : MAGIC_TOKENS"
participant SESS as "Session Cookie"
participant MEM as "Members Template<br/>alliance-members.njk"
U->>W : GET /alliance/verify/?token=<hex>
W->>MT : get("token : <hex>")
alt Found
W->>MT : delete("token : <hex>")
W->>SESS : set ace_member_session cookie (30 days)
W-->>U : 302 to /alliance/members/
U->>W : GET /alliance/members/...
W->>SESS : verify cookie
W-->>U : serve members page
else Not found/expired
W-->>U : redirect /alliance/login/?error=expired
end
Diagram sources
- [worker.js:153-177](file://worker.js#L153-L177)
- [worker.js:81-91](file://worker.js#L81-L91)
- [src/alliance-members.njk:1-58](file://src/alliance-members.njk#L1-L58)
Detailed Component Analysis
Member Approval and Access Control
- Approved members are stored in MEMBER_EMAILS with keys formatted as member:
. - Access to /alliance/members/* requires a valid session cookie. The worker verifies the cookie signature and expiration before serving protected content.
- Non-approved emails receive the same “check your email” response to avoid enumerating membership.
Administrative commands (from repository documentation):
- Add a member: put key member:
with value "1" - Remove a member: delete key member:
- List members: list keys in MEMBER_EMAILS
Operational notes:
- Keys are strings; values are arbitrary (e.g., "1") indicating membership.
- Use the binding name MEMBER_EMAILS as configured in wrangler.jsonc.
Section sources
- [worker.js:81-91](file://worker.js#L81-L91)
- [worker.js:116](file://worker.js#L116)
- [README.md:465-476](file://README.md#L465-L476)
- [wrangler.jsonc:17-26](file://wrangler.jsonc#L17-L26)
Magic Token Management
Lifecycle:
- Generation: On login POST, generate a 32-byte cryptographically random token and store as token:
in MAGIC_TOKENS with TTL 900 seconds. - Delivery: Send email via Resend API with a link to /alliance/verify/?token=
. - Verification: On verify, read token, ensure it exists, then delete it immediately. Create a session token and set HttpOnly/Secure/SameSite=Lax cookie.
- Cleanup: Tokens are single-use and deleted upon successful verification.
Session cookie:
- Name: ace_member_session
- Duration: 30 days
- Signature: HMAC-SHA256 over base64(email|expiry).signature
flowchart TD
Start(["POST /alliance/login/"]) --> Validate["Validate email and honeypot"]
Validate --> CheckKV["Lookup MEMBER_EMAILS for member:<email>"]
CheckKV --> |Approved| GenToken["Generate 32-byte token"]
GenToken --> PutKV["Put MAGIC_TOKENS token:<hex>=<email>, TTL=900"]
PutKV --> SendMail["Send email via Resend"]
SendMail --> RedirectSent["Redirect /alliance/login/?sent=1"]
CheckKV --> |Not approved| RedirectSent
RedirectSent --> End
VerifyStart(["GET /alliance/verify/?token"]) --> ReadKV["Read MAGIC_TOKENS token:<hex>"]
ReadKV --> |Exists| DeleteKV["Delete token:<hex>"]
DeleteKV --> MakeSession["Create session token"]
MakeSession --> SetCookie["Set ace_member_session cookie"]
SetCookie --> ServeMembers["302 to /alliance/members/"]
ReadKV --> |Missing/Expired| Expired["Redirect /alliance/login/?error=expired"]
Diagram sources
- [worker.js:97-147](file://worker.js#L97-L147)
- [worker.js:153-177](file://worker.js#L153-L177)
Section sources
- [worker.js:118-120](file://worker.js#L118-L120)
- [worker.js:158-161](file://worker.js#L158-L161)
- [worker.js:163-171](file://worker.js#L163-L171)
- [worker.js:13-14](file://worker.js#L13-L14)
GitHub OAuth for CMS Access
- Endpoint: /api/auth initiates OAuth with GitHub using client credentials from secrets.
- Callback: /api/auth/callback exchanges the code for an access token and posts it back to the opener window for Sveltia CMS.
- CORS: Preflight handled for https://acestrategies.au origin.
sequenceDiagram
participant CMS as "Sveltia CMS"
participant W as "Worker /api/auth"
participant GH as "GitHub OAuth"
participant CB as "Worker /api/auth/callback"
CMS->>W : GET /api/auth
W->>GH : Redirect to authorize
GH-->>CB : POST with code
CB->>GH : Exchange code for access_token
GH-->>CB : {access_token}
CB-->>CMS : HTML with postMessage(access_token)
Diagram sources
- [worker.js:193-227](file://worker.js#L193-L227)
Section sources
- [worker.js:183-191](file://worker.js#L183-L191)
- [worker.js:193-198](file://worker.js#L193-L198)
- [worker.js:200-227](file://worker.js#L200-L227)
Google Sheets Live Polling API
- Endpoint: /api/polling.json returns current polling data from a Google Sheet range.
- Environment: Requires GOOGLE_SHEETS_ID and GOOGLE_SHEETS_API_KEY.
- Caching: Public cache-control headers applied.
sequenceDiagram
participant FE as "Frontend"
participant W as "Worker /api/polling.json"
participant GS as "Google Sheets API"
FE->>W : GET /api/polling.json?state=(federal|sa)
W->>GS : Fetch spreadsheet range
GS-->>W : JSON values
W-->>FE : JSON with computed fields and cache headers
Diagram sources
- [worker.js:233-276](file://worker.js#L233-L276)
Section sources
- [worker.js:233-276](file://worker.js#L233-L276)
Admin Interface (Sveltia CMS)
- Entry: /admin loads Sveltia CMS from CDN.
- Configuration: YAML defines backend (GitHub), media folders, and content collections (news, cases, team, knowledge, newsletters, settings, pages, services, testimonials, partners, clients, affiliations, resources, capabilities).
graph LR
Admin["/admin/index.html"] --> CMS["Sveltia CMS (CDN)"]
CMS --> CFG["admin/config.yml"]
CFG --> Repo["GitHub Repository"]
Diagram sources
- [src/admin/index.html:1-12](file://src/admin/index.html#L1-L12)
- [src/admin/config.yml:1-774](file://src/admin/config.yml#L1-L774)
Section sources
- [src/admin/index.html:1-12](file://src/admin/index.html#L1-L12)
- [src/admin/config.yml:1-774](file://src/admin/config.yml#L1-L774)
Dependency Analysis
- Worker depends on:
- MEMBER_EMAILS KV for membership lookup
- MAGIC_TOKENS KV for token storage and TTL
- Resend API for email delivery
- GitHub OAuth endpoints for CMS authentication
- Google Sheets API for polling data
- wrangler.jsonc binds assets and KV namespaces and defines compatibility flags.
- Deployment configs (cloudflare-pages.toml, netlify.toml) define redirects and security headers; canonical deployment is via Cloudflare Workers Assets.
graph TB
WRK["worker.js"] --> ME["MEMBER_EMAILS (KV)"]
WRK --> MT["MAGIC_TOKENS (KV)"]
WRK --> RES["Resend API"]
WRK --> GH["GitHub OAuth"]
WRK --> GS["Google Sheets API"]
WCFG["wrangler.jsonc"] --> WRK
CP["cloudflare-pages.toml"] -. legacy .-> WRK
NL["netlify.toml"] --> WRK
Diagram sources
- [worker.js:1-321](file://worker.js#L1-L321)
- [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
- [cloudflare-pages.toml:1-17](file://cloudflare-pages.toml#L1-L17)
- [netlify.toml:1-26](file://netlify.toml#L1-L26)
Section sources
- [worker.js:1-321](file://worker.js#L1-L321)
- [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
- [cloudflare-pages.toml:1-17](file://cloudflare-pages.toml#L1-L17)
- [netlify.toml:1-26](file://netlify.toml#L1-L26)
Performance Considerations
- KV latency: Minimize KV reads/writes by reusing cached values where appropriate and avoiding unnecessary lookups.
- Email delivery: Resend API latency can delay login initiation; ensure API key availability and monitor delivery rates.
- Session reuse: HttpOnly cookies reduce overhead after initial login; leverage long session duration (30 days) for reduced re-auth frequency.
- Polling API: Apply caching headers and consider rate limiting to protect Google Sheets API quotas.
- Static assets: Leverage immutable caching for assets and short TTL for dynamic endpoints.
[No sources needed since this section provides general guidance]
Troubleshooting Guide
Common issues and resolutions:
- KV namespaces not configured
- Symptom: 503 response indicating KV namespaces not configured.
- Action: Create MEMBER_EMAILS and MAGIC_TOKENS namespaces and bind them in wrangler.jsonc.
- Missing environment secrets
- SESSION_SECRET, RESEND_API_KEY, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET must be set via wrangler secret put.
- Login form submission errors
- Invalid form data: redirects with error=invalid
- Invalid email: redirects with error=email
- Honeypot filled: treated as bot submission and redirected appropriately
- Magic link expired or invalid
- Symptom: redirected to login with error=expired or error=invalid
- Action: Request a new magic link; ensure MAGIC_TOKENS TTL is respected and token is consumed once.
- Logout not clearing session
- Symptom: Still able to access members area after logout
- Action: Confirm ace_member_session cookie is cleared (Max-Age=0) and SameSite/Lax flags are set.
- CMS OAuth failures
- Symptom: Callback returns error or token missing
- Action: Verify client credentials and redirect URI; ensure preflight CORS headers are present.
- Polling API errors
- Symptom: 503 or 500 responses from /api/polling.json
- Action: Check GOOGLE_SHEETS_ID and GOOGLE_SHEETS_API_KEY; verify sheet range and permissions.
Section sources
- [worker.js:70-75](file://worker.js#L70-L75)
- [worker.js:98-113](file://worker.js#L98-L113)
- [worker.js:155-159](file://worker.js#L155-L159)
- [worker.js:183-191](file://worker.js#L183-L191)
- [worker.js:244-246](file://worker.js#L244-L246)
Conclusion
The system provides a secure, serverless authentication flow for members, integrated with external services for email, CMS access, and live polling. Administrators can manage approved members via KV commands, configure secrets and namespaces, and rely on built-in protections such as constant-time verification, single-use tokens, and strict cookie flags. Monitoring and backups should focus on KV integrity, secret rotation, and API quota management.
[No sources needed since this section summarizes without analyzing specific files]
Appendices
Administrative Workflows
-
Member onboarding
- Approve member: put MEMBER_EMAILS key member:
=1 - Communicate login instructions to the member
- Verify access via /alliance/members/*
- Approve member: put MEMBER_EMAILS key member:
-
Bulk member management
- List members: list MEMBER_EMAILS keys
- Remove members: delete MEMBER_EMAILS keys member:
- Audit: periodically review keys and token usage in MAGIC_TOKENS
-
Emergency access procedures
- Rotate SESSION_SECRET and regenerate sessions as needed
- Temporarily adjust TTLs or disable tokens via KV operations if required
- Revoke and regenerate Resend/GitHub/Google credentials if compromised
-
Backup and recovery
- KV backup: export MEMBER_EMAILS/MAGIC_TOKENS keys regularly using wrangler commands
- Secret rotation: update secrets via wrangler secret put and test flows
- Recovery: restore KV keys and reconfigure bindings per wrangler.jsonc
-
Monitoring and logging
- Enable Cloudflare Worker observability for request metrics and errors
- Track failed login attempts and token verification outcomes via logs
- Monitor Resend delivery statuses and GitHub OAuth callback responses
- Observe Google Sheets API responses and cache behavior
Section sources
- [README.md:429-476](file://README.md#L429-L476)
- [wrangler.jsonc:6-8](file://wrangler.jsonc#L6-L8)
- [worker.js:12-14](file://worker.js#L12-L14)