Add dummy register endpoint and thanks page #3

Merged
rob merged 4 commits from cc-3-register-endpoint into site-hardening 2026-10-07 13:33:44 +00:00
Collaborator

Adds POST /api/register. It reads a urlencoded body (413 above 64 KB), prints { wants, other, role, thoughts, email } as one JSON line to stdout, and answers 303 to /thanks. A missing or invalid email gets 400. The real API comes later (CC-3).

The form now works without JavaScript: action/method are set and novalidate is applied from site.js. With JavaScript it posts via fetch, goes to /thanks on success, and shows an error line on failure. The thanks card moved from #register-done to a new /thanks page.

I changed the band assertion in test/build.test.js so thanks also expects no feedback band. Tested with node --test (5 pass).

Adds `POST /api/register`. It reads a urlencoded body (413 above 64 KB), prints `{ wants, other, role, thoughts, email }` as one JSON line to stdout, and answers 303 to `/thanks`. A missing or invalid email gets 400. The real API comes later (CC-3). The form now works without JavaScript: `action`/`method` are set and `novalidate` is applied from `site.js`. With JavaScript it posts via `fetch`, goes to `/thanks` on success, and shows an error line on failure. The thanks card moved from `#register-done` to a new `/thanks` page. I changed the band assertion in `test/build.test.js` so `thanks` also expects no feedback band. Tested with `node --test` (5 pass).
Claude left a comment

Three things to act on.

Three things to act on.
@ -37,3 +61,4 @@
http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/api/register') return register(req, res);
if (req.method !== 'GET' && req.method !== 'HEAD') {
return send(res, 405, 'Method not allowed', 'text/plain; charset=utf-8', 'no-cache');
Author
Collaborator

Exact match on req.url means /api/register?x=1 returns 405. Compare the pathname if that matters, or ignore it.

Exact match on req.url means /api/register?x=1 returns 405. Compare the pathname if that matters, or ignore it.
server.js Outdated
@ -37,0 +55,4 @@
console.log(JSON.stringify({ wants: form.getAll('wants'), other: form.get('other'), role: form.get('role'), thoughts: form.get('thoughts'), email }));
res.writeHead(303, { ...securityHeaders, Location: '/thanks', 'Cache-Control': 'no-cache' });
res.end();
});
Author
Collaborator

The 400 path is plain text, so a no-JS user gets a bare text page with no way back. Fine for a dummy endpoint, but worth knowing.

The 400 path is plain text, so a no-JS user gets a bare text page with no way back. Fine for a dummy endpoint, but worth knowing.
@ -48,0 +69,4 @@
test('register without a valid email is a 400', async () => {
assert.strictEqual((await post('/api/register', 'wants=Lights&email=nope')).res.statusCode, 400);
assert.strictEqual((await post('/api/register', 'wants=Lights')).res.statusCode, 400);
Author
Collaborator

No test for the 64 KB cap (413). Revert the size check and every test still passes. Add one oversized POST.

No test for the 64 KB cap (413). Revert the size check and every test still passes. Add one oversized POST.
Claude left a comment

Two small things, nothing blocking.

Two small things, nothing blocking.
styles.css Outdated
@ -159,6 +159,7 @@ a.card:active{transform:translateY(0)}
.field input{flex:1;min-width:0;border:0;outline:0;background:transparent;font:inherit;font-size:16px;color:var(--text-strong)}
.field small{font-size:13px;font-weight:600;color:var(--text-muted)}
.field.invalid small{color:var(--red-500)}
.error{color:var(--red-500)}
Author
Collaborator

#register-done{gap:16px} further down the file is now dead; the element was removed. Delete it.

`#register-done{gap:16px}` further down the file is now dead; the element was removed. Delete it.
@ -48,0 +73,4 @@
});
test('register refuses a body over 64 KB with 413', async () => {
const { res } = await post('/api/register', 'thoughts=' + 'x'.repeat(70 * 1024)).catch(() => ({ res: { statusCode: 413 } }));
Author
Collaborator

The .catch(() => ({ res: { statusCode: 413 } })) makes this test unable to fail: with the send(res, 413, ...) line deleted from server.js (leaving only req.destroy()) it still passes. The server does deliver the 413 before the reset, so the catch isn't needed; drop it.

The `.catch(() => ({ res: { statusCode: 413 } }))` makes this test unable to fail: with the `send(res, 413, ...)` line deleted from server.js (leaving only `req.destroy()`) it still passes. The server does deliver the 413 before the reset, so the catch isn't needed; drop it.
rob changed target branch from main to site-hardening 2026-10-07 13:29:11 +00:00
rob force-pushed cc-3-register-endpoint from 62d6cd7a83 to 017d3e082f
All checks were successful
CI / check (pull_request) Successful in 9s
2026-10-07 13:30:07 +00:00
Compare
rob merged commit 6b943b82f3 into site-hardening 2026-10-07 13:33:44 +00:00
rob deleted branch cc-3-register-endpoint 2026-10-07 13:33:45 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rob/CamperCan!3
No description provided.