/* eslint-disable */ /** * Next.js Instrumentation Hook * 在应用启动时执行关键检查,失败时立即退出 */ import * as crypto from 'crypto'; // 认证相关接口定义 export interface APIResponse { success: boolean; message: string; data?: any; timestamp: number; signature: string; server_fingerprint: string; } export interface ServerInfo { encrypted_public_key: string; fingerprint: string; encryption_method: string; note: string; } // API密钥 - 用于解密公钥 const API_SECRET = 'moontv-is-the-best'; // 验证服务器地址 const AUTH_SERVER = 'https://moontv-auth.ihtw.moe'; // 全局变量存储公钥和指纹 let serverPublicKey: crypto.KeyObject | null = null; let expectedFingerprint = ''; // 验证相关的全局变量 let currentMachineCode = ''; /** * 使用AES-GCM解密数据 */ function decryptWithAES(encryptedData: string, key: string): string { try { // 将密钥转换为32字节(SHA256哈希) const keyHash = crypto.createHash('sha256').update(key).digest(); // Base64解码密文 const encryptedBytes = Buffer.from(encryptedData, 'base64'); // 提取nonce(前12字节)和密文 const nonceSize = 12; const nonce = encryptedBytes.slice(0, nonceSize); const ciphertext = encryptedBytes.slice(nonceSize, -16); // 除去最后16字节的认证标签 const tag = encryptedBytes.slice(-16); // 最后16字节是认证标签 // 创建AES-GCM解密器 const decipher = crypto.createDecipheriv('aes-256-gcm', keyHash, nonce); decipher.setAuthTag(tag); const decrypted = decipher.update(ciphertext); const final = decipher.final(); // 合并 Buffer 并转换为字符串 const result = Buffer.concat([decrypted, final]); return result.toString('utf8'); } catch (error) { throw new Error(`AES解密失败: ${error instanceof Error ? error.message : '未知错误'}`); } } /** * 从验证服务器获取公钥 */ async function fetchServerPublicKey(): Promise<{ publicKey: string, fingerprint: string }> { try { // 设置10秒超时 const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); const response = await fetch(`${AUTH_SERVER}/api/public_key`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'User-Agent': 'MoonTV/1.0.0' }, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const apiResp: APIResponse = await response.json(); if (!apiResp.success) { throw new Error(`API错误: ${apiResp.message}`); } const serverInfo = apiResp.data as ServerInfo; const encryptedPublicKey = serverInfo.encrypted_public_key; const serverFingerprint = serverInfo.fingerprint; const decryptedPublicKeyPem = decryptWithAES(encryptedPublicKey, API_SECRET); console.log('✅ 公钥解密成功'); return { publicKey: decryptedPublicKeyPem, fingerprint: serverFingerprint }; } catch (error) { throw new Error(`获取服务器公钥失败: ${error instanceof Error ? error.message : '未知错误'}`); } } /** * 验证API响应的签名 */ async function verifyResponse(apiResp: APIResponse, requestTimestamp: string): Promise { if (!serverPublicKey) { throw new Error('未获取服务器公钥'); } // 验证服务器指纹 if (expectedFingerprint && apiResp.server_fingerprint !== expectedFingerprint) { throw new Error('服务器指纹不匹配,可能是伪造的服务器'); } try { // 现在服务端只对时间戳字符串进行签名,而不是整个响应对象 // 使用我们发送请求时的时间戳,而不是响应中的时间戳 const timestampToVerify = requestTimestamp; const verified = await verifyTimestampSignature(timestampToVerify, apiResp.signature); if (!verified) { throw new Error('时间戳签名验证失败'); } } catch (error) { throw new Error(`签名验证失败: ${error instanceof Error ? error.message : '未知错误'}`); } } /** * 验证时间戳的RSA签名(服务端现在只对时间戳字符串进行签名) */ async function verifyTimestampSignature(timestamp: string, signature: string): Promise { try { if (!serverPublicKey) { console.error('❌ 服务器公钥未初始化'); return false; } // 将时间戳转换为字符串(与Go服务端保持一致) const timestampString = String(timestamp); // 将十六进制签名转换为Buffer const signatureBuffer = Buffer.from(signature, 'hex'); // 使用正确的方法:验证原始时间戳字符串 // Go服务端实际上是对原始时间戳字符串进行签名的 const verifier = crypto.createVerify('RSA-SHA256'); verifier.update(timestampString, 'utf8'); const result = verifier.verify(serverPublicKey, signatureBuffer); return result; } catch (error) { console.error('❌ 时间戳签名验证出错:', error); return false; } } /** * 模拟Go的json.Marshal行为进行JSON序列化 * Go对map[string]interface{}会按键的字母顺序排序 */ function serializeAsGoJsonMarshal(obj: any): string { if (obj === null) return 'null'; if (obj === undefined) return 'undefined'; if (typeof obj === 'string') { return JSON.stringify(obj); } if (typeof obj === 'number' || typeof obj === 'boolean') { return String(obj); } // 处理BigInt类型 if (typeof obj === 'bigint') { return String(obj); } if (Array.isArray(obj)) { const items = obj.map(item => serializeAsGoJsonMarshal(item)); return '[' + items.join(',') + ']'; } if (typeof obj === 'object') { // 按键的字母顺序排序(Go的map[string]interface{}行为) const sortedKeys = Object.keys(obj).sort(); const pairs: string[] = []; for (const key of sortedKeys) { if (obj[key] !== undefined) { const serializedKey = JSON.stringify(key); const serializedValue = serializeAsGoJsonMarshal(obj[key]); pairs.push(`${serializedKey}:${serializedValue}`); } } return '{' + pairs.join(',') + '}'; } // 处理其他类型,包括可能的BigInt try { return JSON.stringify(obj); } catch (error) { // 如果JSON.stringify失败(比如因为BigInt),尝试转换为字符串 if (error instanceof TypeError && error.message.includes('BigInt')) { return String(obj); } throw error; } } /** * 注册设备到认证服务器 */ async function registerDevice(authCode: string, deviceCode: string) { try { // 生成请求时间戳 const requestTimestamp = Date.now().toString(); // 设置10秒超时 const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); const response = await fetch(`${AUTH_SERVER}/api/register_device`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'User-Agent': 'MoonTV/1.0.0' }, body: JSON.stringify({ auth_code: authCode, device_code: deviceCode, timestamp: requestTimestamp }), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const responseBody = await response.text(); const apiResp: APIResponse = JSON.parse(responseBody); // 验证响应签名(使用我们发送的时间戳) await verifyResponse(apiResp, requestTimestamp); if (!apiResp.success) { throw new Error(`设备注册失败: ${apiResp.message}`); } console.log(`✅ 设备注册成功`); } catch (error) { throw new Error(`设备注册失败: ${error instanceof Error ? error.message : '未知错误'}`); } } /** * 环境变量检查 */ function checkEnvironment(): void { // 检查 USERNAME const username = process.env.USERNAME; if (!username || username.trim() === '') { console.error('❌ USERNAME 环境变量不得为空'); console.error('🚨 环境变量检查失败,服务器即将退出'); process.exit(0); } // 检查 PASSWORD const password = process.env.PASSWORD; if (!password || password.trim() === '') { console.error('❌ PASSWORD 环境变量不得为空'); console.error('🚨 环境变量检查失败,服务器即将退出'); process.exit(0); } // 检查弱密码 const weakPasswords = [ 'admin_password', 'password', '123456', 'admin', 'root', 'password123', '12345678', 'qwerty', 'abc123', 'admin123', 'test123', 'password1', '000000', '111111', '11111111112233', '112233', '123123', '123321', '654321', '666666', '888888', 'abcdef', 'abcabc', 'a1b2c3', 'aaa111', '123qwe', 'qweasd' ]; if (weakPasswords.includes(password.toLowerCase())) { console.error(`❌ PASSWORD 不能使用常见弱密码: ${password}`); console.error('🚨 环境变量检查失败,服务器即将退出'); process.exit(0); } if (password.length < 8) { console.error('❌ PASSWORD 长度不能少于8位'); console.error('🚨 环境变量检查失败,服务器即将退出'); process.exit(0); } // 检查密码不能与用户名相同 if (password.toLowerCase() === username.toLowerCase()) { console.error('❌ PASSWORD 不能与 USERNAME 相同'); console.error('🚨 环境变量检查失败,服务器即将退出'); process.exit(0); } // 检查 AUTH_TOKEN const authToken = process.env.AUTH_TOKEN; if (!authToken || authToken.trim() === '') { console.error('❌ AUTH_TOKEN 不得为空'); console.error('🚨 环境变量检查失败,服务器即将退出'); process.exit(0); } // 检查 AUTH_SERVER(可选,但如果设置了需要验证格式) const authServer = process.env.AUTH_SERVER; if (authServer && authServer.trim() !== '') { if (!authServer.startsWith('https://') && !authServer.startsWith('http://')) { console.error('❌ AUTH_SERVER 必须以 http:// 或 https:// 开头'); console.error('🚨 环境变量检查失败,服务器即将退出'); process.exit(0); } } } /** * 认证检查 */ async function checkAuthentication(): Promise { // 获取环境变量 const authToken = process.env.AUTH_TOKEN; const username = process.env.USERNAME; const password = process.env.PASSWORD; if (!authToken || !username || !password) { console.error('❌ 认证检查失败:缺少必需的环境变量'); console.error('🚨 认证检查失败,服务器即将退出'); process.exit(0); } try { // 第一步:生成机器码(包含存储URL信息) const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage'; let storageUrl = ''; // 根据存储类型获取对应的URL switch (storageType) { case 'kvrocks': storageUrl = process.env.KVROCKS_URL || ''; break; case 'upstash': storageUrl = process.env.UPSTASH_URL || ''; break; case 'redis': storageUrl = process.env.REDIS_URL || ''; break; default: storageUrl = 'localstorage'; } const combinedString = authToken + username + password + storageUrl; const encoder = new TextEncoder(); const data = encoder.encode(combinedString); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); const machineCode = hashHex.substring(0, 16); currentMachineCode = machineCode; // 保存到全局变量 // 第二步:从验证服务器获取公钥 const { publicKey, fingerprint } = await fetchServerPublicKey(); // 设置全局变量供签名验证使用 // 将PEM格式的公钥字符串转换为KeyObject try { serverPublicKey = crypto.createPublicKey({ key: publicKey, format: 'pem', type: 'spki' }); } catch (keyError) { console.error('❌ 公钥KeyObject创建失败:', keyError); throw new Error(`公钥格式错误: ${keyError instanceof Error ? keyError.message : '未知错误'}`); } expectedFingerprint = fingerprint; console.log('🔑 公钥获取成功,准备进行设备注册'); // 第三步:注册设备 // 使用机器码作为认证码和设备码 const deviceCode = machineCode; await registerDevice(authToken, deviceCode); console.log('🎉 设备认证流程完成'); } catch (error) { console.error('❌ 认证流程失败:', error instanceof Error ? error.message : '未知错误'); console.error('🚨 认证检查失败,服务器即将退出'); process.exit(0); } } /** * 数据库配置检查 */ function checkDatabaseConfig(): void { const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage'; // 检查存储类型配置 const allowedStorageTypes = ['localstorage', 'kvrocks', 'upstash', 'redis']; if (!allowedStorageTypes.includes(storageType)) { console.error(`❌ NEXT_PUBLIC_STORAGE_TYPE 必须是 ${allowedStorageTypes.join(', ')} 之一,当前值: ${storageType}`); console.error('🚨 数据库配置检查失败,服务器即将退出'); process.exit(0); } // 根据存储类型检查相应的环境变量 switch (storageType) { case 'kvrocks': const kvrocksUrl = process.env.KVROCKS_URL; if (!kvrocksUrl || kvrocksUrl.trim() === '') { console.error('❌ KVROCKS_URL 环境变量不得为空'); console.error('🚨 数据库配置检查失败,服务器即将退出'); process.exit(0); } if (!kvrocksUrl.startsWith('redis://')) { console.error('❌ KVROCKS_URL 必须以 redis:// 开头'); console.error('🚨 数据库配置检查失败,服务器即将退出'); process.exit(0); } break; case 'upstash': const upstashUrl = process.env.UPSTASH_URL; const upstashToken = process.env.UPSTASH_TOKEN; if (!upstashUrl || upstashUrl.trim() === '') { console.error('❌ UPSTASH_URL 环境变量不得为空'); console.error('🚨 数据库配置检查失败,服务器即将退出'); process.exit(0); } if (!upstashUrl.startsWith('https://')) { console.error('❌ UPSTASH_URL 必须以 https:// 开头'); console.error('🚨 数据库配置检查失败,服务器即将退出'); process.exit(0); } if (!upstashToken || upstashToken.trim() === '') { console.error('❌ UPSTASH_TOKEN 环境变量不得为空'); console.error('🚨 数据库配置检查失败,服务器即将退出'); process.exit(0); } break; case 'redis': const redisUrl = process.env.REDIS_URL; if (!redisUrl || redisUrl.trim() === '') { console.error('❌ REDIS_URL 环境变量不得为空'); console.error('🚨 数据库配置检查失败,服务器即将退出'); process.exit(0); } if (!redisUrl.startsWith('redis://') && !redisUrl.startsWith('rediss://')) { console.error('❌ REDIS_URL 必须以 redis:// 或 rediss:// 开头'); console.error('🚨 数据库配置检查失败,服务器即将退出'); process.exit(0); } break; } } /** * 执行启动检查并在失败时退出 */ async function runCriticalStartupChecks(): Promise { console.log('🔧 执行关键启动检查...'); // 1. 环境变量检查 console.log('📝 检查环境变量...'); checkEnvironment(); console.log('✅ 环境变量检查通过'); // 2. 数据库配置检查 console.log('🗄️ 检查数据库配置...'); checkDatabaseConfig(); console.log('✅ 数据库配置检查通过'); // 3. 认证检查 console.log('🔐 检查认证信息...'); await checkAuthentication(); console.log('✅ 认证检查通过'); console.log('🎉 所有关键检查通过,服务器正常启动'); } /** * Next.js Instrumentation Hook * 这个函数会在应用启动时自动被 Next.js 调用 */ export async function register() { // 只在服务器端运行 if (typeof window === 'undefined' && typeof process !== 'undefined' && process.on && typeof process.on === 'function') { console.log('🚀 MoonTV 启动检查开始...'); // 注册进程退出事件处理 process.on('SIGINT', () => { console.log('\n🛑 收到 SIGINT 信号,正在优雅关闭...'); process.exit(0); }); process.on('SIGTERM', () => { console.log('\n🛑 收到 SIGTERM 信号,正在优雅关闭...'); process.exit(0); }); try { await runCriticalStartupChecks(); } catch (error) { console.error('💥 启动检查过程中发生未预期错误:', error); console.error('🚨 服务器即将退出'); process.exit(0); } } } // 导出检查函数供其他模块使用(如果需要) export { checkAuthentication, checkDatabaseConfig, checkEnvironment, decryptWithAES, fetchServerPublicKey, verifyResponse, verifyTimestampSignature, serializeAsGoJsonMarshal };