dev-1.7.0 #294
@@ -1,94 +0,0 @@
|
||||
# Termix 安全重构计划
|
||||
|
||||
## 现状分析
|
||||
- 当前所有密钥都用base64编码存储在数据库
|
||||
- JWT Secret和数据加密密钥混合管理
|
||||
- 没有真正的KEK-DEK分离
|
||||
- 数据库文件泄露 = 完全沦陷
|
||||
|
||||
## 目标架构
|
||||
|
||||
### 密钥层次
|
||||
```
|
||||
用户密码 → KEK → DEK → 字段加密密钥 → 数据
|
||||
系统启动 → JWT Secret → JWT Token → API认证
|
||||
```
|
||||
|
||||
### 存储分离
|
||||
```
|
||||
系统级:settings.system_jwt_secret (base64保护)
|
||||
用户级:settings.user_kek_salt_${userId}
|
||||
用户级:settings.user_encrypted_dek_${userId} (KEK保护)
|
||||
```
|
||||
|
||||
## 修复步骤
|
||||
|
||||
### 第1步:新建分离的密钥管理类
|
||||
- [ ] 创建 SystemKeyManager (JWT密钥)
|
||||
- [ ] 创建 UserKeyManager (用户数据密钥)
|
||||
- [ ] 创建 SecuritySession (会话管理)
|
||||
|
||||
### 第2步:重构认证流程
|
||||
- [ ] 修改用户注册:生成用户专属KEK salt和DEK
|
||||
- [ ] 修改用户登录:验证密码 + 解锁数据密钥
|
||||
- [ ] 修改JWT验证:系统密钥验证 + 用户会话检查
|
||||
|
||||
### 第3步:重构数据加密
|
||||
- [ ] 分离数据加密和JWT密钥初始化
|
||||
- [ ] 修改EncryptedDBOperations使用用户会话密钥
|
||||
- [ ] 添加会话过期处理
|
||||
|
||||
### 第4步:数据库迁移
|
||||
- [ ] 创建迁移脚本:现有数据 → KEK保护
|
||||
- [ ] 向后兼容处理
|
||||
- [ ] 安全删除旧密钥
|
||||
|
||||
### 第5步:API修改
|
||||
- [ ] 添加用户密码验证接口
|
||||
- [ ] 修改所有加密相关接口
|
||||
- [ ] 添加会话管理接口
|
||||
|
||||
## 文件修改清单
|
||||
|
||||
### 新建文件
|
||||
- src/backend/utils/system-key-manager.ts
|
||||
- src/backend/utils/user-key-manager.ts
|
||||
- src/backend/utils/security-session.ts
|
||||
- src/backend/utils/security-migration.ts
|
||||
|
||||
### 修改文件
|
||||
- src/backend/utils/encryption-key-manager.ts (简化或删除)
|
||||
- src/backend/utils/database-encryption.ts
|
||||
- src/backend/utils/encrypted-db-operations.ts
|
||||
- src/backend/database/routes/users.ts
|
||||
- src/backend/database/database.ts
|
||||
|
||||
### 数据库Schema
|
||||
- 新增:user_kek_salt_${userId}
|
||||
- 新增:user_encrypted_dek_${userId}
|
||||
- 修改:system_jwt_secret (从current混合模式分离)
|
||||
|
||||
## 安全考虑
|
||||
|
||||
### 密钥生命周期
|
||||
- JWT Secret: 应用生命周期
|
||||
- 用户KEK: 永不存储,从密码推导
|
||||
- 用户DEK: 会话期间,内存存储
|
||||
- 字段密钥: 临时推导,立即销毁
|
||||
|
||||
### 会话管理
|
||||
- 数据会话独立于JWT有效期
|
||||
- 非活跃自动过期
|
||||
- 用户登出立即清理
|
||||
|
||||
### 向后兼容
|
||||
- 检测旧格式数据
|
||||
- 用户登录时自动迁移
|
||||
- 迁移完成后删除旧密钥
|
||||
|
||||
## 测试计划
|
||||
- [ ] 密钥生成和推导测试
|
||||
- [ ] 加密解密正确性测试
|
||||
- [ ] 会话管理测试
|
||||
- [ ] 迁移流程测试
|
||||
- [ ] 性能影响评估
|
||||
@@ -13,7 +13,6 @@ import "dotenv/config";
|
||||
import { databaseLogger, apiLogger } from "../utils/logger.js";
|
||||
import { SecuritySession } from "../utils/security-session.js";
|
||||
import { DatabaseEncryption } from "../utils/database-encryption.js";
|
||||
import { SecurityMigration } from "../utils/security-migration.js";
|
||||
import { DatabaseFileEncryption } from "../utils/database-file-encryption.js";
|
||||
|
||||
const app = express();
|
||||
@@ -294,11 +293,9 @@ app.get("/encryption/status", async (req, res) => {
|
||||
try {
|
||||
const securitySession = SecuritySession.getInstance();
|
||||
const securityStatus = await securitySession.getSecurityStatus();
|
||||
const migrationStatus = await SecurityMigration.checkMigrationStatus();
|
||||
|
||||
res.json({
|
||||
security: securityStatus,
|
||||
migration: migrationStatus,
|
||||
version: "v2-kek-dek",
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -337,47 +334,6 @@ app.post("/encryption/initialize", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/encryption/migrate", async (req, res) => {
|
||||
try {
|
||||
const { dryRun = false } = req.body;
|
||||
|
||||
const migration = new SecurityMigration({
|
||||
dryRun,
|
||||
backupEnabled: true,
|
||||
});
|
||||
|
||||
if (dryRun) {
|
||||
apiLogger.info("Starting encryption migration (dry run)", {
|
||||
operation: "encryption_migrate_dry_run",
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Dry run mode - no changes made",
|
||||
dryRun: true,
|
||||
});
|
||||
} else {
|
||||
apiLogger.info("Starting encryption migration", {
|
||||
operation: "encryption_migrate",
|
||||
});
|
||||
|
||||
await migration.runMigration();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Migration completed successfully",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
apiLogger.error("Migration failed", error, {
|
||||
operation: "encryption_migrate_failed",
|
||||
});
|
||||
res.status(500).json({
|
||||
error: "Migration failed",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/encryption/regenerate", async (req, res) => {
|
||||
try {
|
||||
@@ -654,7 +610,6 @@ app.listen(PORT, async () => {
|
||||
"/releases/rss",
|
||||
"/encryption/status",
|
||||
"/encryption/initialize",
|
||||
"/encryption/migrate",
|
||||
"/encryption/regenerate",
|
||||
"/database/export",
|
||||
"/database/import",
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
fileManagerPinned,
|
||||
fileManagerShortcuts,
|
||||
dismissedAlerts,
|
||||
settings,
|
||||
} from "../db/schema.js";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import bcrypt from "bcryptjs";
|
||||
@@ -18,7 +19,6 @@ import type { Request, Response, NextFunction } from "express";
|
||||
import { authLogger, apiLogger } from "../../utils/logger.js";
|
||||
import { SecuritySession } from "../../utils/security-session.js";
|
||||
import { UserKeyManager } from "../../utils/user-key-manager.js";
|
||||
import { SecurityMigration } from "../../utils/security-migration.js";
|
||||
|
||||
// Get security session instance
|
||||
const securitySession = SecuritySession.getInstance();
|
||||
@@ -785,24 +785,29 @@ router.post("/login", async (req, res) => {
|
||||
return res.status(401).json({ error: "Incorrect password" });
|
||||
}
|
||||
|
||||
// Check and handle user migration (from old encryption system)
|
||||
let migrationPerformed = false;
|
||||
// Check if legacy user needs encryption setup
|
||||
try {
|
||||
migrationPerformed = await SecurityMigration.handleUserLoginMigration(userRecord.id, password);
|
||||
if (migrationPerformed) {
|
||||
authLogger.success("User encryption migrated during login", {
|
||||
operation: "login_migration_success",
|
||||
const kekSalt = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, `user_kek_salt_${userRecord.id}`));
|
||||
|
||||
if (kekSalt.length === 0) {
|
||||
// Legacy user first login - set up new encryption
|
||||
await securitySession.registerUser(userRecord.id, password);
|
||||
authLogger.success("Legacy user encryption initialized", {
|
||||
operation: "legacy_user_setup",
|
||||
username,
|
||||
userId: userRecord.id,
|
||||
});
|
||||
}
|
||||
} catch (migrationError) {
|
||||
authLogger.error("Failed to migrate user during login", migrationError, {
|
||||
operation: "login_migration_failed",
|
||||
} catch (setupError) {
|
||||
authLogger.error("Failed to initialize user encryption", setupError, {
|
||||
operation: "user_encryption_setup_failed",
|
||||
username,
|
||||
userId: userRecord.id,
|
||||
});
|
||||
// Migration failure should not block login, but needs to be logged
|
||||
// Encryption setup failure should not block login for existing users
|
||||
}
|
||||
|
||||
// Unlock user data keys
|
||||
|
||||
@@ -1,449 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { db } from "../database/db/index.js";
|
||||
import { settings, users, sshData, sshCredentials } from "../database/db/schema.js";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { SecuritySession } from "./security-session.js";
|
||||
import { UserKeyManager } from "./user-key-manager.js";
|
||||
import { DatabaseEncryption } from "./database-encryption.js";
|
||||
import { EncryptedDBOperations } from "./encrypted-db-operations.js";
|
||||
import { FieldEncryption } from "./encryption.js";
|
||||
import { databaseLogger } from "./logger.js";
|
||||
|
||||
interface MigrationConfig {
|
||||
dryRun?: boolean;
|
||||
backupEnabled?: boolean;
|
||||
forceRegeneration?: boolean;
|
||||
}
|
||||
|
||||
interface MigrationResult {
|
||||
success: boolean;
|
||||
usersProcessed: number;
|
||||
recordsMigrated: number;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* SecurityMigration - Migrate from old encryption system to KEK-DEK architecture
|
||||
*
|
||||
* Migration steps:
|
||||
* 1. Detect existing system state
|
||||
* 2. Backup existing data
|
||||
* 3. Initialize new security system
|
||||
* 4. Set up KEK-DEK for existing users
|
||||
* 5. Migrate encrypted data
|
||||
* 6. Clean up old keys
|
||||
*/
|
||||
class SecurityMigration {
|
||||
private config: MigrationConfig;
|
||||
private securitySession: SecuritySession;
|
||||
private userKeyManager: UserKeyManager;
|
||||
|
||||
constructor(config: MigrationConfig = {}) {
|
||||
this.config = {
|
||||
dryRun: config.dryRun ?? false,
|
||||
backupEnabled: config.backupEnabled ?? true,
|
||||
forceRegeneration: config.forceRegeneration ?? false,
|
||||
};
|
||||
|
||||
this.securitySession = SecuritySession.getInstance();
|
||||
this.userKeyManager = UserKeyManager.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run complete migration
|
||||
*/
|
||||
async runMigration(): Promise<MigrationResult> {
|
||||
const result: MigrationResult = {
|
||||
success: false,
|
||||
usersProcessed: 0,
|
||||
recordsMigrated: 0,
|
||||
errors: [],
|
||||
warnings: [],
|
||||
};
|
||||
|
||||
try {
|
||||
databaseLogger.info("Starting security migration to KEK-DEK architecture", {
|
||||
operation: "security_migration_start",
|
||||
dryRun: this.config.dryRun,
|
||||
backupEnabled: this.config.backupEnabled,
|
||||
});
|
||||
|
||||
// 1. Check migration prerequisites
|
||||
await this.validatePrerequisites();
|
||||
|
||||
// 2. Create backup
|
||||
if (this.config.backupEnabled && !this.config.dryRun) {
|
||||
await this.createBackup();
|
||||
}
|
||||
|
||||
// 3. Initialize new security system
|
||||
await this.initializeNewSecurity();
|
||||
|
||||
// 4. Detect users needing migration
|
||||
const usersToMigrate = await this.detectUsersNeedingMigration();
|
||||
result.warnings.push(`Found ${usersToMigrate.length} users that need migration`);
|
||||
|
||||
// 5. Process each user
|
||||
for (const user of usersToMigrate) {
|
||||
try {
|
||||
await this.migrateUser(user, result);
|
||||
result.usersProcessed++;
|
||||
} catch (error) {
|
||||
const errorMsg = `Failed to migrate user ${user.username}: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
||||
result.errors.push(errorMsg);
|
||||
databaseLogger.error("User migration failed", error, {
|
||||
operation: "user_migration_failed",
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Clean up old system (if all users migrated successfully)
|
||||
if (result.errors.length === 0 && !this.config.dryRun) {
|
||||
await this.cleanupOldSystem();
|
||||
}
|
||||
|
||||
result.success = result.errors.length === 0;
|
||||
|
||||
databaseLogger.success("Security migration completed", {
|
||||
operation: "security_migration_complete",
|
||||
result,
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
const errorMsg = `Migration failed: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
||||
result.errors.push(errorMsg);
|
||||
databaseLogger.error("Security migration failed", error, {
|
||||
operation: "security_migration_failed",
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate migration prerequisites
|
||||
*/
|
||||
private async validatePrerequisites(): Promise<void> {
|
||||
databaseLogger.info("Validating migration prerequisites", {
|
||||
operation: "migration_validation",
|
||||
});
|
||||
|
||||
// Check database connection
|
||||
try {
|
||||
await db.select().from(settings).limit(1);
|
||||
} catch (error) {
|
||||
throw new Error("Database connection failed");
|
||||
}
|
||||
|
||||
// Check for old encryption keys
|
||||
const oldEncryptionKey = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, "db_encryption_key"));
|
||||
|
||||
if (oldEncryptionKey.length === 0) {
|
||||
databaseLogger.info("No old encryption key found - fresh installation", {
|
||||
operation: "migration_validation",
|
||||
});
|
||||
} else {
|
||||
databaseLogger.info("Old encryption key detected - migration needed", {
|
||||
operation: "migration_validation",
|
||||
});
|
||||
}
|
||||
|
||||
databaseLogger.success("Prerequisites validation passed", {
|
||||
operation: "migration_validation_complete",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create pre-migration backup
|
||||
*/
|
||||
private async createBackup(): Promise<void> {
|
||||
databaseLogger.info("Creating migration backup", {
|
||||
operation: "migration_backup",
|
||||
});
|
||||
|
||||
try {
|
||||
const fs = await import("fs");
|
||||
const path = await import("path");
|
||||
const dataDir = process.env.DATA_DIR || "./db/data";
|
||||
const dbPath = path.join(dataDir, "db.sqlite");
|
||||
const backupPath = path.join(dataDir, `migration-backup-${Date.now()}.sqlite`);
|
||||
|
||||
if (fs.existsSync(dbPath)) {
|
||||
fs.copyFileSync(dbPath, backupPath);
|
||||
databaseLogger.success(`Migration backup created: ${backupPath}`, {
|
||||
operation: "migration_backup_complete",
|
||||
backupPath,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
databaseLogger.error("Failed to create migration backup", error, {
|
||||
operation: "migration_backup_failed",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize new security system
|
||||
*/
|
||||
private async initializeNewSecurity(): Promise<void> {
|
||||
databaseLogger.info("Initializing new security system", {
|
||||
operation: "new_security_init",
|
||||
});
|
||||
|
||||
await this.securitySession.initialize();
|
||||
DatabaseEncryption.initialize();
|
||||
|
||||
const isValid = await this.securitySession.validateSecuritySystem();
|
||||
if (!isValid) {
|
||||
throw new Error("New security system validation failed");
|
||||
}
|
||||
|
||||
databaseLogger.success("New security system initialized", {
|
||||
operation: "new_security_init_complete",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect users needing migration
|
||||
*/
|
||||
private async detectUsersNeedingMigration(): Promise<any[]> {
|
||||
const allUsers = await db.select().from(users);
|
||||
const usersNeedingMigration = [];
|
||||
|
||||
for (const user of allUsers) {
|
||||
// Check if user already has KEK salt (new system)
|
||||
const kekSalt = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, `user_kek_salt_${user.id}`));
|
||||
|
||||
if (kekSalt.length === 0) {
|
||||
usersNeedingMigration.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
databaseLogger.info(`Found ${usersNeedingMigration.length} users needing migration`, {
|
||||
operation: "migration_user_detection",
|
||||
totalUsers: allUsers.length,
|
||||
needingMigration: usersNeedingMigration.length,
|
||||
});
|
||||
|
||||
return usersNeedingMigration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate single user
|
||||
*/
|
||||
private async migrateUser(user: any, result: MigrationResult): Promise<void> {
|
||||
databaseLogger.info(`Migrating user: ${user.username}`, {
|
||||
operation: "user_migration_start",
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
});
|
||||
|
||||
if (this.config.dryRun) {
|
||||
databaseLogger.info(`[DRY RUN] Would migrate user: ${user.username}`, {
|
||||
operation: "user_migration_dry_run",
|
||||
userId: user.id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Issue: We need user's plaintext password to set up KEK
|
||||
// but we only have password hash. Solutions:
|
||||
// 1. Require user to re-enter password on first login
|
||||
// 2. Generate temporary password and require user to change it
|
||||
//
|
||||
// For demonstration, we skip actual KEK setup and just mark user for password reset
|
||||
|
||||
try {
|
||||
// Mark user needing encryption reset
|
||||
await db.insert(settings).values({
|
||||
key: `user_migration_required_${user.id}`,
|
||||
value: JSON.stringify({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
migrationTime: new Date().toISOString(),
|
||||
reason: "Security system upgrade - password re-entry required",
|
||||
}),
|
||||
});
|
||||
|
||||
result.warnings.push(`User ${user.username} marked for password re-entry on next login`);
|
||||
|
||||
databaseLogger.success(`User migration prepared: ${user.username}`, {
|
||||
operation: "user_migration_prepared",
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
databaseLogger.error(`Failed to prepare user migration: ${user.username}`, error, {
|
||||
operation: "user_migration_prepare_failed",
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old encryption system
|
||||
*/
|
||||
private async cleanupOldSystem(): Promise<void> {
|
||||
databaseLogger.info("Cleaning up old encryption system", {
|
||||
operation: "old_system_cleanup",
|
||||
});
|
||||
|
||||
try {
|
||||
// Delete old encryption keys
|
||||
await db.delete(settings).where(eq(settings.key, "db_encryption_key"));
|
||||
await db.delete(settings).where(eq(settings.key, "encryption_key_created"));
|
||||
|
||||
// Keep JWT key (now managed by new system)
|
||||
// Delete old jwt_secret, let new system take over
|
||||
await db.delete(settings).where(eq(settings.key, "jwt_secret"));
|
||||
await db.delete(settings).where(eq(settings.key, "jwt_secret_created"));
|
||||
|
||||
databaseLogger.success("Old encryption system cleaned up", {
|
||||
operation: "old_system_cleanup_complete",
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
databaseLogger.error("Failed to cleanup old system", error, {
|
||||
operation: "old_system_cleanup_failed",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check migration status
|
||||
*/
|
||||
static async checkMigrationStatus(): Promise<{
|
||||
migrationRequired: boolean;
|
||||
usersNeedingMigration: number;
|
||||
hasOldSystem: boolean;
|
||||
hasNewSystem: boolean;
|
||||
}> {
|
||||
try {
|
||||
// Check for old system
|
||||
const oldEncryptionKey = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, "db_encryption_key"));
|
||||
|
||||
// Check for new system
|
||||
const newSystemJWT = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, "system_jwt_secret"));
|
||||
|
||||
// Check users needing migration
|
||||
const allUsers = await db.select().from(users);
|
||||
let usersNeedingMigration = 0;
|
||||
|
||||
for (const user of allUsers) {
|
||||
const kekSalt = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, `user_kek_salt_${user.id}`));
|
||||
|
||||
if (kekSalt.length === 0) {
|
||||
usersNeedingMigration++;
|
||||
}
|
||||
}
|
||||
|
||||
const hasOldSystem = oldEncryptionKey.length > 0;
|
||||
const hasNewSystem = newSystemJWT.length > 0;
|
||||
const migrationRequired = hasOldSystem || usersNeedingMigration > 0;
|
||||
|
||||
return {
|
||||
migrationRequired,
|
||||
usersNeedingMigration,
|
||||
hasOldSystem,
|
||||
hasNewSystem,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
databaseLogger.error("Failed to check migration status", error, {
|
||||
operation: "migration_status_check_failed",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle user login migration (when user enters password)
|
||||
*/
|
||||
static async handleUserLoginMigration(userId: string, password: string): Promise<boolean> {
|
||||
try {
|
||||
// Check if user needs migration
|
||||
const migrationRequired = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, `user_migration_required_${userId}`));
|
||||
|
||||
if (migrationRequired.length === 0) {
|
||||
return false; // No migration needed
|
||||
}
|
||||
|
||||
databaseLogger.info("Performing user migration during login", {
|
||||
operation: "login_migration_start",
|
||||
userId,
|
||||
});
|
||||
|
||||
// Initialize user encryption
|
||||
const securitySession = SecuritySession.getInstance();
|
||||
await securitySession.registerUser(userId, password);
|
||||
|
||||
// Delete migration marker
|
||||
await db.delete(settings).where(eq(settings.key, `user_migration_required_${userId}`));
|
||||
|
||||
databaseLogger.success("User migration completed during login", {
|
||||
operation: "login_migration_complete",
|
||||
userId,
|
||||
});
|
||||
|
||||
return true; // Migration completed
|
||||
|
||||
} catch (error) {
|
||||
databaseLogger.error("Login migration failed", error, {
|
||||
operation: "login_migration_failed",
|
||||
userId,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CLI execution
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const config: MigrationConfig = {
|
||||
dryRun: process.env.DRY_RUN === "true",
|
||||
backupEnabled: process.env.BACKUP_ENABLED !== "false",
|
||||
forceRegeneration: process.env.FORCE_REGENERATION === "true",
|
||||
};
|
||||
|
||||
const migration = new SecurityMigration(config);
|
||||
|
||||
migration
|
||||
.runMigration()
|
||||
.then((result) => {
|
||||
console.log("Migration completed:", result);
|
||||
process.exit(result.success ? 0 : 1);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Migration failed:", error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { SecurityMigration, type MigrationConfig, type MigrationResult };
|
||||
Reference in New Issue
Block a user