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

@saasifier/api-keys

v0.1.1

Published

Create, list, revoke, and verify scoped API keys for programmatic access to a tenant's resources.

Readme

@saasifier/api-keys

Create, list, revoke, and verify scoped API keys for programmatic access to a tenant's resources.

Installation

npm install @saasifier/api-keys
# or
pnpm add @saasifier/api-keys

Overview

Many SaaS products need to let their own customers call back into the API programmatically — CI pipelines, integrations, server-to-server calls — without using interactive login. @saasifier/api-keys provides organization-scoped API keys with permission scopes, expiration, and revocation, built around a strict rule: raw key secrets are never persisted. Only a SHA-256 hash is stored, and the plaintext secret is returned to the caller exactly once, at creation time.

Usage

import { ApiKeyService } from "@saasifier/api-keys";
import type { DatabaseAdapter } from "@saasifier/database";

declare const database: DatabaseAdapter;
const apiKeys = new ApiKeyService(database);

// Create a key — the raw secret is only ever available here.
const { apiKey, secret } = await apiKeys.create("org_123", {
  name: "CI deploy key",
  scopes: ["deployments:write"],
  expiresAt: new Date("2027-01-01")
});
console.log(`Save this now, it won't be shown again: ${secret}`);

// List active keys for an organization (no secrets included).
const keys = await apiKeys.list("org_123");

// Verify an incoming request's key and check its scope.
const verifiedKey = await apiKeys.verify(secret);
if (!apiKeys.hasScope(verifiedKey, "deployments:write")) {
  throw new Error("Key is missing the required scope.");
}

// Revoke a key.
await apiKeys.revoke("org_123", apiKey.id);

API Reference

ApiKeyService

saas.apiKeys.create/list/revoke, plus verify, used by whatever layer authenticates inbound API-key-bearing requests. Kept as its own service (rather than folded into @saasifier/core) so hashing/verification logic has a single, auditable home.

Constructor: new ApiKeyService(database: DatabaseAdapter)

  • create(organizationId: string, input: CreateApiKeyInput): Promise<CreatedApiKey> — generates a new secret, stores its hash, and returns both the persisted ApiKey record and the one-time raw secret.
  • list(organizationId: string): Promise<ApiKey[]> — lists an organization's API keys (never includes raw secrets).
  • revoke(organizationId: string, apiKeyId: string): Promise<void> — revokes a key.
  • verify(rawSecret: string): Promise<ApiKey> — hashes the given raw secret and looks up the matching key. Throws InvalidApiKeyError for any of: unknown, revoked, or expired — deliberately without distinguishing which, so callers can't use the error to probe key state. Updates the key's last-used timestamp on success.
  • hasScope(apiKey: ApiKey, scope: string): boolean — checks whether a verified key includes a given scope string.

CreateApiKeyInput

  • name: string — human-readable label for the key.
  • scopes: string[] — permission scopes granted to the key.
  • expiresAt?: Date — optional expiration.

CreatedApiKey

  • apiKey: ApiKey — the persisted record.
  • secret: string — the raw secret, shown exactly once. It is never persisted or retrievable again.

Hashing utilities (hashing.ts)

Lower-level primitives ApiKeyService is built on, exported for advanced use:

  • generateApiKeySecret(): string — generates a new raw secret in the form sk_live_<48 hex chars>. Only this function ever produces plaintext; callers must display it once and never persist it.
  • hashApiKeySecret(rawSecret: string): string — returns the SHA-256 hex digest of a raw secret; this is what gets persisted and compared against.
  • lastFourOf(rawSecret: string): string — returns the last 4 characters of the raw secret, safe to store/display for key identification (e.g. sk_live_...ab12).

Key format, hashing, and verification flow

  • Format: raw secrets look like sk_live_<48 hex characters> (24 random bytes via node:crypto's randomBytes).
  • Storage: only hashApiKeySecret(secret) (SHA-256 hex digest) and lastFourOf(secret) are persisted, via DatabaseAdapter.apiKeys.create. The raw secret itself is never written to storage.
  • Verification: ApiKeyService.verify(rawSecret) hashes the incoming secret and looks it up by hash via database.apiKeys.findByHash. A key is rejected — with the same generic InvalidApiKeyError — if it doesn't exist, has been revoked (revokedAt set), or has expired (expiresAt in the past). A successful verification touches the key's last-used timestamp.
  • Scopes: each key carries a scopes: string[] array set at creation. Callers are expected to check hasScope(apiKey, requiredScope) after verification before allowing an action — scopes are opaque strings defined by the host application, not enforced by this package.