204 lines
7.1 KiB
TypeScript
204 lines
7.1 KiB
TypeScript
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';
|
||
|
||
function maskSecret(value: unknown): string {
|
||
if (typeof value !== 'string' || !value) return '****';
|
||
if (value.length <= 8) return '****';
|
||
return `${value.slice(0, 4)}****${value.slice(-4)}`;
|
||
}
|
||
|
||
function sanitizeLicenseServerLogPayload(payload: unknown) {
|
||
if (!payload || typeof payload !== 'object') return payload;
|
||
|
||
const raw = payload as Record<string, any>;
|
||
return {
|
||
...raw,
|
||
data: raw.data
|
||
? {
|
||
...raw.data,
|
||
activationCode: raw.data.activationCode ? maskSecret(raw.data.activationCode) : raw.data.activationCode,
|
||
}
|
||
: raw.data,
|
||
license: raw.license
|
||
? {
|
||
data: raw.license.data ? '[present]' : raw.license.data,
|
||
signature: raw.license.signature ? '[redacted]' : raw.license.signature,
|
||
}
|
||
: raw.license,
|
||
};
|
||
}
|
||
|
||
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().catch(() => ({}));
|
||
const customerName = body.customerName?.trim();
|
||
const contactInfo = body.contactInfo?.trim();
|
||
|
||
// 验证必填字段
|
||
if (!customerName || customerName.length === 0) {
|
||
return NextResponse.json(
|
||
{ success: false, error: '客户名称不能为空' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
if (!contactInfo || contactInfo.length === 0) {
|
||
return NextResponse.json(
|
||
{ success: false, error: '联系方式不能为空' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// ✅ 安全改进:在后端生成硬件指纹(不暴露给客户端)
|
||
const fingerprint = await generateFingerprint();
|
||
|
||
// 确保指纹有效
|
||
if (!fingerprint.machineId || !fingerprint.instanceId) {
|
||
console.error('[License] Fingerprint验证失败:', fingerprint);
|
||
return NextResponse.json(
|
||
{ success: false, error: '系统信息获取失败,请重试' },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
|
||
// 确保macAddress不是无效值
|
||
if (fingerprint.macAddress === '00:00:00:00:00:00') {
|
||
console.warn('[License] MAC地址无效,使用备用值');
|
||
fingerprint.macAddress = 'unknown';
|
||
}
|
||
|
||
// ✅ 安全改进:代理请求到许可证服务器(隐藏服务器地址)
|
||
const signedRequest = buildSignedLicenseRequest('register-free', {
|
||
fingerprint,
|
||
customerName,
|
||
contactInfo,
|
||
});
|
||
|
||
const response = await fetch(buildLicenseServerLicenseApiUrl('register-free'), {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(signedRequest),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
let errorMessage = '激活失败,请检查网络连接';
|
||
try {
|
||
const errorData = await response.json();
|
||
errorMessage = errorData.error || errorMessage;
|
||
console.error('[License] 免费版注册失败:', sanitizeLicenseServerLogPayload(errorData));
|
||
} catch {
|
||
errorMessage = response.statusText || errorMessage;
|
||
console.error('[License] 免费版注册失败:', response.status, response.statusText);
|
||
}
|
||
return NextResponse.json(
|
||
{ success: false, error: errorMessage },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
const data = await response.json();
|
||
|
||
if (!data.success) {
|
||
console.error('[License] 免费版注册失败:', sanitizeLicenseServerLogPayload(data));
|
||
return NextResponse.json(
|
||
{ success: false, error: data.error || '激活失败,请检查网络连接' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
const activationCode = data.data?.activationCode;
|
||
const signedLicense = data.license;
|
||
|
||
if (!activationCode || !signedLicense?.data || !signedLicense?.signature) {
|
||
console.error('[License] 免费版注册返回数据不完整:', sanitizeLicenseServerLogPayload(data));
|
||
return NextResponse.json(
|
||
{ success: false, error: '许可证服务返回数据异常,请稍后重试' },
|
||
{ status: 502 }
|
||
);
|
||
}
|
||
|
||
// ✅ 免费版也必须使用许可证服务端签名,避免保存不可验证的本地许可证
|
||
if (!verifyLicenseSignature(signedLicense.data, signedLicense.signature)) {
|
||
return NextResponse.json(
|
||
{ success: false, error: '许可证签名验证失败' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
const bindingResult = validateSignedLicenseBinding(signedLicense, {
|
||
licenseKey: activationCode,
|
||
instanceId: fingerprint.instanceId,
|
||
});
|
||
if (!bindingResult.valid) {
|
||
return NextResponse.json(
|
||
{ success: false, error: bindingResult.error },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
console.log('[License] 免费版激活码:', maskSecret(activationCode), data.data.isExisting ? '(已存在)' : '(新注册)');
|
||
|
||
// ✅ 在后端保存许可证
|
||
await saveLicenseLocally(activationCode, signedLicense);
|
||
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 属性
|
||
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 }
|
||
);
|
||
}
|
||
}
|