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

@ratio1/cstore-auth-ts

v0.6.1

Published

Plug-and-play authentication layer for Ratio1 CStore hashes. This TypeScript library wraps the official [@ratio1/ratio1-sdk-ts](https://github.com/Ratio1/ratio1-sdk-ts) SDK, providing a minimal API to bootstrap an admin account and manage simple username/

Readme

@ratio1/cstore-auth-ts

Plug-and-play authentication layer for Ratio1 CStore hashes. This TypeScript library wraps the official @ratio1/ratio1-sdk-ts SDK, providing a minimal API to bootstrap an admin account and manage simple username/password credentials.

Features

  • Hash-backed user store with Argon2id hashing and automatic Node.js scrypt fallback
  • Strict username validation and canonicalisation (lowercase keys)
  • Admin bootstrap workflow driven by environment variables
  • Strong typing, unit tests (Vitest), linting, and dual ESM/CJS builds via tsup
  • Works in Node.js services and Next.js API routes

Installation

pnpm add @ratio1/cstore-auth-ts
# or
npm install @ratio1/cstore-auth-ts

Required environment variables

| Variable | Description | | -------------------------------------- | -------------------------------------------------------------------------------------------------- | | R1EN_CSTORE_AUTH_HKEY | Hash key that stores all user records (e.g. auth:default). | | R1EN_CSTORE_AUTH_SECRET | Long-lived server-side pepper mixed into password hashes. | | R1EN_CSTORE_AUTH_BOOTSTRAP_ADMIN_PWD | One-time bootstrap password for the initial admin user. Required until the admin account exists. |

Note: Legacy environment variable names (EE_CSTORE_AUTH_HKEY, EE_CSTORE_AUTH_SECRET, EE_CSTORE_AUTH_BOOTSTRAP_ADMIN_PW) are still supported for backwards compatibility but are deprecated.

Quick start

import { CStoreAuth } from '@ratio1/cstore-auth-ts';

const auth = new CStoreAuth();
await auth.simple.init();

await auth.simple.createUser('alice', 'S3curePassw0rd', {
  metadata: { email: '[email protected]' }
});

const user = await auth.simple.authenticate('alice', 'S3curePassw0rd');
console.log(user);
// → { username: 'alice', role: 'user', metadata: { email: '[email protected]' }, createdAt: '...', updatedAt: '...', type: 'simple' }

// Retrieve all users
const allUsers = await auth.simple.getAllUsers();
console.log(`Total users: ${allUsers.length}`);
allUsers.forEach((u) => console.log(`- ${u.username} (${u.role})`));

// Update user metadata or role
// IMPORTANT: Implement authorization checks in your application layer!
// Example: Only allow users to edit themselves or admins to edit anyone
await auth.simple.updateUser('alice', {
  metadata: { email: '[email protected]', verified: true }
});

// Change password (requires current password)
await auth.simple.changePassword('alice', 'S3curePassw0rd', 'NewP@ssw0rd!');

Public API

interface CStoreAuthOptions {
  hkey?: string;
  secret?: string;
  client?: CStoreLikeClient;
  hasher?: PasswordHasher;
  now?: () => Date;
  logger?: Pick<Console, 'debug' | 'info' | 'warn' | 'error'>;
}

class CStoreAuth {
  constructor(opts?: CStoreAuthOptions);

  simple: {
    init(): Promise<void>;
    createUser<TMeta = Record<string, unknown>>(
      username: string,
      password: string,
      opts?: CreateUserOptions<TMeta>
    ): Promise<PublicUser<TMeta>>;
    authenticate<TMeta = Record<string, unknown>>(
      username: string,
      password: string
    ): Promise<PublicUser<TMeta>>;
    getUser<TMeta = Record<string, unknown>>(username: string): Promise<PublicUser<TMeta> | null>;
    getAllUsers<TMeta = Record<string, unknown>>(): Promise<PublicUser<TMeta>[]>;
    updateUser<TMeta = Record<string, unknown>>(
      username: string,
      opts: UpdateUserOptions<TMeta>
    ): Promise<PublicUser<TMeta>>;
    changePassword(username: string, currentPassword: string, newPassword: string): Promise<void>;
  };
}

Errors are surfaced as descriptive subclasses (EnvVarMissingError, AuthInitError, InvalidUsernameError, InvalidCredentialsError, UserExistsError, UserNotFoundError, etc.).

Authorization Patterns

⚠️ IMPORTANT: updateUser does not enforce authorization. You must implement authorization checks in your application layer.

// Example: Users can edit themselves, admins can edit anyone
async function updateUserWithAuth(
  currentUser: PublicUser,
  targetUsername: string,
  updates: UpdateUserOptions
) {
  // Check if user is editing themselves OR is an admin
  const isEditingSelf = currentUser.username === targetUsername;
  const isAdmin = currentUser.role === 'admin';

  if (!isEditingSelf && !isAdmin) {
    throw new Error('Unauthorized: You can only edit your own profile');
  }

  // Only admins can change roles
  if (updates.role && !isAdmin) {
    throw new Error('Unauthorized: Only admins can change roles');
  }

  return await auth.simple.updateUser(targetUsername, updates);
}

// Example: Express.js middleware
app.put('/api/users/:username', async (req, res) => {
  const currentUser = req.session.user; // From authenticated session
  const { username } = req.params;
  const updates = req.body;

  try {
    // Authorization check
    if (currentUser.username !== username && currentUser.role !== 'admin') {
      return res.status(403).json({ error: 'Forbidden' });
    }

    // Only admins can change roles
    if (updates.role && currentUser.role !== 'admin') {
      return res.status(403).json({ error: 'Only admins can change roles' });
    }

    const updated = await auth.simple.updateUser(username, updates);
    res.json(updated);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Security notes

Password Security

  • Argon2id (via @node-rs/argon2) is used when available. The module automatically downgrades to Node's built-in crypto.scrypt with strong defaults when Argon2 cannot load.
  • Each user receives a random 16-byte salt and a global pepper (R1EN_CSTORE_AUTH_SECRET).
  • Password hashes are stored versioned to enable future migrations.
  • changePassword always requires current password verification.
  • Secrets and hash material never leave the module through logs.

Authorization

  • ⚠️ updateUser does NOT enforce authorization - implement checks in your application layer
  • Recommended pattern: Users can edit themselves, admins can edit anyone
  • Role changes should be restricted to admins only
  • Consider field-level permissions (e.g., users can't set verified: true on themselves)

Data Validation

  • Usernames are canonicalised to lowercase and must match [a-z0-9._-]{3,64}.
  • Validation happens on every entry point.
  • Only metadata is returned publicly (passwords never exposed).

Development

pnpm install
pnpm run lint
pnpm test                  # Run unit tests
pnpm run test:integration  # Run integration tests
pnpm run test:all          # Run all tests
pnpm run build
pnpm run docs

Testing

The project includes two test suites:

  • Unit tests (test/**/*.spec.ts): Fast, isolated tests with mocked dependencies
  • Integration tests (test/integration/**/*.integration.spec.ts): Complete workflow tests with realistic cleanup patterns

Integration tests follow best practices including:

  • Cleanup before and after each test (setting values to null before deletion)
  • Isolated test environments with unique hash keys
  • Serial execution to avoid race conditions
  • Extended timeouts for password hashing operations

See test/integration/README.md for detailed integration testing documentation.

Typedoc emits HTML documentation to docs/. GitHub Actions (see .github/workflows/ci.yml) runs linting, type-checking, tests, build, and docs generation on Node.js 18 and 20.

Roadmap

  • Additional providers (Google, OAuth 2.0) alongside the current simple method
  • Session issuance (JWT/cookies) and rate limiting/lockouts
  • Password rotation and secret (pepper) rotation workflows
  • Multi-tenant or namespaced user separation