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

@solidus-network/auth-otp

v0.1.0

Published

Pure, provider-agnostic OTP (SMS/email) login core for the Solidus Network protocol — injected clock, rng, store, sender, and identity resolver; the caller owns session issuance and delivery.

Readme

@solidus-network/auth-otp

A pure, provider-agnostic OTP (SMS/email) login core for the Solidus Network protocol. It generates, stores, and verifies one-time codes with an injected clock, RNG, and store — and hands delivery and identity resolution to the caller through two small interfaces. It does nothing useful on its own: you bring your own sender and your own session issuer.

Status — read this first

  • First release (0.1.0). New package, testnet-grade.
  • Zero @solidus/* runtime dependencies. This package never imports a DID SDK, a JWT signer, or an SMS provider SDK — see "What this package deliberately does NOT do" below.
  • Only the pure core ships today. A real SMS sender adapter is a founder-gated follow-on (provider account, credentials, a test handset) — not a code-readiness question. The email channel is fully usable today via any OtpSender you wire in (e.g. Resend, SES, SMTP); the sms channel is structurally supported (the channel param accepts 'sms') but this package ships no real SMS sender itself.

What this package does

  • generateOtp(rng?) — a 6-digit numeric code from an injected RNG (defaults to Math.random; production callers should inject a CSPRNG).
  • memoryOtpStore() — a reference, in-memory OtpStore (generate is store-agnostic; swap in Redis/Postgres/etc. by implementing the same three-method interface).
  • verifyOtp(store, { to, code, now, maxAttempts }) — expiry (via injected now), single-use consumption, attempt-count lockout, constant-time code comparison.
  • startLogin(deps, { channel, to }) — generates a code, stores it, and calls your injected OtpSender.send(channel, to, code). Returns nothing; the code is never in the return value or in any log this function touches.
  • completeLogin(deps, { to, code }) — verifies the code, and only on success calls your injected resolvePrincipal(to) to find or mint the DID behind that phone number/email. Returns { did, isNew } or { error } — nothing else.

What this package deliberately does NOT do

  • It does not send anything. OtpSender is an interface you implement:

    interface OtpSender {
      send(channel: 'sms' | 'email', to: string, code: string): Promise<void>
    }

    Twilio, Vonage, Resend, SES, a test double — this package never imports a provider SDK, and never will. Delivery receipts, sender IDs, and regional routing are the adapter's problem, not this package's.

  • It does not resolve or mint identity. resolvePrincipal(to) => Promise<{ did, isNew }> is yours to implement. Minting a fresh did:solidus for a first-time phone number means generating and custodying a keypair — a decision this package refuses to make on your behalf. Look up an existing DID, mint a new one with your own custody model, or reject unknown numbers entirely; auth-otp only calls the function you give it.

  • It does not issue a session. completeLogin stops at { did, isNew }. Signing a JWT/session token is the calling backend's job, using whatever signer it already has (in the Solidus monorepo, that's the internal @solidus/jwt package — deliberately never a dependency of this package, since it is workspace-private and cannot resolve for an outside installer). Take the returned did, put it in your own session payload, sign it with your own key.

Install

npm i @solidus-network/auth-otp

Example

import { memoryOtpStore, startLogin, completeLogin } from '@solidus-network/auth-otp'
import type { OtpSender } from '@solidus-network/auth-otp'

const store = memoryOtpStore()

// Stand-in sender — replace with a real Twilio/Vonage/Resend/etc. adapter.
const sender: OtpSender = {
  async send(channel, to, code) {
    console.log(`[dev only] would send ${channel} code ${code} to ${to}`)
  },
}

// Your own identity resolution — look up or mint a DID however your product does it.
async function resolvePrincipal(to: string) {
  return { did: `did:solidus:testnet:stub-${to}`, isNew: true }
}

await startLogin({ store, sender, now: Date.now() }, { channel: 'sms', to: '+15555550100' })

// ...user reads the code from their phone/inbox and submits it...

const result = await completeLogin(
  { store, resolvePrincipal, now: Date.now() },
  { to: '+15555550100', code: '123456' },
)

if ('error' in result) {
  // 'expired' | 'wrong' | 'locked' | 'none'
  console.log('login failed:', result.error)
} else {
  // { did, isNew } — now sign your own session with your own JWT/session library.
  console.log('resolved principal:', result.did)
}

Security defaults

  • Hashed at rest. OtpStore never holds the plaintext code — only a SHA-256 hex digest (hashOtp, node:crypto, no new dependency). This is defense against the code showing up in plain sight in a DB browser, a log aggregator, or a backup — not a substitute for the rate limit below. A 6-digit code has only 1,000,000 possible values, so an unsalted hash does not resist offline brute force against a store dump; what actually bounds a live guessing attack is maxAttempts.
  • Codes expire (default 5 minutes) — configurable via startLogin's expiresInMs.
  • Max verification attempts (default 5) before lockout — configurable via maxAttempts.
  • Single-use: a correct code is consumed (deleted from the store) on success.
  • Constant-time comparison — code verification (of the hash) does not leak timing information about how many characters of a guess were correct.
  • The code is never returned by startLogin/completeLogin, and never logged by this package. The only place a real code appears in a call stack is inside your own OtpSender.send.

License

Apache-2.0