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

@redbroomsoftware/dev-impersonation

v0.1.0

Published

Time-boxed, HMAC-signed support/developer impersonation tokens. Pylon/Frontegg-style support tooling primitive.

Readme

@redbroomsoftware/dev-impersonation

Time-boxed, HMAC-signed support / developer impersonation tokens for multi-app SaaS deployments. A small, dependency-free primitive in the spirit of Pylon and Frontegg — the control-plane mints a short-lived signed token; the target app verifies it and mints a bounded session cookie so a support engineer can "act as" a customer without going through their login flow.

  • HMAC-SHA256, constant-time comparison (crypto.timingSafeEqual).
  • Cross-domain token + same-domain cookie, both from one shared secret.
  • Generic over your payload type — bring your own { agent, target, ... }.
  • Zero runtime dependencies. Pure Node node:crypto.
  • Apache 2.0 licensed.

Install

npm install @redbroomsoftware/dev-impersonation

Quickstart

// === support-tool / control-plane side ===
import { createIssuer } from '@redbroomsoftware/dev-impersonation';

interface Payload {
  agentEmail: string;
  targetTenantId: string;
  reason: string;
}

const issuer = createIssuer<Payload>({
  secret: process.env.IMPERSONATE_SECRET!,
  tokenTtlMs: 5 * 60 * 1000, // 5 minutes
});

const token = issuer.issueToken({
  agentEmail: '[email protected]',
  targetTenantId: 'tenant-42',
  reason: 'PMS migration walkthrough',
});

// Hand the agent a one-shot URL.
const redirectUrl = `https://app.example.com/impersonate?token=${token}`;
// === target-app / consumer side ===
import { createReceiver } from '@redbroomsoftware/dev-impersonation';

const receiver = createReceiver<Payload>({
  secret: process.env.IMPERSONATE_SECRET!,
  cookieName: 'support_session',
  cookieTtlMs: 60 * 60 * 1000, // 1 hour
  validatePayload: (p) => p.agentEmail.endsWith('@acme.com'),
});

// 1) Receiver route — runs once when the agent lands.
export async function GET(req: Request) {
  const token = new URL(req.url).searchParams.get('token') ?? '';
  const verify = receiver.verifyToken(token);
  if (!verify.valid) return new Response(`invalid: ${verify.reason}`, { status: 401 });

  const { name, value, attributes } = receiver.createSessionCookie(verify.payload);
  return new Response(null, {
    status: 303,
    headers: {
      Location: `/dashboard?impersonated_by=${encodeURIComponent(verify.payload.agentEmail)}`,
      'Set-Cookie': serializeCookie(name, value, attributes),
    },
  });
}

// 2) Middleware — runs on every request to detect an active impersonation.
export function readImpersonation(req: Request) {
  const cookie = readCookie(req, receiver.cookieName);
  const result = receiver.verifyCookie(cookie);
  return result.valid ? result.payload : null;
}

(serializeCookie / readCookie are intentionally out of scope here — use your framework's primitives or the cookie npm package.)

API

createIssuer<T>({ secret, tokenTtlMs? })

Returns { issueToken(payload: T): string }. The returned token is a base64url-encoded { payload, sig } envelope where payload has had nbf (not-before, ms) and exp (expiry, ms) merged in by the issuer.

| Option | Default | Notes | | ------------- | ------------ | ---------------------------------------------- | | secret | — (required) | At least 32 bytes of entropy. Treat as a key. | | tokenTtlMs | 900_000 | 15 minutes. Tokens are deliberately short. |

createReceiver<T>({ secret, cookieName?, cookieTtlMs?, cookieAttributes?, validatePayload? })

Returns:

{
  cookieName: string;
  verifyToken(token: string): VerifyResult<T>;
  createSessionCookie(payload: T & { exp; nbf }): CookieMintResult;
  verifyCookie(cookieValue: string | undefined | null): VerifyResult<T>;
}

| Option | Default | | ------------------- | ---------------------------------------------------- | | secret | — (required, must match issuer) | | cookieName | 'impersonation_session' | | cookieTtlMs | 3_600_000 (1 hour, capped at payload exp) | | cookieAttributes | { path: '/', httpOnly: true, secure: true, sameSite: 'lax' } | | validatePayload | none — accepts any signed payload |

VerifyResult<T> is a discriminated union:

type VerifyResult<T> =
  | { valid: true;  payload: T & { exp: number; nbf: number } }
  | { valid: false; reason: 'invalid_encoding' | 'malformed_envelope'
                          | 'invalid_signature' | 'expired'
                          | 'not_yet_valid' | 'custom_reject' };

Security notes

  • HMAC-SHA256 signing over JSON.stringify(payload). Tampering any field (including the injected exp / nbf) invalidates the signature.
  • Constant-time signature comparison via crypto.timingSafeEqual. The helper short-circuits on length mismatch to sidestep Node's throw-on-mismatch behaviour.
  • Two clocks, one secret: token exp is checked at verifyToken, and the cookie carries the same exp field which is re-checked at verifyCookie. Cookie maxAge is capped at payload.exp - now so the browser drops the cookie when the underlying authority lapses.
  • No replay store. Reuse is bounded by exp; if you need single-use semantics, layer a jti field into your payload and dedupe at the receiver via validatePayload + your own datastore.
  • Use a long, random secret (32+ bytes). Rotate by deploying issuer and receiver simultaneously, or support both old and new secrets with a small shim at the receiver.

Why this exists

This package is extracted from Red Broom Software's internal multi-app SaaS ecosystem (21 apps, one developer, ~3 paying tenants pre-PMF). Our support console at hub.redbroomsoftware.com issues impersonation tokens; each consuming app (caracol, garita, comal, camino, constanza, ...) verifies them at /dev-access-hub. We open-sourced the primitive because (a) we needed to anyway to share the contract publicly, and (b) the moving parts here are generic — any team running more than one app behind a single support team will end up writing this twice.

The internal RBS-specific wrapper is in @r-bsoftware/ecosystem-sdk (createHubDevAccessReceiver); it's a thin layer that hard-codes the cookie name and payload shape we use across the ecosystem.

License

Apache License 2.0. See LICENSE.