@ezstart/keys
v0.1.1
Published
Neutral API key toolkit for @ezstart services — key format (ez_pk/ez_sk), hashing, Mongoose schema factories and an Express key-auth middleware.
Maintainers
Readme
@ezstart/keys
Neutral API key toolkit for @ezstart services: key format (ez_pk_* / ez_sk_*), SHA-256 hashing, Mongoose schema factories and an Express key-auth middleware.
Install
pnpm add @ezstart/keys
# server entry only: mongoose (peer, optional) and express typesQuickstart — Server (Express + Mongoose)
import {
createApiKeyMiddleware,
createApiKeySchema,
createApiKeyUsageSchema,
} from '@ezstart/keys/server'
const apiKeySchema = createApiKeySchema({ scopeEnum: ['admin', 'user', 'readonly'] })
const usageSchema = createApiKeyUsageSchema()
export const validateApiKey = createApiKeyMiddleware({
getKeyModel: getApiKeyModel, // () => Promise<Model>, built from apiKeySchema
getUsageModel: getApiKeyUsageModel, // built from usageSchema
populateRequest: (req, key) => {
req.apiKeyId = key._id.toString()
req.apiKeyApplicationId = key.applicationId
},
logger, // optional: { warn, error }; silent by default
})
router.get('/api/things', validateApiKey, handler)Create
createApiKeyMiddlewareonce per application (module-level export, as above). Quota reservations, the monthly usage cache and the usage-key budget live in the factory closure: building one per request or per route gives each instance its own counters, so a monthly quota can be exceeded.
Quickstart — Core only (format helpers)
import { generateRawApiKey, hashApiKey, extractKeyPrefix, detectKeyFormat } from '@ezstart/keys'
const raw = generateRawApiKey({ type: 'publishable', env: 'live' }) // ez_pk_live_<64 hex>
const stored = { hash: hashApiKey(raw), prefix: extractKeyPrefix(raw) }
detectKeyFormat(raw) // { type: 'publishable', env: 'live' }API
Root (@ezstart/keys)
generateRawApiKey({ type, env })—ez_(pk|sk)_(live|test)_+ 64 hex chars.hashApiKey(raw)— SHA-256 hex digest for at-rest storage.extractKeyPrefix(raw)— display prefix (prefix + 6 hex chars).detectKeyFormat(raw)—{ type, env } | null;nullfor anything that is notez_(pk|sk)_(live|test)_(legacyezk_*/epk_*keys are no longer supported and get a 401).KEY_PREFIX, typesApiKeyType,ApiKeyEnv,ApiKeyScope,KeyFormat.
Server (@ezstart/keys/server)
createApiKeySchema(options)/createApiKeyUsageSchema()— Mongoose schema factories.createApiKeyMiddleware(config)—X-API-Key/Authorization: ApiKeyvalidation, monthly quota cache, atomic (in-process) quota reservation, bounded per-route usage buckets, logged usage-write failures, UTCRetry-After. InvalidexpiresAtrejects the key; non-ez_keys are rejected before any DB lookup.
Mode note: the global
req.derivedMode(api-coreattachDerivedMode) is computed from the key header prefix before route authentication. The stored keyenvonly matters to code readingreq.apiKeyEnvafter auth (residual risk: a document whoseenvcontradicts its prefix).
Scope note:
populateRequestshould normalise the stored scope before stampingreq.apiKeyScope:adminandreadonlyare kept, everything else (user, legacylive/test, absent) reads asuser.
Known limitations: (1) quota enforcement is per process plus the cached monthly total, so a fleet can overshoot by the traffic of one cache window; (2) while a usage write is in flight, a concurrent aggregate may already count it and the in-flight reservation counts it too, so a request can be refused slightly early. The effect is transient (cleared when the write settles) and never lets a key exceed its quota.
The server entry throws when imported from a browser bundle.
Migration from @ezstart/auth-sdk
The same symbols exported from @ezstart/auth-sdk, /core and /server are deprecated re-exports. Replace the import path with @ezstart/keys (format helpers) or @ezstart/keys/server (schemas, middleware).
Related
@ezstart/api-contracts— canonicalApiKeyType/ApiKeyEnv/ApiKeyScopetypes.@ezstart/auth-sdk— re-exports this package for backward compatibility.- Used by
apps/ezpay/api(and available to ezauth).
