2026-05-25 14:31:58 +08:00

136 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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' },
});
}