Files
Termix/src/backend/utils/import-export-test.ts
ZacharyZcR cfebb690b0 SECURITY FIX: Restore import/export functionality with KEK-DEK architecture
Fix critical missing functionality identified in security audit:

## New Features Implemented:
 User-level data export (encrypted/plaintext formats)
 User-level data import with dry-run validation
 Export preview endpoint for size estimation
 OIDC configuration encryption for sensitive data
 Production environment security checks on startup

## API Endpoints Restored:
- POST /database/export - User data export with password protection
- POST /database/import - User data import with validation
- POST /database/export/preview - Export validation and stats

## Security Improvements:
- OIDC client_secret now encrypted when admin data unlocked
- Production startup checks for required environment variables
- Comprehensive import/export documentation and examples
- Proper error handling and cleanup for uploaded files

## Data Migration Support:
- Cross-instance user data migration
- Selective import (skip credentials/file manager data)
- ID collision handling with automatic regeneration
- Full validation of import data structure

Resolves the critical "503 Service Unavailable" status on import/export
endpoints that was blocking user data migration capabilities.

Maintains KEK-DEK user-level encryption while enabling data portability.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-22 00:13:56 +08:00

216 lines
6.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 { UserDataExport, type UserExportData } from "./user-data-export.js";
import { UserDataImport, type ImportResult } from "./user-data-import.js";
import { databaseLogger } from "./logger.js";
/**
* 导入导出功能测试
*
* Linus原则简单的冒烟测试确保基本功能工作
*/
class ImportExportTest {
/**
* 测试导出功能
*/
static async testExport(userId: string): Promise<boolean> {
try {
databaseLogger.info("Testing user data export functionality", {
operation: "import_export_test",
test: "export",
userId,
});
// 测试加密导出
const encryptedExport = await UserDataExport.exportUserData(userId, {
format: 'encrypted',
scope: 'user_data',
includeCredentials: true,
});
// 验证导出数据结构
const validation = UserDataExport.validateExportData(encryptedExport);
if (!validation.valid) {
databaseLogger.error("Export validation failed", {
operation: "import_export_test",
test: "export_validation",
errors: validation.errors,
});
return false;
}
// 获取统计信息
const stats = UserDataExport.getExportStats(encryptedExport);
databaseLogger.success("Export test completed successfully", {
operation: "import_export_test",
test: "export_success",
totalRecords: stats.totalRecords,
breakdown: stats.breakdown,
encrypted: stats.encrypted,
});
return true;
} catch (error) {
databaseLogger.error("Export test failed", error, {
operation: "import_export_test",
test: "export_failed",
userId,
});
return false;
}
}
/**
* 测试导入功能dry-run
*/
static async testImportDryRun(userId: string, exportData: UserExportData): Promise<boolean> {
try {
databaseLogger.info("Testing user data import functionality (dry-run)", {
operation: "import_export_test",
test: "import_dry_run",
userId,
});
// 执行dry-run导入
const result = await UserDataImport.importUserData(userId, exportData, {
dryRun: true,
replaceExisting: false,
skipCredentials: false,
skipFileManagerData: false,
});
if (result.success) {
databaseLogger.success("Import dry-run test completed successfully", {
operation: "import_export_test",
test: "import_dry_run_success",
summary: result.summary,
});
return true;
} else {
databaseLogger.error("Import dry-run test failed", {
operation: "import_export_test",
test: "import_dry_run_failed",
errors: result.summary.errors,
});
return false;
}
} catch (error) {
databaseLogger.error("Import dry-run test failed with exception", error, {
operation: "import_export_test",
test: "import_dry_run_exception",
userId,
});
return false;
}
}
/**
* 运行完整的导入导出测试
*/
static async runFullTest(userId: string): Promise<boolean> {
try {
databaseLogger.info("Starting full import/export test suite", {
operation: "import_export_test",
test: "full_suite",
userId,
});
// 1. 测试导出
const exportSuccess = await this.testExport(userId);
if (!exportSuccess) {
return false;
}
// 2. 获取导出数据用于导入测试
const exportData = await UserDataExport.exportUserData(userId, {
format: 'encrypted',
scope: 'user_data',
includeCredentials: true,
});
// 3. 测试导入dry-run
const importSuccess = await this.testImportDryRun(userId, exportData);
if (!importSuccess) {
return false;
}
databaseLogger.success("Full import/export test suite completed successfully", {
operation: "import_export_test",
test: "full_suite_success",
userId,
});
return true;
} catch (error) {
databaseLogger.error("Full import/export test suite failed", error, {
operation: "import_export_test",
test: "full_suite_failed",
userId,
});
return false;
}
}
/**
* 验证JSON序列化和反序列化
*/
static async testJSONSerialization(userId: string): Promise<boolean> {
try {
databaseLogger.info("Testing JSON serialization/deserialization", {
operation: "import_export_test",
test: "json_serialization",
userId,
});
// 导出为JSON字符串
const jsonString = await UserDataExport.exportUserDataToJSON(userId, {
format: 'encrypted',
pretty: true,
});
// 解析JSON
const parsedData = JSON.parse(jsonString);
// 验证解析后的数据
const validation = UserDataExport.validateExportData(parsedData);
if (!validation.valid) {
databaseLogger.error("JSON serialization validation failed", {
operation: "import_export_test",
test: "json_validation_failed",
errors: validation.errors,
});
return false;
}
// 测试从JSON导入dry-run
const importResult = await UserDataImport.importUserDataFromJSON(userId, jsonString, {
dryRun: true,
});
if (importResult.success) {
databaseLogger.success("JSON serialization test completed successfully", {
operation: "import_export_test",
test: "json_serialization_success",
jsonSize: jsonString.length,
});
return true;
} else {
databaseLogger.error("JSON import test failed", {
operation: "import_export_test",
test: "json_import_failed",
errors: importResult.summary.errors,
});
return false;
}
} catch (error) {
databaseLogger.error("JSON serialization test failed", error, {
operation: "import_export_test",
test: "json_serialization_exception",
userId,
});
return false;
}
}
}
export { ImportExportTest };