← vibesdk
/**
* API Key Service
* Handles all API key-related database operations
*/
import { BaseService } from './BaseService';
import * as schema from '../schema';
import { eq, and, desc, sql, or, isNull, gt } from 'drizzle-orm';
import { generateId } from '../../utils/idGenerator';
import { createLogger } from '../../logger';
const logger = createLogger('ApiKeyService');
export interface ApiKeyInfo {
id: string;
name: string;
keyPreview: string;
createdAt: Date | null;
lastUsed?: Date | null;
isActive: boolean | null;
}
export interface CreateApiKeyData {
userId: string;
name: string;
keyHash: string;
keyPreview: string;
}
/**
* API Key Service for managing API keys
*/
export class ApiKeyService extends BaseService {
/**
* Get all API keys for a user
*/
async getUserApiKeys(userId: string): Promise<ApiKeyInfo[]> {
try {
const keys = await this.database
.select({
id: schema.apiKeys.id,
name: schema.apiKeys.name,
keyPreview: schema.apiKeys.keyPreview,
createdAt: schema.apiKeys.createdAt,
lastUsed: schema.apiKeys.lastUsed,
isActive: schema.apiKeys.isActive
})
.from(schema.apiKeys)
.where(eq(schema.apiKeys.userId, userId))
.orderBy(desc(schema.apiKeys.createdAt))
.all();
return keys;
} catch (error) {
logger.error('Error fetching user API keys', error);
return [];
}
}
/**
* Create a new API key
*/
async createApiKey(data: CreateApiKeyData): Promise<string> {
try {
const keyId = generateId();
await this.database.insert(schema.apiKeys).values({
id: keyId,
userId: data.userId,
name: data.name,
keyHash: data.keyHash,
keyPreview: data.keyPreview,
scopes: JSON.stringify([]),
createdAt: new Date(),
updatedAt: new Date(),
isActive: true
});
logger.info('API key created', { keyId, userId: data.userId });
return keyId;
} catch (error) {
logger.error('Error creating API key', error);
throw new Error('Failed to create API key');
}
}
/**
* Revoke an API key
*/
async revokeApiKey(keyId: string, userId: string): Promise<boolean> {
try {
await this.database
.update(schema.apiKeys)
.set({
isActive: false,
updatedAt: new Date()
})
.where(
and(
eq(schema.apiKeys.id, keyId),
eq(schema.apiKeys.userId, userId)
)
);
logger.info('API key revoked', { keyId, userId });
return true;
} catch (error) {
logger.error('Error revoking API key', error);
return false;
}
}
/**
* Find an API key by its id (no isActive filter – callers decide).
* Used at auth time to verify an API-key-derived token's source key.
*/
async getApiKeyById(keyId: string): Promise<schema.ApiKey | null> {
try {
const key = await this.database
.select()
.from(schema.apiKeys)
.where(eq(schema.apiKeys.id, keyId))
.get();
return key || null;
} catch (error) {
logger.error('Error finding API key by id', error);
return null;
}
}
/**
* Find API key by hash
*/
async findApiKeyByHash(keyHash: string): Promise<schema.ApiKey | null> {
try {
const now = new Date();
const key = await this.database
.select()
.from(schema.apiKeys)
.where(
and(
eq(schema.apiKeys.keyHash, keyHash),
eq(schema.apiKeys.isActive, true),
or(
isNull(schema.apiKeys.expiresAt),
gt(schema.apiKeys.expiresAt, now)
)
)
)
.get();
return key || null;
} catch (error) {
logger.error('Error finding API key by hash', error);
return null;
}
}
/**
* Update API key last used time and increment request count
*/
async updateApiKeyLastUsed(keyId: string): Promise<void> {
try {
await this.database
.update(schema.apiKeys)
.set({
lastUsed: new Date(),
updatedAt: new Date(),
requestCount: sql`COALESCE(${schema.apiKeys.requestCount}, 0) + 1`
})
.where(eq(schema.apiKeys.id, keyId));
} catch (error) {
logger.error('Error updating API key last used', error);
}
}
/**
* Check if API key name is unique for user
*/
async isApiKeyNameUnique(userId: string, name: string): Promise<boolean> {
try {
const existing = await this.database
.select({ id: schema.apiKeys.id })
.from(schema.apiKeys)
.where(
and(
eq(schema.apiKeys.userId, userId),
eq(schema.apiKeys.name, name),
eq(schema.apiKeys.isActive, true)
)
)
.get();
return !existing;
} catch (error) {
logger.error('Error checking API key name uniqueness', error);
return false;
}
}
/**
* Get active API key count for user
*/
async getActiveApiKeyCount(userId: string): Promise<number> {
try {
const result = await this.database
.select({ count: sql<number>`COUNT(*)` })
.from(schema.apiKeys)
.where(
and(
eq(schema.apiKeys.userId, userId),
eq(schema.apiKeys.isActive, true)
)
)
.get();
return Number(result?.count) || 0;
} catch (error) {
logger.error('Error counting active API keys', error);
return 0;
}
}
}