knowless
v1.3.2
Published
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.
Maintainers
Readme
knowless
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.
npm install knowlessv1.0.0 (walk-away release) | Node.js >= 22.5 | 1 production dep (nodemailer)
What it does
The simpler answer that always worked: magic link in, session cookie out, nothing else stored. Email is HMAC-hashed at the boundary and discarded. The library refuses, by API shape, to send anything but the sign-in link or store anything identifying.
Most auth libraries default to maximum identity collection: full email in plaintext, profile fields, recovery email, federation. Even nominally privacy-focused options store enough that a breach is materially harmful. knowless inverts the default.
The thesis: most services have ten layers of auth tooling where they need two.
How it works
email → HMAC-SHA256(secret, normalize(email)) → opaque handle
| |
v v
magic-link token (256-bit, single-use) sessions, tokens
| |
v v
submitted via localhost SMTP stored as SHA-256 hashes
|
v
user clicks → handle resolved → signed cookie set- Plaintext email is never persisted. Only the salted hash
(
HMAC-SHA256(secret, normalized_email)). - Only the magic link is ever sent. No welcome, no digest, no notification. There is no API to send anything else.
- All outbound mail goes via your localhost MTA. No vendor SDKs, no API tokens.
- Tokens are SHA-256 at rest, single-use, 15-min TTL. Raw token never persisted.
- Session cookies are HMAC-signed. No JWT, no algorithm confusion.
- Sham work on every miss. Unknown emails do the same work as registered ones (compose, submit, log) but the SMTP recipient is a null-route. Times equivalent within 1ms — measured in CI.
Two modes
Same library, two flows. They coexist in one app — pick per action.
- "Sign in, then do the thing" — a normal login.
- "Do the thing, confirm by email" — drop a pin, post a comment, share a link without an account, and the email confirmation creates the account in the background.
The same sham-work flow runs underneath either mode, so unknown emails, rate-limit hits, and real sends look identical to an external observer.
Two deployment shapes
| Shape | When |
|---|---|
| Library mode | Mount the five handlers (login, callback, verify, logout, loginForm) in your existing Node app. |
| Standalone server (npx knowless-server) | Forward-auth gateway behind Caddy / nginx / Traefik for self-hosters gating Uptime Kuma / AdGuard / Pi-hole / Sonarr / Jellyfin / etc. One auth subdomain, SSO across services via the parent-domain cookie. |
What knowless refuses (by design)
These are closed doors, not omissions. If any break your case, knowless isn't the right tool — look at Lucia, Auth.js, or commercial offerings.
- Localhost SMTP only. No Mailgun / Postmark / SES / Resend. Vendor relationships invite reusing the mailer for non-auth mail, which collapses the "one mail purpose" invariant.
- One mail purpose: the sign-in link. No
sendNotification()to be tempted by. - Plain-text 7-bit email. No HTML, no tracking pixels, no click-rewriting, no read-receipts.
- No DKIM/SPF in the library. That's the MTA's job; knowless emits clean RFC822 and your Postfix + opendkim signs it.
- No OAuth / OIDC / SAML. Different audience.
- No 2FA / WebAuthn / TOTP / passkeys. Compose with a separate library if you need them.
- No admin UI.
sqlite3 knowless.dbis the admin UI. - Hardcoded login form. Templating is a slope — today "let me put my logo," tomorrow "let me theme the page," eventually "let me embed a JS framework." Fork, override the route entirely, or live with it.
- No telemetry, analytics, or error reporting. No phone-home of
any kind. Operator-side observability is opt-in via three hooks
(
onMailerSubmit/onTransportFailure/onSuppressionWindow) — wire them or be silent. - Walks away at v1.0.0. Maintenance mode after that — security fixes, bug fixes that don't change the API surface, doc fixes, helper exports.
Operator commitments
By choosing knowless, you commit to running:
- Postfix (or another MTA) on the same host, outbound-only
- SPF, DKIM, PTR records for your sending domain
- Outbound port 25 open (some clouds block it)
- A null-route for the configured
shamRecipientso silent-miss sham mail drops, not bounces
Threat model — one paragraph
Email-based magic links are exactly as strong as the user's mailbox. knowless can harden the auth flow; it cannot harden an inbox the user has already lost. The list below reflects that boundary.
Defends well: DB-only leaks (handles are HMAC-salted),
plaintext-email exfiltration (none persisted), password reuse (no
passwords), silent email enumeration via the login form (timing-
equivalent + same response shape), email-bombing a target (per-handle
token cap), naive bots (honeypot), account-creation spam (per-IP
caps), replay attacks (atomic mark-token-used), open redirects
(next_url whitelist), CSRF on POST endpoints (Origin/Referer
whitelist).
The per-IP defences key on the client IP. Behind a reverse proxy you
must forward the real client IP, or they collapse to a single bucket
and stop biting per-attacker — see GUIDE (proxies / startLogin) and
OPS (nginx).
Partially: HMAC-secret-only leak (allows targeted existence checks but not session forgery), phishing (no password to type into a fake site, but a phished mailbox still receives links).
Does NOT defend against: compromised email accounts (the magic link is a bearer token — anyone who can read the inbox can use it; defense lives at the email provider, not in this library), sophisticated bots that bypass the honeypot, distributed floods from many IPs, full server compromise, social engineering, insider threat at the operator. Layer-2 defences (Cloudflare, fail2ban, reverse-proxy rate-limits) belong above the library. Note the per-IP caps bound a single address, not aggregate outbound volume — under a distributed flood (many IPs, each under the cap) your sender-domain reputation, the asset this auth channel depends on, is the perimeter's responsibility, not the library's.
Adopters
Production users of knowless, in adoption order:
addypin— pin-drop location sharing. First knowless adopter; Mode A (drop-pin-then-confirm).plato— forum (Reddit-shaped, one fingerprint per site). Mode B (sign-in-then-do).gitdone— multi-party email workflows verified via DKIM/SPF inbound. Mode A (start-workflow-then-confirm).
If you're picking knowless up: the addypin and gitdone callsites are both Mode A and good worked references for the use-first / claim-later shape.
Going further
GUIDE.md— integration walkthrough, observability hooks, edge cases, FAQ, troubleshooting.OPS.md— operator setup (Postfix install, SPF/DKIM/PTR/ DMARC at your registrar, null-route, systemd, forward-auth wiring, fail2ban).CHANGELOG.md— version history.
License
Apache 2.0 with NOTICE preservation. Forks
must keep the NOTICE file.
