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

@starasia/license-client

v0.6.0

Published

Backend Express middleware for enforcing a central organization-owned license (license-api) — forwards the caller's verified SSO bearer token, never a separate app secret.

Readme

@starasia/license-client

Backend Express middleware for enforcing a central organization-owned license (license-api) in any Starasia app. Mirrors the role @starasia/sso-api plays for authentication — add a few lines of config, not a custom integration per app.

What it does

  • Resolves which license your organization has for your app, and whether the calling user is allowed to consume it — by forwarding their already-verified SSO bearer token to license-api. No separate per-app secret or service credential.
  • Manages a small signed, HttpOnly cookie holding the device identity (and, once acquired, the active session) — self-contained, no session-store dependency.
  • Forwards the end user's User-Agent and IP (ctx.userAgent / ctx.ip; the guard reads the User-Agent header and your clientIp option) on activate and acquire, so the license console can label devices.
  • Fails closed on every denial and on any upstream outage (403/409/503, never a silent bypass).

What it does not do (by design, for now)

  • No service-to-service credential — matches the current approved design of license-api's organization-owned model. If that changes, this package's auth story changes with it.
  • No application/organization picker UI, no license purchase/allocation admin — those live in the platform console (admin-dashboard), not in a consuming app's backend.
  • No built-in SSE/WebSocket wrapper — assertStillValid() is the primitive; wire it into your own stream loop (see below), since stream shapes differ per app/runtime.

Install

Not yet published. Point your app's package.json at this checkout (e.g. a file: dependency) until a decision is made to publish it to the org's private registry.

Quick start

import express from 'express'
import { createLicenseGuard } from '@starasia/license-client'

const guard = createLicenseGuard({
  application: ssoClientId, // this app's sso-api client_id; no env vars needed
  getBearerToken: req => req.header('authorization')?.replace(/^Bearer /, ''),
  getOrganizationId: req => req.params.organizationId, // however your app resolves this
  getPlatform: req => req.header('x-your-app-platform') === 'electron' ? 'electron' : 'web',
})

const app = express()
app.use('/api/v1/organizations/:organizationId', guard.middleware) // protect everything under here
app.post('/api/v1/license/heartbeat', guard.heartbeat) // call this on an interval from your frontend
app.post('/api/v1/auth/logout', guard.release, (req, res) => res.sendStatus(204))

Inside a protected handler, req.license is { licenseId, sessionId, expiresAt }.

Long-lived connections (SSE/WebSocket)

app.get('/api/v1/organizations/:organizationId/stream', guard.middleware, async (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream')
  const interval = setInterval(() => {
    guard.assertStillValid(req).then(
      () => res.write(`data: ping\n\n`),
      () => { clearInterval(interval); res.end() }, // denied or license-api unavailable
    )
  }, 15000)
  req.on('close', () => clearInterval(interval))
})

Not using Express? Use the framework-agnostic core directly

createLicenseGuard is a thin Express adapter over createLicenseCore, which takes and returns plain strings — no Express/Fetch-API-specific types — so it works in a raw http.createServer handler, a Fetch API (Request/Response) style app, Next.js, etc.

import { createLicenseCore } from '@starasia/license-client'

const core = createLicenseCore({ application: ssoClientId })

// Inside your own request handler:
const { value: license, setCookie } = await core.check({
  token: theCallersVerifiedAccessToken,
  organizationId: theOrganizationIdForThisRequest,
  platform: 'web', // or 'electron' — whichever client is calling right now
  cookieHeader: request.headers.get('cookie'),
  ip: serverObservedIp,
  userAgent: request.headers.get('user-agent'),
})
// `license` is { licenseId, sessionId, expiresAt }, or { unlicensed: true } / { degraded: true }. If `setCookie` is present, add it as a
// Set-Cookie header on your response (the device/session identity changed).

core.heartbeat(ctx), core.release(ctx), and core.assertStillValid(ctx) mirror the guard's equivalents, same ctx shape, same LicenseClientError codes.

Turning licensing on and off

No environment variables are needed. Register the application (pick it from the sso-api dropdown) and keep it enabled in the license console: from then on every organization whose admin grants a user that application's role gets a license automatically. Disable the application to stop enforcing. Unregistered or disabled applications pass every check.

License key before login (device activation)

When the application has Wajib aktivasi (requireActivation) on in the license console, every installation must be activated with a user's license key before it can be used. The key is entered before login:

app.use(express.json())
// Before your auth middleware: nobody is logged in yet.
app.get('/license/device', guard.deviceStatus)          // { activated, organizationId?, degraded? }
app.post('/license/device/activate', guard.activateDevice) // body { licenseKey }
  1. On start, call GET /license/device. While activated is false, show a license-key screen (format PREFIX-XXXX-XXXX-XXXX) instead of the login screen.
  2. POST /license/device/activate. Errors: LICENSE_KEY_INVALID, DEVICE_SLOTS_FULL (every slot of that key's owner is taken; an organization admin can release one in workspaces), TOO_MANY_ATTEMPTS (10 tries per client IP per 10 minutes).
  3. Then log in as usual. The guard middleware refuses ACTIVATION_REQUIRED while the installation isn't activated.

The key tells license-api whose license the device holds; login tells it who is using it. Another user with access of their own may use the device, but it is recorded and the key is flagged as shared in workspaces. A user without access is refused.

Configuration reference

| Option | Required | Notes | | --- | --- | --- | | application | yes | Your app's sso-api client_id (or its key in the license console). Licensing is on only while the application is registered and enabled in the license console; otherwise every call passes (req.license = { unlicensed: true }). | | apiUrl | no | Default https://api.starasia.tech/license-service. Override for local dev (http://127.0.0.1/localhost allowed) or tests. | | cookieSecret | no | HMAC key for the device cookie. Default is derived from application; license-api verifies the device key itself, so this only rejects hand-edited cookies early. Rotating it makes every installation enter its key again. | | getBearerToken | yes | Return the caller's verified SSO access token, or undefined/null. | | getOrganizationId | yes | Return the customer organization context for this request. | | getPlatform | yes | Return 'web' or 'electron' for this request — a single deployment commonly serves both. | | getApplicationVersion | no | Forwarded as audit metadata only — never trusted for authorization. | | clientIp | no | Server-observed IP only; never read this from a client-supplied header yourself. | | cookieName | no | Default sa_license_device. | | cookieSecure | no | Default true. Only set false for plain-http://localhost development. | | failOpen | no | Default true: while license-api is unreachable (network, timeout, 5xx), requests pass with req.license = { degraded: true } and deviceStatus reports activated: true, degraded: true. Explicit denials are always enforced. | | cookieMaxAgeSeconds | no | Default 180 days — this is the device-identity cookie's lifetime, not the license session TTL (that's configured on license-api itself via LEASE_TTL_MS). |

Error handling

Every rejected request throws/returns a LicenseClientError with a stable .code (UNAUTHORIZED, NO_ORGANIZATION_CONTEXT, LICENSE_NOT_FOUND, ALLOCATION_REQUIRED, ALLOCATION_REVOKED, LICENSE_REVOKED, LICENSE_EXPIRED, ACTIVATION_REQUIRED, USER_DEVICE_LIMIT_REACHED, DEVICE_BINDING_MISMATCH, SESSION_NOT_ACTIVE, LICENSE_KEY_INVALID, DEVICE_SLOTS_FULL, TOO_MANY_ATTEMPTS, INVALID_REQUEST, LICENSE_UNAVAILABLE) and matching HTTP status. Map these to your own UI copy the same way license-key-prelogin's admin console does (explainOrganizationError) — this package deliberately does not ship UI strings, since wording belongs to each product.

Local verification

npm install
npm run typecheck
npm test
npm run build

Tests mock fetch against a tiny in-memory fake of license-api's consumption endpoints — no real network or database involved.