Clean Chinese comments from backend codebase

Replace all Chinese comments with English equivalents while preserving:
- Technical meaning and Linus-style direct tone
- Code structure and functionality
- User-facing text in UI components

Backend files cleaned:
- All utils/ TypeScript files
- Database routes and operations
- System architecture comments
- Field encryption documentation

All backend code now uses consistent English comments.
This commit is contained in:
ZacharyZcR
2025-09-22 01:31:54 +08:00
parent 03389ff413
commit 03e876dae9
11 changed files with 216 additions and 216 deletions

View File

@@ -412,7 +412,7 @@ app.post("/database/export", async (req, res) => {
const userId = payload.userId; const userId = payload.userId;
const { format = 'encrypted', scope = 'user_data', includeCredentials = true, password } = req.body; const { format = 'encrypted', scope = 'user_data', includeCredentials = true, password } = req.body;
// 对于明文导出,需要解锁用户数据 // For plaintext export, need to unlock user data
if (format === 'plaintext') { if (format === 'plaintext') {
if (!password) { if (!password) {
return res.status(400).json({ return res.status(400).json({
@@ -441,7 +441,7 @@ app.post("/database/export", async (req, res) => {
includeCredentials, includeCredentials,
}); });
// 生成导出文件名 // Generate export filename
const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const filename = `termix-export-${exportData.username}-${timestamp}.json`; const filename = `termix-export-${exportData.username}-${timestamp}.json`;
@@ -507,10 +507,10 @@ app.post("/database/import", upload.single("file"), async (req, res) => {
dryRun, dryRun,
}); });
// 读取上传的文件 // Read uploaded file
const fileContent = fs.readFileSync(req.file.path, 'utf8'); const fileContent = fs.readFileSync(req.file.path, 'utf8');
// 清理上传的临时文件 // Clean up uploaded temporary file
try { try {
fs.unlinkSync(req.file.path); fs.unlinkSync(req.file.path);
} catch (cleanupError) { } catch (cleanupError) {
@@ -520,7 +520,7 @@ app.post("/database/import", upload.single("file"), async (req, res) => {
}); });
} }
// 解析导入数据 // Parse import data
let importData; let importData;
try { try {
importData = JSON.parse(fileContent); importData = JSON.parse(fileContent);
@@ -528,7 +528,7 @@ app.post("/database/import", upload.single("file"), async (req, res) => {
return res.status(400).json({ error: "Invalid JSON format in uploaded file" }); return res.status(400).json({ error: "Invalid JSON format in uploaded file" });
} }
// 如果导入数据是加密的,需要解锁用户数据 // If import data is encrypted, need to unlock user data
if (importData.metadata?.encrypted) { if (importData.metadata?.encrypted) {
if (!password) { if (!password) {
return res.status(400).json({ return res.status(400).json({
@@ -543,7 +543,7 @@ app.post("/database/import", upload.single("file"), async (req, res) => {
} }
} }
// 执行导入 // Execute import
const result = await UserDataImport.importUserData(userId, importData, { const result = await UserDataImport.importUserData(userId, importData, {
replaceExisting: replaceExisting === 'true' || replaceExisting === true, replaceExisting: replaceExisting === 'true' || replaceExisting === true,
skipCredentials: skipCredentials === 'true' || skipCredentials === true, skipCredentials: skipCredentials === 'true' || skipCredentials === true,
@@ -619,9 +619,9 @@ app.post("/database/export/preview", async (req, res) => {
includeCredentials, includeCredentials,
}); });
// 生成导出数据但不解密敏感字段 // Generate export data but don't decrypt sensitive fields
const exportData = await UserDataExport.exportUserData(userId, { const exportData = await UserDataExport.exportUserData(userId, {
format: 'encrypted', // 始终加密预览 format: 'encrypted', // Always encrypt preview
scope, scope,
includeCredentials, includeCredentials,
}); });

View File

@@ -336,10 +336,10 @@ router.post("/oidc-config", authenticateJWT, async (req, res) => {
scopes: scopes || "openid email profile", scopes: scopes || "openid email profile",
}; };
// 对敏感配置进行加密存储 // Encrypt sensitive configuration for storage
let encryptedConfig; let encryptedConfig;
try { try {
// 使用管理员的数据密钥加密OIDC配置 // Use admin's data key to encrypt OIDC configuration
const adminDataKey = DataCrypto.getUserDataKey(userId); const adminDataKey = DataCrypto.getUserDataKey(userId);
if (adminDataKey) { if (adminDataKey) {
encryptedConfig = DataCrypto.encryptRecord("settings", config, userId, adminDataKey); encryptedConfig = DataCrypto.encryptRecord("settings", config, userId, adminDataKey);
@@ -348,10 +348,10 @@ router.post("/oidc-config", authenticateJWT, async (req, res) => {
userId, userId,
}); });
} else { } else {
// 如果管理员数据未解锁,只加密client_secret // If admin data not unlocked, only encrypt client_secret
encryptedConfig = { encryptedConfig = {
...config, ...config,
client_secret: `encrypted:${Buffer.from(client_secret).toString('base64')}`, // 简单的base64编码 client_secret: `encrypted:${Buffer.from(client_secret).toString('base64')}`, // Simple base64 encoding
}; };
authLogger.warn("OIDC configuration stored with basic encoding - admin should re-save with password", { authLogger.warn("OIDC configuration stored with basic encoding - admin should re-save with password", {
operation: "oidc_config_basic_encoding", operation: "oidc_config_basic_encoding",
@@ -422,10 +422,10 @@ router.get("/oidc-config", async (req, res) => {
let config = JSON.parse((row as any).value); let config = JSON.parse((row as any).value);
// 解密或解码client_secret用于显示 // Decrypt or decode client_secret for display
if (config.client_secret) { if (config.client_secret) {
if (config.client_secret.startsWith('encrypted:')) { if (config.client_secret.startsWith('encrypted:')) {
// 需要管理员权限解密 // Requires admin permission to decrypt
const authHeader = req.headers["authorization"]; const authHeader = req.headers["authorization"];
if (authHeader?.startsWith("Bearer ")) { if (authHeader?.startsWith("Bearer ")) {
const token = authHeader.split(" ")[1]; const token = authHeader.split(" ")[1];
@@ -442,7 +442,7 @@ router.get("/oidc-config", async (req, res) => {
if (adminDataKey) { if (adminDataKey) {
config = DataCrypto.decryptRecord("settings", config, userId, adminDataKey); config = DataCrypto.decryptRecord("settings", config, userId, adminDataKey);
} else { } else {
// 管理员数据未解锁,隐藏client_secret // Admin data not unlocked, hide client_secret
config.client_secret = "[ENCRYPTED - PASSWORD REQUIRED]"; config.client_secret = "[ENCRYPTED - PASSWORD REQUIRED]";
} }
} catch (decryptError) { } catch (decryptError) {
@@ -462,7 +462,7 @@ router.get("/oidc-config", async (req, res) => {
config.client_secret = "[ENCRYPTED - AUTH REQUIRED]"; config.client_secret = "[ENCRYPTED - AUTH REQUIRED]";
} }
} else if (config.client_secret.startsWith('encoded:')) { } else if (config.client_secret.startsWith('encoded:')) {
// base64解码 // base64 decode
try { try {
const decoded = Buffer.from(config.client_secret.substring(8), 'base64').toString('utf8'); const decoded = Buffer.from(config.client_secret.substring(8), 'base64').toString('utf8');
config.client_secret = decoded; config.client_secret = decoded;
@@ -470,7 +470,7 @@ router.get("/oidc-config", async (req, res) => {
config.client_secret = "[ENCODING ERROR]"; config.client_secret = "[ENCODING ERROR]";
} }
} }
// 否则是明文,直接返回 // Otherwise plaintext, return directly
} }
res.json(config); res.json(config);

View File

@@ -15,7 +15,7 @@ import "dotenv/config";
version: version, version: version,
}); });
// 生产环境安全检查 // Production environment security checks
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
systemLogger.info("Running production environment security checks...", { systemLogger.info("Running production environment security checks...", {
operation: "security_checks", operation: "security_checks",
@@ -23,19 +23,19 @@ import "dotenv/config";
const securityIssues: string[] = []; const securityIssues: string[] = [];
// 检查系统主密钥 // Check system master key
if (!process.env.SYSTEM_MASTER_KEY) { if (!process.env.SYSTEM_MASTER_KEY) {
securityIssues.push("SYSTEM_MASTER_KEY environment variable is required in production"); securityIssues.push("SYSTEM_MASTER_KEY environment variable is required in production");
} else if (process.env.SYSTEM_MASTER_KEY.length < 64) { } else if (process.env.SYSTEM_MASTER_KEY.length < 64) {
securityIssues.push("SYSTEM_MASTER_KEY should be at least 64 characters in production"); securityIssues.push("SYSTEM_MASTER_KEY should be at least 64 characters in production");
} }
// 检查数据库文件加密 // Check database file encryption
if (process.env.DB_FILE_ENCRYPTION === 'false') { if (process.env.DB_FILE_ENCRYPTION === 'false') {
securityIssues.push("Database file encryption should be enabled in production"); securityIssues.push("Database file encryption should be enabled in production");
} }
// 检查JWT移密 // Check JWT secret
if (!process.env.JWT_SECRET) { if (!process.env.JWT_SECRET) {
systemLogger.info("JWT_SECRET not set - will use encrypted storage", { systemLogger.info("JWT_SECRET not set - will use encrypted storage", {
operation: "security_checks", operation: "security_checks",
@@ -43,7 +43,7 @@ import "dotenv/config";
}); });
} }
// 检查CORS配置警告 // Check CORS configuration warning
systemLogger.warn("Production deployment detected - ensure CORS is properly configured", { systemLogger.warn("Production deployment detected - ensure CORS is properly configured", {
operation: "security_checks", operation: "security_checks",
warning: "Verify frontend domain whitelist" warning: "Verify frontend domain whitelist"

View File

@@ -23,14 +23,14 @@ interface JWTPayload {
} }
/** /**
* AuthManager - 简化的认证管理器 * AuthManager - Simplified authentication manager
* *
* 职责: * Responsibilities:
* - JWT生成和验证 * - JWT generation and validation
* - 认证中间件 * - Authentication middleware
* - 用户登录登出 * - User login/logout
* *
* 不再有两层session - 直接使用UserKeyManager * No more two-layer sessions - use UserKeyManager directly
*/ */
class AuthManager { class AuthManager {
private static instance: AuthManager; private static instance: AuthManager;
@@ -50,7 +50,7 @@ class AuthManager {
} }
/** /**
* 初始化认证系统 * Initialize authentication system
*/ */
async initialize(): Promise<void> { async initialize(): Promise<void> {
await this.systemCrypto.initializeJWTSecret(); await this.systemCrypto.initializeJWTSecret();
@@ -60,21 +60,21 @@ class AuthManager {
} }
/** /**
* 用户注册 * User registration
*/ */
async registerUser(userId: string, password: string): Promise<void> { async registerUser(userId: string, password: string): Promise<void> {
await this.userCrypto.setupUserEncryption(userId, password); await this.userCrypto.setupUserEncryption(userId, password);
} }
/** /**
* 用户登录 - 使用UserCrypto * User login - use UserCrypto
*/ */
async authenticateUser(userId: string, password: string): Promise<boolean> { async authenticateUser(userId: string, password: string): Promise<boolean> {
return await this.userCrypto.authenticateUser(userId, password); return await this.userCrypto.authenticateUser(userId, password);
} }
/** /**
* 生成JWT Token * Generate JWT Token
*/ */
async generateJWTToken( async generateJWTToken(
userId: string, userId: string,
@@ -93,7 +93,7 @@ class AuthManager {
} }
/** /**
* 验证JWT Token * Verify JWT Token
*/ */
async verifyJWTToken(token: string): Promise<JWTPayload | null> { async verifyJWTToken(token: string): Promise<JWTPayload | null> {
try { try {
@@ -105,7 +105,7 @@ class AuthManager {
} }
/** /**
* 认证中间件 * Authentication middleware
*/ */
createAuthMiddleware() { createAuthMiddleware() {
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
@@ -128,7 +128,7 @@ class AuthManager {
} }
/** /**
* 数据访问中间件 - 要求用户已解锁数据 * Data access middleware - requires user to have unlocked data
*/ */
createDataAccessMiddleware() { createDataAccessMiddleware() {
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
@@ -151,28 +151,28 @@ class AuthManager {
} }
/** /**
* 用户登出 * User logout
*/ */
logoutUser(userId: string): void { logoutUser(userId: string): void {
this.userCrypto.logoutUser(userId); this.userCrypto.logoutUser(userId);
} }
/** /**
* 获取用户数据密钥 * Get user data key
*/ */
getUserDataKey(userId: string): Buffer | null { getUserDataKey(userId: string): Buffer | null {
return this.userCrypto.getUserDataKey(userId); return this.userCrypto.getUserDataKey(userId);
} }
/** /**
* 检查用户是否已解锁 * Check if user is unlocked
*/ */
isUserUnlocked(userId: string): boolean { isUserUnlocked(userId: string): boolean {
return this.userCrypto.isUserUnlocked(userId); return this.userCrypto.isUserUnlocked(userId);
} }
/** /**
* 修改用户密码 * Change user password
*/ */
async changeUserPassword(userId: string, oldPassword: string, newPassword: string): Promise<boolean> { async changeUserPassword(userId: string, oldPassword: string, newPassword: string): Promise<boolean> {
return await this.userCrypto.changeUserPassword(userId, oldPassword, newPassword); return await this.userCrypto.changeUserPassword(userId, oldPassword, newPassword);

View File

@@ -3,13 +3,13 @@ import { UserCrypto } from "./user-crypto.js";
import { databaseLogger } from "./logger.js"; import { databaseLogger } from "./logger.js";
/** /**
* DataCrypto - 简化的数据库加密 * DataCrypto - Simplified database encryption
* *
* Linus原则: * Linus principles:
* - 删除所有"向后兼容"垃圾 * - Remove all "backward compatibility" garbage
* - 删除所有特殊情况处理 * - Remove all special case handling
* - 数据要么正确加密,要么操作失败 * - Data is either properly encrypted or operation fails
* - 没有legacy data概念 * - No legacy data concept
*/ */
class DataCrypto { class DataCrypto {
private static userCrypto: UserCrypto; private static userCrypto: UserCrypto;
@@ -43,12 +43,12 @@ class DataCrypto {
} }
/** /**
* 解密记录 - 要么成功,要么失败 * Decrypt record - either succeeds or fails
* *
* 删除了所有的: * Removed all:
* - isEncrypted()检查 * - isEncrypted() checks
* - legacy data处理 * - legacy data handling
* - "向后兼容"逻辑 * - "backward compatibility" logic
* - migration on access * - migration on access
*/ */
static decryptRecord(tableName: string, record: any, userId: string, userDataKey: Buffer): any { static decryptRecord(tableName: string, record: any, userId: string, userDataKey: Buffer): any {
@@ -59,8 +59,8 @@ class DataCrypto {
for (const [fieldName, value] of Object.entries(record)) { for (const [fieldName, value] of Object.entries(record)) {
if (FieldCrypto.shouldEncryptField(tableName, fieldName) && value) { if (FieldCrypto.shouldEncryptField(tableName, fieldName) && value) {
// 简单规则敏感字段必须是加密的JSON格式 // Simple rule: sensitive fields must be encrypted JSON format
// 如果不是,就是数据损坏,直接失败 // If not, it's data corruption, fail directly
decryptedRecord[fieldName] = FieldCrypto.decryptField( decryptedRecord[fieldName] = FieldCrypto.decryptField(
value as string, value as string,
userDataKey, userDataKey,

View File

@@ -8,13 +8,13 @@ interface EncryptedData {
} }
/** /**
* FieldCrypto - 简单直接的字段加密 * FieldCrypto - Simple direct field encryption
* *
* Linus原则: * Linus principles:
* - 没有特殊情况 * - No special cases
* - 没有兼容性检查 * - No compatibility checks
* - 数据要么加密,要么失败 * - Data is either encrypted or fails
* - 不存在"legacy data"概念 * - No "legacy data" concept
*/ */
class FieldCrypto { class FieldCrypto {
private static readonly ALGORITHM = "aes-256-gcm"; private static readonly ALGORITHM = "aes-256-gcm";
@@ -22,7 +22,7 @@ class FieldCrypto {
private static readonly IV_LENGTH = 16; private static readonly IV_LENGTH = 16;
private static readonly SALT_LENGTH = 32; private static readonly SALT_LENGTH = 32;
// 需要加密的字段 - 简单的映射,没有复杂逻辑 // Fields requiring encryption - simple mapping, no complex logic
private static readonly ENCRYPTED_FIELDS = { private static readonly ENCRYPTED_FIELDS = {
users: new Set(["password_hash", "client_secret", "totp_secret", "totp_backup_codes", "oidc_identifier"]), users: new Set(["password_hash", "client_secret", "totp_secret", "totp_backup_codes", "oidc_identifier"]),
ssh_data: new Set(["password", "key", "keyPassword"]), ssh_data: new Set(["password", "key", "keyPassword"]),
@@ -30,7 +30,7 @@ class FieldCrypto {
}; };
/** /**
* 加密字段 - 没有特殊情况 * Encrypt field - no special cases
*/ */
static encryptField(plaintext: string, masterKey: Buffer, recordId: string, fieldName: string): string { static encryptField(plaintext: string, masterKey: Buffer, recordId: string, fieldName: string): string {
if (!plaintext) return ""; if (!plaintext) return "";
@@ -57,7 +57,7 @@ class FieldCrypto {
} }
/** /**
* 解密字段 - 要么成功,要么失败,没有第三种情况 * Decrypt field - either succeeds or fails, no third option
*/ */
static decryptField(encryptedValue: string, masterKey: Buffer, recordId: string, fieldName: string): string { static decryptField(encryptedValue: string, masterKey: Buffer, recordId: string, fieldName: string): string {
if (!encryptedValue) return ""; if (!encryptedValue) return "";
@@ -77,7 +77,7 @@ class FieldCrypto {
} }
/** /**
* 检查字段是否需要加密 - 简单查表,没有复杂逻辑 * Check if field needs encryption - simple table lookup, no complex logic
*/ */
static shouldEncryptField(tableName: string, fieldName: string): boolean { static shouldEncryptField(tableName: string, fieldName: string): boolean {
const fields = this.ENCRYPTED_FIELDS[tableName as keyof typeof this.ENCRYPTED_FIELDS]; const fields = this.ENCRYPTED_FIELDS[tableName as keyof typeof this.ENCRYPTED_FIELDS];

View File

@@ -6,17 +6,17 @@ import type { SQLiteTable } from "drizzle-orm/sqlite-core";
type TableName = "users" | "ssh_data" | "ssh_credentials"; type TableName = "users" | "ssh_data" | "ssh_credentials";
/** /**
* SimpleDBOps - 简化的加密数据库操作 * SimpleDBOps - Simplified encrypted database operations
* *
* Linus式简化: * Linus-style simplification:
* - 删除所有复杂的抽象层 * - Remove all complex abstraction layers
* - 直接的CRUD操作 * - Direct CRUD operations
* - 自动加密/解密 * - Automatic encryption/decryption
* - 没有特殊情况处理 * - No special case handling
*/ */
class SimpleDBOps { class SimpleDBOps {
/** /**
* 插入加密记录 * Insert encrypted record
*/ */
static async insert<T extends Record<string, any>>( static async insert<T extends Record<string, any>>(
table: SQLiteTable<any>, table: SQLiteTable<any>,
@@ -24,18 +24,18 @@ class SimpleDBOps {
data: T, data: T,
userId: string, userId: string,
): Promise<T> { ): Promise<T> {
// 验证用户访问权限 // Verify user access permissions
if (!DataCrypto.canUserAccessData(userId)) { if (!DataCrypto.canUserAccessData(userId)) {
throw new Error(`User ${userId} data not unlocked`); throw new Error(`User ${userId} data not unlocked`);
} }
// 加密数据 // Encrypt data
const encryptedData = DataCrypto.encryptRecordForUser(tableName, data, userId); const encryptedData = DataCrypto.encryptRecordForUser(tableName, data, userId);
// 插入数据库 // Insert into database
const result = await db.insert(table).values(encryptedData).returning(); const result = await db.insert(table).values(encryptedData).returning();
// 解密返回结果 // Decrypt return result
const decryptedResult = DataCrypto.decryptRecordForUser( const decryptedResult = DataCrypto.decryptRecordForUser(
tableName, tableName,
result[0], result[0],
@@ -53,22 +53,22 @@ class SimpleDBOps {
} }
/** /**
* 查询多条记录 * Query multiple records
*/ */
static async select<T extends Record<string, any>>( static async select<T extends Record<string, any>>(
query: any, query: any,
tableName: TableName, tableName: TableName,
userId: string, userId: string,
): Promise<T[]> { ): Promise<T[]> {
// 验证用户访问权限 // Verify user access permissions
if (!DataCrypto.canUserAccessData(userId)) { if (!DataCrypto.canUserAccessData(userId)) {
throw new Error(`User ${userId} data not unlocked`); throw new Error(`User ${userId} data not unlocked`);
} }
// 执行查询 // Execute query
const results = await query; const results = await query;
// 解密结果 // Decrypt results
const decryptedResults = DataCrypto.decryptRecordsForUser( const decryptedResults = DataCrypto.decryptRecordsForUser(
tableName, tableName,
results, results,
@@ -86,23 +86,23 @@ class SimpleDBOps {
} }
/** /**
* 查询单条记录 * Query single record
*/ */
static async selectOne<T extends Record<string, any>>( static async selectOne<T extends Record<string, any>>(
query: any, query: any,
tableName: TableName, tableName: TableName,
userId: string, userId: string,
): Promise<T | undefined> { ): Promise<T | undefined> {
// 验证用户访问权限 // Verify user access permissions
if (!DataCrypto.canUserAccessData(userId)) { if (!DataCrypto.canUserAccessData(userId)) {
throw new Error(`User ${userId} data not unlocked`); throw new Error(`User ${userId} data not unlocked`);
} }
// 执行查询 // Execute query
const result = await query; const result = await query;
if (!result) return undefined; if (!result) return undefined;
// 解密结果 // Decrypt results
const decryptedResult = DataCrypto.decryptRecordForUser( const decryptedResult = DataCrypto.decryptRecordForUser(
tableName, tableName,
result, result,
@@ -120,7 +120,7 @@ class SimpleDBOps {
} }
/** /**
* 更新记录 * Update record
*/ */
static async update<T extends Record<string, any>>( static async update<T extends Record<string, any>>(
table: SQLiteTable<any>, table: SQLiteTable<any>,
@@ -129,22 +129,22 @@ class SimpleDBOps {
data: Partial<T>, data: Partial<T>,
userId: string, userId: string,
): Promise<T[]> { ): Promise<T[]> {
// 验证用户访问权限 // Verify user access permissions
if (!DataCrypto.canUserAccessData(userId)) { if (!DataCrypto.canUserAccessData(userId)) {
throw new Error(`User ${userId} data not unlocked`); throw new Error(`User ${userId} data not unlocked`);
} }
// 加密更新数据 // Encrypt update data
const encryptedData = DataCrypto.encryptRecordForUser(tableName, data, userId); const encryptedData = DataCrypto.encryptRecordForUser(tableName, data, userId);
// 执行更新 // Execute update
const result = await db const result = await db
.update(table) .update(table)
.set(encryptedData) .set(encryptedData)
.where(where) .where(where)
.returning(); .returning();
// 解密返回数据 // Decrypt return data
const decryptedResults = DataCrypto.decryptRecordsForUser( const decryptedResults = DataCrypto.decryptRecordsForUser(
tableName, tableName,
result, result,
@@ -162,7 +162,7 @@ class SimpleDBOps {
} }
/** /**
* 删除记录 * Delete record
*/ */
static async delete( static async delete(
table: SQLiteTable<any>, table: SQLiteTable<any>,
@@ -183,18 +183,18 @@ class SimpleDBOps {
} }
/** /**
* 健康检查 * Health check
*/ */
static async healthCheck(userId: string): Promise<boolean> { static async healthCheck(userId: string): Promise<boolean> {
return DataCrypto.canUserAccessData(userId); return DataCrypto.canUserAccessData(userId);
} }
/** /**
* 特殊方法:返回加密数据(用于自动启动等场景) * Special method: return encrypted data (for auto-start scenarios)
* 不解密,直接返回加密状态的数据 * No decryption, return data in encrypted state directly
*/ */
static async selectEncrypted(query: any, tableName: TableName): Promise<any[]> { static async selectEncrypted(query: any, tableName: TableName): Promise<any[]> {
// 直接执行查询,不进行解密 // Execute query directly, no decryption
const results = await query; const results = await query;
databaseLogger.debug(`Selected ${results.length} encrypted records from ${tableName}`, { databaseLogger.debug(`Selected ${results.length} encrypted records from ${tableName}`, {

View File

@@ -7,19 +7,19 @@ import { eq } from "drizzle-orm";
import { databaseLogger } from "./logger.js"; import { databaseLogger } from "./logger.js";
/** /**
* SystemCrypto - 开源友好的JWT密钥管理 * SystemCrypto - Open source friendly JWT key management
* *
* Linus原则: * Linus principles:
* - 删除复杂的"系统主密钥"层 - 不解决真实威胁 * - Remove complex "system master key" layer - doesn't solve real threats
* - 删除硬编码默认密钥 - 开源软件的安全灾难 * - Remove hardcoded default keys - security disaster for open source software
* - 首次启动自动生成 - 每个实例独立安全 * - Auto-generate on first startup - each instance independently secure
* - 简单直接,专注真正的安全边界 * - Simple and direct, focus on real security boundaries
*/ */
class SystemCrypto { class SystemCrypto {
private static instance: SystemCrypto; private static instance: SystemCrypto;
private jwtSecret: string | null = null; private jwtSecret: string | null = null;
// 存储路径配置 // Storage path configuration
private static readonly JWT_SECRET_FILE = path.join(process.cwd(), '.termix', 'jwt.key'); private static readonly JWT_SECRET_FILE = path.join(process.cwd(), '.termix', 'jwt.key');
private static readonly JWT_SECRET_DB_KEY = 'system_jwt_secret'; private static readonly JWT_SECRET_DB_KEY = 'system_jwt_secret';
@@ -33,7 +33,7 @@ class SystemCrypto {
} }
/** /**
* 初始化JWT密钥 - 开源友好的方式 * Initialize JWT secret - open source friendly way
*/ */
async initializeJWTSecret(): Promise<void> { async initializeJWTSecret(): Promise<void> {
try { try {
@@ -41,7 +41,7 @@ class SystemCrypto {
operation: "jwt_init", operation: "jwt_init",
}); });
// 1. 环境变量优先(生产环境最佳实践) // 1. Environment variable priority (production best practice)
const envSecret = process.env.JWT_SECRET; const envSecret = process.env.JWT_SECRET;
if (envSecret && envSecret.length >= 64) { if (envSecret && envSecret.length >= 64) {
this.jwtSecret = envSecret; this.jwtSecret = envSecret;
@@ -52,7 +52,7 @@ class SystemCrypto {
return; return;
} }
// 2. 检查文件系统存储 // 2. Check filesystem storage
const fileSecret = await this.loadSecretFromFile(); const fileSecret = await this.loadSecretFromFile();
if (fileSecret) { if (fileSecret) {
this.jwtSecret = fileSecret; this.jwtSecret = fileSecret;
@@ -63,7 +63,7 @@ class SystemCrypto {
return; return;
} }
// 3. 检查数据库存储 // 3. Check database storage
const dbSecret = await this.loadSecretFromDB(); const dbSecret = await this.loadSecretFromDB();
if (dbSecret) { if (dbSecret) {
this.jwtSecret = dbSecret; this.jwtSecret = dbSecret;
@@ -74,7 +74,7 @@ class SystemCrypto {
return; return;
} }
// 4. 生成新密钥并持久化 // 4. Generate new key and persist
await this.generateAndStoreSecret(); await this.generateAndStoreSecret();
} catch (error) { } catch (error) {
@@ -86,7 +86,7 @@ class SystemCrypto {
} }
/** /**
* 获取JWT密钥 * Get JWT secret
*/ */
async getJWTSecret(): Promise<string> { async getJWTSecret(): Promise<string> {
if (!this.jwtSecret) { if (!this.jwtSecret) {
@@ -96,7 +96,7 @@ class SystemCrypto {
} }
/** /**
* 生成新密钥并持久化存储 * Generate new key and persist storage
*/ */
private async generateAndStoreSecret(): Promise<void> { private async generateAndStoreSecret(): Promise<void> {
const newSecret = crypto.randomBytes(32).toString('hex'); const newSecret = crypto.randomBytes(32).toString('hex');
@@ -107,7 +107,7 @@ class SystemCrypto {
instanceId instanceId
}); });
// 尝试文件存储(优先,因为更快且不依赖数据库) // Try file storage (priority, faster and doesn't depend on database)
try { try {
await this.saveSecretToFile(newSecret); await this.saveSecretToFile(newSecret);
databaseLogger.info("✅ JWT secret saved to file", { databaseLogger.info("✅ JWT secret saved to file", {
@@ -120,7 +120,7 @@ class SystemCrypto {
error: fileError instanceof Error ? fileError.message : "Unknown error" error: fileError instanceof Error ? fileError.message : "Unknown error"
}); });
// 文件存储失败,使用数据库 // File storage failed, use database
await this.saveSecretToDB(newSecret, instanceId); await this.saveSecretToDB(newSecret, instanceId);
databaseLogger.info("✅ JWT secret saved to database", { databaseLogger.info("✅ JWT secret saved to database", {
operation: "jwt_db_saved" operation: "jwt_db_saved"
@@ -136,21 +136,21 @@ class SystemCrypto {
}); });
} }
// ===== 文件存储方法 ===== // ===== File storage methods =====
/** /**
* 保存密钥到文件 * Save key to file
*/ */
private async saveSecretToFile(secret: string): Promise<void> { private async saveSecretToFile(secret: string): Promise<void> {
const dir = path.dirname(SystemCrypto.JWT_SECRET_FILE); const dir = path.dirname(SystemCrypto.JWT_SECRET_FILE);
await fs.mkdir(dir, { recursive: true }); await fs.mkdir(dir, { recursive: true });
await fs.writeFile(SystemCrypto.JWT_SECRET_FILE, secret, { await fs.writeFile(SystemCrypto.JWT_SECRET_FILE, secret, {
mode: 0o600 // 只有owner可读写 mode: 0o600 // Only owner can read/write
}); });
} }
/** /**
* 从文件加载密钥 * Load key from file
*/ */
private async loadSecretFromFile(): Promise<string | null> { private async loadSecretFromFile(): Promise<string | null> {
try { try {
@@ -163,15 +163,15 @@ class SystemCrypto {
length: secret.length length: secret.length
}); });
} catch (error) { } catch (error) {
// 文件不存在或无法读取,这是正常的 // File doesn't exist or can't be read, this is normal
} }
return null; return null;
} }
// ===== 数据库存储方法 ===== // ===== Database storage methods =====
/** /**
* 保存密钥到数据库(明文存储,不假装加密有用) * Save key to database (plaintext storage, don't pretend encryption helps)
*/ */
private async saveSecretToDB(secret: string, instanceId: string): Promise<void> { private async saveSecretToDB(secret: string, instanceId: string): Promise<void> {
const secretData = { const secretData = {
@@ -202,7 +202,7 @@ class SystemCrypto {
} }
/** /**
* 从数据库加载密钥 * Load key from database
*/ */
private async loadSecretFromDB(): Promise<string | null> { private async loadSecretFromDB(): Promise<string | null> {
try { try {
@@ -217,7 +217,7 @@ class SystemCrypto {
const secretData = JSON.parse(result[0].value); const secretData = JSON.parse(result[0].value);
// 检查密钥有效性 // Check key validity
if (!secretData.secret || secretData.secret.length < 64) { if (!secretData.secret || secretData.secret.length < 64) {
databaseLogger.warn("Invalid JWT secret in database", { databaseLogger.warn("Invalid JWT secret in database", {
operation: "jwt_db_invalid", operation: "jwt_db_invalid",
@@ -238,7 +238,7 @@ class SystemCrypto {
} }
/** /**
* 重新生成JWT密钥管理功能 * Regenerate JWT secret (admin function)
*/ */
async regenerateJWTSecret(): Promise<string> { async regenerateJWTSecret(): Promise<string> {
databaseLogger.warn("🔄 Regenerating JWT secret - ALL TOKENS WILL BE INVALIDATED", { databaseLogger.warn("🔄 Regenerating JWT secret - ALL TOKENS WILL BE INVALIDATED", {
@@ -256,7 +256,7 @@ class SystemCrypto {
} }
/** /**
* 验证JWT密钥系统 * Validate JWT secret system
*/ */
async validateJWTSecret(): Promise<boolean> { async validateJWTSecret(): Promise<boolean> {
try { try {
@@ -265,7 +265,7 @@ class SystemCrypto {
return false; return false;
} }
// 测试JWT操作 // Test JWT operations
const jwt = await import("jsonwebtoken"); const jwt = await import("jsonwebtoken");
const testPayload = { test: true, timestamp: Date.now() }; const testPayload = { test: true, timestamp: Date.now() };
const token = jwt.default.sign(testPayload, secret, { expiresIn: "1s" }); const token = jwt.default.sign(testPayload, secret, { expiresIn: "1s" });
@@ -281,22 +281,22 @@ class SystemCrypto {
} }
/** /**
* 获取JWT密钥状态简化版本 * Get JWT key status (simplified version)
*/ */
async getSystemKeyStatus() { async getSystemKeyStatus() {
const isValid = await this.validateJWTSecret(); const isValid = await this.validateJWTSecret();
const hasSecret = this.jwtSecret !== null; const hasSecret = this.jwtSecret !== null;
// 检查文件存储 // Check file storage
let hasFileStorage = false; let hasFileStorage = false;
try { try {
await fs.access(SystemCrypto.JWT_SECRET_FILE); await fs.access(SystemCrypto.JWT_SECRET_FILE);
hasFileStorage = true; hasFileStorage = true;
} catch { } catch {
// 文件不存在 // File doesn't exist
} }
// 检查数据库存储 // Check database storage
let hasDBStorage = false; let hasDBStorage = false;
let dbInfo = null; let dbInfo = null;
try { try {
@@ -315,10 +315,10 @@ class SystemCrypto {
}; };
} }
} catch (error) { } catch (error) {
// 数据库读取失败 // Database read failed
} }
// 检查环境变量 // Check environment variable
const hasEnvVar = !!(process.env.JWT_SECRET && process.env.JWT_SECRET.length >= 64); const hasEnvVar = !!(process.env.JWT_SECRET && process.env.JWT_SECRET.length >= 64);
return { return {

View File

@@ -20,36 +20,36 @@ interface EncryptedDEK {
} }
interface UserSession { interface UserSession {
dataKey: Buffer; // 直接存储DEK删除just-in-time幻想 dataKey: Buffer; // Store DEK directly, delete just-in-time fantasy
lastActivity: number; lastActivity: number;
expiresAt: number; expiresAt: number;
} }
/** /**
* UserCrypto - 简单直接的用户加密 * UserCrypto - Simple direct user encryption
* *
* Linus原则: * Linus principles:
* - 删除just-in-time幻想直接缓存DEK * - Delete just-in-time fantasy, cache DEK directly
* - 合理的2小时超时不是5分钟的用户体验灾难 * - Reasonable 2-hour timeout, not 5-minute user experience disaster
* - 简单可工作的实现,不是理论上完美的垃圾 * - Simple working implementation, not theoretically perfect garbage
* - 服务器重启后session失效这是合理的 * - Server restart invalidates sessions (this is reasonable)
*/ */
class UserCrypto { class UserCrypto {
private static instance: UserCrypto; private static instance: UserCrypto;
private userSessions: Map<string, UserSession> = new Map(); private userSessions: Map<string, UserSession> = new Map();
// 配置常量 - 合理的超时设置 // Configuration constants - reasonable timeout settings
private static readonly PBKDF2_ITERATIONS = 100000; private static readonly PBKDF2_ITERATIONS = 100000;
private static readonly KEK_LENGTH = 32; private static readonly KEK_LENGTH = 32;
private static readonly DEK_LENGTH = 32; private static readonly DEK_LENGTH = 32;
private static readonly SESSION_DURATION = 2 * 60 * 60 * 1000; // 2小时,合理的用户体验 private static readonly SESSION_DURATION = 2 * 60 * 60 * 1000; // 2 hours, reasonable user experience
private static readonly MAX_INACTIVITY = 30 * 60 * 1000; // 30分钟不是1分钟的灾难 private static readonly MAX_INACTIVITY = 30 * 60 * 1000; // 30 minutes, not 1-minute disaster
private constructor() { private constructor() {
// 合理的清理间隔 // Reasonable cleanup interval
setInterval(() => { setInterval(() => {
this.cleanupExpiredSessions(); this.cleanupExpiredSessions();
}, 5 * 60 * 1000); // 每5分钟清理一次不是30秒 }, 5 * 60 * 1000); // Clean every 5 minutes, not 30 seconds
} }
static getInstance(): UserCrypto { static getInstance(): UserCrypto {
@@ -60,7 +60,7 @@ class UserCrypto {
} }
/** /**
* 用户注册:生成KEK saltDEK * User registration: generate KEK salt and DEK
*/ */
async setupUserEncryption(userId: string, password: string): Promise<void> { async setupUserEncryption(userId: string, password: string): Promise<void> {
const kekSalt = await this.generateKEKSalt(); const kekSalt = await this.generateKEKSalt();
@@ -71,7 +71,7 @@ class UserCrypto {
const encryptedDEK = this.encryptDEK(DEK, KEK); const encryptedDEK = this.encryptDEK(DEK, KEK);
await this.storeEncryptedDEK(userId, encryptedDEK); await this.storeEncryptedDEK(userId, encryptedDEK);
// 立即清理临时密钥 // Immediately clean temporary keys
KEK.fill(0); KEK.fill(0);
DEK.fill(0); DEK.fill(0);
@@ -82,12 +82,12 @@ class UserCrypto {
} }
/** /**
* 用户认证:验证密码并缓存DEK * User authentication: validate password and cache DEK
* 删除了just-in-time幻想,直接工作 * Deleted just-in-time fantasy, works directly
*/ */
async authenticateUser(userId: string, password: string): Promise<boolean> { async authenticateUser(userId: string, password: string): Promise<boolean> {
try { try {
// 验证密码并解密DEK // Validate password and decrypt DEK
const kekSalt = await this.getKEKSalt(userId); const kekSalt = await this.getKEKSalt(userId);
if (!kekSalt) return false; if (!kekSalt) return false;
@@ -99,24 +99,24 @@ class UserCrypto {
} }
const DEK = this.decryptDEK(encryptedDEK, KEK); const DEK = this.decryptDEK(encryptedDEK, KEK);
KEK.fill(0); // 立即清理KEK KEK.fill(0); // Immediately clean KEK
// 创建用户会话直接缓存DEK // Create user session, cache DEK directly
const now = Date.now(); const now = Date.now();
// 清理旧会话 // Clean old session
const oldSession = this.userSessions.get(userId); const oldSession = this.userSessions.get(userId);
if (oldSession) { if (oldSession) {
oldSession.dataKey.fill(0); oldSession.dataKey.fill(0);
} }
this.userSessions.set(userId, { this.userSessions.set(userId, {
dataKey: Buffer.from(DEK), // 复制DEK dataKey: Buffer.from(DEK), // Copy DEK
lastActivity: now, lastActivity: now,
expiresAt: now + UserCrypto.SESSION_DURATION, expiresAt: now + UserCrypto.SESSION_DURATION,
}); });
DEK.fill(0); // 清理临时DEK DEK.fill(0); // Clean temporary DEK
databaseLogger.success("User authenticated and DEK cached", { databaseLogger.success("User authenticated and DEK cached", {
operation: "user_crypto_auth", operation: "user_crypto_auth",
@@ -136,8 +136,8 @@ class UserCrypto {
} }
/** /**
* 获取用户数据密钥 - 简单直接从缓存返回 * Get user data key - simple direct return from cache
* 删除了just-in-time推导垃圾 * Deleted just-in-time derivation garbage
*/ */
getUserDataKey(userId: string): Buffer | null { getUserDataKey(userId: string): Buffer | null {
const session = this.userSessions.get(userId); const session = this.userSessions.get(userId);
@@ -147,7 +147,7 @@ class UserCrypto {
const now = Date.now(); const now = Date.now();
// 检查会话是否过期 // Check if session has expired
if (now > session.expiresAt) { if (now > session.expiresAt) {
this.userSessions.delete(userId); this.userSessions.delete(userId);
session.dataKey.fill(0); session.dataKey.fill(0);
@@ -158,7 +158,7 @@ class UserCrypto {
return null; return null;
} }
// 检查是否超过最大不活跃时间 // Check if max inactivity time exceeded
if (now - session.lastActivity > UserCrypto.MAX_INACTIVITY) { if (now - session.lastActivity > UserCrypto.MAX_INACTIVITY) {
this.userSessions.delete(userId); this.userSessions.delete(userId);
session.dataKey.fill(0); session.dataKey.fill(0);
@@ -169,19 +169,19 @@ class UserCrypto {
return null; return null;
} }
// 更新最后活动时间 // Update last activity time
session.lastActivity = now; session.lastActivity = now;
return session.dataKey; return session.dataKey;
} }
/** /**
* 用户登出:清理会话 * User logout: clear session
*/ */
logoutUser(userId: string): void { logoutUser(userId: string): void {
const session = this.userSessions.get(userId); const session = this.userSessions.get(userId);
if (session) { if (session) {
session.dataKey.fill(0); // 安全清理密钥 session.dataKey.fill(0); // Securely clear key
this.userSessions.delete(userId); this.userSessions.delete(userId);
} }
databaseLogger.info("User logged out", { databaseLogger.info("User logged out", {
@@ -191,22 +191,22 @@ class UserCrypto {
} }
/** /**
* 检查用户是否已解锁 * Check if user is unlocked
*/ */
isUserUnlocked(userId: string): boolean { isUserUnlocked(userId: string): boolean {
return this.getUserDataKey(userId) !== null; return this.getUserDataKey(userId) !== null;
} }
/** /**
* 修改用户密码 * Change user password
*/ */
async changeUserPassword(userId: string, oldPassword: string, newPassword: string): Promise<boolean> { async changeUserPassword(userId: string, oldPassword: string, newPassword: string): Promise<boolean> {
try { try {
// 验证旧密码 // Validate old password
const isValid = await this.validatePassword(userId, oldPassword); const isValid = await this.validatePassword(userId, oldPassword);
if (!isValid) return false; if (!isValid) return false;
// 获取当前DEK // Get current DEK
const kekSalt = await this.getKEKSalt(userId); const kekSalt = await this.getKEKSalt(userId);
if (!kekSalt) return false; if (!kekSalt) return false;
@@ -216,21 +216,21 @@ class UserCrypto {
const DEK = this.decryptDEK(encryptedDEK, oldKEK); const DEK = this.decryptDEK(encryptedDEK, oldKEK);
// 生成新的KEK salt和加密DEK // Generate new KEK salt and encrypt DEK
const newKekSalt = await this.generateKEKSalt(); const newKekSalt = await this.generateKEKSalt();
const newKEK = this.deriveKEK(newPassword, newKekSalt); const newKEK = this.deriveKEK(newPassword, newKekSalt);
const newEncryptedDEK = this.encryptDEK(DEK, newKEK); const newEncryptedDEK = this.encryptDEK(DEK, newKEK);
// 存储新的salt和encrypted DEK // Store new salt and encrypted DEK
await this.storeKEKSalt(userId, newKekSalt); await this.storeKEKSalt(userId, newKekSalt);
await this.storeEncryptedDEK(userId, newEncryptedDEK); await this.storeEncryptedDEK(userId, newEncryptedDEK);
// 清理所有临时密钥 // Clean all temporary keys
oldKEK.fill(0); oldKEK.fill(0);
newKEK.fill(0); newKEK.fill(0);
DEK.fill(0); DEK.fill(0);
// 清理用户会话,要求重新登录 // Clean user session, require re-login
this.logoutUser(userId); this.logoutUser(userId);
return true; return true;
@@ -239,7 +239,7 @@ class UserCrypto {
} }
} }
// ===== 私有方法 ===== // ===== Private methods =====
private async validatePassword(userId: string, password: string): Promise<boolean> { private async validatePassword(userId: string, password: string): Promise<boolean> {
try { try {
@@ -252,7 +252,7 @@ class UserCrypto {
const DEK = this.decryptDEK(encryptedDEK, KEK); const DEK = this.decryptDEK(encryptedDEK, KEK);
// 清理临时密钥 // Clean temporary keys
KEK.fill(0); KEK.fill(0);
DEK.fill(0); DEK.fill(0);
@@ -268,7 +268,7 @@ class UserCrypto {
for (const [userId, session] of this.userSessions.entries()) { for (const [userId, session] of this.userSessions.entries()) {
if (now > session.expiresAt || now - session.lastActivity > UserCrypto.MAX_INACTIVITY) { if (now > session.expiresAt || now - session.lastActivity > UserCrypto.MAX_INACTIVITY) {
session.dataKey.fill(0); // 安全清理密钥 session.dataKey.fill(0); // Securely clear key
expiredUsers.push(userId); expiredUsers.push(userId);
} }
} }
@@ -285,7 +285,7 @@ class UserCrypto {
} }
} }
// ===== 数据库操作和加密方法(简化版本) ===== // ===== Database operations and encryption methods (simplified version) =====
private async generateKEKSalt(): Promise<KEKSalt> { private async generateKEKSalt(): Promise<KEKSalt> {
return { return {
@@ -337,7 +337,7 @@ class UserCrypto {
return decrypted; return decrypted;
} }
// 数据库操作方法 // Database operation methods
private async storeKEKSalt(userId: string, kekSalt: KEKSalt): Promise<void> { private async storeKEKSalt(userId: string, kekSalt: KEKSalt): Promise<void> {
const key = `user_kek_salt_${userId}`; const key = `user_kek_salt_${userId}`;
const value = JSON.stringify(kekSalt); const value = JSON.stringify(kekSalt);

View File

@@ -28,19 +28,19 @@ interface UserExportData {
} }
/** /**
* UserDataExport - 用户级数据导入导出 * UserDataExport - User-level data import/export
* *
* Linus原则: * Linus principles:
* - 用户拥有自己的数据,应该能自由导出 * - Users own their data and should be able to export freely
* - 简单直接,没有复杂的权限检查 * - Simple and direct, no complex permission checks
* - 支持加密和明文两种格式 * - Support both encrypted and plaintext formats
* - 不破坏现有系统架构 * - Don't break existing system architecture
*/ */
class UserDataExport { class UserDataExport {
private static readonly EXPORT_VERSION = "v2.0"; private static readonly EXPORT_VERSION = "v2.0";
/** /**
* 导出用户数据 * Export user data
*/ */
static async exportUserData( static async exportUserData(
userId: string, userId: string,
@@ -61,7 +61,7 @@ class UserDataExport {
includeCredentials, includeCredentials,
}); });
// 验证用户存在 // Verify user exists
const user = await db.select().from(users).where(eq(users.id, userId)); const user = await db.select().from(users).where(eq(users.id, userId));
if (!user || user.length === 0) { if (!user || user.length === 0) {
throw new Error(`User not found: ${userId}`); throw new Error(`User not found: ${userId}`);
@@ -69,7 +69,7 @@ class UserDataExport {
const userRecord = user[0]; const userRecord = user[0];
// 获取用户数据密钥(如果需要解密) // Get user data key (if decryption needed)
let userDataKey: Buffer | null = null; let userDataKey: Buffer | null = null;
if (format === 'plaintext') { if (format === 'plaintext') {
userDataKey = DataCrypto.getUserDataKey(userId); userDataKey = DataCrypto.getUserDataKey(userId);
@@ -78,13 +78,13 @@ class UserDataExport {
} }
} }
// 导出SSH主机配置 // Export SSH host configurations
const sshHosts = await db.select().from(sshData).where(eq(sshData.userId, userId)); const sshHosts = await db.select().from(sshData).where(eq(sshData.userId, userId));
const processedSshHosts = format === 'plaintext' && userDataKey const processedSshHosts = format === 'plaintext' && userDataKey
? sshHosts.map(host => DataCrypto.decryptRecord("ssh_data", host, userId, userDataKey!)) ? sshHosts.map(host => DataCrypto.decryptRecord("ssh_data", host, userId, userDataKey!))
: sshHosts; : sshHosts;
// 导出SSH凭据如果包含 // Export SSH credentials (if included)
let sshCredentialsData: any[] = []; let sshCredentialsData: any[] = [];
if (includeCredentials) { if (includeCredentials) {
const credentials = await db.select().from(sshCredentials).where(eq(sshCredentials.userId, userId)); const credentials = await db.select().from(sshCredentials).where(eq(sshCredentials.userId, userId));
@@ -93,17 +93,17 @@ class UserDataExport {
: credentials; : credentials;
} }
// 导出文件管理器数据 // Export file manager data
const [recentFiles, pinnedFiles, shortcuts] = await Promise.all([ const [recentFiles, pinnedFiles, shortcuts] = await Promise.all([
db.select().from(fileManagerRecent).where(eq(fileManagerRecent.userId, userId)), db.select().from(fileManagerRecent).where(eq(fileManagerRecent.userId, userId)),
db.select().from(fileManagerPinned).where(eq(fileManagerPinned.userId, userId)), db.select().from(fileManagerPinned).where(eq(fileManagerPinned.userId, userId)),
db.select().from(fileManagerShortcuts).where(eq(fileManagerShortcuts.userId, userId)), db.select().from(fileManagerShortcuts).where(eq(fileManagerShortcuts.userId, userId)),
]); ]);
// 导出已忽略的警告 // Export dismissed alerts
const alerts = await db.select().from(dismissedAlerts).where(eq(dismissedAlerts.userId, userId)); const alerts = await db.select().from(dismissedAlerts).where(eq(dismissedAlerts.userId, userId));
// 构建导出数据 // Build export data
const exportData: UserExportData = { const exportData: UserExportData = {
version: this.EXPORT_VERSION, version: this.EXPORT_VERSION,
exportedAt: new Date().toISOString(), exportedAt: new Date().toISOString(),
@@ -148,7 +148,7 @@ class UserDataExport {
} }
/** /**
* 导出为JSON字符串 * Export as JSON string
*/ */
static async exportUserDataToJSON( static async exportUserDataToJSON(
userId: string, userId: string,
@@ -165,7 +165,7 @@ class UserDataExport {
} }
/** /**
* 验证导出数据格式 * Validate export data format
*/ */
static validateExportData(data: any): { valid: boolean; errors: string[] } { static validateExportData(data: any): { valid: boolean; errors: string[] } {
const errors: string[] = []; const errors: string[] = [];
@@ -191,7 +191,7 @@ class UserDataExport {
errors.push("Missing or invalid metadata field"); errors.push("Missing or invalid metadata field");
} }
// 检查必需的数据字段 // Check required data fields
if (data.userData) { if (data.userData) {
const requiredFields = ['sshHosts', 'sshCredentials', 'fileManagerData', 'dismissedAlerts']; const requiredFields = ['sshHosts', 'sshCredentials', 'fileManagerData', 'dismissedAlerts'];
for (const field of requiredFields) { for (const field of requiredFields) {
@@ -214,7 +214,7 @@ class UserDataExport {
} }
/** /**
* 获取导出数据统计信息 * Get export data statistics
*/ */
static getExportStats(data: UserExportData): { static getExportStats(data: UserExportData): {
version: string; version: string;

View File

@@ -27,18 +27,18 @@ interface ImportResult {
} }
/** /**
* UserDataImport - 用户数据导入 * UserDataImport - User data import
* *
* Linus原则: * Linus principles:
* - 导入不应该破坏现有数据(除非明确要求) * - Import should not break existing data (unless explicitly requested)
* - 支持dry-run模式验证 * - Support dry-run mode for validation
* - 处理ID冲突的简单策略重新生成 * - Simple strategy for ID conflicts: regenerate
* - 错误处理要明确,不能静默失败 * - Error handling must be explicit, no silent failures
*/ */
class UserDataImport { class UserDataImport {
/** /**
* 导入用户数据 * Import user data
*/ */
static async importUserData( static async importUserData(
targetUserId: string, targetUserId: string,
@@ -64,19 +64,19 @@ class UserDataImport {
skipFileManagerData, skipFileManagerData,
}); });
// 验证目标用户存在 // Verify target user exists
const targetUser = await db.select().from(users).where(eq(users.id, targetUserId)); const targetUser = await db.select().from(users).where(eq(users.id, targetUserId));
if (!targetUser || targetUser.length === 0) { if (!targetUser || targetUser.length === 0) {
throw new Error(`Target user not found: ${targetUserId}`); throw new Error(`Target user not found: ${targetUserId}`);
} }
// 验证导出数据格式 // Validate export data format
const validation = UserDataExport.validateExportData(exportData); const validation = UserDataExport.validateExportData(exportData);
if (!validation.valid) { if (!validation.valid) {
throw new Error(`Invalid export data: ${validation.errors.join(', ')}`); throw new Error(`Invalid export data: ${validation.errors.join(', ')}`);
} }
// 验证用户数据已解锁(如果数据是加密的) // Verify user data is unlocked (if data is encrypted)
let userDataKey: Buffer | null = null; let userDataKey: Buffer | null = null;
if (exportData.metadata.encrypted) { if (exportData.metadata.encrypted) {
userDataKey = DataCrypto.getUserDataKey(targetUserId); userDataKey = DataCrypto.getUserDataKey(targetUserId);
@@ -98,7 +98,7 @@ class UserDataImport {
dryRun, dryRun,
}; };
// 导入SSH主机配置 // Import SSH host configurations
if (exportData.userData.sshHosts && exportData.userData.sshHosts.length > 0) { if (exportData.userData.sshHosts && exportData.userData.sshHosts.length > 0) {
const importStats = await this.importSshHosts( const importStats = await this.importSshHosts(
targetUserId, targetUserId,
@@ -110,7 +110,7 @@ class UserDataImport {
result.summary.errors.push(...importStats.errors); result.summary.errors.push(...importStats.errors);
} }
// 导入SSH凭据 // Import SSH credentials
if (!skipCredentials && exportData.userData.sshCredentials && exportData.userData.sshCredentials.length > 0) { if (!skipCredentials && exportData.userData.sshCredentials && exportData.userData.sshCredentials.length > 0) {
const importStats = await this.importSshCredentials( const importStats = await this.importSshCredentials(
targetUserId, targetUserId,
@@ -122,7 +122,7 @@ class UserDataImport {
result.summary.errors.push(...importStats.errors); result.summary.errors.push(...importStats.errors);
} }
// 导入文件管理器数据 // Import file manager data
if (!skipFileManagerData && exportData.userData.fileManagerData) { if (!skipFileManagerData && exportData.userData.fileManagerData) {
const importStats = await this.importFileManagerData( const importStats = await this.importFileManagerData(
targetUserId, targetUserId,
@@ -134,7 +134,7 @@ class UserDataImport {
result.summary.errors.push(...importStats.errors); result.summary.errors.push(...importStats.errors);
} }
// 导入忽略的警告 // Import dismissed alerts
if (exportData.userData.dismissedAlerts && exportData.userData.dismissedAlerts.length > 0) { if (exportData.userData.dismissedAlerts && exportData.userData.dismissedAlerts.length > 0) {
const importStats = await this.importDismissedAlerts( const importStats = await this.importDismissedAlerts(
targetUserId, targetUserId,
@@ -167,7 +167,7 @@ class UserDataImport {
} }
/** /**
* 导入SSH主机配置 * Import SSH host configurations
*/ */
private static async importSshHosts( private static async importSshHosts(
targetUserId: string, targetUserId: string,
@@ -185,16 +185,16 @@ class UserDataImport {
continue; continue;
} }
// 重新生成ID避免冲突 // Regenerate ID to avoid conflicts
const newHostData = { const newHostData = {
...host, ...host,
id: undefined, // 让数据库自动生成 id: undefined, // Let database auto-generate
userId: targetUserId, userId: targetUserId,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}; };
// 如果数据需要重新加密 // If data needs re-encryption
let processedHostData = newHostData; let processedHostData = newHostData;
if (options.userDataKey) { if (options.userDataKey) {
processedHostData = DataCrypto.encryptRecord("ssh_data", newHostData, targetUserId, options.userDataKey); processedHostData = DataCrypto.encryptRecord("ssh_data", newHostData, targetUserId, options.userDataKey);
@@ -212,7 +212,7 @@ class UserDataImport {
} }
/** /**
* 导入SSH凭据 * Import SSH credentials
*/ */
private static async importSshCredentials( private static async importSshCredentials(
targetUserId: string, targetUserId: string,
@@ -230,18 +230,18 @@ class UserDataImport {
continue; continue;
} }
// 重新生成ID避免冲突 // Regenerate ID to avoid conflicts
const newCredentialData = { const newCredentialData = {
...credential, ...credential,
id: undefined, // 让数据库自动生成 id: undefined, // Let database auto-generate
userId: targetUserId, userId: targetUserId,
usageCount: 0, // 重置使用计数 usageCount: 0, // Reset usage count
lastUsed: null, lastUsed: null,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}; };
// 如果数据需要重新加密 // If data needs re-encryption
let processedCredentialData = newCredentialData; let processedCredentialData = newCredentialData;
if (options.userDataKey) { if (options.userDataKey) {
processedCredentialData = DataCrypto.encryptRecord("ssh_credentials", newCredentialData, targetUserId, options.userDataKey); processedCredentialData = DataCrypto.encryptRecord("ssh_credentials", newCredentialData, targetUserId, options.userDataKey);
@@ -259,7 +259,7 @@ class UserDataImport {
} }
/** /**
* 导入文件管理器数据 * Import file manager data
*/ */
private static async importFileManagerData( private static async importFileManagerData(
targetUserId: string, targetUserId: string,
@@ -271,7 +271,7 @@ class UserDataImport {
const errors: string[] = []; const errors: string[] = [];
try { try {
// 导入最近文件 // Import recent files
if (fileManagerData.recent && Array.isArray(fileManagerData.recent)) { if (fileManagerData.recent && Array.isArray(fileManagerData.recent)) {
for (const item of fileManagerData.recent) { for (const item of fileManagerData.recent) {
try { try {
@@ -292,7 +292,7 @@ class UserDataImport {
} }
} }
// 导入固定文件 // Import pinned files
if (fileManagerData.pinned && Array.isArray(fileManagerData.pinned)) { if (fileManagerData.pinned && Array.isArray(fileManagerData.pinned)) {
for (const item of fileManagerData.pinned) { for (const item of fileManagerData.pinned) {
try { try {
@@ -313,7 +313,7 @@ class UserDataImport {
} }
} }
// 导入快捷方式 // Import shortcuts
if (fileManagerData.shortcuts && Array.isArray(fileManagerData.shortcuts)) { if (fileManagerData.shortcuts && Array.isArray(fileManagerData.shortcuts)) {
for (const item of fileManagerData.shortcuts) { for (const item of fileManagerData.shortcuts) {
try { try {
@@ -341,7 +341,7 @@ class UserDataImport {
} }
/** /**
* 导入忽略的警告 * Import dismissed alerts
*/ */
private static async importDismissedAlerts( private static async importDismissedAlerts(
targetUserId: string, targetUserId: string,
@@ -359,7 +359,7 @@ class UserDataImport {
continue; continue;
} }
// 检查是否已存在相同的警告 // Check if alert already exists
const existing = await db const existing = await db
.select() .select()
.from(dismissedAlerts) .from(dismissedAlerts)
@@ -402,7 +402,7 @@ class UserDataImport {
} }
/** /**
* 从JSON字符串导入 * Import from JSON string
*/ */
static async importUserDataFromJSON( static async importUserDataFromJSON(
targetUserId: string, targetUserId: string,