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

@touchque/node

v3.1.0

Published

Official Node.js SDK for TouchQue Authenticator (2FA/MFA, passkeys, adaptive auth)

Readme

@touchque/node

The official Node.js server SDK for TouchQue — biometric push 2FA, passkeys, and offline approval codes, added to any backend with one line per route.

npm version TypeScript License: MIT

📘 Full docs: authenticator.touchque.com/docs

Install

npm install @touchque/node

Setup

Get an API key and secret from your TouchQue Dashboard, then set them as environment variables:

TQ_API_KEY=tq_auth_your_key
TQ_API_SECRET=your_api_secret

The client picks these up automatically — new TouchQue() with no arguments.

Quick start (Express)

import express from 'express';
import { requireTouchQue } from '@touchque/node';

const app = express();
app.use(express.json());

app.post('/transfer',
  requireTouchQue('SEND_MONEY', {
    details: (req) => ({ Amount: `${req.body.amount} EUR`, To: req.body.iban }),
  }),
  (req, res) => {
    // Only reached once the user approved on their phone.
    res.json({ ok: true, assurance: req.touchque.assurance });
  }
);

app.listen(3000);

That's the whole integration for one route. What actually happens:

  1. The first request comes in with no pending approval → the middleware calls TouchQue, sends a push to the user's phone, and answers 202 { touchque, token } instead of running your handler.
  2. Your frontend shows touchque in its own UI — a matching number for the user to tap on their phone, or a QR code the first time they link the app — then sends the same request again with header X-TouchQue-Token: <token> (see @touchque/web, which does this loop for you).
  3. Once the user approves, that retried request reaches your handler exactly once, with req.touchque populated (assurance, approvalProof, …).

No hosted page, no redirect, no new domain — you keep your own UI end to end.

The three primitives, if you don't use a framework adapter

import { TouchQue } from '@touchque/node';

const tq = new TouchQue(); // from TQ_API_KEY / TQ_API_SECRET

const step = await tq.start('SEND_MONEY', {
  user: '[email protected]',
  details: { Amount: '250 EUR', To: 'DE89...' },
});
// step.state: 'waiting' (show step.number) | 'enroll' (show step.enroll.qrCodeDataUrl)
//             | 'approved' | 'rejected' | 'expired' | 'passkey_required' | 'frozen' | 'blocked'

const latest = await tq.check(step.id);

// Once approved, consume it exactly once, right before doing the protected thing:
const approval = await tq.complete(step.id, {
  user: '[email protected]',
  action: 'SEND_MONEY',
  details: { Amount: '250 EUR', To: 'DE89...' },
});

complete() verifies the approval was actually issued for this user, action and transaction — it will not let an approval for a different amount or recipient be replayed against this call, and consuming it twice fails on the second call.

Framework adapters

  • Express: requireTouchQue(action, options?) — shown above.
  • Next.js / Fetch API route handlers: withTouchQue(action, options?).
  • touchqueRouter: mounts every relay route a frontend needs (POST /login, enrollment, passkey ceremonies, offline codes) so you don't hand-write them.
import { touchqueRouter } from '@touchque/node';

app.use(touchqueRouter(tq, {
  getUserId: (req) => req.session.user?.email,
  // The user who already passed your password step. Binds POST /login and the
  // offline routes to them; the offline routes stay off (403) without it.
  getLoginUser: (req) => req.session.passwordVerifiedUser,
}));

Passkeys (phishing-resistant)

Push approval and offline codes stop password reuse and push fatigue, but a real-time phishing proxy can still relay them. A passkey can't be phished: the browser signs your site's real origin and TouchQue refuses any other (NIST SP 800-63B-4 §3.2.5).

  1. Set your passkey domain in the Dashboard (Security Policy → Passkeys).
  2. Let users register one via tq.webauthn (server) + @touchque/web's passkeys.register() (browser).
  3. Optionally require it for critical actions or every sign-in — TouchQue then skips the push and your route resolves passkey_required instead.
  4. Check assurance.phishingResistant before treating a session as high-assurance.

Offline approval (no internet on the phone)

const ch = await tq.offline.challenge({
  user: '[email protected]',
  type: 'WITHDRAW',
  details: { Amount: '1,250.00 USD', Recipient: 'Jane Doe' },
});
// show ch.qrDataUrl — the phone scans it offline and shows a 7-character code

const { approved } = await tq.offline.verify({ challengeId: ch.challengeId, code });

A QR that follows a push. Pass the push's requestId when the offline QR is the fallback for a push the user already started (requireTouchQue / touchqueRouter do this for you):

const ch = await tq.offline.challenge({ externalUsername, type: 'LOGIN', requestId: step.requestId });
// ch.challengeCode is the number to print under the QR when number matching applies.
  • If the user rejects the push on the phone, the offline QR dies with it: no new QR is issued for that sign-in (409 request_rejected), a code for a QR already on screen is refused (reason: 'request_rejected') and so is the time-based code (verifyTotp({ …, requestId })). Treat it as a final "no".
  • With number matching, the page prints ch.challengeCode under the QR; the phone shows it among two decoys after scanning and the user taps the one that matches. The phone is never told which is right — a wrong tap produces a code that fails verification.

Webhooks

app.post('/webhooks/touchque', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const event = tq.webhook.verify({
      rawBody: req.body.toString(),
      signature: req.headers['x-touchque-signature'] as string,
    });
    // handle event.event: 'login.confirmed' | 'login.rejected' | …
    res.sendStatus(200);
  } catch {
    res.sendStatus(403); // not from TouchQue
  }
});

Errors

All SDK errors extend TouchQueError: TouchQueAPIError, TouchQueNetworkError, TouchQueRejectedError, TouchQueTimeoutError, TouchQueWebhookSignatureError, TouchQueConfigError, TouchQuePasskeyRequiredError.

Security

  • Every API request is signed HMAC-SHA256 (method, path+query, timestamp, nonce, body hash).
  • The X-TouchQue-Token a frontend echoes back is itself signed and bound to one user + action + transaction digest — it can't be replayed for a different amount, recipient or user.
  • An approval is consumed exactly once, server-side.
  • Your API secret never leaves your server.

See SECURITY.md to report a vulnerability.

Requirements

License

MIT © TouchQue