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.26.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)
./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.

. — 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 impersonation_target, 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(raw, { stripParams? }) | Strict same-origin return-path sanitiser (rejects //, ://, encoded slashes, control chars, anything that does not round-trip). stripParams drops named query params (e.g. a one-shot auth/from flag) from the accepted value so it doesn't survive into the redirect target. | | 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. eagerReauthBufferSeconds demotes a token expiring within that window to not-live, so a failed refresh rejects the session instead of passing through on the still-valid token. | | 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. | | ADMIN_HOST_ROOT, isAdminRootHost, adminSlugFromHost, recoveryAdminOrigin, brandedAdminHost | The <slug>.saasdash.app / admin.<domain> admin-host rules, mirroring the backend's internal/platform/domainmanagement/admin_dash_hostnames.go. | | SIGN_IN_HINT_COOKIE, signInHintDomain, signInHintCookie, hasSignInHint | The non-secret "signed in" display hint an admin host sets on its own domain so the Dash's public site can show "Open your workspace" instead of "Sign in". signInHintDomain/signInHintCookie are the pure writer half; hasSignInHint the reader. The hint carries no identity and must never gate access. |

// 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.dashes.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.dashes.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.