You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Third-party integrations and automation scripts need a stable, long-lived credential that doesn't require user login. An API key system provides this as an alternative to JWT tokens. Enables FE-26 (Developer portal API key management UI).
Context
API keys should be scoped (read-only vs full access) so integrations only get the permissions they need
Only the raw key is shown at creation time — the stored value is a bcrypt hash (like a password)
The key is passed via Authorization: Bearer ak_<key> header — the JwtStrategy detects the ak_ prefix and validates via ApiKeyStrategy instead
Acceptance Criteria
Create ApiKey entity: id, userId, name, keyHash (bcrypt hash of the raw key), prefix (first 8 chars, shown in list for identification), scopes (text array: ['read:assets', 'write:assets', 'read:reports', ...]), lastUsedAt, expiresAt (nullable), isActive, createdAt
POST /api-keys body: { name, scopes, expiresAt? } — generates a crypto.randomBytes(32).toString('hex') key, returns it once in the response, stores only the hash
GET /api-keys — list user's keys showing prefix, name, scopes, lastUsedAt (never the full key)
DELETE /api-keys/:id — revokes key immediately
Create ApiKeyStrategy for Passport — validates ak_ prefixed bearer tokens against stored hashes, updates lastUsedAt
Update JwtAuthGuard to support both JWT and API key authentication
Overview
Third-party integrations and automation scripts need a stable, long-lived credential that doesn't require user login. An API key system provides this as an alternative to JWT tokens. Enables FE-26 (Developer portal API key management UI).
Context
Authorization: Bearer ak_<key>header — theJwtStrategydetects theak_prefix and validates viaApiKeyStrategyinsteadAcceptance Criteria
ApiKeyentity:id,userId,name,keyHash(bcrypt hash of the raw key),prefix(first 8 chars, shown in list for identification),scopes(text array:['read:assets', 'write:assets', 'read:reports', ...]),lastUsedAt,expiresAt(nullable),isActive,createdAtPOST /api-keysbody:{ name, scopes, expiresAt? }— generates acrypto.randomBytes(32).toString('hex')key, returns it once in the response, stores only the hashGET /api-keys— list user's keys showing prefix, name, scopes, lastUsedAt (never the full key)DELETE /api-keys/:id— revokes key immediatelyApiKeyStrategyfor Passport — validatesak_prefixed bearer tokens against stored hashes, updateslastUsedAtJwtAuthGuardto support both JWT and API key authentication