Skip to main content

Implementation Plan: API Keys

Feature ID: api-keys Spec: ./spec.md Status: Done (Retrospective) Last updated: 2026-05-01


1. Architecture

2. Tech Choices

ConcernChoiceRationale
Hash algorithmSHA-256 (Node.js crypto)Fast, deterministic, no salt needed for high-entropy keys
Random secretcrypto.randomBytes(32).toString('hex') → 64 hex chars256 bits of entropy
Storageapi_keys table with unique index on hashO(log n) lookup; uniqueness enforces no hash collisions
Header parsingCustom guard runs before JwtAuthGuardSingle auth surface, no per-route opt-in needed
lastUsedAt strategyUpdate on success only, fire-and-forgetDoesn't block the request; eventual consistency acceptable

3. Data Model

@Entity('api_keys')
@Index(['hash'], { unique: true })
export class ApiKey {
@PrimaryGeneratedColumn('uuid') id: string;
@Column() userId: string;
@Column() name: string;
@Column() hash: string; // SHA-256 hex, 64 chars
@Column() prefix: string; // first 12 chars of full key
@Column({ nullable: true }) expiresAt: Date | null;
@Column({ nullable: true }) lastUsedAt: Date | null;
@CreateDateColumn() createdAt: Date;
}

Migration: additive, indexed by (userId) and unique (hash).

4. API Surface

MethodEndpointAuthDescription
POST/api/auth/api-keysJWTCreate a key
GET/api/auth/api-keysJWTList user's keys
DELETE/api/auth/api-keys/:idJWTRevoke a key

Create returns the full key once. List returns prefix + metadata only.

5. Plugin Surface

None — API keys are core auth.

6. Web / CLI Surface

  • Web: Settings → API Keys page with create/list/revoke controls. Create modal shows the full key once with a "Copy" button and an explicit "I've stored this key, dismiss" gate.
  • CLI: not exposed (CLI uses the same key transport but doesn't manage them).

7. Background Jobs

A daily cron deletes keys whose expiresAt is in the past — cleanup only, not security-critical (auth check rejects expired keys regardless).

8. Security & Permissions

  • Key management endpoints reject API-key auth (JWT only).
  • The display prefix is safe to log; the full key is never logged.
  • SHA-256 (no salt) is acceptable because keys carry 256 bits of entropy — pre-image resistance is the only property we need.

9. Observability

  • Activity log: api_key_created, api_key_revoked actions with the key's name and prefix.
  • Auth failures: log prefix + reason (expired / revoked / not_found).

10. Risks & Mitigations

RiskMitigation
Hash collisionUNIQUE index on hash — collision would fail insert
lastUsedAt write contention on hot keysFire-and-forget update; loss of a single timestamp acceptable
Per-user cap bypass via concurrent createsCap check + insert in a transaction

11. Constitution Reconciliation

See spec.md §9.

12. References

  • Spec: ./spec.md
  • Implementation: apps/api/src/auth/api-keys/