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

@buddy-works/identity

v0.2.0

Published

Zero-config authentication for apps running behind Buddy tunnels. One call to know who the current user is — no logins, sessions or user databases.

Readme

@buddy-works/identity

Zero-config authentication for apps running behind Buddy tunnels.

When a tunnel has Buddy authentication enabled, Buddy handles login and access control and issues a session cookie. This library gives your application the identity of the current user in a single call — no logins, no OAuth, no sessions, no user database:

import { getCurrentUser } from "@buddy-works/identity";

const user = await getCurrentUser(req);
// => { name, email, owner, admin, avatar, sso_id } — or null when not logged in

Perfect for internal tools, dashboards and AI-built apps running in Buddy sandboxes: access is managed centrally in the tunnel settings, and your code just receives trusted user information.

Installation

npm install @buddy-works/identity

Requires Node.js 18+ (built-in fetch). Works in every modern browser.

How it works

Buddy tunnels expose a user-info endpoint on your app's domain:

GET /.buddy/auth/me

The tunnel intercepts this path before the request reaches your app, verifies the Buddy session (a signed JWT stored in an HttpOnly cookie) and responds with:

{
  "id": 42,
  "name": "John Doe",
  "email": "[email protected]",
  "owner": false,
  "admin": true,
  "avatar": "https://…",
  "sso_id": "[email protected]",
  "groups": [
    { "id": 1, "name": "everyone" },
    { "id": 2, "name": "finance" }
  ]
}

This library is a thin, typed client for that endpoint. All security logic — JWT signature verification, JWKS key management, claim validation — stays on Buddy's side, so your app never touches a token.

Usage

Client-side (browser)

Call it with no arguments. The request goes to the current origin and the browser attaches the session cookie automatically:

import { getCurrentUser } from "@buddy-works/identity";

const user = await getCurrentUser();

if (user) {
  document.querySelector("#hello").textContent = `Welcome ${user.name}`;
}

Server-side (Express / Connect / plain Node)

Pass the incoming request so its cookie can be forwarded:

import express from "express";
import { getCurrentUser } from "@buddy-works/identity";

const app = express();

app.get("/api/whoami", async (req, res) => {
  const user = await getCurrentUser(req);
  if (!user) return res.status(401).json({ error: "Not logged in" });
  res.json({ message: `Welcome ${user.name}` });
});

Or use the middleware to get req.buddyUser everywhere:

import { buddyIdentity } from "@buddy-works/identity";

app.use(buddyIdentity());
// app.use(buddyIdentity({ required: true })); // reject anonymous requests with 401

app.get("/api/admin", (req, res) => {
  if (!req.buddyUser?.admin) return res.status(403).json({ error: "Admins only" });
  res.json({ secret: "…" });
});

Groups managed centrally in Buddy make simple role systems free:

app.get("/api/reports", (req, res) => {
  const inFinance = req.buddyUser?.groups.some((g) => g.name === "finance");
  if (!inFinance) return res.status(403).json({ error: "Finance only" });
  res.json(reports);
});

Next.js / Remix / anything with Fetch API requests

getCurrentUser also accepts a Fetch API Request or a Headers instance:

// Next.js route handler
export async function GET(request: Request) {
  const user = await getCurrentUser(request);
  return Response.json({ user });
}

// Next.js server component
import { headers } from "next/headers";
const user = await getCurrentUser(await headers());

Explicit options

When no request object is available, pass the pieces yourself:

const user = await getCurrentUser({
  baseUrl: "https://myapp.buddytunnels.site",
  cookie: rawCookieHeader,
});

Local development (no tunnel)

Locally there is no tunnel to answer /.buddy/auth/me. The /dev entry point ships everything needed to develop against realistic user states anyway.

Every mocking API accepts the same input: a preset name ("owner", "admin", "member"), a partial user (merged onto the admin preset), or null for the logged-out state.

Server / dev-server — mount the fake endpoint as middleware. Anything that talks to /.buddy/auth/me over HTTP (your browser code, getCurrentUser(req) on the server) now works locally:

import { mockAuthEndpoint } from "@buddy-works/identity/dev";

if (process.env.NODE_ENV !== "production") {
  app.use(mockAuthEndpoint(process.env.MOCK_USER ?? "admin"));
  // mockAuthEndpoint("member")                      — preset
  // mockAuthEndpoint({ name: "Jane", owner: true }) — custom user
  // mockAuthEndpoint(null)                          — logged-out
}

SPA / tests / no middleware — install an in-process mock; every getCurrentUser() call in this runtime returns it without any HTTP request:

if (import.meta.env.DEV) {
  const { mockUser } = await import("@buddy-works/identity/dev");
  mockUser("member");
}

clearMockUser() removes it again (handy in test teardown), and the raw presets are exported as MOCK_USERS if you want to build your own states from them.

In Next.js, mount the endpoint with a dev-only rewrite instead of middleware — see examples/nextjs. Behind a real tunnel the endpoint is intercepted before requests reach your app, so a leftover mock endpoint is never hit in production — but only enable mocks in development anyway.

Examples

Two runnable examples live in examples/:

  • examples/express — Express + a vanilla-JS page: client-side, server-side and admin-gating in one screen.
  • examples/nextjs — Next.js App Router: Server Component (getCurrentUser(await headers())), Client Component, Route Handler and an admin-gated page. The local mock is wired up via a rewrite in next.config.mjs.
pnpm install
pnpm example          # Express example on http://localhost:3000
pnpm example:next     # Next.js example on http://localhost:3000

Both understand MOCK=logged-out (simulate a logged-out user) and MOCK=0 (disable the mock — use when running behind a real Buddy tunnel).

API

getCurrentUser(input?)

Returns Promise<BuddyUser | null>.

  • input — optional. One of:
    • (nothing) — browser only; requests /.buddy/auth/me on the current origin.
    • Node IncomingMessage / Express request — cookie and host are read from it.
    • Fetch API Request or Headers — same, for edge/serverless frameworks.
    • { baseUrl?, cookie?, fetch? } — explicit options.
  • Returns null when the user is not logged in (the endpoint responds with a non-2xx status or a login redirect).
  • Throws when the endpoint cannot be reached at all (e.g. running locally without the dev mock).

buddyIdentity(options?)

Connect/Express-style middleware. Resolves the user once per request and sets req.buddyUser: BuddyUser | null. With { required: true } it responds 401 to unauthenticated requests.

In TypeScript, teach Express about the new property once, e.g. in a .d.ts file:

import type { BuddyUser } from "@buddy-works/identity";

declare global {
  namespace Express {
    interface Request {
      buddyUser?: BuddyUser | null;
    }
  }
}

@buddy-works/identity/dev

Development helpers — see Local development:

  • mockAuthEndpoint(user?) — middleware serving a fake /.buddy/auth/me.
  • mockUser(user?) / clearMockUser() — in-process mock for SPAs and tests.
  • MOCK_USERS — the owner / admin / member presets.
  • resolveMockUser(input) — resolves any mock input to a full BuddyUser | null.

Types

interface BuddyUser {
  id: number;            // Buddy user id
  name: string;
  email: string;
  owner: boolean;        // workspace owner
  admin: boolean;        // workspace administrator
  avatar: string | null;
  sso_id: string | null; // SSO identifier of the user in this workspace
  groups: BuddyGroup[];  // workspace groups the user belongs to
}

interface BuddyGroup {
  id: number;
  name: string;
}

sso_id is the user's identifier in the workspace's SSO provider — it is a string only when the workspace has SSO enabled and the user is bound to an SSO identity; otherwise it is null.

groups is defensively normalized to [] (and sso_id to null) when missing from the response, so you can always rely on both fields being present.

Client-side vs server-side

The package is isomorphic — one import works in both environments, because in both cases it is just an HTTP call to the same endpoint with the same cookie:

| | Client-side | Server-side | |---|---|---| | How the cookie travels | attached automatically by the browser (same-origin, HttpOnly) | forwarded from the incoming request's Cookie header | | Where the call goes | relative /.buddy/auth/me, intercepted by the tunnel | app's public origin (derived from X-Forwarded-Host/Host), intercepted by the tunnel | | Best for | showing who's logged in, personalizing UI | authorization decisions, SSR, API routes |

Things to keep in mind:

  • Authorization must happen server-side. Client-side checks like user.admin && renderAdminPanel() are UI conveniences — anyone can flip them in DevTools. The tunnel already guarantees that only allowed users reach the app at all; for anything finer-grained (admin-only endpoints, per-user data), check owner/admin on the server.
  • Server-side calls need outbound network access to the app's own public hostname, since the endpoint lives on the tunnel edge, not inside your app. This costs one extra HTTP round-trip per call — cache the result per request (the buddyIdentity() middleware does this for you).
  • Client-side calls must be same-origin. The session cookie belongs to the tunnel domain and is HttpOnly, so a different origin can neither read nor send it.

An alternative design would verify the JWT locally in the SDK (reading the cookie, fetching Buddy's JWKS, checking signature and claims). It would save the extra HTTP call on the server, but it drags token internals, key caching/rotation and claim validation into every app — and it wouldn't work in the browser at all, since the cookie is HttpOnly. Delegating verification to the tunnel keeps the SDK tiny and lets the endpoint evolve (e.g. richer user data) without shipping new SDK versions.

Development

This repo is a pnpm workspace (the library at the root, examples in examples/*) with pinned dependencies:

pnpm install               # install everything
pnpm build                 # build the library (tsup → ESM + CJS + d.ts)
pnpm typecheck             # typecheck the library

CI lives in .buddy/ as Buddy pipelines, one per file:

  • tests.yml — build + typecheck on every push.
  • publish-npm.yml — manual pnpm publish from main; requires an encrypted NPM_TOKEN variable on the pipeline.

License

MIT © Buddy