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>
This commit is contained in:
ZacharyZcR
2025-09-22 00:13:56 +08:00
parent 37ef6c973d
commit cfebb690b0
7 changed files with 1537 additions and 33 deletions

View File

@@ -0,0 +1,216 @@
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 };