added email verification

This commit is contained in:
andrei-mihnea-cerbu
2025-02-04 19:08:34 +02:00
parent 9baec7a7cb
commit dcf505c89b
67 changed files with 3922 additions and 3638 deletions
+151 -168
View File
@@ -1,192 +1,175 @@
import fs from 'fs';
import path from 'path';
import { FileEncryptor } from './file_encryptor';
import { MemoryManager } from './memory_manager';
import { JsonManager } from './json_manager';
import { TcpCommunicator } from './tcp_communicator';
import { operationCodes } from '../network/operation_codes';
import { ParsedMessage } from "../network/message_handler";
import fs from 'fs'
import path from 'path'
import { FileEncryptor } from './file_encryptor'
import { TcpCommunicator } from './tcp_communicator'
import { operationCodes } from '../network/operation_codes'
import { ParsedMessage } from '../network/message_handler'
import { JsonDatabase } from '../database/database'
import { DatabaseScheme } from '../database/schemes/database_scheme'
import { NetworkUserScheme } from '../database/schemes/network_scheme'
export class BackupManager {
private fileEncryptor: FileEncryptor | null = null;
private memoryManager: MemoryManager;
private applicationInfo: JsonManager;
private userConfig: JsonManager;
private readonly clientPort: number;
private isBusy: boolean = false;
private intervalId: NodeJS.Timeout | null = null;
private stopRequested: boolean = false;
private fileEncryptor: FileEncryptor | null = null
private readonly db: JsonDatabase<DatabaseScheme, any>
private readonly clientPort: number
private isBusy: boolean = false
private intervalId: NodeJS.Timeout | null = null
private stopRequested: boolean = false
constructor(userConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, clientPort: number) {
this.applicationInfo = new JsonManager(applicationInfoPath);
this.userConfig = new JsonManager(userConfigPath);
this.memoryManager = new MemoryManager(memoryManagerPath);
this.clientPort = clientPort;
constructor(pathToDatabaseFile: string, clientPort: number) {
this.db = new JsonDatabase(pathToDatabaseFile)
this.clientPort = clientPort
}
async start(): Promise<void> {
this.intervalId = setInterval(async () => {
if (!this.isBusy || !this.stopRequested) {
this.isBusy = true
this.log('Start successfully. Backup files to users.')
await this.initialize()
}
if (global.gc) {
global.gc()
}
}, 10000) // 10-second interval for testing
}
private async initialize(): Promise<void> {
this.isBusy = true
try {
const data = await this.db.read()
const userInfo = data.app_config.user_info
const encryptionKey = data.app_config.encryption_key
const usersIp = data.network.usersInLan.map((user: NetworkUserScheme) => user.ip)
const backupDirectoryData = data.local_resources.directory_schemes.backup
this.fileEncryptor = new FileEncryptor(encryptionKey.key, encryptionKey.iv)
await this.sendFilesToUsers(
userInfo.name,
usersIp,
backupDirectoryData.structure,
backupDirectoryData.path,
)
} catch (error: any) {
this.log(`Error in initialize process: ${error.message}`, 'error')
} finally {
this.log('Backup process completed.')
this.isBusy = false
}
}
private encryptFile(filePath: string): string {
if (!this.fileEncryptor) {
return filePath
}
async start(): Promise<void> {
this.intervalId = setInterval(async () => {
if (!this.isBusy || !this.stopRequested) {
this.isBusy = true;
this.log('Start successfully. Backup files to users.');
await this.initialize();
}
if (global.gc) {
global.gc();
}
}, 10000); // 10-second interval for testing
if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error')
return ''
}
private async initialize(): Promise<void> {
this.isBusy = true;
return this.fileEncryptor.encryptFileToBase64(filePath)
}
private async sendFilesToUsers(
userName: string,
usersIp: string[],
fileStructure: { [key: string]: string },
backupDirectoryPath: string,
): Promise<void> {
let unsentFiles = Object.keys(fileStructure)
for (const fileName of unsentFiles) {
const filePath = fileStructure[fileName]
const encryptedFileContent = this.encryptFile(filePath)
if (!encryptedFileContent) {
this.log(`Failed to encrypt file: ${fileName}`, 'error')
continue
}
const relativeFilePath = path.relative(backupDirectoryPath, filePath)
const metaInfo = { userName, relativeFilePath }
for (const ip of usersIp) {
const tcpCommunicator = new TcpCommunicator(ip, this.clientPort)
try {
const encryptionKeyData = await this.userConfig.readValue('encryption_key');
if (!encryptionKeyData || !encryptionKeyData.key || !encryptionKeyData.iv) {
this.log('Encryption key data is missing in user configuration.', 'error');
return;
}
await tcpCommunicator.connect()
this.log(`Connected to ${ip}`)
this.fileEncryptor = new FileEncryptor(encryptionKeyData.key, encryptionKeyData.iv);
const sendSuccess = await tcpCommunicator.sendMessage(
operationCodes.BACKUP_FILE,
metaInfo,
Buffer.from(encryptedFileContent, 'base64'),
)
if (!sendSuccess) {
throw new Error('Failed to send file content.')
}
const userInfo = await this.userConfig.readValue('user_info');
if (!userInfo || !userInfo.name) {
this.log('User information is missing in user configuration.', 'error');
return;
}
const userName = userInfo.name;
const responseReceived = await this.waitForResponse(tcpCommunicator)
if (!responseReceived) {
throw new Error('Timeout waiting for the message response.')
}
const backupDirectoryData = await this.applicationInfo.readValue('backupDirectory');
if (!backupDirectoryData || !backupDirectoryData.id || !backupDirectoryData.path) {
this.log('Backup directory information is missing in application info.', 'error');
return;
}
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
if (!activeUsersIp || !activeUsersIp.length) {
this.log('No active users found.', 'error');
return;
}
const backupDirectoryId = backupDirectoryData.id;
const backupDirectoryPath = backupDirectoryData.path;
const directoryData = await this.memoryManager.retrieveMetaInformation(backupDirectoryId);
if (!directoryData || !directoryData.structure) {
this.log('Backup directory structure is missing in memory.', 'error');
return;
}
await this.sendFilesToUsers(directoryData.structure, userName, activeUsersIp, backupDirectoryPath);
} catch (error: any) {
this.log(`Error in initialize process: ${error.message}`, 'error');
this.log(`Successfully sent file: ${fileName} to ${ip}`)
unsentFiles = unsentFiles.filter((f) => f !== fileName)
break
} catch (error) {
this.log(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`, 'error')
} finally {
this.log('Backup process completed.');
this.isBusy = false;
await tcpCommunicator.disconnect()
this.log(`Disconnected from ${ip}`)
}
}
}
private encryptFile(filePath: string): string {
if (!this.fileEncryptor) {
return filePath;
}
if (unsentFiles.length > 0) {
process.send?.({ type: 'log', message: 'Backup could not be completed for all files' })
} else {
process.send?.({ type: 'log', message: 'Backup completed successfully' })
}
}
if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error');
return '';
private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> {
return new Promise((resolve) => {
const idResponseCheck = setInterval(() => {
if (!tcpCommunicator) return null
if (tcpCommunicator.hasResponseArrived()) {
clearInterval(idResponseCheck)
resolve(tcpCommunicator.getLastResult())
}
}, 100)
})
}
return this.fileEncryptor.encryptFileToBase64(filePath);
async stop(): Promise<void> {
this.stopRequested = true // Signal that stop is requested
if (this.intervalId) {
clearInterval(this.intervalId)
this.intervalId = null
}
private async sendFilesToUsers(fileStructure: { [key: string]: string }, userName: string, usersIp: string[], backupDirectoryPath: string): Promise<void> {
let unsentFiles = Object.keys(fileStructure);
for (const fileName of unsentFiles) {
const filePath = fileStructure[fileName];
const encryptedFileContent = this.encryptFile(filePath);
if (!encryptedFileContent) {
this.log(`Failed to encrypt file: ${fileName}`, 'error');
continue;
}
const relativeFilePath = path.relative(backupDirectoryPath, filePath);
const metaInfo = { userName, relativeFilePath };
for (const ip of usersIp) {
const tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
try {
await tcpCommunicator.connect();
this.log(`Connected to ${ip}`);
const sendSuccess = await tcpCommunicator.sendMessage(operationCodes.BACKUP_FILE, metaInfo, Buffer.from(encryptedFileContent, 'base64'));
if (!sendSuccess) {
throw new Error('Failed to send file content.');
}
const responseReceived = await this.waitForResponse(tcpCommunicator);
if (!responseReceived) {
throw new Error('Timeout waiting for the message response.');
}
this.log(`Successfully sent file: ${fileName} to ${ip}`);
unsentFiles = unsentFiles.filter(f => f !== fileName);
break;
} catch (error) {
this.log(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`, 'error');
} finally {
await tcpCommunicator.disconnect();
this.log(`Disconnected from ${ip}`);
}
}
}
if (unsentFiles.length > 0) {
process.send?.({type: 'log', message: 'Backup could not be completed for all files'});
} else {
process.send?.({type: 'log', message: 'Backup completed successfully' });
}
// Wait for any ongoing process to complete if busy
while (this.isBusy) {
await new Promise((resolve) => setTimeout(resolve, 100))
}
private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> {
return new Promise((resolve) => {
const idResponseCheck = setInterval(() => {
if (!tcpCommunicator) return null;
if (tcpCommunicator.hasResponseArrived()) {
clearInterval(idResponseCheck);
resolve(tcpCommunicator.getLastResult());
}
}, 100);
});
}
async stop(): Promise<void> {
this.stopRequested = true; // Signal that stop is requested
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
// Wait for any ongoing process to complete if busy
while (this.isBusy) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
console.log("[BackupManager] Stopped successfully.");
}
// Unified logging function
private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void {
const prefix = '[BackupManager]';
if (level === 'error') {
console.error(`${prefix} ${message}`);
} else if (level === 'warn') {
console.warn(`${prefix} ${message}`);
} else {
console.log(`${prefix} ${message}`);
}
console.log('[BackupManager] Stopped successfully.')
}
// Unified logging function
private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void {
const prefix = '[BackupManager]'
if (level === 'error') {
console.error(`${prefix} ${message}`)
} else if (level === 'warn') {
console.warn(`${prefix} ${message}`)
} else {
console.log(`${prefix} ${message}`)
}
}
}