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 +56 -0
- package/GUIDE.md +68 -3
- package/README.md +14 -5
- package/knowless.context.md +7 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
##
|
|
151
|
+
## Adopters
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
- [`
|
|
156
|
-
|
|
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
|
|
package/knowless.context.md
CHANGED
|
@@ -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.
|
|
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",
|