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

@alluxi/mfa

v0.3.0

Published

Alluxi/Axionix shared two-factor authentication — passkeys, TOTP, emailed backup codes, recovery codes and cross-app device trust

Downloads

1,011

Readme

@alluxi/mfa

Shared two-factor authentication for the Axionix suite: Chrome/platform passkeys (WebAuthn) and authenticator apps (TOTP), with single-use recovery codes, an emailed backup code for lost devices, and cross-app device trust.

A user enrolls once. All eight tools — admin, bdr, compliance, gov, hr, otto, pm, v3 — honor that enrollment.

How "enroll once" works

Two things make it possible:

  1. One shared store. All MFA records live in an mfa schema in the shared Neon database, keyed on lowercased email. Not on a user id — users are provisioned separately in each tool and axionix-gov has no user table at all, so email is the only identity spanning the suite.

  2. One Relying Party ID. Every tool is served from *.alluxi.com, so the WebAuthn RP ID is the registrable parent alluxi.com. A passkey created at time.alluxi.com is valid at pm.alluxi.com with no re-enrollment.

    Passkeys are bound to the RP ID. Reaching an app by its *.vercel.app hostname instead of its *.alluxi.com domain will reject them.

Verification is tracked separately from enrollment: each app's own session carries the result, and a .alluxi.com-scoped signed cookie means clearing the challenge in one tool satisfies the rest for MFA_TRUSTED_DEVICE_DAYS (default 30). The cookie is HMAC-signed and backed by a database row, so a lost laptop can be cut off immediately rather than waiting for expiry.

Install

npm install @alluxi/mfa          # or: file:../axionix-mfa for local development

Apply the schema once per database (idempotent, safe to re-run):

MFA_DATABASE_URL="postgresql://…?schema=mfa" npm run migrate --prefix ../axionix-mfa

The tables sit outside every app's Prisma schema on purpose: gov uses Drizzle and could never share a Prisma client, and folding them into v3 would force multiSchema — and an @@schema annotation on all ~40 existing models — onto that app.

Environment

| Variable | Required | Notes | |---|---|---| | MFA_DATABASE_URL | yes | Shared Neon URL with ?schema=mfa | | MFA_ENCRYPTION_KEY | yes | 32 bytes, hex or base64. openssl rand -hex 32. Encrypts TOTP secrets at rest | | MFA_SESSION_SECRET | yes | ≥32 chars. Must be byte-identical across all eight apps | | MFA_RP_ID | prod | alluxi.com in production, localhost in development (default) | | MFA_RP_ORIGIN | no | Comma-separated allowed origins. Defaults to https://<rp-id>, or ports 3000-3006 on localhost | | MFA_RP_NAME | no | Shown in the passkey prompt. Default Alluxi | | MFA_ENFORCE | no | true turns on the gate. Default false | | MFA_GRACE_DAYS | no | Days to enroll before the hard gate. Default 7 | | MFA_TRUSTED_DEVICE_DAYS | no | Default 30 | | MFA_RESEND_API_KEY | email | Falls back to the app's own RESEND_API_KEY | | MFA_MAIL_FROM | email | Falls back to the app's own RESEND_FROM_EMAIL |

The two mail variables are only read when someone actually uses an emailed backup code, so seven of the eight apps need no new configuration — they already carry Resend credentials. axionix-compliance ships no mailer of its own and needs RESEND_API_KEY and RESEND_FROM_EMAIL (or the MFA_-prefixed pair) added before the backup route works there.

A mismatched MFA_SESSION_SECRET degrades silently to re-prompting rather than erroring — worth asserting at startup.

Server usage

import { evaluateMfa, blocksAccess, TRUSTED_DEVICE_COOKIE } from "@alluxi/mfa";

const state = await evaluateMfa({
  email: session.user.email,
  trustedDeviceCookie: cookies().get(TRUSTED_DEVICE_COOKIE)?.value,
  sessionVerified: token.mfa === "ok",   // skips the DB round-trip
});

if (blocksAccess(state.status)) redirect("/mfa");

state.status is one of:

| Status | Meaning | Gates? | |---|---|---| | ok | Second factor satisfied | no | | challenge | Enrolled, but this session/device hasn't proven it | yes | | enroll | Nothing registered and grace has expired | yes | | grace | Nothing registered, still inside the window — show the nag banner | no | | off | Enforcement disabled and nothing registered | no |

Enrolled users are always challenged, even with MFA_ENFORCE=false: turning enforcement off must never silently weaken an account that already has a second factor.

Non-interactive callers must bypass thisaxk_ personal access tokens and cron routes authenticate as machines and have no way to answer a challenge.

Client usage

import { registerPasskey, authenticateWithPasskey, verifyTotpCode } from "@alluxi/mfa/browser";

These post to the /api/mfa/* routes each app mounts.

Recovery

Ten single-use codes are issued at enrollment and shown exactly once; only SHA-256 hashes are stored. resetMfa(email) clears every factor for a locked-out user and is what an admin "reset MFA" action should call.

Emailed backup codes

A user may confirm an address that can be mailed a 6-digit code at the challenge screen — the self-service way out when both the device and the written-down recovery codes are gone.

It is not a standalone factor and cannot be enrolled with. Sign-in across the suite is Google OAuth on the same address, so a code delivered to the account mailbox proves nothing the OAuth session did not already prove; treating it as a factor would be a gate that unlocks itself. evaluateMfa therefore reports factors.email but excludes it from enrolled. Pointing the backup at a different mailbox — which email/setup accepts and the UI recommends — is what makes it genuinely independent.

The split across the gate matters:

| Endpoint | Reachable while gated | Why | |---|---|---| | email/send | yes | Only ever mails the address already confirmed on the account | | email/verify | yes | It is how someone proves the factor | | email/setup | no | Chooses the destination; a hijacked, unverified session could otherwise redirect the gate to its own mailbox | | email/disable | no | Factor management |

Codes live 10 minutes, are stored as SHA-256 hashes, allow 5 guesses each, and a resend is refused inside a 30-second cooldown. Verification runs through verifySecondFactor, so the suite-wide 10-failures-per-15-minutes lockout covers it too. Unlike the other methods, email/verify defaults remember to false: someone reaching for the backup has usually just lost a device, which is the wrong moment to grant a 30-day pass by default.

Tests

npm test

Covers TOTP drift windows, recovery-code reuse, tampered/expired/cross-account trust cookies, emailed-code expiry/replay/attempt caps, and the grace-period boundaries. No database required — the store layer is mocked, and the mailer never sends.