136 lines
3.1 KiB
TypeScript
136 lines
3.1 KiB
TypeScript
import { createHash, randomBytes } from 'crypto';
|
||
import type { PrismaClient as ProjectPrismaClient } from '.prisma/project-client';
|
||
|
||
type ApiKeyPrismaClient = Pick<ProjectPrismaClient, 'apiKey'>;
|
||
|
||
/**
|
||
* 生成新的 API Key
|
||
* 返回原始 key(只显示一次)和哈希值(存储用)
|
||
*/
|
||
export function generateApiKey(): { key: string; hash: string } {
|
||
// 生成 32 字节随机数,转为 base64,约 43 字符
|
||
const key = `flnxi_${randomBytes(32).toString('base64url')}`;
|
||
const hash = hashApiKey(key);
|
||
return { key, hash };
|
||
}
|
||
|
||
/**
|
||
* 对 API Key 进行哈希
|
||
*/
|
||
export function hashApiKey(key: string): string {
|
||
return createHash('sha256').update(key).digest('hex');
|
||
}
|
||
|
||
/**
|
||
* 验证 API Key
|
||
* 返回 API Key 记录(如果有效)或 null
|
||
*/
|
||
export async function verifyApiKey(
|
||
key: string | null | undefined,
|
||
prismaClient: ApiKeyPrismaClient
|
||
): Promise<{
|
||
valid: boolean;
|
||
apiKey?: { id: string; name: string };
|
||
error?: string;
|
||
}> {
|
||
if (!key) {
|
||
return { valid: false, error: 'API Key 未提供' };
|
||
}
|
||
|
||
const hash = hashApiKey(key);
|
||
|
||
try {
|
||
const apiKey = await prismaClient.apiKey.findUnique({
|
||
where: { keyHash: hash },
|
||
});
|
||
|
||
if (!apiKey) {
|
||
return { valid: false, error: 'API Key 无效' };
|
||
}
|
||
|
||
if (!apiKey.isActive) {
|
||
return { valid: false, error: 'API Key 已被禁用' };
|
||
}
|
||
|
||
// 更新最后使用时间(异步,不阻塞)
|
||
prismaClient.apiKey.update({
|
||
where: { id: apiKey.id },
|
||
data: { lastUsedAt: new Date() },
|
||
}).catch((err: Error) => {
|
||
console.warn('[ApiKey] 更新最后使用时间失败:', err.message);
|
||
});
|
||
|
||
return {
|
||
valid: true,
|
||
apiKey: { id: apiKey.id, name: apiKey.name },
|
||
};
|
||
} catch (error) {
|
||
console.error('[ApiKey] 验证失败:', error);
|
||
return { valid: false, error: '验证服务异常' };
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建新的 API Key(管理员用)
|
||
*/
|
||
export async function createApiKey(
|
||
name: string,
|
||
prismaClient: ApiKeyPrismaClient
|
||
): Promise<{
|
||
success: boolean;
|
||
key?: string;
|
||
error?: string;
|
||
}> {
|
||
try {
|
||
const { key, hash } = generateApiKey();
|
||
|
||
await prismaClient.apiKey.create({
|
||
data: {
|
||
name,
|
||
keyHash: hash,
|
||
},
|
||
});
|
||
|
||
// 返回原始 key,只显示这一次
|
||
return { success: true, key };
|
||
} catch (error) {
|
||
console.error('[ApiKey] 创建失败:', error);
|
||
return { success: false, error: '创建失败' };
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 禁用 API Key
|
||
*/
|
||
export async function disableApiKey(
|
||
id: string,
|
||
prismaClient: ApiKeyPrismaClient
|
||
): Promise<boolean> {
|
||
try {
|
||
await prismaClient.apiKey.update({
|
||
where: { id },
|
||
data: { isActive: false },
|
||
});
|
||
return true;
|
||
} catch (error) {
|
||
console.error('[ApiKey] 禁用失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 列出所有 API Key(不含原始 key)
|
||
*/
|
||
export async function listApiKeys(prismaClient: ApiKeyPrismaClient) {
|
||
return prismaClient.apiKey.findMany({
|
||
select: {
|
||
id: true,
|
||
name: true,
|
||
isActive: true,
|
||
lastUsedAt: true,
|
||
createdAt: true,
|
||
},
|
||
orderBy: { createdAt: 'desc' },
|
||
});
|
||
}
|