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

@c1m-public/platform-auth

v0.13.0

Published

Session and authorization helpers for C1M platform surfaces. The root is runtime-neutral (Web Request/Response): token/session reads, the first-party handoff (PKCE target + source), proxy session gate and coalesced refresh; ./next adds the server-only gua

Readme

@c1m-public/platform-auth

Server-side session and authorization helpers for C1M platform surfaces (Platform Admin Center, Knowledge Library, SaaSDash BFFs, and starter BFFs).

Two halves, one package. The root and ./next entry points carry session protocol behaviour and stay framework-neutral; everything under ./access carries the resolved authorization snapshot. Import only the entry point you need — the boundary is the subpath.

.               session: token reads, handoff, proxy gate, refresh   (Web Request/Response)
./next          session, server-only guard                            (Next route handlers)
./mcp-tokens    self-service MCP PAT lifecycle                        (framework-neutral)
./service-accounts  operator-owned service accounts and credentials   (framework-neutral)
./access        authorization: snapshot, keys, predicates             (framework-neutral)
./access/next   access loader: require / forbidden / invalidate       (Next, server-only)
./access/react  <Can>, useCan, AccessProvider                         (client)

next and react are optional peer dependencies: a BFF that only reads sessions never needs them.

./mcp-tokens — MCP personal access tokens

createMCPTokenClient lists, mints, and revokes the signed-in platform user's own MCP tokens under /api/v1/platform/auth/me/mcp-tokens. The client resolves the bearer on every request, never retries mint or revoke, and emits bounded observations containing operation metadata but no URL, bearer, or PAT material.

The subpath also exports MCP_ACCESS_PERMISSION, MCP_TOKENS_MANAGE_PERMISSION, MCP_TOOL_PERMISSIONS, MCP_WILDCARD_TOOL_PERMISSIONS, the request/response types, and the structural MCPTokenAPIError predicate. The permission ceiling ["*"] follows future read-only tool permissions; mutating permissions must always be named explicitly. A minted raw_token is a one-time value: callers must keep it out of URLs, logs, storage, and telemetry. A recent_authentication_required API error is a step-up challenge, not an expired session; send the user through the host application's re-authentication path before retrying the mint.

./service-accounts — operator service accounts

createServiceAccountClient manages the operator-owned service accounts under /api/v1/platform/identity/service-accounts: list, create, update, replace grants, and list, mint or revoke credentials. Reads need SERVICE_ACCOUNTS_VIEW_PERMISSION; every mutation needs SERVICE_ACCOUNTS_MANAGE_PERMISSION and is never retried.

A credential is minted from reviewed operation ids, not from arbitrary permissions: callers name operation_ids and the backend derives the permission ceiling, so a caller cannot widen its own grant. The client also holds the caller to the wire contract before a request leaves the process — a dash:<id> audience must carry exactly its route Dash in dash_ids, a Dash grant must name one Dash rather than *, expires_in_days is 1–30, and max_output_bytes is 1–1048576.

Every mutation carries expected_version; a 409 with service_account_version_conflict means the record moved under the caller and the read has to be repeated. A recent_authentication_required API error is a step-up challenge, not an expired session. The minted secret is one-time and is checked to correlate with the credential it belongs to: keep it out of URLs, logs, storage, and telemetry — the server never returns it again.

. — session

| Export | Purpose | |---|---| | decodeTokenPayload, decodeDefaultSession, hasTokenAudience, isTokenExpired, isTokenExpiringSoon, tokenMaxAgeSeconds | Read an access token for routing. Decoding is not verification — the backend re-verifies every call. decodeDefaultSession prefers effective_user_id, so a support session resolves as the impersonated user. | | parseAuthResponse, AuthResponse | Validate a backend auth envelope before trusting it. | | readCookie, serializeCookie, setCookie, deleteCookie | Cookie helpers over Web Request/Response. __Host- names get Secure automatically. | | createDeviceIdentity | The X-Client-Device-ID cookie the backend requires to create a session. | | applyForwardedClientHeaders, forwardedRequestHeaders | Copy client identity (IP, geo, UA) onto a backend call. Never used for auth. | | safeInternalPath | Strict same-origin return-path sanitiser (rejects //, ://, encoded slashes, control chars, anything that does not round-trip). | | createSessionRefresher | Coalesced refresh-token exchange: one backend call per token per window. | | evaluateSessionGate, hasSessionShape | Pure proxy decision — pass / redirect_home / anonymous_public / unauthorized_api / redirect_login. The proxy owns response construction. | | createHandoffTarget, createHandoffSource, brokerPaths, sourceStartPath | Browser halves of the first-party session handoff (/api/v1/platform/auth/handoffs/{app}): PKCE S256 + state, one-time code, host-local cookies, no tokens in URLs. | | HANDOFF_ERROR_MESSAGES, SSO_ERROR_MESSAGES, AUTH_ERROR_MESSAGES, authErrorMessage(code, overrides?), isAuthErrorCode, handoffErrorMessage, isHandoffErrorCode | One sign-in error vocabulary — handoff, admission and SSO codes. Surfaces pass their own copy as overrides instead of forking the map. |

// proxy.ts
const refresher = createSessionRefresher({ apiUrl: API_URL, refreshPath, clientType, audience });
const decision = await evaluateSessionGate({
  accessToken: request.cookies.get(SESSION)?.value,
  refreshToken: request.cookies.get(REFRESH)?.value,
  audience, refresher,
  isPublic, isLoginPath, isApiPath: pathname.startsWith("/api/"),
});
// target: app/api/auth/platform-sso/{start,callback}
const target = createHandoffTarget({ app: "docs", apiUrl, audience: "platform_docs", clientType,
  sources: { admin: ADMIN_URL, saasdash: SAASDASH_URL }, defaultSource: "admin",
  cookiePrefix: "docs", loginPath: "/login", defaultReturnPath: "/docs", device, establishSession });

// source: app/api/auth/handoffs/[app]/start
const source = createHandoffSource({ apiUrl, clientType, targets: { docs: { origin: DOCS_URL } },
  getAccessToken, loginPath: "/login" });

./next — a Next.js surface's session, fetch and errors

Server-only. Everything under . plus the three things every first-party app used to hand-roll:

| Export | Purpose | |---|---| | createSessionStore({ audience, cookies, maxAgeSeconds?, secure?, cookieDomain?, defaultRememberMe?, refresh?, requestBoundary?, onError? }) | The session/refresh/remember cookies over next/headers: createSession, writeSessionCookies(response, …), getSession, getAccessToken, getRefreshToken, getSessionWithRefresh, updateAccessToken, isRememberMe, destroySession, clearSessionCookies(response), requireSession, isAuthenticated. rememberMe=false → no Max-Age; the remember choice survives refresh; deletes carry the same attributes as sets (a __Host- cookie ignores a Secure-less delete); a token for another audience is never stored or read. Pass requestBoundary: connection (from next/server) so Cache Components treat session state as request-time. | | createPlatformFetch({ apiUrl, getAccessToken, forwardHeaders?, timeoutMs?, fetch? }) | Bearer + forwarded client headers + no-store + per-method timeout (10 s reads, 30 s writes): fetchAuthed, fetchAuthedJson (null on 204/404), fetchAuthedJsonStrict, fetchJsonWithAccessToken, fetchPublicJson, fetchAuthedField, fetchAuthedItems. A missing session throws (401), never returns null. | | buildForwardedRequestHeaders | The incoming request's client identity as headers, for a call you make yourself. | | SessionExpiredError, RecentAuthenticationRequiredError, SSOReauthenticationRequiredError, PlatformServerFetchError{path,status?,code?,requestId?} + is*Error predicates | One error vocabulary; 401 bodies map by code. | | mapControlError, controlRequest(run) | Translate a @c1m-public/control-client failure into that vocabulary, keeping the backend's code and request id. |

// lib/auth/session.ts
export const sessions = createSessionStore({
  audience: "platform_admin",
  cookies: { session: "__Host-platform_session", refreshToken: "__Host-platform_refresh_token", rememberMe: "platform_remember" },
  requestBoundary: connection,
  refresh: { apiUrl: getApiUrl, refreshPath: "/api/v1/platform/auth/refresh", clientType: "nextjs-platform" },
});

// lib/platform/fetch.ts
export const platform = createPlatformFetch({ apiUrl: getApiUrl, getAccessToken: sessions.getAccessToken });
const items = await platform.fetchAuthedItems<Workspace>("/api/v1/platform/workspaces");

./access — authorization

The resolved snapshot from GET /api/v1/platform/access.

| Export | Purpose | |---|---| | PLATFORM_PERMISSIONS, LEADBROKER_PERMISSIONS, WORKSPACE_PERMISSIONS, WORKSPACE_ROLES + key types | Generated from the backend catalog — see the drift guard below. | | parseAccessSnapshot, emptySnapshot, AccessSnapshot, AccessEntry | Validate and shape the snapshot. | | hasPermission, hasAnyPermission, hasAllPermissions, hasWorkspacePermission, isPlatformOwner, appScopes, permissionsFor | Scope-aware predicates. | | managedDashes, dashRole | The dashes a subject runs, from the mirrored dash.owner / dash.admin bundle grants (snapshot.dash_roles). Admission only — gate an action with hasPermission(snapshot, "dash.publish", { dashId }). Platform managers are not listed; admit them on platform.tenants.manage. | | fetchAccessSnapshot | Framework-neutral fetch. Pass request and it forwards client identity for you. | | AccessSnapshotError, isAccessSnapshotError | unauthenticated / forbidden / unavailable / invalid_snapshot. |

./access/next adds createAccessLoader (request-scoped via React cache), requireAccess, requireWorkspaceAccess, invalidateAccess, accessCacheTag. ./access/react adds AccessProvider, useCan, useCanWorkspace, useIsPlatformOwner, <Can>.

// lib/access.server.ts
export const access = createAccessLoader({
  apiUrl: getApiUrl,
  getAccessToken: async () => (await cookies()).get(SESSION)?.value ?? null,
  // Cross-request caching is the APP's job: the "use cache" directive does not
  // apply inside node_modules, so wrap the fetch here and tag it yourself.
  fetchSnapshot: cachedSnapshot,
});
await access.requireAccess("platform.tenants.manage");

Omit getSubjectId and the loader derives the cache-tag subject from the access token, so per-user tagging cannot silently collapse to one shared tag.

Pipeline stages

PIPELINE_STAGES / PipelineStage / isPipelineStage under ./access are the onboarding funnel strings in order (created, awaiting_payment, provisioning, attention, active), generated from the backend's schemas/api/pipeline-stages.json the same way the permission keys are. Labels and descriptions stay with the app; the strings are the backend's.

Keys drift guard

src/access/generated/keys.ts is generated from the backend authz catalog (c1m-server/schemas/api/authz-keys.json, produced by make authzgen) and vendored to contracts/authz-keys.json. Never hand-edit it. Regenerate with node scripts/generate-access-types.mjs; pnpm contract:check runs --check and fails on drift, so a backend rename cannot silently diverge from the frontend key unions.

Notes

  • Import from @c1m-public/platform-auth/next (or /access/next) inside Next.js server code — those entries carry the server-only guard.
  • Session cookies stay host-local per surface; this package never shares a session across origins. Crossing hosts is always a fresh handoff.