knowless 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -12,6 +12,12 @@ Versioning is [SemVer](https://semver.org/).
12
12
  auth+mail layer. ~1,150 LOC of bespoke auth/mail code removed,
13
13
  ~35 LOC of knowless wiring added (~33× reduction). Drove audit
14
14
  findings AF-7 → AF-17 across v0.1.5–v0.1.10.
15
+ - **2026-05-02 — Three adopters in production.** plato (Mode B,
16
+ forum) and gitdone (Mode A, multi-party email workflows) joined
17
+ addypin (Mode A, location sharing). gitdone's pre-merge review
18
+ surfaced the "wrong-shape integration" failure mode (parallel
19
+ tokens table + manual session minting alongside knowless instead
20
+ of `auth.startLogin`). Patched docs in v1.1.1; no API change.
15
21
 
16
22
  ## [Unreleased]
17
23
 
@@ -24,6 +30,56 @@ v1.0.0 are:
24
30
  - Documentation corrections
25
31
  - Helper exports that pull existing mechanism back into the library
26
32
 
33
+ ## [1.1.2] — 2026-05-03
34
+
35
+ Documentation-only release. Adopters were repeatedly missing that
36
+ `auth.loginForm` is an unstyled contract-minimal fallback they
37
+ should override, and that `/login` is also where every silent-miss
38
+ failure (used / expired / sham / malformed token) redirects — so
39
+ the page deserves first-class UI in the host app, not the bundled
40
+ default. No code changes.
41
+
42
+ ### Documented
43
+
44
+ - `GUIDE.md` FAQ — added section "Branding the GET /login page
45
+ (you almost certainly want to override it)" with the three
46
+ reasons (brand consistency, silent-miss redirect target, opt-in
47
+ fallback) and the override pattern. Extended the existing
48
+ custom-form contract with `next` validation against
49
+ `baseUrl` + `cookieDomain` and the optional honeypot field name
50
+ from `cfg.honeypotFieldName`.
51
+ - `knowless.context.md` gotcha #10 — expanded the "operators
52
+ wanting branding fork the project" note to surface the more
53
+ common path (skip mounting `auth.loginForm`, serve your own
54
+ `GET /login`) and call out that `/login` is the silent-miss
55
+ redirect target.
56
+
57
+ ## [1.1.1] — 2026-05-02
58
+
59
+ Documentation-only release. Adds the wrong-shape-integration
60
+ anti-pattern callout that gitdone's pre-merge review surfaced, and
61
+ records plato + gitdone as the second and third production adopters.
62
+ No code changes.
63
+
64
+ ### Documented
65
+
66
+ - `README.md` — replaced "Sibling projects" with an "Adopters"
67
+ section explicitly listing addypin (Mode A), plato (Mode B), and
68
+ gitdone (Mode A) with a pointer that addypin and gitdone are the
69
+ Mode A worked references.
70
+ - `GUIDE.md` § "Two adoption modes" — added an anti-pattern callout
71
+ at the top: if you're considering writing pending rows to a
72
+ custom tokens table or minting your own confirmation links, that's
73
+ Mode A and `auth.startLogin({ subjectOverride, bodyOverride })`
74
+ already does it. Includes a side-by-side wrong-shape vs Mode A
75
+ sketch. Surfaced by gitdone's pre-merge review where a parallel
76
+ activation system was built before the existing Mode A flow was
77
+ noticed.
78
+ - `GUIDE.md` Mode A/B sub-headings — appended the API entrypoint
79
+ (`auth.startLogin` for Mode A, `auth.login` for Mode B) so the
80
+ use-case leads and `startLogin` doesn't read as "login button
81
+ click."
82
+
27
83
  ## [1.1.0] — 2026-05-02
28
84
 
29
85
  Mailer-contract clarification release. Pulls FR-6 sham-routing
package/GUIDE.md CHANGED
@@ -286,7 +286,31 @@ both out of the box; pick per-action, not per-app — they coexist.
286
286
  The Mode A/B labels are used here and in the CHANGELOG so
287
287
  discussions across the docs stay unambiguous.
288
288
 
289
- **Mode B "sign in, then do the thing" (register-first, the default).**
289
+ > **Stop before you build a parallel activation system.** If
290
+ > you're considering writing pending rows to a custom tokens table,
291
+ > minting your own confirmation links, or calling into the sessions
292
+ > table directly to mark an account "activated by email" — that is
293
+ > Mode A, and it's already in this library. Use
294
+ > `auth.startLogin({ email, subjectOverride, bodyOverride })` and
295
+ > promote your pending resource in the callback handler. The
296
+ > wrong-shape integration is what every adopter has tried first; the
297
+ > right shape is the worked example below.
298
+
299
+ The wrong shape vs Mode A, side by side:
300
+
301
+ ```
302
+ WRONG SHAPE MODE A (use auth.startLogin)
303
+ ───────────────────────────── ─────────────────────────────
304
+ your_tokens table (none — knowless owns the token)
305
+ your custom email composer subjectOverride + bodyOverride
306
+ your /confirm/:token handler auth.callback (already mounted)
307
+ manual session insert handled by callback
308
+ your duplicate rate-limit code knowless rate-limit applies
309
+ sham-work + timing equivalence
310
+ preserved automatically
311
+ ```
312
+
313
+ **Mode B — "sign in, then do the thing" (register-first, the default, `auth.login`).**
290
314
  User must log in before performing the action. Wire `auth.login` /
291
315
  `auth.callback` as above; gate your action with
292
316
  `auth.handleFromRequest(req)`. Use when the action requires a session
@@ -301,7 +325,7 @@ app.post('/api/comments', (req, res) => {
301
325
  });
302
326
  ```
303
327
 
304
- **Mode A — "do the thing, confirm by email" (use-first, claim-later).**
328
+ **Mode A — "do the thing, confirm by email" (use-first, claim-later, `auth.startLogin`).**
305
329
  User performs the action without logging in; you capture their email
306
330
  and trigger a magic link. Clicking it opens a session and your
307
331
  callback handler "promotes" the deferred resource. Use for "drop a
@@ -905,7 +929,48 @@ own form, provided it satisfies the handler contract:
905
929
  stream itself. A body-parser middleware mounted before `auth.login`
906
930
  will silently steal the data (see gotcha #15 in
907
931
  [`knowless.context.md`](knowless.context.md)).
908
- - Optional: include a `next` field for the post-callback redirect URL.
932
+ - Optional: include a `next` field for the post-callback redirect URL
933
+ (knowless validates `next` against `baseUrl` + `cookieDomain`).
934
+ - Optional but recommended: include the honeypot field using the name
935
+ from `cfg.honeypotFieldName`.
936
+
937
+ ### Branding the GET /login page (you almost certainly want to override it)
938
+
939
+ Knowless ships `auth.loginForm(req, res)` as a turnkey fallback so
940
+ adopters can wire `GET /login` in one line and have a working magic-link
941
+ form. The page is intentionally unstyled — it's the contract-minimal
942
+ renderer needed to make the flow demonstrable, not a UI you ship to
943
+ end users. Three reasons most adopters override it:
944
+
945
+ 1. **Brand consistency.** The fallback page has no header, footer, nav,
946
+ or styling, so users redirected here from elsewhere in your app land
947
+ on what looks like a different site. That's especially jarring after
948
+ a sham-token failure (a deliberate part of the silent-miss design —
949
+ see "Silent miss" in [`knowless.context.md`](knowless.context.md)),
950
+ where a user clicked a magic link and ended up on a "Sign in" page
951
+ that looks unrelated to where they started.
952
+ 2. **`/login` is load-bearing in the silent-miss contract.** Knowless
953
+ redirects to `loginPath` on every failure mode that must be
954
+ indistinguishable from success — used token, expired token, sham
955
+ token, malformed token. That redirect is correct and required. But
956
+ it means `/login` is the page users actually land on during
957
+ anti-enumeration failures, not just the page they navigate to
958
+ deliberately. It deserves first-class UI in your app.
959
+ 3. **`auth.loginForm` is opt-in, not opt-out.** Adopters who never wire
960
+ the GET route still get a working app — just without a friendly
961
+ `/login` page. Override it whenever you want your app's chrome on
962
+ the page. The POST handler can stay as-is (or also be wrapped — for
963
+ example, plato wraps it for the "we sent a link" confirmation).
964
+
965
+ Override pattern (mount your own handler instead of `auth.loginForm`):
966
+
967
+ ```js
968
+ app.get('/login', (req, res) => renderMyOwnLogin(req, res));
969
+ app.post('/login', auth.login); // unchanged
970
+ ```
971
+
972
+ The form just needs to satisfy the contract listed in the previous
973
+ question.
909
974
 
910
975
  ### How do I add 2FA / WebAuthn / TOTP?
911
976
 
package/README.md CHANGED
@@ -148,12 +148,21 @@ rate-limits) belong above the library — patterns in
148
148
  Full detail in [`knowless.context.md`](knowless.context.md) §
149
149
  "Threat model summary."
150
150
 
151
- ## Sibling projects
151
+ ## Adopters
152
152
 
153
- - [`addypin`](https://github.com/hamr0/addypin) location sharing,
154
- first knowless adopter
155
- - [`gitdone`](https://github.com/hamr0/gitdone) — verified email
156
- actions via DKIM/SPF inbound
153
+ Production users of knowless, in adoption order:
154
+
155
+ - [`addypin`](https://github.com/hamr0/addypin) — pin-drop location
156
+ sharing. First knowless adopter; Mode A (drop-pin-then-confirm).
157
+ - [`plato`](https://github.com/hamr0/plato) — forum (Reddit-shaped,
158
+ one fingerprint per site). Mode B (sign-in-then-do).
159
+ - [`gitdone`](https://github.com/hamr0/gitdone) — multi-party email
160
+ workflows verified via DKIM/SPF inbound. Mode A
161
+ (start-workflow-then-confirm).
162
+
163
+ If you're picking knowless up: the addypin and gitdone callsites are
164
+ both Mode A and good worked references for the use-first / claim-later
165
+ shape.
157
166
 
158
167
  ## License
159
168
 
@@ -699,7 +699,13 @@ rate-limits) belongs above the library.
699
699
  10. **No JavaScript in any HTML page.** The login form, the
700
700
  confirmation page, error pages — all static HTML5. Works in
701
701
  text-mode browsers (Lynx, w3m). Operators wanting branding
702
- fork the project.
702
+ fork the project — **or, more commonly, skip mounting
703
+ `auth.loginForm` and serve their own `GET /login`**. The
704
+ fallback is intentionally a contract-minimal renderer, not a
705
+ UI to ship. `/login` is also where every silent-miss failure
706
+ redirects (used / expired / sham / malformed token), so it
707
+ deserves first-class chrome in the host app. See GUIDE.md
708
+ § "Branding the GET /login page".
703
709
 
704
710
  11. **Process cleanup matters.** `auth.close()` stops the
705
711
  sweeper and closes the SQLite handle. Without it, your
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knowless",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Small, opinionated, full-stack passwordless auth for Node.js services that don't need to email their users for anything but the sign-in link.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",