No description
  • JavaScript 100%
Find a file
2026-05-08 05:08:59 +02:00
docs feat: add folder sharing 2026-05-07 18:02:42 -04:00
tests fix: protect passworded share metadata 2026-05-07 23:08:31 -04:00
.gitignore chore: track wrangler.toml in version control 2026-05-04 22:51:07 -04:00
package-lock.json feat: support admin-owned receive folders 2026-05-06 20:45:01 -04:00
package.json chore: initial commit 2026-05-04 22:00:53 -04:00
README.md feat: add receive link foundation 2026-05-05 23:18:33 -04:00
worker.js fix: protect passworded share metadata 2026-05-07 23:08:31 -04:00
wrangler.toml feat: add receive link foundation 2026-05-05 23:18:33 -04:00

EdgeStash

A self-contained cloud-drive built as a single Cloudflare Worker, backed by R2 (files) and Workers KV (metadata).

Cloudflare Workers R2 KV Single File


What it is

EdgeStash is a personal/team file storage app that runs entirely on Cloudflare's edge:

  • Browser-based file manager with folders, upload, rename, delete, preview
  • Password-protected, expiring share links for sending files to people without an account
  • Receive links for collecting small files from anonymous visitors into an owner-selected folder
  • Admin dashboard for user management and usage stats
  • Inline previews for images, PDFs, text/code, video, and audio
  • JWT auth with two roles: admin (single password) and user (email + password)

The entire deployable application — backend, frontend, router — lives in worker.js. There is no build step.


Repository layout

.
├── worker.js          # The whole app: backend + frontend + router (single file)
├── package.json       # Local syntax check + regression tests only (no build)
├── tests/             # Node-based regression tests
└── docs/              # Architecture, API, data model, contributing

Deployment

This repository includes a wrangler.toml for the current YCloud deployment. For a new deployment, copy it and replace the bucket/KV IDs, or use the dashboard path below when you are not enabling receive links.

If receive links are enabled, use Path B because dashboard-driven setup does not include a migration step equivalent to [[migrations]] for new Durable Object classes (RECEIVE_QUOTA_DO, OBJECT_KEY_RESERVATION_DO).

For deployments without receive links, do:

  1. Create the bindings in the Cloudflare Dashboard:

    • R2 → Create bucket → e.g. edgestash-files
    • Workers & Pages → KV → Create namespace → e.g. edgestash-kv
  2. Create the Worker:

    • Workers & Pages → Create application → Create Worker
    • Edit code → delete the scaffold → paste the entire contents of worker.js
    • Save and deploy
  3. Attach bindings and secrets (Worker → Settings → Variables):

    • R2 binding: R2_BUCKETedgestash-files
    • KV binding: KV_STOREedgestash-kv
    • Omit receive-link Durable Object bindings here; use Path B for receive-link-capable deployments.
    • Secret (encrypted): ADMIN_PASSWORD → a strong password
    • Secret (recommended): JWT_SECRET → a separate strong random string
    • Variable (for Turnstile receive links, optional): TURNSTILE_SITE_KEY
    • Secret (for receive challenge/bootstrap cookies, required for receive links): RECEIVE_CHAL_HMAC_SECRETS
    • Secret (for Turnstile receive links, optional): TURNSTILE_SECRET
    • Re-deploy after adding bindings — the Worker needs a redeploy to pick them up
  4. Sign in. Visit https://<worker-subdomain>.workers.dev/login, switch to the Admin tab, enter ADMIN_PASSWORD.

Create wrangler.toml at the repo root:

name = "edgestash"
main = "worker.js"
compatibility_date = "2024-09-01"

[[r2_buckets]]
binding = "R2_BUCKET"
bucket_name = "edgestash-files"

[[kv_namespaces]]
binding = "KV_STORE"
id = "<paste-namespace-id>"

[[durable_objects.bindings]]
name = "RECEIVE_QUOTA_DO"
class_name = "ReceiveQuotaDO"

[[durable_objects.bindings]]
name = "OBJECT_KEY_RESERVATION_DO"
class_name = "ObjectKeyReservationDO"

[[migrations]]
tag = "v1-receive-reservations"
new_sqlite_classes = ["ReceiveQuotaDO", "ObjectKeyReservationDO"]

Then:

# one-time
npm install -g wrangler
wrangler login
wrangler kv:namespace create edgestash-kv
# copy the returned id into wrangler.toml
wrangler secret put ADMIN_PASSWORD
wrangler secret put JWT_SECRET
wrangler secret put RECEIVE_CHAL_HMAC_SECRETS
# optional, only if using Turnstile-required receive links
wrangler secret put TURNSTILE_SECRET

# deploy
wrangler deploy

Local dev:

wrangler dev              # local emulation at http://127.0.0.1:8787
wrangler dev --remote     # uses real R2/KV

Required bindings

Name Type Required Purpose
R2_BUCKET R2 binding yes File blob storage
KV_STORE KV binding yes Users, shares, stats, rate-limit counters
RECEIVE_QUOTA_DO Durable Object yes Receive quota reservation binding
OBJECT_KEY_RESERVATION_DO Durable Object yes Final object-key reservation binding
ADMIN_PASSWORD Secret yes Admin login password (also JWT fallback)
JWT_SECRET Secret recommended Dedicated JWT HMAC key; falls back to ADMIN_PASSWORD if absent
RECEIVE_CHAL_HMAC_SECRETS Secret receive links Comma-separated HMAC keys for receive bootstrap/challenge/auth cookies
TURNSTILE_SITE_KEY Variable optional Public site key rendered on /r/<linkId> for Turnstile-required links
TURNSTILE_SECRET Secret optional Required only for links with Turnstile enabled
ALLOW_OPEN_RECEIVE_LINKS Variable optional Set to true only if owners may create receive links without password or Turnstile

A missing binding causes 500 on the first authenticated request — the Worker does not pre-check bindings at startup.

Receive links are same-origin only; this is not a public cross-origin upload API. Files up to 90 MiB use direct upload; larger receive-link files use R2 multipart upload with in-memory-only fileToken state, so refresh/close cannot resume an in-progress large upload. V1 sends no email notification when files arrive, so owners should check the app regularly for received files and misuse. By default, new receive links must have a password or Turnstile enabled; fully open anonymous links require ALLOW_OPEN_RECEIVE_LINKS=true.

For planned RECEIVE_CHAL_HMAC_SECRETS rotation, deploy new,old, wait at least 10 minutes for active receive challenge/auth cookies to expire, then deploy new. For emergency invalidation, deploy a single fresh key immediately; active Turnstile-required uploaders must re-challenge before auth, upload, or multipart init.

CI deploy (optional)

# .github/workflows/deploy.yml
name: deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}

API token scopes: Workers Scripts: Edit, Workers KV Storage: Edit, R2: Edit.


Local checks

npm run check     # node --check worker.js (syntax)
npm test          # regression tests in tests/

There is no Docker image — Workers run on Cloudflare's runtime, not in containers. wrangler dev is the local equivalent.


Documentation

File Audience
docs/ARCHITECTURE.md System design, components, data flow
docs/TECH_STACK.md Technology choices and rationale
docs/FEATURES.md Functional capabilities by domain
docs/LOGIC.md Core algorithms and non-obvious details
docs/RUNNING.md Deployment and environment config (extended)
docs/API.md REST endpoints with request/response shapes
docs/DATA_MODEL.md R2 key conventions, KV schemas, JWT payload
docs/CONTRIBUTING.md Conventions, branching, PR checklist

Troubleshooting

Symptom Likely cause
401 on every request after login ADMIN_PASSWORD (or JWT_SECRET) was rotated → all JWTs invalidated; re-login
Upload fails with 413 / large body error Workers free-plan request body limit (~100 MB)
500 on /api/files/... R2_BUCKET or KV_STORE not attached to the Worker
Login succeeds but / redirects to /login Cookie blocked — check SameSite=Strict isn't stripping it