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-tokens

v0.3.0

Published

One scoped-bearer-token engine for the fleet: <prefix>_<id>_<secret> mint/parse/hash, ranked or orthogonal capabilities, adapter-backed bearer authentication

Readme

@rubriclab/rubrot-tokens

One scoped-bearer-token engine for the fleet — vault's lib/service-tokens.ts pattern (the acknowledged origin; metal's copy cites it), extracted so the next app stops forking it. Zero dependencies; storage is an injected adapter (the rubrot-auth idiom), so each app binds its own table names.

The invariants (all kept)

  • Format <prefix>_<8-byte-hex-id>_<32-byte-base64url-secret>. The id is hex, so the first _ after the prefix delimits — the secret may itself contain _.
  • Plaintext once: mintToken is the only place the full token exists; only secretHash (SHA-256 of the secret part) is stored. Rotation = revoke + re-mint.
  • Check order (load-bearing): invalid token → revoked → expired → creator inactive → capability. Constant-time on the secret; a wrong secret is indistinguishable from an unknown id.
  • Creator ceiling: the adapter answers creatorActive — revoking a person revokes their tokens.
  • last_used_at stamps best-effort on success, never on failure, never fails the call.
  • Scope rows narrow a token to resources: Scope is app-defined, loaded by the adapter, carried onto the identity untouched. What "in scope" means stays an app rule.

Capability strategies

The one real fork between the sources, kept as a choice:

rankedCapabilities(["read", "operate", "exec"])   // metal: higher covers lower
orthogonalCapabilities(["read", "write"])         // vault: disjoint — write NEVER reads

Both fail closed on capabilities outside the declared set.

Wiring

// lib/tokens.ts — bind once per app
import { authenticateBearer, mintToken, rankedCapabilities } from "@rubriclab/rubrot-tokens";

const strategy = rankedCapabilities(["read", "operate", "exec"] as const);
const adapter = {
  findById: (id) => /* select from service_tokens join users → TokenRecord | null */,
  loadScope: (tokenId) => /* select from service_token_scopes → your Scope */,
  touchLastUsed: (tokenId) => /* update ... set last_used_at = now() */,
};

export const authenticate = (authorization: string | null, required: Capability) =>
  authenticateBearer(adapter, strategy, { prefix: "metal", authorization, required });

The BearerResult failure shape plugs straight into @rubriclab/rubrot-api's withAuth: { ok: false, status, error, code? }. code is an optional hint that lines up with rubrot-api's AuthFailure.code — the engine sets AUTH_MISSING when no bearer was presented; that wrapper carries a set code through and otherwise defaults it by status. evaluateToken (the pure gate) and parseToken/hashToken/constantTimeEqualHex/parseBearerAuthorization are exported for adapters and tests.

Typing the identity

BearerResult<Cap, Scope> and TokenIdentity<Cap, Scope> ARE the types to alias — don't re-declare them locally:

type Capability = "read" | "operate" | "exec";
type FleetScope = { machineIds: readonly string[]; appNames: readonly string[] };

export type Identity = TokenIdentity<Capability, FleetScope>;
export type Result = BearerResult<Capability, FleetScope>;

Admin (mint / list / revoke)

The CRUD half both forks shipped, extracted. Bind a TokenAdminAdapter (the WRITE port, separate from the auth-path adapter so an auth-only app needn't implement it) and the package owns the discipline: plaintext exists once (only secretHash reaches the adapter), the scope check runs before persist (a rejected mint leaves nothing behind), and status derives one way everywhere.

import {
  createServiceToken,
  revokeServiceToken,
  statusOf,          // (expiresAt, revokedAt, now?) → "active" | "expired" | "revoked"
  tokenView,         // ServiceTokenRow → ServiceTokenView (ISO timestamps + status)
  type TokenAdminAdapter,
} from "@rubriclab/rubrot-tokens";

const admin: TokenAdminAdapter<Capability, FleetScope> = {
  // Optional: throw to reject a mint out of reach (unknown machine, ScopeBeyondReach).
  checkScope: async (scope, createdBy) => { /* app rule — throws abort the mint */ },
  persist: async (token) => { /* insert service_tokens (token.secretHash) + scope rows, atomically */ },
  revoke: async (id) => { /* update ... set revoked_at = coalesce(revoked_at, now()); return rowCount > 0 */ },
};

// Mint — the plaintext is in the result exactly once; hand it out, never store it.
const { id, token } = await createServiceToken(admin, {
  prefix: "metal", name, capability, scope, expiresInDays, createdBy,
});

await revokeServiceToken(admin, id); // WHO may revoke stays an app rule, enforced first.

listServiceTokens stays app-side (its SQL and scope columns are yours) — build the list with your query, then map each row through tokenView.