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

@rubriclab/rubrot-auth

v0.2.3

Published

Passkeys, magic links, invites, recovery codes and sessions for Next 16 apps — storage-adapter based, zero lock-in

Readme

@rubriclab/rubrot-auth

Passkeys, magic links, invites, recovery and DB-backed sessions for the fleet's Next 16 apps (rubrot, metal, vault, uptick). Ships as TypeScript source — consume via transpilePackages: ["@rubriclab/rubrot-auth", "@rubriclab/rubrot-ui"] or bun natively. Hardened semantics from rubrot, folded together with metal/vault/uptick's doors: transactional session rotation, SQL-side idle/absolute expiry, atomic single-use challenges and links, enumeration-quiet everywhere, sha256-only at rest, one live link per user.

Shape

@rubriclab/rubrot-auth            createAuth(config) → { sessions, passkeys, links, team, stepUp }
@rubriclab/rubrot-auth/next       createAuthRoutes(auth), presenceMiddleware, jsonError
@rubriclab/rubrot-auth/postgres   createPostgresStore(sql, opts), ensureSchema, schemaSql
@rubriclab/rubrot-auth/client     browser ceremony + fetch helpers, defaultAuthApiPaths
@rubriclab/rubrot-auth/react      LoginForm, PasskeysCard, PasskeyNudge, TeamCard,
                       AcceptInviteCard, RecoverCard, useStepUp, StepUpDialog

Config

createAuth({
  store,                    // AuthStore adapter (postgres or your own)
  mailer,                   // { send(msg) → {sent:true}|{sent:false,error} , health?() }
  rp: { name, publicUrl },  // or (request) => ({ name, publicUrl }) — rpID = hostname
  cookie: { name, tokenPrefix, secure? },   // secure defaults true; false = dev escape
  userVerification?,        // "required" (default) | "preferred"
  ttls?,                    // session 14d absolute / 3d idle / 5min touch,
                            // challenge 5min, magic 15min, invite 7d, recovery 15min
  features?: { magicLinkLogin, invites, recovery, passkeyNudge },
                            // defaults: rubrot posture — passkey-only, invites+recovery on
  paths?,                   // magicConsume "/api/auth/link", acceptInvite "/accept-invite",
                            // recover "/recover", afterLogin "/", login "/login"
})

The mailer port is transport-only — the package never owns mailbox OAuth. mailer.health() (optional) feeds the login form's honesty flags (delivered/healthy), global state that never enumerates accounts. Email bodies are pure functions (inviteEmail, recoveryEmail, magicLinkEmail): explicit white background (dark-mode clients paint their own canvas otherwise) and a saturated CTA fill (neutral fills get repainted into white-on-white).

Route wiring (per app, once)

// lib/auth.ts
export const auth = createAuth({ store: createPostgresStore(sql), mailer, rp, cookie });
export const authRoutes = createAuthRoutes(auth);

// app/api/auth/passkey/login/options/route.ts
import { authRoutes } from "@/lib/auth";
export const dynamic = "force-dynamic";
export const { POST } = authRoutes.passkeyLoginOptions;

Handler → file map (matches defaultAuthApiPaths in ./client): passkeyLoginOptions|passkeyLoginVerifypasskey/login/options|verify, passkeyRegisterOptionspasskey/register/options, passkeys (GET/POST) → passkeys, passkey (PATCH/DELETE) → passkeys/[id], magicLinkRequestlink/request, magicLinkConsume (GET redirect) → link, inviteOptions|inviteVerifyinvite/options|verify, recoveryRequest|recoveryVerifyrecovery, recovery/verify, plus logout and session. Disabled features answer 404. Team endpoints are app-wired: point your /api/team* handlers at auth.team.* (the react TeamCard defaults to /api/team, /api/team/invites[/:id], /api/team/members/:id, /api/team/resend). Step-up: auth.stepUp.begin(user.id) in your POST, auth.stepUp.verify({ userId, challengeId, response }) before the gated read — wrong-owner assertions are 403.

Edge gate (UX, not the security boundary — real verification stays server-side):

// middleware.ts
export default presenceMiddleware({ cookieName: "metal_session", publicPaths: ["/api/health"] });

Errors: core throws AuthError(status, code, message); handlers map via jsonError to { error, code }.

Postgres adapter

createPostgresStore(sql, opts) needs postgres(url, { transform: postgres.camel }). Schema DDL is exported two ways: await ensureSchema(sql, opts) (idempotent, metal/vault/uptick's in-code style) and schemaSql(opts) (one string for rubrot's migration framework). Both create missing tables AND alter … add column if not exists the columns the package needs (rp_id, revoked_at, invited_by, last_seen_at) — adopting an existing schema is a config choice, never a data move.

Options: tablePrefix, per-table tables overrides, disabledStatus (what removal writes), userInsertDefaults (extra NOT NULL columns on upsert, e.g. name), userColumns (extra camelCase columns selected onto the session user).

Per-app adoption

| | rubrot | metal | vault | uptick | |---|---|---|---|---| | cookie.name | rubrot_admin_session | metal_session | vault_session | uptick_session | | cookie.tokenPrefix | admin_session | metal_session | vault | uptick | | store opts | tablePrefix: "admin_" | disabledStatus: "removed", userInsertDefaults: { role: "member" }, userColumns: ["role"] | disabledStatus: "removed", userInsertDefaults: name+role, userColumns: ["name","role"] | same as vault | | tables hit | admin_users/_sessions/_passkeys/_auth_challenges/_invites/_recovery_tokens | users/sessions/passkeys/auth_challenges/login_links | same as metal | same as metal | | features | invites+recovery (defaults) | { magicLinkLogin: true, invites: false, recovery: false } | same as metal | metal's + passkeyNudge: true | | userVerification | required (default) | required | preferred (keeps current behavior) | preferred | | rp | Rubrot / RUBROT_ADMIN_PUBLIC_URL | Metal / METAL_PUBLIC_URL | Vault / VAULT_PUBLIC_URL | radar / UPTICK_PUBLIC_URL | | extras | — | — | stepUp gates reveals | mailer.healthmailStatus(); nudge dismissal via server action prop |

Notes: session/link tokens verify by sha256 hash, so live sessions survive the cutover regardless of prefix. Old vault/uptick challenge rows use register/authenticate kinds — challenges live 5 minutes, nothing to migrate. Bootstrap (auth.links.createAdminSignInLink) refuses once anyone can sign in unless force; passkey-only apps get an invite-ceremony link instead of a raw sign-in link.

Tests

bun test packages/auth — the suite runs the full core against a pure in-memory store (tests/fakes.ts) with deterministic WebAuthn ceremonies injected through the webauthn config seam; cookieJar is the matching seam for next/headers.