Skip to content

vllnt/convex-api-keys

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Convex Component npm version CI License

@vllnt/convex-api-keys

Secure API key management as a Convex component — create, validate, revoke, rotate, and track usage with built-in auth boundaries and structured audit logging.

const apiKeys = new ApiKeys(components.apiKeys, { prefix: "myapp" });
const { key, keyId } = await apiKeys.create(ctx, { name: "SDK Key", ownerId: orgId });
const result = await apiKeys.validate(ctx, { key: bearerToken });
// result.valid → { keyId, ownerId, scopes, ... } | result.reason

Features

  • Secure by default — SHA-256 hashed storage, constant-time comparison, prefix-indexed O(1) lookup, server-side secret generation.
  • Auth boundaryownerId required on all admin mutations; prevents cross-tenant access.
  • Key typessecret and publishable keys with type-encoded prefixes.
  • Finite-use keysremaining counter with atomic decrement for one-time-use tokens.
  • Disable / enable — reversible pause without revoking.
  • Rotation — configurable grace period (60s–30d) where both old and new keys are valid.
  • Bulk revoke by tag, tags & environments, multi-tenant scoping, usage tracking, input validation, structured audit logging.

Installation

Peer dependency: convex@^1.36.1

npm install convex@^1.36.1 @vllnt/convex-api-keys

Register in your convex/convex.config.ts:

import { defineApp } from "convex/server";
import apiKeys from "@vllnt/convex-api-keys/convex.config";

const app = defineApp();
app.use(apiKeys);
export default app;

Rate limiting is your responsibility — add @convex-dev/rate-limiter at your HTTP action/mutation layer where you have real caller context (IP, auth, plan tier). The component has none.

Usage

import { ApiKeys } from "@vllnt/convex-api-keys";
import { components } from "./_generated/api";

const apiKeys = new ApiKeys(components.apiKeys, {
  prefix: "myapp",       // key prefix (default: "vk")
  defaultType: "secret",
});

// Create — secret material is generated server-side, returned once.
const { key, keyId } = await apiKeys.create(ctx, {
  name: "Production SDK Key",
  ownerId: orgId,
  type: "secret",
  scopes: ["read:users", "write:orders"],
  tags: ["sdk", "v2"],
  env: "live",
});
// key = "myapp_secret_live_a1b2c3d4_<64-char-hex>"

// Validate — track usage and narrow on `valid`.
const result = await apiKeys.validate(ctx, { key: bearerToken });
if (!result.valid) {
  // result.reason: "malformed" | "not_found" | "revoked" | "expired" | "exhausted" | "disabled"
  return new Response("Unauthorized", { status: 401 });
}
const { ownerId, scopes, tags, env, type, metadata, remaining } = result;

See docs/API.md for the full method reference, key format, and lifecycle.

API Reference

Method Ctx Description
create(ctx, options) mutation Create a new API key
validate(ctx, { key }) mutation Validate and track usage
revoke(ctx, { keyId, ownerId }) mutation Permanently revoke a key
revokeByTag(ctx, { ownerId, tag }) mutation Bulk revoke by tag
rotate(ctx, { keyId, ownerId, gracePeriodMs? }) mutation Rotate with grace period
update(ctx, { keyId, ownerId, name?, ... }) mutation Update metadata in-place
disable(ctx, { keyId, ownerId }) mutation Temporarily disable
enable(ctx, { keyId, ownerId }) mutation Re-enable a disabled key
configure(ctx, { ... }) mutation Runtime config (admin-only)
list(ctx, { ownerId, env?, status?, limit? }) query List keys (paginated, default 100)
listByTag(ctx, { ownerId, tag, limit? }) query Filter by tag
getUsage(ctx, { keyId, ownerId }) query Usage counter (O(1))

Full reference: docs/API.md.

Security

  • Protects against accidental cross-tenant bugs in honest host apps — the ownerId check does NOT defend against a compromised host passing a forged ownerId.
  • Derive ownerId from your own auth layer (e.g. ctx.auth.getUserIdentity()) before passing it in.
  • Raw keys are never stored — only the SHA-256 hash; the raw value is returned once at creation.

See docs/API.md.

Testing

import { convexTest } from "convex-test";
import { register } from "@vllnt/convex-api-keys/test";
import shardedCounterTest from "@convex-dev/sharded-counter/test";

const t = convexTest(schema, modules);
register(t, "apiKeys");
shardedCounterTest.register(t, "apiKeys/shardedCounter");

Contributing

See CONTRIBUTING.md for development setup, testing, and PR guidelines.

Author

Built by bntvllnt · bntvllnt.com · X @bntvllnt

Part of the @vllnt Convex component fleet — vllnt.com

If this is useful, sponsor the work.

License

MIT — see LICENSE.

About

Convex component for secure API key management — create, validate, revoke, rotate, rate-limit, track usage

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors