dream: forms (urlencoded) + stateless signed CSRF + 26 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 59s

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 14:43:41 +00:00
parent edff7735e7
commit 9a67ced748
4 changed files with 429 additions and 5 deletions

View File

@@ -64,11 +64,11 @@ The five types: `request`, `response`, `handler = request -> response`, `middlew
- `dream-flash-middleware` — single-request cookie store.
- `dream-add-flash-message req category msg`.
- `dream-flash-messages req` — returns list of `(category, msg)`.
- [ ] **Forms + CSRF** in `lib/dream/form.sx`:
- `dream-form req` — returns `(Ok fields)` or `(Err :csrf-token-invalid)`.
- `dream-multipart req` — streaming multipart form data.
- CSRF middleware: stateless signed tokens, session-scoped.
- `dream-csrf-tag req` — returns hidden input fragment for SX templates.
- [~] **Forms + CSRF** in `lib/dream/form.sx`:
- [x] `dream-form req` — returns `(Ok fields)` or `(Err :csrf-token-invalid)`.
- [ ] `dream-multipart req` — streaming multipart form data. *(next commit)*
- [x] CSRF middleware: stateless signed tokens, session-scoped.
- [x] `dream-csrf-tag req` — returns hidden input fragment for SX templates.
- [ ] **WebSockets** in `lib/dream/websocket.sx`:
- `dream-websocket handler` — upgrades request; handler `(fn (ws) ...)`.
- `dream-send ws msg`, `dream-receive ws`, `dream-close ws`.
@@ -151,6 +151,19 @@ Confirm scope before starting; some of these may be addable as Dream-internal he
PREVIOUS request's messages) / `dream-flash-of` (by category) / accessors. Cookie
codec percent-escapes the `|`/`~`/`%` separators so categories/messages round-trip.
Read-after-write verified across request boundaries incl. multi-category.
- **2026-06-07 — Forms + CSRF (urlencoded)** (`lib/dream/form.sx`, 26 tests). Ok/Err
result values (`dream-ok`/`dream-err` + predicates/accessors). `dream-form-fields`
parses `application/x-www-form-urlencoded` with a full percent-decoder
(`%XX` via `char-from-code`, `+`→space). CSRF is stateless + signed + session-
scoped: token = `sid.signature`, verified by recomputing the signature and checking
the session id — no server storage. Signing is **injectable** (`dream-csrf-with`);
the default `dream-csrf-sign-default` is a pure-SX dual-base polynomial keyed hash
(NOT cryptographic — production should inject a host HMAC). `dream-csrf` attaches
context (needs the session middleware upstream for the sid); `dream-csrf-token` /
`dream-csrf-tag` (hidden input for templates); `dream-form` returns `Ok fields` or
`Err :csrf-token-invalid`; `dream-csrf-protect` auto-rejects unsafe methods (403)
lacking a valid token. Full session→csrf→form stack verified accept + reject.
Multipart deferred to the next commit.
## Blockers