You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
createKvRateLimiter({ namespace, windowMs, max, prefix? }), or createSecurity({ rateLimit: { kv, windowMs, max } }) with security.createRateLimiter(), runs the rate limiter on a Cloudflare KV namespace, for Pages Functions, which cannot bind the Rate Limiting binding: a limit holds across isolates there too. It answers the same RateLimiter with remaining and resetAt; reset(key) deletes the key. The end of the window is stored with the count and decides, so a steady stream of hits cannot push the window out, and a hit over the limit writes nothing. A window under 60 seconds throws, KV keeps no key for less. The limit is soft and the security page says how: KV is eventually consistent, the count is read-modify-write, and KV takes one write per second per key, in which case check rejects instead of letting the hit through uncounted. The namespace is typed structurally as RateLimitKvNamespace and checked against @cloudflare/workers-types. Decision: 0019. (#33)
Passwordless login by e-mail code: auth.sendLoginCode(user, sendFn) mails six digits (loginCodeLength, 6 to 12) and auth.verifyLoginCode(user, code) returns the user id once, within loginCodeTtl (10 minutes). The code is looked up per user, so two users holding the same digits cannot log in as each other; a new code replaces the earlier one; every verification counts against loginCodeAttempts (5), and with the last one the code is deleted, so after five wrong codes the right one fails until a new code is sent. The store holds the code's HMAC under the new loginCodeSecret, not its plain hash, which a million tries would undo; both methods throw without the secret. The auth page shows the flow and what to rate limit. Decision: 0018. (#31)
Breaking for adapters:AuthDbAdapter gains findUserToken(userId, type) and countTokenAttempt(id), and AuthToken an optional attempts; createToken stores it when given. The flatdb adapter implements both; a zod schema on the tokens collection declares attempts: z.number().optional(), and the adapter refuses to store a login code through a schema that strips it. Its count is read-modify-write, so the attempt limit is soft under concurrent guesses.
AuthConfig in FullstackConfig is the auth module's own type instead of a copy, so createStack({ auth: { loginCodeSecret } }) reads the same options as createAuth.
@loewen-digital/fullstack/adapters/fetch: auth for handlers that get a Request and return a Response, where no framework hook runs (Cloudflare Pages Functions behind a static build, bare Workers, Hono). createFetchAdapter({ auth }, { authCookie?, secure?, sameSite?, maxAge? }) gives sessionOf(request), which answers { session, clearCookie } (the validated AuthSession or null, plus the deleting Set-Cookie value when the cookie's token is unknown or expired), and setAuthCookie(headers, token) and clearAuthCookie(headers) with the attributes the SvelteKit adapter writes. The cookie is Secure unless secure says otherwise; isSecureRequest(request) supplies the value for local HTTP. isSameOrigin(request, allowed?) checks Origin and Sec-Fetch-Site as the CSRF guard of a JSON API. No node:*, no Workers types; the new adapter page builds login, logout and a guarded route on Pages Functions. Decision: 0017. (#32)
Remix and Nuxt adapters: a Cookie header with a value that is not valid percent-encoding no longer throws out of the request; the value is read as it came.
For contributors: files are formatted on save when Claude Code or Codex edits them (vp fmt through the hooks in .claude/settings.json and .codex/hooks.json); npm run format stays for everything else.
Built with Vite+ 1.0 (vite-plus) instead of Vite library mode plus tsc for the declarations (decision 0016). For consumers: the same 27 subpaths with the same runtime and type exports (compared name by name against 0.3.0); each subpath now ships one bundled .d.ts instead of one per source file, and the JavaScript is no longer minified. fullstack keeps its shebang and execute bit.
For contributors: npm run check replaces npm run lint and npm run typecheck (Oxfmt, Oxlint with type-aware rules, type check in one pass) and runs in CI and in the pre-push hook; CI now also builds and smoke-tests dist. Code is formatted for the first time (single quotes, no semicolons, width 100; Markdown, docs/ and examples/ untouched). Tests import from vite-plus/test. The benchmarks use Vitest 5's bench fixture; the auth benchmark's in-memory adapter had fallen behind AuthDbAdapter and did not run, it does again.