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:
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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}`, {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 salt和DEK
|
* 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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user