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
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.
Description
apiKeyRepositoryalready implements key generation, prefix lookup, timing-safe verification, revocation, and arotate(id, userId)method (src/repositories/apiKeyRepository.ts:122), but rotation is never exposed over HTTP —src/routes/apiKeyRoutes.tsonly mountsPOST /apis/:apiId/keys,GET /apis/:apiId/keys, andDELETE /keys/:id. There is also no expiration concept: theapi_keystable (migrations/0001_create_api_keys_and_vaults.up.sql) has noexpires_atcolumn (onlyrefresh_tokensandidempotency_storecarry expiry). This issue wires up rotation and adds optional key expiration so long-lived credentials can be aged out.Requirements and context
expires_at TIMESTAMPTZcolumn toapi_keys(follow the up/down pattern inmigrations/0005_add_api_key_revocation.sql/.down.sql).ApiKeyRecordand the verification path insrc/repositories/apiKeyRepository.tsso an expired key is rejected exactly like a revoked key is today (verify(...)already rejects revoked keys).POST /api/keys/:id/rotate) insrc/routes/apiKeyRoutes.tsthat calls the existingapiKeyRepository.rotate(id, userId)and returns the new plaintext key once, matching the create-key response contract validated bycreateApiKeyBodySchema.expiresAt/expiresInDaysfield on key creation, validated with Zod alongside the existingscopes/rateLimitPerMinutefields.migrations/0006_api_key_prefix_unique.sqlenforces a partial unique index on active prefixes); rotation must not violate it.Acceptance criteria
expires_at(nullable) toapi_keyswith a working down migration.verify(...)rejects keys whoseexpires_atis in the past with the same failure semantics as revoked keys.POST /api/keys/:id/rotaterotates the key, returns the new plaintext value once, and enforces ownership (forbidden/not_foundmap to403/404).detailson error.Suggested execution
1. Fork the repo and create a branch
2. Implement changes — new migration under
migrations/, repository changes insrc/repositories/apiKeyRepository.ts, and the rotate route insrc/routes/apiKeyRoutes.ts.3. Write/extend tests — extend
src/repositories/apiKeyRepository.test.ts(which already covers rotation at the repo layer) andtests/integration/apiKeys.test.ts.4. Test and commit
npm run lint npm run typecheck npm test -- apiKey --runInBandExample commit message
Guidelines
Hold the line at the repo's 90%+ coverage target (
README.md). Document the rotation endpoint and expiry semantics in JSDoc and indocs/gateway-api-key-auth.md. Timeframe: 96 hours.