convex-secret-manager
v0.2.0
Published
Convex component for encrypted secret vaults and issued API key lifecycle
Maintainers
Readme
convex-secret-manager
Convex component for encrypted secret vaults and issued API key lifecycle.
Combines the roles of gaganref/convex-secret-store and gaganref/convex-api-keys into one package with per-ownerId tenancy. Any Convex app can use it (SaaS orgs, agents, multi-tenant backends).
Two modules
| Module | Use |
|--------|-----|
| Vault | Store user-supplied credentials (OpenAI, Venice, webhooks) encrypted at rest |
| Issued keys | Issue sm_ machine tokens with hash-only storage, refresh, revoke, and audit |
vs gaganref
| | gaganref (2 packages) | convex-secret-manager |
|--|----------------------|-------------------------|
| Path model | namespace + name | ownerId + namespace + name |
| Use case | Generic apps | Multi-tenant backends that issue keys too |
| Install | Two components | One component |
| Vault crypto | Envelope + KEK rotation | Envelope (defineKeys) + legacy single-key |
| Issued validate | Query (side-effect free) | Query |
| Sweeps | Hourly crons | Hourly crons (built-in) |
Use gaganref when you need only one concern. Use this package when one owner should hold both third-party credentials and issued machine tokens.
Install
npm install convex-secret-manager// convex/convex.config.ts
import { defineApp } from "convex/server";
import { v } from "convex/values";
import { defineKeys } from "convex-secret-manager";
import secretManager from "convex-secret-manager/convex.config.js";
const app = defineApp({
env: {
MY_APP_KEK_V1: v.string(),
},
});
app.use(secretManager, {
env: {
SECRET_MANAGER_KEYS: defineKeys({
1: process.env.MY_APP_KEK_V1!,
}),
},
});
export default app;// convex/secrets.ts
import { SecretManager } from "convex-secret-manager";
import { components } from "./_generated/api.js";
export const secretManager = new SecretManager(components.secretManager);Set on the Convex deployment:
SECRET_MANAGER_KEYS=1:<kek-material>
# or legacy single key:
SECRET_MANAGER_ENCRYPTION_KEY=...Vault paths
ownerId = orgId | userId | deployment
namespace = providers | integrations | webhooks
name = openai.apiKeyIssued keys API (parity highlights)
issued.create/validate(query) /touch/revoke/revokeAllissued.refresh— rotate with grace periodissued.update/getKey/list(paginated +effectiveStatus)- Hourly sweep crons for expired and idle keys
cleanupKeys/cleanupEventsinternal jobs
Vault API (parity highlights)
vault.putPlaintext— component-side envelope encryption with AADvault.getResult—{ ok, value } | { ok: false, reason }vault.update— metadata/TTL without re-encryptvault.list— paginated witheffectiveStateauditEvents.listEvents— paginated audit trailvaultRotate.rotate/isRotationComplete— KEK rotation drainvaultCleanup.cleanupSecrets— expired secret cleanup
Example
See example/ for a minimal Convex + Vite dashboard.
License
MIT
