Releases: 3M1RY33T/urthreads
Release list
v1.2.0
I've saved the no-emoji rule to core memory so it applies across all projects. Here is the re-drafted plan, cleaned of emoji and decorative unicode.
Local Admin Development Environment for urthreads
Context
The project has no working local admin testing path. wrangler dev alone cannot drive the admin dashboard because of two compounding root causes, both discovered and half-fixed in the previous session (whose uncommitted changes were then reverted, so the working tree is clean today):
- Cookie dropped over HTTP.
src/worker-security.mjs:355-364always emits__Host-urthreads_admin_sessionplusSecure. Browsers silently dropSecurecookies over plain HTTP, so the session cookie set byPOST /admin/sessiononhttp://localhost:8787never sticks. (The previous session proved this fix works end-to-end via curl: login -> cookie ->{"authenticated":true}.) admin_key_expiredfootgun, the actual blocker the user kept hitting.getAdminKeyAccess(worker-security.mjs:462) checksisAdminKeyExpiredbefore comparing the key, andverifyAdminSessionToken(line 423) re-checks key expiry on every request.isAdminKeyExpired(236-250) fails closed: empty/never-> not expired, but unparseable -> expired, and a past date -> expired. The expiry value is written by theadmin-keyCLI (src/admin-key.js:477-480) into.env/wrangler.tomland sits in.dev.vars, so a stale/pastADMIN_API_KEY_EXPIRES_ATmakes even the correct key return{"error":"Admin access is required.","reason":"admin_key_expired"}andweb/dashboard.js:549-551then tells the user to "deploy again", which is wrong for a local worker.
Compounding gaps: no npm run dev/setup:dev scripts, no automated .dev.vars creation, no local D1 schema init step, CONTRIBUTING.md:36-40 only says wrangler dev, and package.json has zero dependencies.
Goal: npm install && npm run setup:dev && npm run dev is the complete local admin testing workflow, with no Cloudflare account, no CLOUDFLARE_API_TOKEN, no manual secret or schema management, and with production cookie/expiry behavior provably unchanged.
Security constraint (explicit): every dev-only behavior must be strictly gated so it cannot leak into a real deployment. Gates used: (a) request-origin detection (http + loopback hostname) for the cookie; (b) .dev.vars is gitignored (.gitignore:18-19) and is the only file setup:dev writes; (c) production expiry semantics unchanged.
User decision: add wrangler as a devDependency (pinned current stable) so npm install alone yields a runnable dev environment.
Phase 1 - Localhost-aware admin session cookie (root cause 1)
Goal: http://localhost, http://127.0.0.1, http://[::1] get a plain urthreads_admin_session cookie without Secure. Everything else gets the byte-identical __Host-urthreads_admin_session plus Secure.
Tasks
-
src/worker-security.mjs- addisLocalHttpOrigin(request)(nearsafeParseUrl, ~line 21)new URL(request.url); returnfalseon parse failure.- Return
falseunlessurl.protocol === "http:". - Return
trueiff hostname (lowercased, brackets stripped) islocalhost,127.0.0.1, or::1.
-
src/worker-security.mjs:9- two cookie-name constants- Keep
ADMIN_SESSION_COOKIE_NAME = "__Host-urthreads_admin_session"(production). - Add
ADMIN_SESSION_COOKIE_NAME_LOCAL = "urthreads_admin_session". - Add
getAdminSessionCookieName(request)-> local name iffisLocalHttpOrigin(request), else prod name.
- Keep
-
src/worker-security.mjs:355-364-buildAdminSessionCookie- Cookie name from
getAdminSessionCookieName(request). - Omit
"Secure"from the attribute array whenisLocalHttpOrigin(request)is true. - Keep
HttpOnly,Path=/,Max-Age,SameSitein both paths.buildExpiredAdminSessionCookie(366) delegates, so it inherits the fix.
- Cookie name from
-
src/worker-security.mjs:326-332-getAdminSessionCookie- Look for prod name first, then local name; return first match;
""if neither.
- Look for prod name first, then local name; return first match;
-
Tests -
test/worker-security.test.mjs(helpers exist:req()builds https URLs,parseSetCookie,baseEnv; add areqLocal(path)helper buildinghttp://localhost:8787)- Localhost request -> cookie name
urthreads_admin_session, nosecureattr,httponly/path/max-agepresent. - HTTPS request ->
__Host-plussecure(make the existing test at line 309 explicit). getAdminSessionCookiefinds a localhost-name cookie, finds a prod-name cookie, returns""when neither.
- Localhost request -> cookie name
-
Tests -
test/worker-admin-flow.test.mjs(mockedenv.DB, distinct CF-Connecting-IP per test)- POST valid key to
http://localhost:8787/admin/session-> 200,Set-Cookienameurthreads_admin_session, noSecure,HttpOnlyplusPath=/present. - Follow-up GET
/admin/sessionwith that cookie -> 200{authenticated:true}. - Existing HTTPS
__Host-tests (lines ~162-181) unchanged.
- POST valid key to
Key files: src/worker-security.mjs:9,21,326-364, test/worker-security.test.mjs, test/worker-admin-flow.test.mjs
Phase 2 - Kill the local admin_key_expired footgun (root cause 2)
Goal: the local worker can never reject the correct key as expired due to a stale env value, and the dashboard stops giving "deploy again" advice for localhost.
Tasks
-
src/setup-dev.js(new, Phase 3) must ALWAYS normalize expiry in.dev.vars- Whether the key is reused or generated,
.dev.varsis written with exactly:ADMIN_API_KEY=<key> ADMIN_API_KEY_EXPIRES_AT= ADMIN_API_KEY_EXPIRES_AT=(empty) means never-expires perisAdminKeyExpired(worker-security.mjs:240-242). The previous attempt's reuse path left a stale expiry in place, which is what kept producingadmin_key_expired; do not repeat it.- After writing, re-read
.dev.varsand validate with the same semantics as the worker (empty/unset/never-> OK; parseable -> warn if in the past; unparseable -> error). Print a warning when a non-empty expiry was overwritten.
- Whether the key is reused or generated,
-
web/dashboard.js:545-556- localhost-aware expired message- When
state.workerUrlishttp://plus loopback (reuse the hostname check fromcanAttemptCookieSession, lines 135-153), render: "The local worker says this admin key is expired. Runnpm run setup:devto reset.dev.vars, then restartnpm run dev." - Keep the existing "deployed Worker ... deploy again" message for all other URLs.
- When
-
Tests
test/worker-security.test.mjs: explicitisAdminKeyExpiredcases -"", unset,"never","none"-> not expired; unparseable string -> expired; past ISO -> expired (past-date case exists at line ~470; add the empty/unparseable ones).test/setup-dev.test.js(Phase 3): a.dev.varscontaining a staleADMIN_API_KEY_EXPIRES_AT=2020-...is rewritten to empty; a key present is reused, not regenerated.
-
No change to production semantics: unparseable/past -> expired stays (fail-closed).
verifyAdminSessionToken's per-request key-expiry check (line 423) is untouched; with empty expiry it is a no-op locally, and in production key rotation still kills sessions.
Key files: src/setup-dev.js (new), web/dashboard.js:135-153,545-556, test/worker-security.test.mjs, test/setup-dev.test.js
Phase 3 - npm run setup:dev plus npm run dev one-command setup
Tasks
-
New
src/setup-dev.js(Node built-ins only; mirrorsrc/setup-env.jsreadline pattern; importgenerateAdminApiKeyplus clipboard helper fromsrc/admin-key.js)- Read D1
database_namefromwrangler.toml[[d1_databases]](current value:test). Missing file/binding -> helpful error pointing atnpm run setup:env; never auto-editwrangler.toml. .dev.vars: reuse existingADMIN_API_KEYif present, else generate; always write emptyADMIN_API_KEY_EXPIRES_AT=(per Phase 2).- Local D1 schema:
npx wrangler d1 execute <database_name> --local --file=src/schema.sqlviachild_process.execSync; catch errors with actionable messages (wrangler missing -> point atnpm install).src/schema.sqlis idempotent (allCREATE TABLE IF NOT EXISTS, verified). - Print the admin key, copy to clipboard when available, and print next steps including "restart
npm run devif it was already running" (wrangler reads.dev.varsat startup).
- Read D1
-
package.json- Add
"dev": "wrangler dev"and"setup:dev": "node src/setup-dev.js"toscripts(line 42 block). - Append
&& node --check src/setup-dev.jsto thecheckscript. - Add
wrangleras a devDependency, pinned to the current stable v4 (confirm the exact version during implementation withnpm view wrangler version; do not guess). This creates the firstpackage-lock.json; commit it so installs are reproducible.
- Add
-
test/setup-dev.test.js(new) - all file ops inos.tmpdir(); never touch the realwrangler.toml/.dev.vars:- Reads D1 database name from a temp
wrangler.toml. - Writes key plus empty expiry when
.dev.varsabsent; reuses key but overwrites stale expiry when present. - Missing D1 binding -> prompt asked (mock the prompt) or clean exit.
- Reads D1 database name from a temp
Key files: src/setup-dev.js (new), package.json (plus new package-lock.json), test/setup-dev.test.js (new)
Phase 4 - admin-key CLI local-dev hint
src/admin-key.js:500-507next-steps: after thewrangler secret putline, add:Informational only; the CLI still never writesFor local development (no Cloudflare account needed): run: npm run setup:dev.dev.vars(that stayssetup-dev.js's job).- Test in
test/admin-key.test.js: the next-steps output mentionssetup:devwhen the key is not stored as a secret.
Key files: src/admin-key.js:500-507, `test/adm...
v1.1.2
v1.1.1
Urthreads v1.1.1
This release improves setup safety, environment syncing, dashboard installation, cleanup behavior, and CLI polish.
Highlights
- Added hosted dashboard build/update commands:
urthreads dashboard set <local-path> <endpoint>urthreads dashboard buildurthreads dashboard update
- Dashboard builds now remember
DASHBOARD_LOCAL_PATHandDASHBOARD_ENDPOINTin.env. dashboard buildprompts for missing path settings and asks before creating a missing output path.- Cleanup now removes copied dashboard files, while preserving directories and unrelated site files.
- Allowed origins now sync more safely between
.envandwrangler.toml. admin-sessionnow updates both.envandwrangler.toml, then offers to deploy.clean-allnow cancels fully if Worker or D1 deletion is cancelled, and prints commands for finishing cleanup later.- Removed test-only CLI commands for manually creating/updating comments and forcing like counts.
Setup And Environment
urthreads env add-originnow preserves existing origins across.envandwrangler.toml.- Origins can be added to staging and production together:
urthreads env add-origin https://example.com --staging --production- Setup defaults allowed origins to localhost-only values for extra safety.
- Shared values such as Worker URL, Worker name, D1 values, admin session TTL, and runtime settings sync to wrangler.toml when applicable.
Dashboard
You can now install the dashboard into a static site output directory directly from the CLI:
urthreads dashboard set ./public urthreadsRefresh that same dashboard after package updates:
urthreads dashboard buildIf no saved dashboard path exists, dashboard build prompts for one and saves it. If the output path does not exist, the CLI asks before creating it.
Cleanup
Back-out commands now include copied dashboard files in cleanup:
urthreads clean
urthreads clean-allDashboard cleanup removes only known urthreads dashboard files and copied urthreads assets. It does not remove dashboard directories, asset directories, or unrelated site files.
CLI Cleanup
Removed test-only commands from the public CLI:
- set-like
- increment-like
- create-comment
- update-comment
Public comment creation through the browser client and Worker API is unchanged.
Validation
Validated with:
npm run check
npm testv1.0.0
Initial public release of urthreads is now available!
Highlights:
- Cloudflare Worker and D1 backend for static-site likes and comments
- Browser client scripts for likes and moderated comments
- Admin dashboard for moderation, analytics, session auth, and Worker status
- Guided setup for .env, Wrangler config, D1 creation, schema initialization, and deployment
- Cleanup/back-out commands for release testing
- GPL-3.0 licensed open-source release