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

davepi-plugin-passkey

v0.1.0

Published

Passkey (WebAuthn) sign-in and recovery codes for dAvePi: a way in that doesn't depend on email. Usernameless discoverable-credential sign-in with required user verification, single-use challenges, signature-counter checks, and scrypt-hashed single-use re

Readme

davepi-plugin-passkey

Passkey (WebAuthn) sign-in and recovery codes for dAvePi: a way in that doesn't depend on email.

Magic links stop working when a person's mail provider is down. A passkey (Face ID, Touch ID, Windows Hello, a phone or a security key) doesn't, and neither does a recovery code. Like davepi-plugin-magic-link, a successful sign-in mints a normal davepi session with utils/tokens.issueTokenPair, so the rest of your API, your claims provider and your session validator treat it like any other sign-in.

Install

npm install davepi-plugin-passkey
{
  "davepi": {
    "plugins": ["davepi-plugin-magic-link", "davepi-plugin-passkey"]
  }
}

Set:

| Variable | | |---|---| | PASSKEY_RP_ID | Required. Your site's domain, e.g. example.com. Passkeys are bound to it and work on it and its subdomains. Changing it later strands every existing passkey. | | PASSKEY_ORIGINS | Required. Comma-separated origins your web app runs on, e.g. https://app.example.com. Each must be https:// (or http://localhost) on PASSKEY_RP_ID or a subdomain of it. | | PASSKEY_RP_NAME | The name browsers show. Defaults to APP_NAME. | | PASSKEY_PATH | Default /auth/passkey. | | PASSKEY_RECOVERY_CODES | Codes per set, 6–20. Default 10. |

Without PASSKEY_RP_ID and PASSKEY_ORIGINS (or with origins it can't serve) the plugin logs a warning and mounts nothing.

Routes

Public (rate-limited with the framework's authLimiter):

| | Body | Answer | |---|---|---| | POST /auth/passkey/login/options | | WebAuthn request options for navigator.credentials.get | | POST /auth/passkey/login/verify | { response } | { accessToken, refreshToken, user } | | POST /auth/passkey/recovery/verify | { email, code } | { accessToken, refreshToken, user, recovery: { remaining } } |

Signed in (a person's own session; API keys and X-Client-Id callers get 403):

| | Body | Answer | |---|---|---| | POST /auth/passkey/register/options | | WebAuthn creation options for navigator.credentials.create | | POST /auth/passkey/register/verify | { response, name? } | 201 { passkey, recoveryCodes }. recoveryCodes is set when the person had none left (show them once); otherwise null. | | GET /auth/passkey/credentials | | { results: [{ id, name, deviceType, backedUp, createdAt, lastUsedAt }] } | | PATCH /auth/passkey/credentials/:id | { name } | { passkey } | | DELETE /auth/passkey/credentials/:id | | 204 | | GET /auth/passkey/recovery | | { remaining, total, createdAt } | | POST /auth/passkey/recovery/regenerate | | { recoveryCodes }. The old codes stop working. |

In the browser, @simplewebauthn/browser turns the options into a response: startRegistration({ optionsJSON }) and startAuthentication({ optionsJSON }).

How it keeps sign-in safe

  • Usernameless sign-in. Sign-in options name no account (discoverable credentials), so asking for them reveals nothing. The passkey the browser offers identifies itself, and the stored public key decides.
  • User verification is required. A passkey is the whole sign-in here, not a second factor, so the authenticator must check a PIN, fingerprint or face.
  • Single-use challenges. Each ceremony's challenge is random, lasts 5 minutes and is claimed atomically before the response is checked, so a response can't be replayed or raced. Origin and RP ID are checked by @simplewebauthn/server.
  • Cloned authenticators. A signature counter that goes backwards is refused.
  • Only public keys are stored. WebAuthn never shares the private key.
  • Recovery codes are 10 characters of Crockford base32 (50 bits), shown once, stored only as salted scrypt hashes, and spent on use. Wrong codes and unknown emails get the same answer after the same amount of work.

Hooks

const passkey = require('davepi-plugin-passkey');

// After passkey.added, passkey.removed, passkey.signin,
// recovery.signin and recovery.regenerated — e.g. to email a security notice.
passkey.onEvent(async ({ type, userId, req }) => { /* … */ });

// Which of these users have a passkey (a Set of strings).
const withPasskeys = await passkey.passkeyUserIds(userIds);

The hook is best-effort: a throw is logged and the request still succeeds.

Data

Four collections, none of them a davepi schema (so no generated route, GraphQL type or MCP tool exposes them): passkey_credentials, passkey_challenges (TTL-indexed), passkey_recovery_codes, and passkey_recovery_sets (each person's current set of codes; only that set counts).