A members-only Cloudflare Workers blueprint. Ships auth, RBAC, email pipeline, runtime-mutable settings, idempotency + audit logging, observability, and security hardening — all wired, tested, and documented from day one.
Start a new members-only product without re-solving these problems:
- Password auth (scrypt + JWT + refresh CAS)
- RBAC (typed catalog + Hono middleware)
- Email (React Email templates + Resend + retry queue + cron backstop)
- System settings (typed registry + KV cache + admin API)
- Idempotency (CAS + DLQ handoff)
- Structured audit logging
- Rate limits, security headers, split health probes, cron pruners
# 1. Clone + install
git clone <this-repo> && cd runway
pnpm bootstrap
# 2. Wrangler auth (one-time)
wrangler login
# 3. Local dev + tests
pnpm dev # http://localhost:8787
pnpm test # 191 tests + 2 skipped
# 4. Open the golden path
bru run docs/bruno --env localPrereqs: Node 22, pnpm 10.28+, wrangler login. Budget those
separately.
pnpm db:migrate:local
RUNWAY_LOCAL=1 pnpm dev:seed-team # 5 tài khoản dưới đây
RUNWAY_LOCAL=1 pnpm dev:seed-products # sản phẩm + giá DEMO
pnpm dev # http://localhost:8787Mật khẩu chung: correct-horse-battery-staple
| Vai trò | Dùng để thử | |
|---|---|---|
admin@runway.local |
Quản trị | người dùng, vai trò, duyệt yêu cầu đổi quyền |
giamdoc@runway.local |
Giám đốc | duyệt (giảm > 10%), phát hành, nhập mẫu từ Word, cấp quản trị tạm thời, rà soát quyền |
quanly@runway.local |
Quản lý | duyệt, phát hành, sản phẩm & giá |
nhanvien@runway.local |
Nhân viên | tạo báo giá / hợp đồng / phiếu xuất kho, lập tài liệu con |
root@runway.local |
Root admin | bật/tắt cơ chế duyệt 2 lớp |
Chỉ có 5 email này — email khác (vd. test@runway.local) không đăng nhập được.
apps/api Cloudflare Workers app
├── src/
│ ├── routes/ signup, verify, login, refresh, logout, me,
│ │ admin/users, admin/settings/*, demo/notes,
│ │ healthz, readyz
│ ├── middleware/ requireAuth, requireOrigin, requireFetchHeader,
│ │ requirePerm, withIdempotency, rateLimit,
│ │ securityHeaders, logger, error-handler
│ ├── services/ auth-service, admin-service, email-service
│ ├── settings/ SettingsService (typed registry)
│ ├── dao/ pure functions returning DTOs — no BaseDao
│ ├── ports/ EmailPort, ErrorReporterPort
│ ├── adapters/ noop + resend + retry-wrapped email; noop + sentry-scaffold reporters
│ ├── crons/ verify-email sweeper, nightly expired-rows pruner
│ └── queues/ email-retry + DLQ consumers
├── db/schema.ts drizzle-orm — 12 tables + settings + notes
└── db/migrations/ SQL migrations 0000–0004
packages/
├── auth scrypt, jose HS256 JWT, HMAC token hashing, URL sanitizer, timing-safe compare
├── rbac pure policy engine + Hono middleware factory
├── email-templates React Email + Zod prop schemas + URL allowlist
├── contracts exported OpenAPI JSON
├── config shared ESLint / tsconfig / vitest config
└── (money & audit-store) NOT included in v1; recipes in docs/recipes/
New engineer? Start with docs/index.md — it lists every domain doc plus the recipes for extending the blueprint.
Skipping ahead:
- Add a resource (notes, orders, whatever): docs/recipes/add-resource.md
- Add a permission: docs/rbac.md → §"Adding a permission"
- Add an email template: docs/email.md → §"Templates"
- Configure Sentry / Logpush: docs/observability.md
- Deploy to prod: docs/deploy.md
- Ports & adapters: every external system (email, error reporter, even KV cache) hides behind a port so tests inject fakes.
- DAO returns DTOs: no leaking drizzle row types past
src/dao/. Rules enforced by ESLint +docs/dao-pattern.md. - Explicit env-vs-settings split: security-critical values live
in env (Zod-parsed at boot, redeploy to change). Operational
values live in settings (typed registry, mutable via
PUT /admin/settings/:key). - Application-level cascade: no PRAGMA foreign_keys (D1 unreliable); DAO helpers batch dependent deletes in one atomic transaction.
- YAGNI over DRY: three similar lines beat one leaky abstraction. If a shape emerges twice, keep both. Extract on the third.
- PR: lint / typecheck / test / OpenAPI diff / bundle-size gate / secret scan (gitleaks). No preview deploy by default — enable when needed.
- Nightly: dep audit + secret scan
- Prod deploy: manual dispatch only
Full workflow map: docs/ci.md (coming soon).
- 27 email-templates (URL allowlist, XSS, deterministic render)
- 37 auth (password, JWT, tokens, origin, timing-safe compare)
- 17 rbac (policy + middleware)
- 3 config (shared ESLint rules)
- 99 api (integration + unit; auth flow, RBAC flow, idempotency, settings, email sweeper, /readyz, security headers, DLQ hand-off, cron pruner)
- 2 skipped (require production-env override)
UNLICENSED — All rights reserved.