126 lines
4.4 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 { generateFingerprint } from '@/lib/license/fingerprint-service';
import { verifyLicenseSignature } from '@/lib/license/crypto-service';
import { saveLicenseLocally, initializeSystem, startHeartbeat } from '@/lib/license/license-service';
import { buildLicenseServerLicenseApiUrl } from '@/lib/license/server-config';
import { buildSignedLicenseRequest } from '@/lib/license/request-signature';
import { syncPremiumModules } from '@/lib/license/premium-module-service';
import { requireSuperAdminAfterInitialSetup } from '@/lib/license/license-route-guards';
import { validateSignedLicenseBinding } from '@/lib/license/license-binding';
import type { ActivateResponse } from '@/lib/license/types';
export async function POST(request: NextRequest) {
try {
const guard = await requireSuperAdminAfterInitialSetup();
if (!guard.allowed) {
return NextResponse.json(
{ success: false, error: guard.error },
{ status: guard.status }
);
}
const body = await request.json();
// 支持两种参数名:activationCode(新)和 licenseKey(旧)
const licenseKey = body.activationCode || body.licenseKey;
if (!licenseKey) {
return NextResponse.json(
{ success: false, error: '请输入激活码' },
{ status: 400 }
);
}
// ✅ 安全改进:在后端生成硬件指纹(不暴露给客户端)
const fingerprint = await generateFingerprint();
// ✅ 安全改进:代理请求到许可证服务器(隐藏服务器地址)
const signedRequest = buildSignedLicenseRequest('activate', {
licenseKey,
fingerprint,
});
const response = await fetch(buildLicenseServerLicenseApiUrl('activate'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signedRequest),
});
const data: ActivateResponse = await response.json();
if (!data.success || !data.license) {
return NextResponse.json(
{ success: false, error: data.error || '激活失败' },
{ status: 400 }
);
}
// ✅ 在后端验证签名
if (!verifyLicenseSignature(data.license.data, data.license.signature)) {
return NextResponse.json(
{ success: false, error: '许可证签名验证失败' },
{ status: 400 }
);
}
const bindingResult = validateSignedLicenseBinding(data.license, {
licenseKey,
instanceId: fingerprint.instanceId,
});
if (!bindingResult.valid) {
return NextResponse.json(
{ success: false, error: bindingResult.error },
{ status: 400 }
);
}
// ✅ 在后端保存许可证
await saveLicenseLocally(licenseKey, data.license);
const moduleSyncResult = await syncPremiumModules();
if (!moduleSyncResult.synced && !moduleSyncResult.skipped) {
console.warn('[License] 付费模块清单同步失败:', moduleSyncResult.error);
}
// 初始化系统(创建默认管理员、部门、店铺等)
const initResult = await initializeSystem();
// 启动心跳
startHeartbeat();
const result = NextResponse.json({
success: true,
initialAdmin: initResult.admin?.created
? {
email: initResult.admin.email,
username: initResult.admin.username,
password: initResult.admin.passwordSource === 'generated' ? initResult.admin.initialPassword || null : null,
mustChangePassword: initResult.admin.mustChangePassword,
passwordSource: initResult.admin.passwordSource || null,
}
: null,
});
// 自动检测协议:根据请求的协议决定 cookie 的 secure 属性
// - HTTP 请求:secure = false(内网部署)
// - HTTPS 请求:secure = true(Pro 版外网穿透)
const isHttps = request.headers.get('x-forwarded-proto') === 'https'
|| request.url.startsWith('https://');
// 设置许可证激活 cookie(用于 middleware 快速检查)
result.cookies.set('license_activated', 'true', {
httpOnly: true,
secure: isHttps,
sameSite: 'lax',
maxAge: 365 * 24 * 60 * 60, // 1年
path: '/',
});
return result;
} catch (error) {
console.error('激活失败:', error);
return NextResponse.json(
{ success: false, error: '服务器错误' },
{ status: 500 }
);
}
}