226 lines
7.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 { NextRequest, NextResponse } from 'next/server';
import { getPublicProjectDb } from '@/lib/project-auth';
import { hashFingerprint, signLicense } from '@/lib/crypto';
import { verifyLicenseRequestSignature } from '@/lib/request-signature';
import { LICENSE_CONFIGS } from '@/lib/types';
import { createPendingAlertOnce, findFreeFingerprintReuse } from '@/lib/abuse-detection';
import { buildLicenseSigningPayload } from '@/lib/license-payload';
import { z } from 'zod';
const registerFreeSchema = z.object({
fingerprint: z.object({
machineId: z.string({ required_error: '系统信息不完整:machineId 缺失' }),
hostname: z.string({ required_error: '系统信息不完整:hostname 缺失' }),
macAddress: z.string({ required_error: '系统信息不完整:macAddress 缺失' }),
cpuId: z.string().optional(),
instanceId: z.string({ required_error: '系统信息不完整:instanceId 缺失' }),
}),
customerName: z.string().min(1, '客户名称不能为空'),
contactInfo: z.string().min(1, '联系方式不能为空'),
timestamp: z.number({ required_error: '请求时间戳缺失' }),
});
function generateFreeActivationCode(): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const segments: string[] = [];
for (let i = 0; i < 4; i++) {
let segment = '';
for (let j = 0; j < 4; j++) {
segment += chars.charAt(Math.floor(Math.random() * chars.length));
}
segments.push(segment);
}
return `FLNXI-FREE-${segments.join('-')}`;
}
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ projectCode: string }> }
) {
try {
const { projectCode } = await params;
const projectDbResult = await getPublicProjectDb(projectCode);
if (!projectDbResult.success) {
return projectDbResult.response;
}
const body = await request.json().catch(() => ({}));
const signatureResult = verifyLicenseRequestSignature('register-free', projectCode, body);
if (!signatureResult.valid) {
return NextResponse.json(
{ success: false, error: '请求签名无效' },
{ status: signatureResult.response.status }
);
}
const parsed = registerFreeSchema.safeParse(body);
if (!parsed.success) {
const firstError = parsed.error.errors[0]?.message || '参数验证失败';
return NextResponse.json(
{ success: false, error: firstError },
{ status: 400 }
);
}
const { fingerprint, customerName, contactInfo, timestamp } = parsed.data;
const now = Date.now();
if (Math.abs(now - timestamp) > 5 * 60 * 1000) {
return NextResponse.json(
{ success: false, error: '请求已过期' },
{ status: 400 }
);
}
const existingActivation = await projectDbResult.prisma.activation.findFirst({
where: {
instanceId: fingerprint.instanceId,
isActive: true,
},
include: {
license: true,
},
});
if (existingActivation) {
if (existingActivation.license.status === 'disabled') {
return NextResponse.json(
{ success: false, error: '许可证已被禁用' },
{ status: 403 }
);
}
if (
existingActivation.license.status === 'expired' ||
(existingActivation.license.expiresAt && existingActivation.license.expiresAt < new Date())
) {
return NextResponse.json(
{ success: false, error: '许可证已过期' },
{ status: 403 }
);
}
const signedLicense = signLicense(buildLicenseSigningPayload({
projectCode,
licenseKey: existingActivation.license.licenseKey,
licenseType: existingActivation.license.licenseType,
expiresAt: existingActivation.license.expiresAt,
fingerprint,
activationId: existingActivation.id,
}));
return NextResponse.json({
success: true,
license: signedLicense,
data: {
activationCode: existingActivation.license.licenseKey,
plan: existingActivation.license.licenseType,
planName:
existingActivation.license.licenseType === 'FREE'
? '免费版'
: existingActivation.license.licenseType === 'STANDARD'
? '标准版'
: '专业版',
isExisting: true,
},
});
}
const reuseResult = await findFreeFingerprintReuse(projectDbResult.prisma, fingerprint);
if (reuseResult.reused) {
const relatedLicense = await projectDbResult.prisma.license.findUnique({
where: { licenseKey: reuseResult.existingLicenseKey },
select: { id: true },
});
if (relatedLicense) {
await createPendingAlertOnce(projectDbResult.prisma, relatedLicense.id, 'free_reactivation_blocked', {
requestedInstanceId: fingerprint.instanceId,
existingInstanceId: reuseResult.existingInstanceId,
existingLicenseKey: reuseResult.existingLicenseKey,
matchScore: reuseResult.matchScore,
});
}
return NextResponse.json(
{
success: false,
error: '该设备已注册过免费版,请继续使用原免费许可证或升级付费版',
},
{ status: 409 }
);
}
const activationCode = generateFreeActivationCode();
let createdActivationId = '';
const clientIP =
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
request.headers.get('x-real-ip') ||
'unknown';
await projectDbResult.prisma.$transaction(async (tx) => {
const license = await tx.license.create({
data: {
licenseKey: activationCode,
licenseType: 'FREE',
customerName,
contactInfo,
maxUsers: LICENSE_CONFIGS.FREE.limits.maxUsers,
maxMaterials: LICENSE_CONFIGS.FREE.limits.maxMaterials,
features: LICENSE_CONFIGS.FREE.features,
expiresAt: null,
},
});
const activation = await tx.activation.create({
data: {
licenseId: license.id,
instanceId: fingerprint.instanceId,
fingerprint: fingerprint as object,
lastHeartbeat: new Date(),
},
});
createdActivationId = activation.id;
await tx.heartbeatLog.create({
data: {
licenseId: license.id,
instanceId: fingerprint.instanceId,
fingerprintHash: hashFingerprint(fingerprint),
ipAddress: clientIP,
},
});
});
const signedLicense = signLicense(buildLicenseSigningPayload({
projectCode,
licenseKey: activationCode,
licenseType: 'FREE',
expiresAt: null,
fingerprint,
activationId: createdActivationId,
}));
return NextResponse.json({
success: true,
license: signedLicense,
data: {
activationCode,
plan: 'FREE',
planName: '免费版',
isExisting: false,
},
});
} catch (error) {
console.error('项目免费版注册失败:', error);
return NextResponse.json(
{ success: false, error: '服务器内部错误' },
{ status: 500 }
);
}
}