Colby McHenry d61bd62022 chore(telemetry-dashboard): simplify the chart-library probe in the shell 1 개월 전
..
public d61bd62022 chore(telemetry-dashboard): simplify the chart-library probe in the shell 1 개월 전
scripts 58251e7ef1 feat(telemetry): admin dashboard worker — scaffold + shared-password auth 1 개월 전
src 58251e7ef1 feat(telemetry): admin dashboard worker — scaffold + shared-password auth 1 개월 전
.dev.vars.example 58251e7ef1 feat(telemetry): admin dashboard worker — scaffold + shared-password auth 1 개월 전
.gitignore 58251e7ef1 feat(telemetry): admin dashboard worker — scaffold + shared-password auth 1 개월 전
README.md 58251e7ef1 feat(telemetry): admin dashboard worker — scaffold + shared-password auth 1 개월 전
package-lock.json 58251e7ef1 feat(telemetry): admin dashboard worker — scaffold + shared-password auth 1 개월 전
package.json 58251e7ef1 feat(telemetry): admin dashboard worker — scaffold + shared-password auth 1 개월 전
tsconfig.json 58251e7ef1 feat(telemetry): admin dashboard worker — scaffold + shared-password auth 1 개월 전
wrangler.jsonc 58251e7ef1 feat(telemetry): admin dashboard worker — scaffold + shared-password auth 1 개월 전

README.md

codegraph telemetry dashboard

The private admin view behind stats.getcodegraph.com. Its sibling telemetry-worker/ writes anonymous usage events into a D1 database; this worker reads them back and draws the charts. Two people use it, so the auth is deliberately the simplest thing that is actually safe: one shared password in a secret, and a long-lived signed cookie.

This directory is in the public repo for the same reason the ingest worker is — the code that touches telemetry should be readable by the people it collects from. Nothing secret lives here: the password and the cookie-signing key are deployment secrets, and the D1 database ID is an identifier, not a credential.

What is gated

Everything except the login page and robots.txt. assets.run_worker_first is true in wrangler.jsonc, so Cloudflare hands every request to src/index.ts before the static asset server sees it — the dashboard HTML, its JS, its CSS and the chart library are all behind the session check, and a request without a valid cookie gets a redirect (pages) or a 401 (/api/*). The login page is rendered inline by the worker rather than served from public/, so the asset directory needs no "is this file public?" judgement calls.

Route Auth Notes
GET /login public Password form. Redirects to / if already signed in.
POST /login public Rate-limited per IP; sets the session cookie on success.
POST /logout public Clears the cookie.
GET /robots.txt public Disallow: /.
GET /api/* required JSON. 401 without a session. CG-12 adds the chart endpoints.
everything else required Static assets from public/. 302 /login without a session.

How the session works

  • The password is compared in constant time, over SHA-256 digests so the operands are always the same length and nothing about the secret leaks through timing.
  • The cookie is a signed assertion — base64url(payload).base64url(HMAC-SHA256) — not a lookup key. There is no session store; a tampered payload fails the signature check.
  • HttpOnly; Secure; SameSite=Lax; Path=/, Max-Age one year. You sign in once per browser and it survives restarts.
  • The payload carries a fingerprint of the password it was minted against, so rotating ADMIN_PASSWORD signs everyone out — that is the revocation story.
  • Login attempts are capped at 5/min per IP. Unlike the ingest worker, which never reads the client IP at all, this one does — solely as a rate-limit key, never stored or logged.

Deploy

Prereqs: the getcodegraph.com zone on the deploying Cloudflare account (the custom domain auto-provisions DNS + cert), and the D1 database from telemetry-worker/ already created.

cd telemetry-dashboard
npm install
npx wrangler login                      # once

npx wrangler secret put ADMIN_PASSWORD  # the shared password
npx wrangler secret put SESSION_SECRET  # cookie-signing key, e.g. `openssl rand -base64 48`

npm run deploy

Both secrets are required — the worker refuses every request if either is missing, so a half-configured deployment fails closed rather than becoming an open dashboard.

Rotating either one is a wrangler secret put away. Rotating SESSION_SECRET invalidates outstanding cookies too, and is the right move if you think one leaked.

Migrations belong to the writer, not to this worker: apply schema changes from telemetry-worker/ (npm run db:migrate). D1 is read-only here.

Local dev & checks

cp .dev.vars.example .dev.vars   # placeholder secrets; also feeds `wrangler types`
npm run check                    # vendor + wrangler types + tsc --noEmit + deploy --dry-run
npm run dev                      # http://localhost:8787

./scripts/smoke-auth.sh          # end-to-end auth suite against a throwaway `wrangler dev`

smoke-auth.sh is the regression net for the gate: it asserts that unauthenticated requests reach nothing (pages, API and static assets), that the cookie is persistent and correctly flagged, that flipped/truncated/forged cookies are all rejected, that brute force is capped, and that rotating the password invalidates existing sessions. Run it after touching src/auth.ts or the route table in src/index.ts. It needs a local D1 to answer /api/health, which it seeds itself from the ingest worker's migration.

Frontend

Plain static files in public/ — one HTML page, ES modules, no framework, no build step. Workers Static Assets serves them verbatim, so third-party libraries are copied out of node_modules into public/vendor/ by npm run vendor (wired into dev and deploy). That keeps the version pinned by the lockfile, avoids a third-party origin at runtime, and lets the CSP stay script-src 'self'. public/vendor/ is gitignored — it is build output.

Visual conventions follow the rest of codegraph: flat and editorial, square corners, hairline rules, sentence-case headings, one oxblood accent, no tiny all-caps tracked labels.