npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@clientsdemo/mfa-client

v0.1.0

Published

Server-side client for the clientsdemo MFA push-authentication service — create enrollments, dispatch sign-in requests, verify number matching, and consume webhooks.

Readme

@clientsdemo/mfa-client

Server-side client for the clientsdemo MFA push-authentication service. Your web application uses it to enroll users' phones, push sign-in approval requests, and verify number matching — the phone side is handled by the MFA Authenticator Android app.

Zero dependencies; runs on Node 18+, Cloudflare Workers, and edge runtimes.

Status: the service (Cloudflare Worker + D1) is live in production at https://mfa.clientsdemo.site; this package is the supported way to talk to it. The full API contract lives in docs/API.md in the repo.

Install

npm install @clientsdemo/mfa-client

Setup

import { MfaServerClient } from '@clientsdemo/mfa-client';

const mfa = new MfaServerClient({
  serverUrl: process.env.MFA_SERVER_URL!,   // https://mfa.clientsdemo.site
  appId: 'ehs',                             // your registered app id
  apiKey: process.env.MFA_API_KEY!,         // server-to-server key; never ship to browsers
});

Enroll a device (account security settings page)

const enrollment = await mfa.createEnrollment({ userId: user.id, userEmail: user.email });
// Render enrollment.setupUri as a QR code AND as copyable text.
// The user scans it with the MFA Authenticator app. Codes are single-use and expire.

Manage devices:

const devices = await mfa.listEnrollments(user.id);
await mfa.revokeEnrollment(deviceToRemove.enrollmentId);

Protect a login (number matching)

// After the first factor (password/SSO) succeeds:
const req = await mfa.createRequest({
  userId: user.id,
  context: { ip: clientIp, userAgent, locationHint: 'Bengaluru, IN' },
});

// The user's phone shows a verification number. The user types it into your
// login form and taps Approve on the phone.
const numberOk = await mfa.verifyNumber(req.requestId, submittedNumber);
if (!numberOk.valid) return reject('Wrong verification number');

const decision = await mfa.waitForDecision(req.requestId, { timeoutMs: 120_000 });
if (decision.status !== 'approved') return reject(`MFA ${decision.status}`);

// Both factors passed — create the session.

Treat anything other than approved + valid: true as a failed login. Requests expire server-side after 5 minutes; denials and expiries are final.

Second-factor login bridge (stateless flows)

Push approval happens on the phone, out-of-band, while a web login is stateless across requests. createSecondFactorBridge() carries the approval between steps with two short-lived HMAC tokens, so you don't hand-roll it per app:

import { createSecondFactorBridge } from '@clientsdemo/mfa-client';
const bridge = createSecondFactorBridge(process.env.AUTH_SECRET!); // reuse your app secret

// 1) After the password check, create the request and issue a challenge:
const req = await mfa.createRequest({ userId, context });
const challenge = await bridge.mintChallenge(email, req.requestId); // send to the browser

// 2) Verify endpoint — the user typed the number from their phone:
const requestId = await bridge.verifyChallenge(challenge, email);   // null if invalid/expired
if (!requestId) throw new Error('bad challenge');
if (!(await mfa.verifyNumber(requestId, typedNumber)).valid) throw new Error('wrong number');
if ((await mfa.waitForDecision(requestId)).status !== 'approved') throw new Error('not approved');
const proof = await bridge.mintProof(userId);                        // hand back to the login step

// 3) Session-mint step (e.g. NextAuth authorize) — accept only a valid proof:
if (!(await bridge.verifyProof(proof, userId))) return null;

Challenge default TTL 5 min (the request lifetime), proof 2 min. Tokens are HMAC-SHA256, tamper-evident, and kind-isolated (a proof can't be replayed as a challenge).

Webhooks (optional, instead of polling)

Register a webhook URL for your app (via the service admin API). Deliveries are HMAC-SHA256 signed (X-Webhook-Signature: sha256=<hex> over the raw body):

const event = await MfaServerClient.verifyWebhook(rawBody, signatureHeader, process.env.MFA_WEBHOOK_SECRET!);
if (!event) return new Response('bad signature', { status: 401 });
// event.type ∈ request.approved | request.denied | request.expired
//            | enrollment.completed | enrollment.revoked | enrollment.recovered
//            | security.request_reported           — user tapped "this wasn't me"
//            | security.push_fatigue_suspected     — spray blocked; consider locking the login
//            | security.additional_device_enrolled — a 2nd+ device was added; warn the user

Act on the security.* events — they are the service telling you a login is likely under attack.

Required environment variables

| Variable | Purpose | |---|---| | MFA_SERVER_URL | MFA service base URL | | MFA_API_KEY | Server-to-server API key for your app | | MFA_WEBHOOK_SECRET | Only if you consume webhooks |

Security notes

  • The verification number is generated by the service and delivered only to the enrolled device — your app never sees it; verifyNumber() is a server-side comparison and is rate-limited.
  • Device approvals are signed on-device with a hardware-backed key; the service verifies that signature before a request can become approved.
  • Always pass real context — the user sees it on the approval screen, and it is what lets them spot a phishing attempt.

License

MIT