Skip to content

hamr0/knowless

Repository files navigation

knowless

CI version (auto from package.json) license: Apache 2.0

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 knowless

v1.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.db is 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 shamRecipient so 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.

About

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.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors