feat: enhance api key api
This commit is contained in:
parent
f1335fb4d3
commit
d1806bfd7e
|
|
@ -112,20 +112,25 @@ router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const keys = await apiKeyService.listKeys();
|
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) => ({
|
const safeKeys = keys.map((key) => ({
|
||||||
id: key.id,
|
id: key.id,
|
||||||
type: key.keyType,
|
type: key.keyType,
|
||||||
projectId: key.projectId,
|
|
||||||
name: key.name,
|
name: key.name,
|
||||||
scopes: key.scopes,
|
scopes: key.scopes,
|
||||||
isActive: key.isActive,
|
isActive: key.isActive,
|
||||||
createdAt: key.createdAt,
|
createdAt: key.createdAt.toISOString(),
|
||||||
expiresAt: key.expiresAt,
|
expiresAt: key.expiresAt ? key.expiresAt.toISOString() : null,
|
||||||
lastUsedAt: key.lastUsedAt,
|
lastUsedAt: key.lastUsedAt ? key.lastUsedAt.toISOString() : null,
|
||||||
createdBy: key.createdBy,
|
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({
|
res.json({
|
||||||
keys: safeKeys,
|
keys: safeKeys,
|
||||||
total: safeKeys.length,
|
total: safeKeys.length,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,21 @@ export interface ApiKeyWithSlugs extends ApiKey {
|
||||||
projectSlug?: string;
|
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 {
|
export class ApiKeyService {
|
||||||
/**
|
/**
|
||||||
* Generate a new API key
|
* 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[]> {
|
async listKeys(): Promise<ApiKeyWithDetails[]> {
|
||||||
return db.select().from(apiKeys).orderBy(desc(apiKeys.createdAt));
|
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,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue