feat: enhance api key api

This commit is contained in:
Oleg Proskurin 2025-10-25 18:57:19 +07:00
parent f1335fb4d3
commit d1806bfd7e
2 changed files with 91 additions and 8 deletions

View File

@ -112,20 +112,25 @@ router.get('/', async (req, res) => {
try {
const keys = await apiKeyService.listKeys();
// Don't expose key hashes
// Format response with nested objects, ISO dates, and no sensitive data
const safeKeys = keys.map((key) => ({
id: key.id,
type: key.keyType,
projectId: key.projectId,
name: key.name,
scopes: key.scopes,
isActive: key.isActive,
createdAt: key.createdAt,
expiresAt: key.expiresAt,
lastUsedAt: key.lastUsedAt,
createdAt: key.createdAt.toISOString(),
expiresAt: key.expiresAt ? key.expiresAt.toISOString() : null,
lastUsedAt: key.lastUsedAt ? key.lastUsedAt.toISOString() : null,
createdBy: key.createdBy,
organization: key.organization,
project: key.project,
}));
console.log(
`[${new Date().toISOString()}] API keys listed by admin: ${req.apiKey!.id} - returned ${safeKeys.length} keys`,
);
res.json({
keys: safeKeys,
total: safeKeys.length,

View File

@ -9,6 +9,21 @@ export interface ApiKeyWithSlugs extends ApiKey {
projectSlug?: string;
}
// Extended API key type with full organization and project details for admin listing
export interface ApiKeyWithDetails extends ApiKey {
organization: {
id: string;
slug: string;
name: string;
email: string;
} | null;
project: {
id: string;
slug: string;
name: string;
} | null;
}
export class ApiKeyService {
/**
* Generate a new API key
@ -182,10 +197,73 @@ export class ApiKeyService {
}
/**
* List all keys (for admin)
* List all keys (for admin) with organization and project details
*/
async listKeys(): Promise<ApiKey[]> {
return db.select().from(apiKeys).orderBy(desc(apiKeys.createdAt));
async listKeys(): Promise<ApiKeyWithDetails[]> {
const results = await db
.select({
// API key fields
id: apiKeys.id,
keyHash: apiKeys.keyHash,
keyPrefix: apiKeys.keyPrefix,
keyType: apiKeys.keyType,
organizationId: apiKeys.organizationId,
projectId: apiKeys.projectId,
scopes: apiKeys.scopes,
createdAt: apiKeys.createdAt,
expiresAt: apiKeys.expiresAt,
lastUsedAt: apiKeys.lastUsedAt,
isActive: apiKeys.isActive,
name: apiKeys.name,
createdBy: apiKeys.createdBy,
// Organization fields
orgId: organizations.id,
orgSlug: organizations.slug,
orgName: organizations.name,
orgEmail: organizations.email,
// Project fields
projId: projects.id,
projSlug: projects.slug,
projName: projects.name,
})
.from(apiKeys)
.leftJoin(organizations, eq(apiKeys.organizationId, organizations.id))
.leftJoin(projects, eq(apiKeys.projectId, projects.id))
.orderBy(desc(apiKeys.createdAt));
// Transform flat results to nested structure
return results.map((row) => ({
id: row.id,
keyHash: row.keyHash,
keyPrefix: row.keyPrefix,
keyType: row.keyType,
organizationId: row.organizationId,
projectId: row.projectId,
scopes: row.scopes,
createdAt: row.createdAt,
expiresAt: row.expiresAt,
lastUsedAt: row.lastUsedAt,
isActive: row.isActive,
name: row.name,
createdBy: row.createdBy,
organization:
row.orgId && row.orgSlug && row.orgName && row.orgEmail
? {
id: row.orgId,
slug: row.orgSlug,
name: row.orgName,
email: row.orgEmail,
}
: null,
project:
row.projId && row.projSlug && row.projName
? {
id: row.projId,
slug: row.projSlug,
name: row.projName,
}
: null,
}));
}
/**