# console.pattayatogether.com · edge-auth Worker · operator runbook **Goal:** turn on true edge-level lockdown of `console.pattayatogether.com/*` so that direct links, curl, and view-source all require auth. After this is live, the client-side `gate.js` becomes redundant (kept as defense-in-depth). **Status at time of writing:** client-side gate is active now. Edge Worker ships deploy-ready in this folder but requires an **NS migration** first (one-time operator step). **Cost:** $0 on Cloudflare Free plan for internal team traffic. --- ## The architectural truth `console.pattayatogether.com` currently serves from **GitHub Pages**. Pages is a static host with no authentication hooks. Any HTML served there is readable by anyone who knows the URL. To enforce auth *at the server* we need **something in front of Pages**. Cloudflare Workers fit exactly — they intercept every request, can check cookies, can serve a login page in place of origin content, and can proxy authed traffic to origin. **But** `pattayatogether.com` is currently DNS-hosted at **Hostinger** (NS = `ns1.dns-parking.com`, `ns2.dns-parking.com`). Cloudflare cannot run a Worker in front of traffic it doesn't control. → first step is the NS migration. --- ## Step 1 · Migrate the zone to Cloudflare (one-time, ~15 min operator, ~24–48 h DNS propagate) ### 1.1 · Add the zone on Cloudflare 1. Sign in at `https://dash.cloudflare.com`. 2. **Add Site** → enter `pattayatogether.com` → Free plan → Continue. 3. Cloudflare will scan existing records (the Hostinger zone) and mirror them. 4. Cloudflare will display **two assigned nameservers** like: - `xxx.ns.cloudflare.com` - `yyy.ns.cloudflare.com` ### 1.2 · Change NS at the domain registrar 1. Sign in at your registrar (the place you bought `pattayatogether.com`). 2. Find the domain's nameserver settings. 3. **Replace** `ns1.dns-parking.com` and `ns2.dns-parking.com` with the two Cloudflare nameservers. 4. Save. ### 1.3 · Wait for propagation Cloudflare will email you when the zone is active (usually 1–24 h). You can also check: ```bash dig +short NS pattayatogether.com # Expected: two *.ns.cloudflare.com names ``` ### 1.4 · Verify records after migration In the Cloudflare dashboard → DNS → Records, confirm the `console` row is: | Type | Name | Target | Proxy | |------|---------|----------------------------------|---------------| | CNAME | console | montienvic-dotcom.github.io | **Proxied** (orange cloud) | > **Proxied (orange) is required** — if Proxy is off, Cloudflare forwards raw DNS and the Worker never intercepts. Do the same for `ecm` (Batch 19's second Pages site). Every subdomain you want the Worker to protect must be Proxied. --- ## Step 2 · Deploy the Worker From the repo root: ```bash cd docs/ecm/deploy # 1 · Confirm Wrangler sees the right files ls console-auth-worker.js console-auth-wrangler.toml # 2 · Generate the three secrets ADMIN_EMAIL="admin@example.com" # Email + ":" + password, SHA-256 hex ADMIN_CRED_HASH=$(printf '%s:%s' "$ADMIN_EMAIL" "Kod@362990" | shasum -a 256 | awk '{print $1}') # Random 64-char hex, signs session cookies SESSION_SIGNING_KEY=$(openssl rand -hex 32) echo "ADMIN_EMAIL=$ADMIN_EMAIL" echo "ADMIN_CRED_HASH=$ADMIN_CRED_HASH" echo "SESSION_SIGNING_KEY=$SESSION_SIGNING_KEY" # 3 · Push the secrets to Cloudflare echo "$ADMIN_EMAIL" | wrangler secret put ADMIN_EMAIL --config console-auth-wrangler.toml echo "$ADMIN_CRED_HASH" | wrangler secret put ADMIN_CRED_HASH --config console-auth-wrangler.toml echo "$SESSION_SIGNING_KEY" | wrangler secret put SESSION_SIGNING_KEY --config console-auth-wrangler.toml # 4 · Deploy wrangler deploy --config console-auth-wrangler.toml # Expected tail: # Published console-auth-worker (...) # console.pattayatogether.com/* ``` --- ## Step 3 · Verify ```bash # Health — public (no auth) curl -s https://console.pattayatogether.com/__auth/health | jq # Expected: # { "service": "console-auth-worker@batch-20", "admin_email_set": true, # "cred_hash_set": true, "signing_key_set": true, ... } # Anonymous root → served login HTML (401) curl -sI https://console.pattayatogether.com/ | head -3 # HTTP/2 401 # content-type: text/html;charset=utf-8 # Anonymous protected page → login HTML (NOT origin content) curl -s https://console.pattayatogether.com/planning/document-shell-standard.html | grep -c '

Console

' # 1 (= served login page) # Anonymous ECM route → also blocked curl -sI https://console.pattayatogether.com/ecm/portal.html | head -1 # HTTP/2 401 # Authed smoke-test: log in via browser, copy the ptt_console_session cookie, then: curl -sI 'https://console.pattayatogether.com/' \ -H 'Cookie: ptt_console_session=' | head -3 # HTTP/2 200 · x-ptt-auth: ok · x-ptt-user: admin@example.com ``` --- ## Step 4 · Lock down the source of truth After the edge is live, **make the GitHub repo private** to close the last bypass (anyone could `git clone` and read the HTML source): ```bash gh repo edit montienvic-dotcom/Thailand-Together --visibility private --accept-visibility-change-consequences ``` > GitHub Pages on a private repo requires **GitHub Pro** (paid). If that's not available, keep the repo public and rely on the Worker + cookie enforcement — attackers can still see the static HTML source from git clone, but cannot read them from the live console URL without auth. Note this limitation in known-gaps.html until Pro is available. Alternative: move to **Cloudflare Pages** (free for private repos) instead of GitHub Pages, then point the Worker at the Pages Functions origin. This changes hosting and is out of scope for this batch. --- ## Roll back ```bash wrangler delete --config console-auth-wrangler.toml # console.pattayatogether.com goes back to serving raw GH Pages directly, # client-side gate.js remains as soft protection. ``` --- ## Rotate credentials When the operator confirms login is working, rotate the bootstrap password: ```bash # new password NEW_PWD="" NEW_HASH=$(printf '%s:%s' "$ADMIN_EMAIL" "$NEW_PWD" | shasum -a 256 | awk '{print $1}') echo "$NEW_HASH" | wrangler secret put ADMIN_CRED_HASH --config console-auth-wrangler.toml # Also update the embedded hash in docs/login.html so the client-side gate # stays consistent, and commit. ``` Also rotate `SESSION_SIGNING_KEY` to invalidate all existing sessions: ```bash NEW_KEY=$(openssl rand -hex 32) echo "$NEW_KEY" | wrangler secret put SESSION_SIGNING_KEY --config console-auth-wrangler.toml ``` --- ## What this runbook does NOT cover - **Multiple users**: the Worker currently supports one admin. Add a KV-backed user list (similar to ECM Worker's user_store) when multi-user auth is needed. - **Password hashing (salted / key-stretched)**: current scheme is SHA-256 of plaintext email:password. Upgrade to Argon2/bcrypt via Workers subtleCrypto PBKDF2 if threat model evolves. - **Audit log**: Worker can log every auth event to a KV namespace; not yet wired. - **Session revocation list**: current cookies are self-signed with expiry only; to revoke before expiry, change `SESSION_SIGNING_KEY`. - **OAuth / SSO**: not yet wired. All of these remain on `known-gaps.html` until addressed.