knowless 1.1.4 → 1.1.5

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/GUIDE.md +33 -0
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -30,6 +30,41 @@ v1.0.0 are:
30
30
  - Documentation corrections
31
31
  - Helper exports that pull existing mechanism back into the library
32
32
 
33
+ ## [1.1.5] — 2026-05-09
34
+
35
+ Documentation-only release. plato (Mode A adopter) hit a real seam
36
+ under `bodyOverride`: they want to reorder the body (expiry warning
37
+ before URL) AND keep the "Last sign-in" security signal. The current
38
+ contract drops the line under override — deliberately, since override
39
+ is full-content replacement — and that read as a missing capability.
40
+ It isn't: `auth.deriveHandle(email)` and a parallel `createStore`
41
+ handle calling `getLastLogin(handle)` already give adopters everything
42
+ they need to compose the signal themselves. The gap was discoverability,
43
+ not surface area. Considered exporting a `formatLastLogin` helper to
44
+ centralize the canonical wording across adopters; held off under the
45
+ walk-away default-no, since the population that hits the
46
+ override + reorder + want-signal intersection is narrow (most
47
+ `bodyOverride` uses are per-call subject branding without a sign-in
48
+ event), and the drift risk for the few who do is small and recoverable.
49
+ Cross-product wording consistency for operators running multiple
50
+ knowless adopters belongs in a shared module on the operator side, not
51
+ in knowless. Revisit if a second independent adopter asks for the
52
+ helper. No code changes.
53
+
54
+ ### Documented
55
+
56
+ - `GUIDE.md` Mode A walkthrough — added a "Composing the 'Last
57
+ sign-in' security signal under `bodyOverride`" callout right after
58
+ the `auth.deriveHandle` paragraph. Explains why the line doesn't
59
+ auto-append on overridden bodies (override is full-content
60
+ replacement, AF-26), points at the existing recipe
61
+ (`auth.deriveHandle` + parallel `createStore` + `getLastLogin`),
62
+ notes that `upsertLastLogin` only fires on callback consumption so
63
+ a pre-call read matches what knowless reads internally, and
64
+ includes a worked example. Targeted at adopters who reorder the
65
+ body and want the signal back; default-path adopters (no override)
66
+ are unaffected.
67
+
33
68
  ## [1.1.4] — 2026-05-08
34
69
 
35
70
  Documentation + small bug fix. Adopters (plato, addypin, bareagent,
package/GUIDE.md CHANGED
@@ -384,6 +384,39 @@ and `startLogin` would compute. The bare `deriveHandle` re-export
384
384
  takes pre-normalized input; use the instance method unless you
385
385
  have a specific reason to call the lower-level primitive.
386
386
 
387
+ > **Composing the "Last sign-in" security signal under
388
+ > `bodyOverride`.** The default last-sign-in line lives in
389
+ > `composeBody` and does **not** auto-append on overridden bodies —
390
+ > override is full-content replacement (handlers.js AF-26). If your
391
+ > override needs the same signal, look up the timestamp before
392
+ > calling `startLogin` and interpolate it yourself. Everything you
393
+ > need is already exported: `auth.deriveHandle(email)` returns the
394
+ > handle, and a parallel read-only `createStore(dbPath)` exposes
395
+ > `getLastLogin(handle)` (Unix ms when the handle exists, `null` for
396
+ > sham / new / opted-out). `upsertLastLogin` only fires on callback
397
+ > consumption, so a pre-call read returns the same value knowless
398
+ > reads internally. Wording stays your responsibility under override —
399
+ > small drift cost for taking the wheel.
400
+ >
401
+ > ```js
402
+ > import { createStore } from 'knowless';
403
+ > const store = createStore(process.env.KNOWLESS_DB_PATH);
404
+ > // ... in your request handler ...
405
+ > const handle = auth.deriveHandle(email);
406
+ > const lastLoginAt = store.getLastLogin(handle); // null for sham/new
407
+ > await auth.startLogin({
408
+ > email,
409
+ > bodyOverride: ({ url }) =>
410
+ > `Click to sign in:\n\n${url}\n\n` +
411
+ > `This link expires in 15 minutes. If you didn't request this,\n` +
412
+ > `ignore this email.\n` +
413
+ > (lastLoginAt != null
414
+ > ? `\nLast sign-in: ${new Date(lastLoginAt).toISOString()}.\n` +
415
+ > `If that wasn't you, do not click the link above.\n`
416
+ > : ''),
417
+ > });
418
+ > ```
419
+
387
420
  > **Set `failureRedirect: '/'` — it's part of the silent-miss
388
421
  > contract, not just a UX knob.** The default falls back to
389
422
  > `loginPath` (typically `/login`). That's a partial enumeration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knowless",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
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",