Skip to content

Add API-key expiration (expires_at) and a rotation HTTP endpoint #376

@greatest0fallt1me

Description

@greatest0fallt1me

Description

apiKeyRepository already implements key generation, prefix lookup, timing-safe verification, revocation, and a rotate(id, userId) method (src/repositories/apiKeyRepository.ts:122), but rotation is never exposed over HTTP — src/routes/apiKeyRoutes.ts only mounts POST /apis/:apiId/keys, GET /apis/:apiId/keys, and DELETE /keys/:id. There is also no expiration concept: the api_keys table (migrations/0001_create_api_keys_and_vaults.up.sql) has no expires_at column (only refresh_tokens and idempotency_store carry expiry). This issue wires up rotation and adds optional key expiration so long-lived credentials can be aged out.

Requirements and context

  • Add a migration adding a nullable expires_at TIMESTAMPTZ column to api_keys (follow the up/down pattern in migrations/0005_add_api_key_revocation.sql / .down.sql).
  • Extend ApiKeyRecord and the verification path in src/repositories/apiKeyRepository.ts so an expired key is rejected exactly like a revoked key is today (verify(...) already rejects revoked keys).
  • Expose key rotation via a new authenticated route (e.g. POST /api/keys/:id/rotate) in src/routes/apiKeyRoutes.ts that calls the existing apiKeyRepository.rotate(id, userId) and returns the new plaintext key once, matching the create-key response contract validated by createApiKeyBodySchema.
  • Optionally accept an expiresAt/expiresInDays field on key creation, validated with Zod alongside the existing scopes/rateLimitPerMinute fields.
  • Non-functional: keep prefix uniqueness intact (migrations/0006_api_key_prefix_unique.sql enforces a partial unique index on active prefixes); rotation must not violate it.

Acceptance criteria

  • Migration adds expires_at (nullable) to api_keys with a working down migration.
  • verify(...) rejects keys whose expires_at is in the past with the same failure semantics as revoked keys.
  • POST /api/keys/:id/rotate rotates the key, returns the new plaintext value once, and enforces ownership (forbidden/not_found map to 403/404).
  • Key creation can optionally set an expiry, validated via Zod with field-level details on error.
  • Rotated keys preserve scopes/metadata and never collide with the active-prefix unique index.
  • Tests cover rotation success/ownership failures and expired-key rejection.

Suggested execution

1. Fork the repo and create a branch

git checkout -b feature/api-key-expiry-rotation

2. Implement changes — new migration under migrations/, repository changes in src/repositories/apiKeyRepository.ts, and the rotate route in src/routes/apiKeyRoutes.ts.
3. Write/extend tests — extend src/repositories/apiKeyRepository.test.ts (which already covers rotation at the repo layer) and tests/integration/apiKeys.test.ts.
4. Test and commit

npm run lint
npm run typecheck
npm test -- apiKey --runInBand

Example commit message

feat(api-keys): add expires_at column and key rotation endpoint

Guidelines

Hold the line at the repo's 90%+ coverage target (README.md). Document the rotation endpoint and expiry semantics in JSDoc and in docs/gateway-api-key-auth.md. Timeframe: 96 hours.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions