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

View File

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

View File

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

View File

@@ -23,14 +23,14 @@ interface JWTPayload {
}
/**
* AuthManager - 简化的认证管理器
* AuthManager - Simplified authentication manager
*
* 职责:
* - JWT生成和验证
* - 认证中间件
* - 用户登录登出
* Responsibilities:
* - JWT generation and validation
* - Authentication middleware
* - User login/logout
*
* 不再有两层session - 直接使用UserKeyManager
* No more two-layer sessions - use UserKeyManager directly
*/
class AuthManager {
private static instance: AuthManager;
@@ -50,7 +50,7 @@ class AuthManager {
}
/**
* 初始化认证系统
* Initialize authentication system
*/
async initialize(): Promise<void> {
await this.systemCrypto.initializeJWTSecret();
@@ -60,21 +60,21 @@ class AuthManager {
}
/**
* 用户注册
* User registration
*/
async registerUser(userId: string, password: string): Promise<void> {
await this.userCrypto.setupUserEncryption(userId, password);
}
/**
* 用户登录 - 使用UserCrypto
* User login - use UserCrypto
*/
async authenticateUser(userId: string, password: string): Promise<boolean> {
return await this.userCrypto.authenticateUser(userId, password);
}
/**
* 生成JWT Token
* Generate JWT Token
*/
async generateJWTToken(
userId: string,
@@ -93,7 +93,7 @@ class AuthManager {
}
/**
* 验证JWT Token
* Verify JWT Token
*/
async verifyJWTToken(token: string): Promise<JWTPayload | null> {
try {
@@ -105,7 +105,7 @@ class AuthManager {
}
/**
* 认证中间件
* Authentication middleware
*/
createAuthMiddleware() {
return async (req: Request, res: Response, next: NextFunction) => {
@@ -128,7 +128,7 @@ class AuthManager {
}
/**
* 数据访问中间件 - 要求用户已解锁数据
* Data access middleware - requires user to have unlocked data
*/
createDataAccessMiddleware() {
return async (req: Request, res: Response, next: NextFunction) => {
@@ -151,28 +151,28 @@ class AuthManager {
}
/**
* 用户登出
* User logout
*/
logoutUser(userId: string): void {
this.userCrypto.logoutUser(userId);
}
/**
* 获取用户数据密钥
* Get user data key
*/
getUserDataKey(userId: string): Buffer | null {
return this.userCrypto.getUserDataKey(userId);
}
/**
* 检查用户是否已解锁
* Check if user is unlocked
*/
isUserUnlocked(userId: string): boolean {
return this.userCrypto.isUserUnlocked(userId);
}
/**
* 修改用户密码
* Change user password
*/
async changeUserPassword(userId: string, oldPassword: string, newPassword: string): Promise<boolean> {
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";
/**
* DataCrypto - 简化的数据库加密
* DataCrypto - Simplified database encryption
*
* Linus原则:
* - 删除所有"向后兼容"垃圾
* - 删除所有特殊情况处理
* - 数据要么正确加密,要么操作失败
* - 没有legacy data概念
* Linus principles:
* - Remove all "backward compatibility" garbage
* - Remove all special case handling
* - Data is either properly encrypted or operation fails
* - No legacy data concept
*/
class DataCrypto {
private static userCrypto: UserCrypto;
@@ -43,12 +43,12 @@ class DataCrypto {
}
/**
* 解密记录 - 要么成功,要么失败
* Decrypt record - either succeeds or fails
*
* 删除了所有的:
* - isEncrypted()检查
* - legacy data处理
* - "向后兼容"逻辑
* Removed all:
* - isEncrypted() checks
* - legacy data handling
* - "backward compatibility" logic
* - migration on access
*/
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)) {
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(
value as string,
userDataKey,

View File

@@ -8,13 +8,13 @@ interface EncryptedData {
}
/**
* FieldCrypto - 简单直接的字段加密
* FieldCrypto - Simple direct field encryption
*
* Linus原则:
* - 没有特殊情况
* - 没有兼容性检查
* - 数据要么加密,要么失败
* - 不存在"legacy data"概念
* Linus principles:
* - No special cases
* - No compatibility checks
* - Data is either encrypted or fails
* - No "legacy data" concept
*/
class FieldCrypto {
private static readonly ALGORITHM = "aes-256-gcm";
@@ -22,7 +22,7 @@ class FieldCrypto {
private static readonly IV_LENGTH = 16;
private static readonly SALT_LENGTH = 32;
// 需要加密的字段 - 简单的映射,没有复杂逻辑
// Fields requiring encryption - simple mapping, no complex logic
private static readonly ENCRYPTED_FIELDS = {
users: new Set(["password_hash", "client_secret", "totp_secret", "totp_backup_codes", "oidc_identifier"]),
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 {
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 {
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 {
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";
/**
* SimpleDBOps - 简化的加密数据库操作
* SimpleDBOps - Simplified encrypted database operations
*
* Linus式简化:
* - 删除所有复杂的抽象层
* - 直接的CRUD操作
* - 自动加密/解密
* - 没有特殊情况处理
* Linus-style simplification:
* - Remove all complex abstraction layers
* - Direct CRUD operations
* - Automatic encryption/decryption
* - No special case handling
*/
class SimpleDBOps {
/**
* 插入加密记录
* Insert encrypted record
*/
static async insert<T extends Record<string, any>>(
table: SQLiteTable<any>,
@@ -24,18 +24,18 @@ class SimpleDBOps {
data: T,
userId: string,
): Promise<T> {
// 验证用户访问权限
// Verify user access permissions
if (!DataCrypto.canUserAccessData(userId)) {
throw new Error(`User ${userId} data not unlocked`);
}
// 加密数据
// Encrypt data
const encryptedData = DataCrypto.encryptRecordForUser(tableName, data, userId);
// 插入数据库
// Insert into database
const result = await db.insert(table).values(encryptedData).returning();
// 解密返回结果
// Decrypt return result
const decryptedResult = DataCrypto.decryptRecordForUser(
tableName,
result[0],
@@ -53,22 +53,22 @@ class SimpleDBOps {
}
/**
* 查询多条记录
* Query multiple records
*/
static async select<T extends Record<string, any>>(
query: any,
tableName: TableName,
userId: string,
): Promise<T[]> {
// 验证用户访问权限
// Verify user access permissions
if (!DataCrypto.canUserAccessData(userId)) {
throw new Error(`User ${userId} data not unlocked`);
}
// 执行查询
// Execute query
const results = await query;
// 解密结果
// Decrypt results
const decryptedResults = DataCrypto.decryptRecordsForUser(
tableName,
results,
@@ -86,23 +86,23 @@ class SimpleDBOps {
}
/**
* 查询单条记录
* Query single record
*/
static async selectOne<T extends Record<string, any>>(
query: any,
tableName: TableName,
userId: string,
): Promise<T | undefined> {
// 验证用户访问权限
// Verify user access permissions
if (!DataCrypto.canUserAccessData(userId)) {
throw new Error(`User ${userId} data not unlocked`);
}
// 执行查询
// Execute query
const result = await query;
if (!result) return undefined;
// 解密结果
// Decrypt results
const decryptedResult = DataCrypto.decryptRecordForUser(
tableName,
result,
@@ -120,7 +120,7 @@ class SimpleDBOps {
}
/**
* 更新记录
* Update record
*/
static async update<T extends Record<string, any>>(
table: SQLiteTable<any>,
@@ -129,22 +129,22 @@ class SimpleDBOps {
data: Partial<T>,
userId: string,
): Promise<T[]> {
// 验证用户访问权限
// Verify user access permissions
if (!DataCrypto.canUserAccessData(userId)) {
throw new Error(`User ${userId} data not unlocked`);
}
// 加密更新数据
// Encrypt update data
const encryptedData = DataCrypto.encryptRecordForUser(tableName, data, userId);
// 执行更新
// Execute update
const result = await db
.update(table)
.set(encryptedData)
.where(where)
.returning();
// 解密返回数据
// Decrypt return data
const decryptedResults = DataCrypto.decryptRecordsForUser(
tableName,
result,
@@ -162,7 +162,7 @@ class SimpleDBOps {
}
/**
* 删除记录
* Delete record
*/
static async delete(
table: SQLiteTable<any>,
@@ -183,18 +183,18 @@ class SimpleDBOps {
}
/**
* 健康检查
* Health check
*/
static async healthCheck(userId: string): Promise<boolean> {
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[]> {
// 直接执行查询,不进行解密
// Execute query directly, no decryption
const results = await query;
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";
/**
* 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 {
private static instance: SystemCrypto;
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_DB_KEY = 'system_jwt_secret';
@@ -33,7 +33,7 @@ class SystemCrypto {
}
/**
* 初始化JWT密钥 - 开源友好的方式
* Initialize JWT secret - open source friendly way
*/
async initializeJWTSecret(): Promise<void> {
try {
@@ -41,7 +41,7 @@ class SystemCrypto {
operation: "jwt_init",
});
// 1. 环境变量优先(生产环境最佳实践)
// 1. Environment variable priority (production best practice)
const envSecret = process.env.JWT_SECRET;
if (envSecret && envSecret.length >= 64) {
this.jwtSecret = envSecret;
@@ -52,7 +52,7 @@ class SystemCrypto {
return;
}
// 2. 检查文件系统存储
// 2. Check filesystem storage
const fileSecret = await this.loadSecretFromFile();
if (fileSecret) {
this.jwtSecret = fileSecret;
@@ -63,7 +63,7 @@ class SystemCrypto {
return;
}
// 3. 检查数据库存储
// 3. Check database storage
const dbSecret = await this.loadSecretFromDB();
if (dbSecret) {
this.jwtSecret = dbSecret;
@@ -74,7 +74,7 @@ class SystemCrypto {
return;
}
// 4. 生成新密钥并持久化
// 4. Generate new key and persist
await this.generateAndStoreSecret();
} catch (error) {
@@ -86,7 +86,7 @@ class SystemCrypto {
}
/**
* 获取JWT密钥
* Get JWT secret
*/
async getJWTSecret(): Promise<string> {
if (!this.jwtSecret) {
@@ -96,7 +96,7 @@ class SystemCrypto {
}
/**
* 生成新密钥并持久化存储
* Generate new key and persist storage
*/
private async generateAndStoreSecret(): Promise<void> {
const newSecret = crypto.randomBytes(32).toString('hex');
@@ -107,7 +107,7 @@ class SystemCrypto {
instanceId
});
// 尝试文件存储(优先,因为更快且不依赖数据库)
// Try file storage (priority, faster and doesn't depend on database)
try {
await this.saveSecretToFile(newSecret);
databaseLogger.info("✅ JWT secret saved to file", {
@@ -120,7 +120,7 @@ class SystemCrypto {
error: fileError instanceof Error ? fileError.message : "Unknown error"
});
// 文件存储失败,使用数据库
// File storage failed, use database
await this.saveSecretToDB(newSecret, instanceId);
databaseLogger.info("✅ JWT secret saved to database", {
operation: "jwt_db_saved"
@@ -136,21 +136,21 @@ class SystemCrypto {
});
}
// ===== 文件存储方法 =====
// ===== File storage methods =====
/**
* 保存密钥到文件
* Save key to file
*/
private async saveSecretToFile(secret: string): Promise<void> {
const dir = path.dirname(SystemCrypto.JWT_SECRET_FILE);
await fs.mkdir(dir, { recursive: true });
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> {
try {
@@ -163,15 +163,15 @@ class SystemCrypto {
length: secret.length
});
} catch (error) {
// 文件不存在或无法读取,这是正常的
// File doesn't exist or can't be read, this is normal
}
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> {
const secretData = {
@@ -202,7 +202,7 @@ class SystemCrypto {
}
/**
* 从数据库加载密钥
* Load key from database
*/
private async loadSecretFromDB(): Promise<string | null> {
try {
@@ -217,7 +217,7 @@ class SystemCrypto {
const secretData = JSON.parse(result[0].value);
// 检查密钥有效性
// Check key validity
if (!secretData.secret || secretData.secret.length < 64) {
databaseLogger.warn("Invalid JWT secret in database", {
operation: "jwt_db_invalid",
@@ -238,7 +238,7 @@ class SystemCrypto {
}
/**
* 重新生成JWT密钥管理功能
* Regenerate JWT secret (admin function)
*/
async regenerateJWTSecret(): Promise<string> {
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> {
try {
@@ -265,7 +265,7 @@ class SystemCrypto {
return false;
}
// 测试JWT操作
// Test JWT operations
const jwt = await import("jsonwebtoken");
const testPayload = { test: true, timestamp: Date.now() };
const token = jwt.default.sign(testPayload, secret, { expiresIn: "1s" });
@@ -281,22 +281,22 @@ class SystemCrypto {
}
/**
* 获取JWT密钥状态简化版本
* Get JWT key status (simplified version)
*/
async getSystemKeyStatus() {
const isValid = await this.validateJWTSecret();
const hasSecret = this.jwtSecret !== null;
// 检查文件存储
// Check file storage
let hasFileStorage = false;
try {
await fs.access(SystemCrypto.JWT_SECRET_FILE);
hasFileStorage = true;
} catch {
// 文件不存在
// File doesn't exist
}
// 检查数据库存储
// Check database storage
let hasDBStorage = false;
let dbInfo = null;
try {
@@ -315,10 +315,10 @@ class SystemCrypto {
};
}
} catch (error) {
// 数据库读取失败
// Database read failed
}
// 检查环境变量
// Check environment variable
const hasEnvVar = !!(process.env.JWT_SECRET && process.env.JWT_SECRET.length >= 64);
return {

View File

@@ -20,36 +20,36 @@ interface EncryptedDEK {
}
interface UserSession {
dataKey: Buffer; // 直接存储DEK删除just-in-time幻想
dataKey: Buffer; // Store DEK directly, delete just-in-time fantasy
lastActivity: number;
expiresAt: number;
}
/**
* UserCrypto - 简单直接的用户加密
* UserCrypto - Simple direct user encryption
*
* Linus原则:
* - 删除just-in-time幻想直接缓存DEK
* - 合理的2小时超时不是5分钟的用户体验灾难
* - 简单可工作的实现,不是理论上完美的垃圾
* - 服务器重启后session失效这是合理的
* Linus principles:
* - Delete just-in-time fantasy, cache DEK directly
* - Reasonable 2-hour timeout, not 5-minute user experience disaster
* - Simple working implementation, not theoretically perfect garbage
* - Server restart invalidates sessions (this is reasonable)
*/
class UserCrypto {
private static instance: UserCrypto;
private userSessions: Map<string, UserSession> = new Map();
// 配置常量 - 合理的超时设置
// Configuration constants - reasonable timeout settings
private static readonly PBKDF2_ITERATIONS = 100000;
private static readonly KEK_LENGTH = 32;
private static readonly DEK_LENGTH = 32;
private static readonly SESSION_DURATION = 2 * 60 * 60 * 1000; // 2小时,合理的用户体验
private static readonly MAX_INACTIVITY = 30 * 60 * 1000; // 30分钟不是1分钟的灾难
private static readonly SESSION_DURATION = 2 * 60 * 60 * 1000; // 2 hours, reasonable user experience
private static readonly MAX_INACTIVITY = 30 * 60 * 1000; // 30 minutes, not 1-minute disaster
private constructor() {
// 合理的清理间隔
// Reasonable cleanup interval
setInterval(() => {
this.cleanupExpiredSessions();
}, 5 * 60 * 1000); // 每5分钟清理一次不是30秒
}, 5 * 60 * 1000); // Clean every 5 minutes, not 30 seconds
}
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> {
const kekSalt = await this.generateKEKSalt();
@@ -71,7 +71,7 @@ class UserCrypto {
const encryptedDEK = this.encryptDEK(DEK, KEK);
await this.storeEncryptedDEK(userId, encryptedDEK);
// 立即清理临时密钥
// Immediately clean temporary keys
KEK.fill(0);
DEK.fill(0);
@@ -82,12 +82,12 @@ class UserCrypto {
}
/**
* 用户认证:验证密码并缓存DEK
* 删除了just-in-time幻想,直接工作
* User authentication: validate password and cache DEK
* Deleted just-in-time fantasy, works directly
*/
async authenticateUser(userId: string, password: string): Promise<boolean> {
try {
// 验证密码并解密DEK
// Validate password and decrypt DEK
const kekSalt = await this.getKEKSalt(userId);
if (!kekSalt) return false;
@@ -99,24 +99,24 @@ class UserCrypto {
}
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();
// 清理旧会话
// Clean old session
const oldSession = this.userSessions.get(userId);
if (oldSession) {
oldSession.dataKey.fill(0);
}
this.userSessions.set(userId, {
dataKey: Buffer.from(DEK), // 复制DEK
dataKey: Buffer.from(DEK), // Copy DEK
lastActivity: now,
expiresAt: now + UserCrypto.SESSION_DURATION,
});
DEK.fill(0); // 清理临时DEK
DEK.fill(0); // Clean temporary DEK
databaseLogger.success("User authenticated and DEK cached", {
operation: "user_crypto_auth",
@@ -136,8 +136,8 @@ class UserCrypto {
}
/**
* 获取用户数据密钥 - 简单直接从缓存返回
* 删除了just-in-time推导垃圾
* Get user data key - simple direct return from cache
* Deleted just-in-time derivation garbage
*/
getUserDataKey(userId: string): Buffer | null {
const session = this.userSessions.get(userId);
@@ -147,7 +147,7 @@ class UserCrypto {
const now = Date.now();
// 检查会话是否过期
// Check if session has expired
if (now > session.expiresAt) {
this.userSessions.delete(userId);
session.dataKey.fill(0);
@@ -158,7 +158,7 @@ class UserCrypto {
return null;
}
// 检查是否超过最大不活跃时间
// Check if max inactivity time exceeded
if (now - session.lastActivity > UserCrypto.MAX_INACTIVITY) {
this.userSessions.delete(userId);
session.dataKey.fill(0);
@@ -169,19 +169,19 @@ class UserCrypto {
return null;
}
// 更新最后活动时间
// Update last activity time
session.lastActivity = now;
return session.dataKey;
}
/**
* 用户登出:清理会话
* User logout: clear session
*/
logoutUser(userId: string): void {
const session = this.userSessions.get(userId);
if (session) {
session.dataKey.fill(0); // 安全清理密钥
session.dataKey.fill(0); // Securely clear key
this.userSessions.delete(userId);
}
databaseLogger.info("User logged out", {
@@ -191,22 +191,22 @@ class UserCrypto {
}
/**
* 检查用户是否已解锁
* Check if user is unlocked
*/
isUserUnlocked(userId: string): boolean {
return this.getUserDataKey(userId) !== null;
}
/**
* 修改用户密码
* Change user password
*/
async changeUserPassword(userId: string, oldPassword: string, newPassword: string): Promise<boolean> {
try {
// 验证旧密码
// Validate old password
const isValid = await this.validatePassword(userId, oldPassword);
if (!isValid) return false;
// 获取当前DEK
// Get current DEK
const kekSalt = await this.getKEKSalt(userId);
if (!kekSalt) return false;
@@ -216,21 +216,21 @@ class UserCrypto {
const DEK = this.decryptDEK(encryptedDEK, oldKEK);
// 生成新的KEK salt和加密DEK
// Generate new KEK salt and encrypt DEK
const newKekSalt = await this.generateKEKSalt();
const newKEK = this.deriveKEK(newPassword, newKekSalt);
const newEncryptedDEK = this.encryptDEK(DEK, newKEK);
// 存储新的salt和encrypted DEK
// Store new salt and encrypted DEK
await this.storeKEKSalt(userId, newKekSalt);
await this.storeEncryptedDEK(userId, newEncryptedDEK);
// 清理所有临时密钥
// Clean all temporary keys
oldKEK.fill(0);
newKEK.fill(0);
DEK.fill(0);
// 清理用户会话,要求重新登录
// Clean user session, require re-login
this.logoutUser(userId);
return true;
@@ -239,7 +239,7 @@ class UserCrypto {
}
}
// ===== 私有方法 =====
// ===== Private methods =====
private async validatePassword(userId: string, password: string): Promise<boolean> {
try {
@@ -252,7 +252,7 @@ class UserCrypto {
const DEK = this.decryptDEK(encryptedDEK, KEK);
// 清理临时密钥
// Clean temporary keys
KEK.fill(0);
DEK.fill(0);
@@ -268,7 +268,7 @@ class UserCrypto {
for (const [userId, session] of this.userSessions.entries()) {
if (now > session.expiresAt || now - session.lastActivity > UserCrypto.MAX_INACTIVITY) {
session.dataKey.fill(0); // 安全清理密钥
session.dataKey.fill(0); // Securely clear key
expiredUsers.push(userId);
}
}
@@ -285,7 +285,7 @@ class UserCrypto {
}
}
// ===== 数据库操作和加密方法(简化版本) =====
// ===== Database operations and encryption methods (simplified version) =====
private async generateKEKSalt(): Promise<KEKSalt> {
return {
@@ -337,7 +337,7 @@ class UserCrypto {
return decrypted;
}
// 数据库操作方法
// Database operation methods
private async storeKEKSalt(userId: string, kekSalt: KEKSalt): Promise<void> {
const key = `user_kek_salt_${userId}`;
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 {
private static readonly EXPORT_VERSION = "v2.0";
/**
* 导出用户数据
* Export user data
*/
static async exportUserData(
userId: string,
@@ -61,7 +61,7 @@ class UserDataExport {
includeCredentials,
});
// 验证用户存在
// Verify user exists
const user = await db.select().from(users).where(eq(users.id, userId));
if (!user || user.length === 0) {
throw new Error(`User not found: ${userId}`);
@@ -69,7 +69,7 @@ class UserDataExport {
const userRecord = user[0];
// 获取用户数据密钥(如果需要解密)
// Get user data key (if decryption needed)
let userDataKey: Buffer | null = null;
if (format === 'plaintext') {
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 processedSshHosts = format === 'plaintext' && userDataKey
? sshHosts.map(host => DataCrypto.decryptRecord("ssh_data", host, userId, userDataKey!))
: sshHosts;
// 导出SSH凭据如果包含
// Export SSH credentials (if included)
let sshCredentialsData: any[] = [];
if (includeCredentials) {
const credentials = await db.select().from(sshCredentials).where(eq(sshCredentials.userId, userId));
@@ -93,17 +93,17 @@ class UserDataExport {
: credentials;
}
// 导出文件管理器数据
// Export file manager data
const [recentFiles, pinnedFiles, shortcuts] = await Promise.all([
db.select().from(fileManagerRecent).where(eq(fileManagerRecent.userId, userId)),
db.select().from(fileManagerPinned).where(eq(fileManagerPinned.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));
// 构建导出数据
// Build export data
const exportData: UserExportData = {
version: this.EXPORT_VERSION,
exportedAt: new Date().toISOString(),
@@ -148,7 +148,7 @@ class UserDataExport {
}
/**
* 导出为JSON字符串
* Export as JSON string
*/
static async exportUserDataToJSON(
userId: string,
@@ -165,7 +165,7 @@ class UserDataExport {
}
/**
* 验证导出数据格式
* Validate export data format
*/
static validateExportData(data: any): { valid: boolean; errors: string[] } {
const errors: string[] = [];
@@ -191,7 +191,7 @@ class UserDataExport {
errors.push("Missing or invalid metadata field");
}
// 检查必需的数据字段
// Check required data fields
if (data.userData) {
const requiredFields = ['sshHosts', 'sshCredentials', 'fileManagerData', 'dismissedAlerts'];
for (const field of requiredFields) {
@@ -214,7 +214,7 @@ class UserDataExport {
}
/**
* 获取导出数据统计信息
* Get export data statistics
*/
static getExportStats(data: UserExportData): {
version: string;

View File

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