BACKEND DONE FOR ALL APPS
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import path from "path";
|
||||
|
||||
import { UserDatabase } from "./user_database";
|
||||
import { DepartmentDatabase } from "./department_database";
|
||||
import { KeyDatabase } from "./key_database";
|
||||
|
||||
const departmentDatabase = new DepartmentDatabase();
|
||||
const userDatabase = new UserDatabase();
|
||||
const keyDatabase = new KeyDatabase();
|
||||
|
||||
export {
|
||||
keyDatabase,
|
||||
userDatabase,
|
||||
departmentDatabase
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import SQLiteDatabase from './sql_lite_database'; // Singleton instance of SQLite DB
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export interface Department {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class DepartmentDatabase {
|
||||
private db: any;
|
||||
|
||||
constructor() {
|
||||
this.db = SQLiteDatabase.getInstance();
|
||||
this.createTableIfNotExists();
|
||||
}
|
||||
|
||||
// Create the table if it doesn't exist
|
||||
private createTableIfNotExists() {
|
||||
const createTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS departments (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
`;
|
||||
this.db.exec(createTableQuery);
|
||||
|
||||
const ceoDepartment = this.db.prepare('SELECT COUNT(*) as count FROM departments WHERE name = ?').get('CEO').count;
|
||||
const defaultDepartment = this.db.prepare('SELECT COUNT(*) as count FROM departments WHERE name = ?').get('Worker').count;
|
||||
|
||||
// Create CEO department if it doesn't exist
|
||||
if (ceoDepartment === 0) {
|
||||
this.createDepartment('CEO');
|
||||
console.log('Created CEO department.');
|
||||
} else {
|
||||
console.log('CEO department already exists.');
|
||||
}
|
||||
|
||||
if (defaultDepartment === 0) {
|
||||
this.createDepartment('Worker');
|
||||
console.log('Created Worker department.');
|
||||
} else {
|
||||
console.log('Worker department already exists.');
|
||||
}
|
||||
|
||||
console.log('Departments table checked/created.');
|
||||
}
|
||||
|
||||
|
||||
// Find a department by name
|
||||
public findByName(name: string): Department | null {
|
||||
const stmt = this.db.prepare('SELECT * FROM departments WHERE name = ?');
|
||||
const department = stmt.get(name);
|
||||
return department || null;
|
||||
}
|
||||
|
||||
// Create a new department
|
||||
public createDepartment(name: string): Department {
|
||||
const department: Department = {
|
||||
id: uuidv4(),
|
||||
name,
|
||||
};
|
||||
|
||||
const stmt = this.db.prepare('INSERT INTO departments (id, name) VALUES (?, ?)');
|
||||
stmt.run(department.id, department.name);
|
||||
return department;
|
||||
}
|
||||
|
||||
// Find a department by ID
|
||||
public findById(id: string): Department | null {
|
||||
const stmt = this.db.prepare('SELECT * FROM departments WHERE id = ?');
|
||||
const department = stmt.get(id);
|
||||
return department || null;
|
||||
}
|
||||
|
||||
// Modify an existing department by ID
|
||||
public modifyDepartment(departmentId: string, newName: string): boolean {
|
||||
const stmt = this.db.prepare('UPDATE departments SET name = ? WHERE id = ?');
|
||||
const result = stmt.run(newName, departmentId);
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
// Delete a department by ID
|
||||
public deleteDepartment(departmentId: string): boolean {
|
||||
const stmt = this.db.prepare('DELETE FROM departments WHERE id = ?');
|
||||
const result = stmt.run(departmentId);
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
// Get all departments
|
||||
public getAllDepartments(): Department[] {
|
||||
const stmt = this.db.prepare('SELECT * FROM departments');
|
||||
return stmt.all();
|
||||
}
|
||||
|
||||
public cleanTable(): { success: boolean; message: string } {
|
||||
try {
|
||||
// Fetch the CEO department ID
|
||||
const ceoDepartmentRow = this.db.prepare(`SELECT id FROM departments WHERE LOWER(name) = 'ceo'`).get();
|
||||
if (!ceoDepartmentRow) {
|
||||
console.error('CEO department not found in the database.');
|
||||
return { success: false, message: 'Failed to find CEO department.' };
|
||||
}
|
||||
|
||||
const ceoDepartmentId = ceoDepartmentRow.id;
|
||||
|
||||
// Delete all departments except the CEO department
|
||||
const deleteDepartmentsStmt = this.db.prepare(`DELETE FROM departments WHERE id != ?`);
|
||||
deleteDepartmentsStmt.run(ceoDepartmentId);
|
||||
|
||||
return { success: true, message: 'All non-CEO departments have been deleted.' };
|
||||
} catch (error) {
|
||||
console.error('Error while cleaning the departments table:', error);
|
||||
return { success: false, message: 'Failed to clean departments table.' };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import { randomBytes } from 'crypto';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import SQLiteDatabase from './sql_lite_database';
|
||||
import { userDatabase, departmentDatabase } from './db';
|
||||
|
||||
export interface UserKey {
|
||||
id: string; // Unique ID for the key entry
|
||||
userId: string; // ID of the user this key belongs to
|
||||
key: string; // AES key in Base64 format
|
||||
iv: string; // Initialization Vector (IV) in Base64 format
|
||||
}
|
||||
|
||||
export class KeyDatabase {
|
||||
private db: any;
|
||||
|
||||
constructor() {
|
||||
// Get the singleton instance of the database
|
||||
this.db = SQLiteDatabase.getInstance();
|
||||
|
||||
// Create the keys table if it doesn't exist
|
||||
this.createTableIfNotExists();
|
||||
}
|
||||
|
||||
// Create the table if it doesn't exist
|
||||
private createTableIfNotExists() {
|
||||
const createTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS user_keys (
|
||||
id TEXT PRIMARY KEY,
|
||||
userId TEXT UNIQUE NOT NULL, -- Ensure one key per user
|
||||
key TEXT NOT NULL,
|
||||
iv TEXT NOT NULL,
|
||||
FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
this.db.exec(createTableQuery);
|
||||
|
||||
// Fetch the CEO department and create keys for all users in the CEO department
|
||||
this.createKeysForCeoUsers();
|
||||
}
|
||||
|
||||
// Generate AES key and IV
|
||||
private generateAESKeyAndIV(): { key: string, iv: string } {
|
||||
const key = randomBytes(32).toString('base64'); // AES-256 key (32 bytes)
|
||||
const iv = randomBytes(16).toString('base64'); // IV for AES (16 bytes)
|
||||
return { key, iv };
|
||||
}
|
||||
|
||||
// Create a key for a specific user
|
||||
private createKeyForUser(userId: string): UserKey {
|
||||
const { key, iv } = this.generateAESKeyAndIV();
|
||||
|
||||
// Delete any existing key for the user
|
||||
this.deleteKeysByUserId(userId);
|
||||
|
||||
// Insert the new key
|
||||
const newKey: UserKey = {
|
||||
id: uuidv4(),
|
||||
userId: userId,
|
||||
key: key,
|
||||
iv: iv
|
||||
};
|
||||
|
||||
const stmt = this.db.prepare('INSERT INTO user_keys (id, userId, key, iv) VALUES (?, ?, ?, ?)');
|
||||
stmt.run(newKey.id, newKey.userId, newKey.key, newKey.iv);
|
||||
|
||||
return newKey;
|
||||
}
|
||||
|
||||
// Create keys for all users in the CEO department
|
||||
private createKeysForCeoUsers() {
|
||||
const departments = departmentDatabase.getAllDepartments();
|
||||
|
||||
// Find the CEO department by name
|
||||
const ceoDepartment = departments.find(dept => dept.name.toLowerCase() === 'ceo');
|
||||
|
||||
if (!ceoDepartment) {
|
||||
console.error('CEO department not found in the database.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch all users in the CEO department
|
||||
const ceoUsers = userDatabase.findByDepartmentId(ceoDepartment.id);
|
||||
|
||||
// Create keys for each user in the CEO department
|
||||
ceoUsers.forEach(user => {
|
||||
console.log(`Creating key for user ${user.name} (ID: ${user.id})`);
|
||||
this.createKeyForUser(user.id);
|
||||
});
|
||||
|
||||
console.log('Keys created for all users in the CEO department.');
|
||||
}
|
||||
|
||||
public createKey(userId: string): UserKey {
|
||||
const { key, iv } = this.generateAESKeyAndIV();
|
||||
|
||||
// Check if a key for this user already exists
|
||||
const existingKey = this.findByUserId(userId);
|
||||
if (existingKey) {
|
||||
// Delete the existing key if present
|
||||
this.deleteKeysByUserId(userId);
|
||||
}
|
||||
|
||||
// Create the new key
|
||||
const newKey: UserKey = {
|
||||
id: uuidv4(),
|
||||
userId: userId,
|
||||
key: key,
|
||||
iv: iv
|
||||
};
|
||||
|
||||
const stmt = this.db.prepare('INSERT INTO user_keys (id, userId, key, iv) VALUES (?, ?, ?, ?)');
|
||||
stmt.run(newKey.id, newKey.userId, newKey.key, newKey.iv);
|
||||
|
||||
return newKey;
|
||||
}
|
||||
|
||||
// Find a key by userId (returns null if no key is found)
|
||||
public findByUserId(userId: string): UserKey | null {
|
||||
const stmt = this.db.prepare('SELECT * FROM user_keys WHERE userId = ?');
|
||||
const row = stmt.get(userId);
|
||||
return row || null;
|
||||
}
|
||||
|
||||
// Delete a key by userId (only one key per user is allowed)
|
||||
public deleteKeysByUserId(userId: string): boolean {
|
||||
const stmt = this.db.prepare('DELETE FROM user_keys WHERE userId = ?');
|
||||
const result = stmt.run(userId);
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
// Get all keys (for potential admin purposes)
|
||||
public getAllKeys(): UserKey[] {
|
||||
const stmt = this.db.prepare('SELECT * FROM user_keys');
|
||||
return stmt.all();
|
||||
}
|
||||
|
||||
// Clean the table but keep CEO keys
|
||||
public cleanTable(): { success: boolean; message: string } {
|
||||
try {
|
||||
const departments = departmentDatabase.getAllDepartments();
|
||||
const ceoDepartment = departments.find(dept => dept.name.toLowerCase() === 'ceo');
|
||||
|
||||
if (!ceoDepartment) {
|
||||
console.error('CEO department not found in the database.');
|
||||
return { success: false, message: 'Failed to find CEO department.' };
|
||||
}
|
||||
|
||||
// Fetch all users in the CEO department
|
||||
const ceoUsers = userDatabase.findByDepartmentId(ceoDepartment.id);
|
||||
const ceoUserIds = ceoUsers.map(user => user.id);
|
||||
|
||||
// Delete all keys except for the CEO users
|
||||
const stmt = this.db.prepare(`
|
||||
DELETE FROM user_keys WHERE userId NOT IN (${ceoUserIds.map(() => '?').join(', ')})
|
||||
`);
|
||||
stmt.run(...ceoUserIds);
|
||||
|
||||
return { success: true, message: 'All non-CEO keys have been deleted.' };
|
||||
} catch (error) {
|
||||
console.error('Error while cleaning the keys table:', error);
|
||||
return { success: false, message: 'Failed to clean keys table.' };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import path from "path";
|
||||
|
||||
class SQLiteDatabase {
|
||||
// Static variable to hold the single instance
|
||||
private static instance: Database.Database | null = null;
|
||||
private static readonly dbFilePath = path.join(__dirname, '..', '..', 'db', 'database.db');
|
||||
|
||||
// Private constructor prevents direct instantiation
|
||||
private constructor() {}
|
||||
|
||||
// Static method to get the instance of the database
|
||||
public static getInstance(): Database.Database {
|
||||
if (!SQLiteDatabase.instance) {
|
||||
// If no instance exists, create it
|
||||
SQLiteDatabase.instance = new Database(SQLiteDatabase.dbFilePath, {
|
||||
verbose: console.log, // Log queries (optional)
|
||||
});
|
||||
console.log('Database initialized');
|
||||
}
|
||||
|
||||
// Return the existing instance
|
||||
return SQLiteDatabase.instance;
|
||||
}
|
||||
|
||||
// Optional method to close the database connection
|
||||
public static closeDatabase(): void {
|
||||
if (SQLiteDatabase.instance) {
|
||||
SQLiteDatabase.instance.close();
|
||||
SQLiteDatabase.instance = null;
|
||||
console.log('Database connection closed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SQLiteDatabase;
|
||||
@@ -0,0 +1,235 @@
|
||||
import { pbkdf2Sync, randomBytes } from 'crypto';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import SQLiteDatabase from './sql_lite_database';
|
||||
import { departmentDatabase} from "./db";
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
dotenv.config(); // Load environment variables from the .env file
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
hashedPassword: string,
|
||||
salt: string,
|
||||
departmentId: string;
|
||||
app_type: string;
|
||||
}
|
||||
|
||||
export class UserDatabase {
|
||||
private db: any;
|
||||
|
||||
constructor() {
|
||||
// Get the singleton instance of the database
|
||||
this.db = SQLiteDatabase.getInstance();
|
||||
|
||||
// Check if the table exists and create it if not
|
||||
this.createTableIfNotExists();
|
||||
}
|
||||
|
||||
// Create the table if it doesn't exist
|
||||
private createTableIfNotExists() {
|
||||
const createTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
salt TEXT NOT NULL,
|
||||
hashedPassword TEXT NOT NULL,
|
||||
departmentId TEXT NOT NULL,
|
||||
app_type TEXT NOT NULL
|
||||
);
|
||||
`;
|
||||
this.db.exec(createTableQuery);
|
||||
|
||||
// Check if any users already exist
|
||||
const userCount = this.db.prepare('SELECT COUNT(*) as count FROM users').get().count;
|
||||
|
||||
if (userCount === 0) {
|
||||
console.log('No users found, creating default CEO user...');
|
||||
|
||||
// Fetch departments
|
||||
const departments = departmentDatabase.getAllDepartments();
|
||||
|
||||
// Find department ID for CEO
|
||||
const ceoDepartment = departments.find(dept => dept.name.toLowerCase() === 'ceo');
|
||||
|
||||
if (!ceoDepartment) {
|
||||
console.error('CEO department not found in the database.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get CEO credentials from the .env file
|
||||
const ceoEmail = process.env.CEO_EMAIL || 'ceo@yourfirm.com';
|
||||
const ceoPassword = process.env.CEO_PASSWORD || 'Password123!';
|
||||
|
||||
// Create the CEO user with the credentials from .env
|
||||
this.createUser('CEO', ceoEmail, ceoPassword, ceoDepartment.id, 'ceo');
|
||||
|
||||
console.log('Default CEO user created.');
|
||||
} else {
|
||||
console.log('Users already exist, skipping creation of default CEO user.');
|
||||
}
|
||||
}
|
||||
|
||||
// Find a user by email
|
||||
public findByEmail(email: string): User | null {
|
||||
const stmt = this.db.prepare('SELECT * FROM users WHERE email = ?');
|
||||
const row = stmt.get(email);
|
||||
return row || null;
|
||||
}
|
||||
|
||||
// Find a user by ID
|
||||
public findById(id: string): User | null {
|
||||
const stmt = this.db.prepare('SELECT * FROM users WHERE id = ?');
|
||||
const row = stmt.get(id);
|
||||
return row || null;
|
||||
}
|
||||
|
||||
// Create a new user
|
||||
public createUser(name: string, email: string, password: string, departmentId: string, app_type: string): User {
|
||||
const salt = randomBytes(16).toString('hex');
|
||||
const hashedPassword = this.hashPassword(password, salt);
|
||||
|
||||
const user: User = {
|
||||
id: uuidv4(),
|
||||
name,
|
||||
email,
|
||||
salt,
|
||||
hashedPassword,
|
||||
departmentId: departmentId,
|
||||
app_type: app_type
|
||||
};
|
||||
|
||||
const stmt = this.db.prepare('INSERT INTO users (id, name, email, salt, hashedPassword, departmentId, app_type) VALUES (?, ?, ?, ?, ?, ?, ?)');
|
||||
stmt.run(user.id, user.name, user.email, user.salt, user.hashedPassword, user.departmentId, user.app_type);
|
||||
return user;
|
||||
}
|
||||
|
||||
// Modify an existing user
|
||||
public modifyUser(id: string, name: string, email: string, password: string, departmentId: string, app_type: string): boolean {
|
||||
// Find the user by ID
|
||||
const user = this.findById(id);
|
||||
if (!user) return false;
|
||||
|
||||
// Hash the new password using the existing salt
|
||||
const hashedPassword = this.hashPassword(password, user.salt);
|
||||
|
||||
// Prepare the updated user data
|
||||
const updatedUser: User = {
|
||||
...user,
|
||||
name: name,
|
||||
email: email,
|
||||
salt: user.salt, // Use the same salt
|
||||
hashedPassword: hashedPassword, // Use the newly hashed password
|
||||
departmentId: departmentId,
|
||||
app_type: app_type,
|
||||
};
|
||||
|
||||
// Prepare and execute the SQL query to update the user
|
||||
const stmt = this.db.prepare(`
|
||||
UPDATE users
|
||||
SET name = ?, email = ?, salt = ?, hashedPassword = ?, departmentId = ?, app_type = ?
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
stmt.run(updatedUser.name, updatedUser.email, updatedUser.salt, updatedUser.hashedPassword, updatedUser.departmentId, updatedUser.app_type, updatedUser.id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Delete a user by ID
|
||||
public deleteUser(id: string): boolean {
|
||||
const stmt = this.db.prepare('DELETE FROM users WHERE id = ?');
|
||||
const result = stmt.run(id);
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
public findByDepartmentId(departmentId: string): User[] {
|
||||
const stmt = this.db.prepare('SELECT * FROM users WHERE departmentId = ?');
|
||||
return stmt.all(departmentId);
|
||||
}
|
||||
|
||||
// Get all users
|
||||
public getAllUsers(): User[] {
|
||||
const stmt = this.db.prepare('SELECT * FROM users');
|
||||
return stmt.all();
|
||||
}
|
||||
|
||||
// Verify email, password, and app_type for authentication
|
||||
public verifyCredentials(email: string, password: string, app_type: string): { success: boolean; message: string } {
|
||||
const user = this.findByEmail(email);
|
||||
|
||||
if (!user) {
|
||||
return { success: false, message: 'User not found.' };
|
||||
}
|
||||
|
||||
// Check if app_type matches
|
||||
if (user.app_type !== app_type) {
|
||||
return { success: false, message: `Access denied for app type: ${app_type}.` };
|
||||
}
|
||||
|
||||
// Check if password matches
|
||||
const hashedPassword = this.hashPassword(password, user.salt);
|
||||
if (hashedPassword === user.hashedPassword) {
|
||||
return { success: true, message: 'Authentication successful.' };
|
||||
} else {
|
||||
return { success: false, message: 'Incorrect password.' };
|
||||
}
|
||||
}
|
||||
|
||||
// Reset password by user email and new password
|
||||
public resetPassword(email: string, newPassword: string, app_type: string): { success: boolean; message: string } {
|
||||
const user = this.findByEmail(email);
|
||||
if (!user) {
|
||||
return { success: false, message: 'User not found.' };
|
||||
}
|
||||
|
||||
if(user.app_type !== app_type){
|
||||
return { success: false, message: 'Invalid app type for operation,' };
|
||||
}
|
||||
|
||||
// Generate a new salt for the new password
|
||||
const newSalt = randomBytes(16).toString('hex');
|
||||
const newHashedPassword = this.hashPassword(newPassword, newSalt);
|
||||
|
||||
const stmt = this.db.prepare('UPDATE users SET salt = ?, hashedPassword = ? WHERE email = ?');
|
||||
const result = stmt.run(newSalt, newHashedPassword, email);
|
||||
|
||||
if (result.changes > 0) {
|
||||
return { success: true, message: 'Password reset successfully.' };
|
||||
} else {
|
||||
return { success: false, message: 'Failed to reset password.' };
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to hash the password with the salt
|
||||
private hashPassword(password: string, salt: string): string {
|
||||
return pbkdf2Sync(password, salt, 1000, 64, 'sha256').toString('hex');
|
||||
}
|
||||
|
||||
public cleanTable(): { success: boolean; message: string } {
|
||||
try {
|
||||
// Fetch departments
|
||||
const departments = departmentDatabase.getAllDepartments();
|
||||
|
||||
const ceoDepartment = departments.find(dept => dept.name.toLowerCase() === 'ceo');
|
||||
|
||||
if (!ceoDepartment) {
|
||||
console.error('Admin or CEO department not found in the database.');
|
||||
return { success: false, message: 'Failed to find Admin or CEO department.' };
|
||||
}
|
||||
|
||||
// Delete users from the table except for those in the Admin and CEO departments
|
||||
const stmt = this.db.prepare(`
|
||||
DELETE FROM users WHERE departmentId NOT IN (?, ?)
|
||||
`);
|
||||
stmt.run(ceoDepartment.id);
|
||||
|
||||
return { success: true, message: 'All users except Admin and CEO have been deleted.' };
|
||||
} catch (error) {
|
||||
console.error('Error while cleaning the users table:', error);
|
||||
return { success: false, message: 'Failed to clean users table.' };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {TcpServer} from "./tcp_server";
|
||||
import {UdpServer} from "./udp_server";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
|
||||
dotenv.config({ path: path.join('..', '.env') });
|
||||
|
||||
const UDP_PORT: number = process.env.UDP_PORT ? parseInt(process.env.UDP_PORT) : 41234;
|
||||
const TCP_PORT: number = process.env.TCP_PORT? parseInt(process.env.TCP_PORT): 41233
|
||||
const HOST: string = process.env.HOST || '0.0.0.0';
|
||||
|
||||
// Function to handle server logs (not needed when starting directly)
|
||||
const handleServerLogs = (serverName: string): void => {
|
||||
console.log(`${serverName} started successfully.`);
|
||||
};
|
||||
|
||||
// Start the UDP server
|
||||
const udpServer = new UdpServer(HOST, UDP_PORT);
|
||||
udpServer.start();
|
||||
handleServerLogs('UDP Server');
|
||||
|
||||
// Start the TCP server
|
||||
const tcpServer = new TcpServer(HOST, TCP_PORT);
|
||||
tcpServer.start();
|
||||
handleServerLogs('TCP Server');
|
||||
@@ -0,0 +1,46 @@
|
||||
import { SocketCommunicatorBase } from "./socket_communicator/socket_communicator_base";
|
||||
|
||||
interface Connection {
|
||||
communicator: SocketCommunicatorBase;
|
||||
}
|
||||
|
||||
export class ConnectionManager {
|
||||
private readonly connections: { [key: string]: Connection };
|
||||
|
||||
constructor() {
|
||||
this.connections = {};
|
||||
}
|
||||
|
||||
// Adds a new communicator, keyed by both IP and port
|
||||
addConnection(ip: string, port: number, communicator: SocketCommunicatorBase): void {
|
||||
const key = `${ip}:${port}`;
|
||||
|
||||
// Store the communicator along with the client's public and private keys
|
||||
this.connections[key] = {
|
||||
communicator
|
||||
};
|
||||
|
||||
console.log(`Added communicator for ${ip}:${port}, generated public and private keys.`);
|
||||
}
|
||||
|
||||
// Removes a communicator based on IP and port
|
||||
removeCommunicator(ip: string, port: number): void {
|
||||
const key = `${ip}:${port}`;
|
||||
if (this.connections[key]) {
|
||||
delete this.connections[key];
|
||||
console.log(`Removed communicator for ${ip}:${port}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieves a communicator based on IP and port
|
||||
getCommunicator(ip: string, port: number): SocketCommunicatorBase | null {
|
||||
const key = `${ip}:${port}`;
|
||||
return this.connections[key] ? this.connections[key].communicator : null;
|
||||
}
|
||||
|
||||
// Checks if a communicator exists for a given IP and port
|
||||
communicatorExists(ip: string, port: number): boolean {
|
||||
const key = `${ip}:${port}`;
|
||||
return this.connections[key] !== undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
export interface ParsedMessage {
|
||||
operationCode: string;
|
||||
metaInfo?: { [key: string]: any };
|
||||
fileContent?: Buffer;
|
||||
}
|
||||
|
||||
export class MessageHandler {
|
||||
// Format the message with operationCode, guid, metaInfo, and fileContent (Base64 for fileContent)
|
||||
static formatMessage(
|
||||
operationCode: string,
|
||||
metaInfo?: { [key: string]: any },
|
||||
fileContent?: Buffer
|
||||
): string {
|
||||
let message = `${operationCode}\n`; // First part: operationCode and guid
|
||||
|
||||
if (metaInfo && Object.keys(metaInfo).length > 0) {
|
||||
message += `${JSON.stringify(metaInfo)}\n`; // Add metaInfo
|
||||
}
|
||||
|
||||
if (fileContent && fileContent.length > 0) {
|
||||
message += fileContent.toString('base64'); // Convert buffer to Base64 for fileContent
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
// Parse the incoming message (convert Base64 back to Buffer if fileContent is present)
|
||||
static parseMessage(msg: string): ParsedMessage {
|
||||
const parts = msg.split('\n'); // Split by \n (operationCode, metaInfo, and fileContent are on separate lines)
|
||||
|
||||
// First part should always be the operation code
|
||||
const operationCode = parts[0]?.trim();
|
||||
if (!operationCode) {
|
||||
throw new Error('Missing operation code in the message');
|
||||
}
|
||||
|
||||
let metaInfo: { [key: string]: any } | undefined = undefined;
|
||||
let fileContent: Buffer | undefined = undefined;
|
||||
|
||||
// Parse the metaInfo (JSON object) if present
|
||||
if (parts[1]) {
|
||||
try {
|
||||
metaInfo = JSON.parse(parts[1].trim());
|
||||
} catch (err) {
|
||||
console.error('Invalid metaInfo JSON format:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert Base64 string back to Buffer for fileContent if present
|
||||
if (parts[2]) {
|
||||
fileContent = Buffer.from(parts[2].trim(), 'base64');
|
||||
}
|
||||
|
||||
return {
|
||||
operationCode,
|
||||
metaInfo,
|
||||
fileContent,
|
||||
};
|
||||
}
|
||||
|
||||
// Validate if the parsed message contains an operation code
|
||||
static validateMessage(parsedMessage: ParsedMessage | null): boolean {
|
||||
return !!parsedMessage?.operationCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { OperationHandler } from './operation_handler';
|
||||
import { OperationPlugin } from './operation_plugin';
|
||||
|
||||
export abstract class OperationBase implements OperationPlugin {
|
||||
// Shared operation codes
|
||||
public static readonly operationCodes = {
|
||||
OK: 'OK',
|
||||
ERR: 'ERR',
|
||||
END: 'END',
|
||||
};
|
||||
|
||||
// Default handler for OK operation
|
||||
public static handleOk(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
console.log('OK operation received');
|
||||
return parsedMessage; // Typically, you would just acknowledge with OK, returning as-is
|
||||
}
|
||||
|
||||
// Default handler for ERR operation
|
||||
public static handleErr(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
console.log('ERR operation received: ', parsedMessage.metaInfo?.message || 'No error details provided');
|
||||
return parsedMessage; // Typically, you would log the error and return
|
||||
}
|
||||
|
||||
// Default handler for END operation
|
||||
public static handleEnd(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
console.log('END operation received');
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.END,
|
||||
metaInfo: { message: 'Connection ended.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Register the common OK, ERR, and END handlers
|
||||
public static registerCommonOperations(operationHandler: OperationHandler): void {
|
||||
operationHandler.registerHandler(OperationBase.operationCodes.OK, OperationBase.handleOk);
|
||||
operationHandler.registerHandler(OperationBase.operationCodes.ERR, OperationBase.handleErr);
|
||||
operationHandler.registerHandler(OperationBase.operationCodes.END, OperationBase.handleEnd);
|
||||
}
|
||||
|
||||
// Abstract register method that will be implemented by subclasses
|
||||
public abstract register(operationHandler: OperationHandler): void;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// operation_handler.ts
|
||||
import { ParsedMessage, MessageHandler } from '../message_handler';
|
||||
import { OperationPlugin } from './operation_plugin';
|
||||
|
||||
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => ParsedMessage;
|
||||
|
||||
export class OperationHandler {
|
||||
private static instance: OperationHandler;
|
||||
private handlers: { [operationCode: string]: OperationHandlerFunction } = {};
|
||||
|
||||
private constructor() {
|
||||
// Register only the unknown command handler on initialization
|
||||
this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand);
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
public static getInstance(): OperationHandler {
|
||||
if (!OperationHandler.instance) {
|
||||
OperationHandler.instance = new OperationHandler();
|
||||
}
|
||||
return OperationHandler.instance;
|
||||
}
|
||||
|
||||
// Register a handler for a specific operation code
|
||||
public registerHandler(operationCode: string, handler: OperationHandlerFunction): void {
|
||||
this.handlers[operationCode] = handler;
|
||||
}
|
||||
|
||||
// Handle operation request
|
||||
public handleOperation(rawMessage: string): ParsedMessage {
|
||||
// Parse and validate the message
|
||||
const parsedMessage = MessageHandler.parseMessage(rawMessage);
|
||||
if (!parsedMessage || !MessageHandler.validateMessage(parsedMessage)) {
|
||||
return this.handleUnknownCommand(parsedMessage);
|
||||
}
|
||||
|
||||
// Dispatch the handler for the given operation code
|
||||
const handler = this.handlers[parsedMessage.operationCode];
|
||||
if (handler) {
|
||||
return handler(parsedMessage);
|
||||
} else {
|
||||
return this.handleUnknownCommand(parsedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all registered operation codes
|
||||
public getAvailableOperationCodes(): string[] {
|
||||
return Object.keys(this.handlers);
|
||||
}
|
||||
|
||||
// Default handler for unknown commands
|
||||
private handleUnknownCommand(parsedMessage?: ParsedMessage): ParsedMessage {
|
||||
return {
|
||||
operationCode: 'UNKNOWN_COMMAND',
|
||||
metaInfo: { message: 'Unknown command received.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Plugin system: Load plugins to register handlers
|
||||
public loadPlugin(plugin: OperationPlugin): void {
|
||||
plugin.register(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// operation_plugin.ts
|
||||
import { OperationHandler } from './operation_handler';
|
||||
|
||||
export interface OperationPlugin {
|
||||
register(operationHandler: OperationHandler): void;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import {userDatabase, departmentDatabase, keyDatabase} from '../../db_managers/db';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
|
||||
export class AuthOperations extends OperationBase {
|
||||
public static readonly operationCodes = {
|
||||
...OperationBase.operationCodes,
|
||||
LOGIN: 'LOGIN',
|
||||
SIGN_UP: 'SIGN_UP',
|
||||
RESET_PASSWORD: 'RESET_PASSWORD'
|
||||
};
|
||||
|
||||
// Utility function to validate email format
|
||||
private static isValidEmail(email: string): boolean {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
// Utility function to validate password strength (min 8 chars, at least 1 number and 1 special char)
|
||||
private static isStrongPassword(password: string): boolean {
|
||||
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
|
||||
return passwordRegex.test(password);
|
||||
}
|
||||
|
||||
// Utility function to check name is not empty
|
||||
private static isValidName(name: string): boolean {
|
||||
return name.trim().length > 0;
|
||||
}
|
||||
|
||||
private static isValidAppType(app_type: string){
|
||||
const app_types = ['client', 'ceo', 'admin'];
|
||||
return app_types.includes(app_type);
|
||||
}
|
||||
|
||||
// Handle Login operation with validation
|
||||
public static handleLogin(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const { email, password, app_type } = parsedMessage.metaInfo || {};
|
||||
|
||||
// Check if email and password are provided
|
||||
if (!email || !password || !app_type) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: { message: 'Both email and password are required.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Verify email and password with the database
|
||||
const result = userDatabase.verifyCredentials(email, password, app_type);
|
||||
|
||||
// Handle the case where the credentials are incorrect
|
||||
if (!result.success) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: { message: result.message },
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.OK,
|
||||
metaInfo: { message: 'Login successful.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Handle SignUp operation with validation
|
||||
public static handleSignUp(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const { name, email, password, departmentId, app_type } = parsedMessage.metaInfo || {};
|
||||
|
||||
// Validate name
|
||||
if (!AuthOperations.isValidName(name)) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: { message: 'Invalid name. Please provide a valid name.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Validate email format
|
||||
if (!AuthOperations.isValidEmail(email)) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: { message: 'Invalid email format.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Validate password strength
|
||||
if (!AuthOperations.isStrongPassword(password)) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: {
|
||||
message: 'Weak password. It must be at least 8 characters long, contain at least 1 letter, 1 number, and 1 special character.',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!AuthOperations.isValidAppType(app_type)) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: {
|
||||
message: 'Not valid app_type.',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Check if user with this email already exists
|
||||
const existingUser = userDatabase.findByEmail(email);
|
||||
if (existingUser) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: { message: 'Email already in use.' },
|
||||
};
|
||||
}
|
||||
|
||||
const existingUserByName = userDatabase.getAllUsers().find((user) => user.name === name);
|
||||
if (existingUserByName) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: { message: 'Name already in use.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Validate department ID
|
||||
const departmentEntry = departmentDatabase.findById(departmentId);
|
||||
if (!departmentEntry) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: { message: 'Invalid department.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Create new user in the database
|
||||
const newUser = userDatabase.createUser(name, email, password, departmentEntry.id, app_type);
|
||||
|
||||
// Create a key for the new user
|
||||
keyDatabase.createKey(newUser.id);
|
||||
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.OK,
|
||||
metaInfo: {
|
||||
message: 'User created successfully.',
|
||||
userId: newUser.id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Reset Password operation
|
||||
public static handleResetPassword(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const { email, newPassword, app_type } = parsedMessage.metaInfo || {};
|
||||
|
||||
if (!AuthOperations.isStrongPassword(newPassword)) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: {
|
||||
message: 'Weak password. It must be at least 8 characters long, contain at least 1 letter, 1 number, and 1 special character.',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Logic to reset password (could be sending a reset link, or generating a temp password)
|
||||
const resetResult = userDatabase.resetPassword(email, newPassword, app_type);
|
||||
|
||||
if (resetResult.success) {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.OK,
|
||||
metaInfo: { message: 'Password reset successfully. Please check your email for instructions.' },
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.ERR,
|
||||
metaInfo: { message: 'Failed to reset password.' },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Register specific operations for AuthOperations
|
||||
public register(operationHandler: OperationHandler): void {
|
||||
operationHandler.registerHandler(AuthOperations.operationCodes.LOGIN, AuthOperations.handleLogin);
|
||||
operationHandler.registerHandler(AuthOperations.operationCodes.SIGN_UP, AuthOperations.handleSignUp);
|
||||
operationHandler.registerHandler(AuthOperations.operationCodes.RESET_PASSWORD, AuthOperations.handleResetPassword)
|
||||
|
||||
// Register common OK, ERR, and END operations from the base class
|
||||
OperationBase.registerCommonOperations(operationHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import {
|
||||
departmentDatabase,
|
||||
keyDatabase,
|
||||
userDatabase
|
||||
} from '../../db_managers/db';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
|
||||
export class CeoOperations extends OperationBase {
|
||||
public static readonly operationCodes = {
|
||||
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END)
|
||||
RESET_DATABASE: 'RESET_DATABASE',
|
||||
};
|
||||
|
||||
// Handle reset database operation
|
||||
public static handleResetDatabase(): ParsedMessage {
|
||||
console.log('Resetting the database...');
|
||||
|
||||
departmentDatabase.cleanTable();
|
||||
userDatabase.cleanTable();
|
||||
keyDatabase.cleanTable();
|
||||
|
||||
// Create the departments
|
||||
departmentDatabase.createDepartment('Worker');
|
||||
|
||||
return {
|
||||
operationCode: CeoOperations.operationCodes.OK,
|
||||
metaInfo: {
|
||||
message: 'Database reset successfully.',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Register general operations with the OperationHandler
|
||||
public register(operationHandler: OperationHandler): void {
|
||||
operationHandler.registerHandler(CeoOperations.operationCodes.RESET_DATABASE, CeoOperations.handleResetDatabase);
|
||||
|
||||
// Register common operations inherited from the base class
|
||||
OperationBase.registerCommonOperations(operationHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import {departmentDatabase} from '../../db_managers/db';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
|
||||
export class DepartmentOperations extends OperationBase {
|
||||
public static readonly operationCodes = {
|
||||
...OperationBase.operationCodes,
|
||||
GET_DEPARTMENTS: 'GET_DEPARTMENTS',
|
||||
CREATE_DEPARTMENT: 'CREATE_DEPARTMENT',
|
||||
MODIFY_DEPARTMENT: 'MODIFY_DEPARTMENT',
|
||||
DELETE_DEPARTMENT: 'DELETE_DEPARTMENT',
|
||||
FIND_DEPARTMENT_BY_ID: 'FIND_DEPARTMENT_BY_ID'
|
||||
};
|
||||
|
||||
// Get all departments
|
||||
public static handleGetDepartments(): ParsedMessage {
|
||||
const departments = departmentDatabase.getAllDepartments();
|
||||
return {
|
||||
operationCode: DepartmentOperations.operationCodes.OK,
|
||||
metaInfo: { departments },
|
||||
};
|
||||
}
|
||||
|
||||
public static handleGetDepartmentById(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const { id } = parsedMessage.metaInfo || {};
|
||||
|
||||
if(!id){
|
||||
return {
|
||||
operationCode: DepartmentOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Id is required.' },
|
||||
};
|
||||
}
|
||||
|
||||
const user = departmentDatabase.findById(id);
|
||||
return {
|
||||
operationCode: user === null ? DepartmentOperations.operationCodes.ERR : DepartmentOperations.operationCodes.OK,
|
||||
metaInfo: user === null ? { message: 'Department not found.' } : { message: user }
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new department
|
||||
public static handleCreateDepartment(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// @ts-ignore
|
||||
const { departmentName } = parsedMessage.metaInfo;
|
||||
|
||||
// Check if department name is provided
|
||||
if (!departmentName) {
|
||||
return {
|
||||
operationCode: DepartmentOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Department name is required.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Check if the department already exists
|
||||
const existingDepartment = departmentDatabase.findByName(departmentName);
|
||||
if (existingDepartment) {
|
||||
return {
|
||||
operationCode: DepartmentOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Department already exists.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Create the new department
|
||||
const newDepartment = departmentDatabase.createDepartment(departmentName);
|
||||
|
||||
return {
|
||||
operationCode: DepartmentOperations.operationCodes.OK,
|
||||
metaInfo: { message: 'Department created successfully.', departmentId: newDepartment.id },
|
||||
};
|
||||
}
|
||||
|
||||
// Modify an existing department
|
||||
public static handleModifyDepartment(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// @ts-ignore
|
||||
const { departmentId, newDepartmentName } = parsedMessage.metaInfo;
|
||||
|
||||
// Validate input
|
||||
if (!departmentId || !newDepartmentName) {
|
||||
return {
|
||||
operationCode: DepartmentOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Department ID and new department name are required.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Modify the department
|
||||
const isSuccess = departmentDatabase.modifyDepartment(departmentId, newDepartmentName);
|
||||
|
||||
return {
|
||||
operationCode: isSuccess ? DepartmentOperations.operationCodes.OK : DepartmentOperations.operationCodes.ERR,
|
||||
metaInfo: { message: isSuccess ? 'Department modified successfully.' : 'Failed to modify department.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Delete an existing department
|
||||
public static handleDeleteDepartment(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// @ts-ignore
|
||||
const { departmentId } = parsedMessage.metaInfo;
|
||||
|
||||
// Validate input
|
||||
if (!departmentId) {
|
||||
return {
|
||||
operationCode: DepartmentOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Department ID is required.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Delete the department
|
||||
const isSuccess = departmentDatabase.deleteDepartment(departmentId);
|
||||
|
||||
return {
|
||||
operationCode: isSuccess ? DepartmentOperations.operationCodes.OK : DepartmentOperations.operationCodes.ERR,
|
||||
metaInfo: { message: isSuccess ? 'Department deleted successfully.' : 'Failed to delete department.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Register department operations_base with the OperationHandler
|
||||
public register(operationHandler: OperationHandler): void {
|
||||
// Register the department operation handlers
|
||||
operationHandler.registerHandler(DepartmentOperations.operationCodes.GET_DEPARTMENTS, DepartmentOperations.handleGetDepartments);
|
||||
operationHandler.registerHandler(DepartmentOperations.operationCodes.CREATE_DEPARTMENT, DepartmentOperations.handleCreateDepartment);
|
||||
operationHandler.registerHandler(DepartmentOperations.operationCodes.MODIFY_DEPARTMENT, DepartmentOperations.handleModifyDepartment);
|
||||
operationHandler.registerHandler(DepartmentOperations.operationCodes.DELETE_DEPARTMENT, DepartmentOperations.handleDeleteDepartment);
|
||||
operationHandler.registerHandler(DepartmentOperations.operationCodes.FIND_DEPARTMENT_BY_ID, DepartmentOperations.handleGetDepartmentById);
|
||||
|
||||
// Register common operations_base inherited from the base class
|
||||
OperationBase.registerCommonOperations(operationHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
import os from 'node:os';
|
||||
|
||||
export class GeneralOperations extends OperationBase {
|
||||
public static readonly operationCodes = {
|
||||
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END)
|
||||
HEARTBEAT: 'HEARTBEAT',
|
||||
ALIVE: 'ALIVE',
|
||||
SET_PUBLIC_KEY: 'SET_PUBLIC_KEY',
|
||||
SET_AES_KEY: 'SET_AES_KEY', // New operation for AES key
|
||||
};
|
||||
|
||||
// Handle heartbeat operation
|
||||
public static handleHeartbeat(): ParsedMessage {
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
let ipAddress = 'Unknown';
|
||||
|
||||
for (const iface of Object.values(networkInterfaces)) {
|
||||
// @ts-ignore
|
||||
for (const address of iface) {
|
||||
if (address.family === 'IPv4' && !address.internal) {
|
||||
ipAddress = address.address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ipAddress !== 'Unknown') break;
|
||||
}
|
||||
|
||||
return {
|
||||
operationCode: GeneralOperations.operationCodes.ALIVE,
|
||||
metaInfo: { ipAddress },
|
||||
};
|
||||
}
|
||||
|
||||
// Handle public key exchange
|
||||
public static handlePublicKey(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const clientPublicKey = parsedMessage.metaInfo?.publicKey;
|
||||
if (clientPublicKey) {
|
||||
return {
|
||||
operationCode: GeneralOperations.operationCodes.SET_PUBLIC_KEY,
|
||||
metaInfo: { message: clientPublicKey },
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
operationCode: GeneralOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'No public key provided.' },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle AES key exchange
|
||||
public static handleAESKey(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const aesKey = parsedMessage.metaInfo?.aesKey;
|
||||
if (aesKey) {
|
||||
return {
|
||||
operationCode: GeneralOperations.operationCodes.SET_AES_KEY,
|
||||
metaInfo: { message: aesKey },
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
operationCode: GeneralOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'No AES key provided.' },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Register general operations with the OperationHandler
|
||||
public register(operationHandler: OperationHandler): void {
|
||||
// Register specific handlers for the general operations
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.HEARTBEAT, GeneralOperations.handleHeartbeat);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_PUBLIC_KEY, GeneralOperations.handlePublicKey);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey); // Register AES key handler
|
||||
|
||||
// Register common operations inherited from the base class
|
||||
OperationBase.registerCommonOperations(operationHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { keyDatabase } from '../../db_managers/db';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
|
||||
export class KeyOperations extends OperationBase {
|
||||
public static readonly operationCodes = {
|
||||
...OperationBase.operationCodes,
|
||||
GET_KEYS: 'GET_KEYS',
|
||||
CREATE_KEY: 'CREATE_KEY',
|
||||
DELETE_KEY: 'DELETE_KEY',
|
||||
FIND_KEY_BY_USER_ID: 'FIND_KEY_BY_USER_ID',
|
||||
};
|
||||
|
||||
// Get all keys
|
||||
public static handleGetKeys(): ParsedMessage {
|
||||
const keys = keyDatabase.getAllKeys();
|
||||
console.log(keys);
|
||||
return {
|
||||
operationCode: KeyOperations.operationCodes.OK,
|
||||
metaInfo: { keys },
|
||||
};
|
||||
}
|
||||
|
||||
// Create a key for a user
|
||||
public static handleCreateKey(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const { userId } = parsedMessage.metaInfo || {};
|
||||
|
||||
if (!userId) {
|
||||
return {
|
||||
operationCode: KeyOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'User ID is required to create a key.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Create the key for the user
|
||||
const key = keyDatabase.createKey(userId);
|
||||
return {
|
||||
operationCode: KeyOperations.operationCodes.OK,
|
||||
metaInfo: { key },
|
||||
};
|
||||
}
|
||||
|
||||
// Find a key by user ID
|
||||
public static handleFindKeyByUserId(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const { userId } = parsedMessage.metaInfo || {};
|
||||
|
||||
if (!userId) {
|
||||
return {
|
||||
operationCode: KeyOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'User ID is required.' },
|
||||
};
|
||||
}
|
||||
|
||||
const key = keyDatabase.findByUserId(userId);
|
||||
return {
|
||||
operationCode: key === null ? KeyOperations.operationCodes.ERR : KeyOperations.operationCodes.OK,
|
||||
metaInfo: key === null ? { message: 'Key not found.' } : { key },
|
||||
};
|
||||
}
|
||||
|
||||
// Delete a key by user ID
|
||||
public static handleDeleteKey(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const { userId } = parsedMessage.metaInfo || {};
|
||||
|
||||
if (!userId) {
|
||||
return {
|
||||
operationCode: KeyOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'User ID is required for deletion.' },
|
||||
};
|
||||
}
|
||||
|
||||
const isSuccess = keyDatabase.deleteKeysByUserId(userId);
|
||||
return {
|
||||
operationCode: isSuccess ? KeyOperations.operationCodes.OK : KeyOperations.operationCodes.ERR,
|
||||
metaInfo: { message: isSuccess ? 'Key deleted successfully.' : 'Failed to delete key.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Register key operations with the OperationHandler
|
||||
public register(operationHandler: OperationHandler): void {
|
||||
operationHandler.registerHandler(KeyOperations.operationCodes.GET_KEYS, KeyOperations.handleGetKeys);
|
||||
operationHandler.registerHandler(KeyOperations.operationCodes.CREATE_KEY, KeyOperations.handleCreateKey);
|
||||
operationHandler.registerHandler(KeyOperations.operationCodes.DELETE_KEY, KeyOperations.handleDeleteKey);
|
||||
operationHandler.registerHandler(KeyOperations.operationCodes.FIND_KEY_BY_USER_ID, KeyOperations.handleFindKeyByUserId);
|
||||
|
||||
// Register common operations inherited from OperationBase
|
||||
OperationBase.registerCommonOperations(operationHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import {userDatabase} from '../../db_managers/db';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
|
||||
export class UserOperations extends OperationBase {
|
||||
public static readonly operationCodes = {
|
||||
...OperationBase.operationCodes,
|
||||
GET_USERS: 'GET_USERS',
|
||||
CREATE_USER: 'CREATE_USER',
|
||||
MODIFY_USER: 'MODIFY_USER',
|
||||
DELETE_USER: 'DELETE_USER',
|
||||
FIND_BY_ID: 'FIND_BY_ID',
|
||||
FIND_BY_EMAIL: 'FIND_BY_EMAIL',
|
||||
};
|
||||
|
||||
// Get all users
|
||||
public static handleGetUsers(): ParsedMessage {
|
||||
const users = userDatabase.getAllUsers();
|
||||
console.log(users);
|
||||
return {
|
||||
operationCode: UserOperations.operationCodes.OK,
|
||||
metaInfo: { users },
|
||||
};
|
||||
}
|
||||
|
||||
public static handleGetUserById(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const { id } = parsedMessage.metaInfo || {};
|
||||
|
||||
if(!id){
|
||||
return {
|
||||
operationCode: UserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Id is required.' },
|
||||
};
|
||||
}
|
||||
|
||||
const user = userDatabase.findById(id);
|
||||
return {
|
||||
operationCode: user === null ? UserOperations.operationCodes.ERR : UserOperations.operationCodes.OK,
|
||||
metaInfo: user === null ? { message: 'User not found.' } : { message: user }
|
||||
}
|
||||
}
|
||||
|
||||
public static handleGetUserByEmail(parsedMessage: ParsedMessage) : ParsedMessage{
|
||||
const { email } = parsedMessage.metaInfo || {};
|
||||
if(!email){
|
||||
return {
|
||||
operationCode: UserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Email is required.' },
|
||||
};
|
||||
}
|
||||
|
||||
const user = userDatabase.findByEmail(email);
|
||||
const response = {
|
||||
operationCode: user === null ? UserOperations.operationCodes.ERR : UserOperations.operationCodes.OK,
|
||||
metaInfo: user === null ? { message: 'User not found.' } : user
|
||||
}
|
||||
|
||||
console.log(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
// Modify an existing user
|
||||
public static handleModifyUser(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const { id, name, email, password, departmentId, app_type } = parsedMessage.metaInfo || {};
|
||||
|
||||
if (!id || !name || !email || !password || !departmentId || !app_type) {
|
||||
return {
|
||||
operationCode: UserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Some information are missing.' },
|
||||
};
|
||||
}
|
||||
|
||||
const user = userDatabase.findById(id);
|
||||
if (!user) return {
|
||||
operationCode: UserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'User not found in the system.' },
|
||||
};
|
||||
|
||||
const listOfUsers = userDatabase.getAllUsers()
|
||||
|
||||
// Check if another user already exists with the same email or name (excluding the current user)
|
||||
const existingUserByEmail = listOfUsers.find((existingUser) => existingUser.email === email && existingUser.id !== id);
|
||||
const existingUserByName = listOfUsers.find((existingUser) => existingUser.name === name && existingUser.id !== id);
|
||||
|
||||
if (existingUserByEmail) {
|
||||
return {
|
||||
operationCode: UserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Email ${email} is already in use by another user.` },
|
||||
};
|
||||
}
|
||||
|
||||
if (existingUserByName) {
|
||||
return {
|
||||
operationCode: UserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Name ${name} is already in use by another user.` },
|
||||
};
|
||||
}
|
||||
|
||||
const isSuccess = userDatabase.modifyUser(id, name, email, password, departmentId, app_type);
|
||||
return {
|
||||
operationCode: isSuccess ? UserOperations.operationCodes.OK : UserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: isSuccess ? 'User modified successfully.' : 'Failed to modify user.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Delete a user by ID
|
||||
public static handleDeleteUser(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const { id } = parsedMessage.metaInfo || {};
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
operationCode: UserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'User ID is required for deletion.' },
|
||||
};
|
||||
}
|
||||
|
||||
const isSuccess = userDatabase.deleteUser(id);
|
||||
return {
|
||||
operationCode: isSuccess ? UserOperations.operationCodes.OK : UserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: isSuccess ? 'User deleted successfully.' : 'Failed to delete user.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Register user operations with the OperationHandler
|
||||
public register(operationHandler: OperationHandler): void {
|
||||
operationHandler.registerHandler(UserOperations.operationCodes.GET_USERS, UserOperations.handleGetUsers);
|
||||
operationHandler.registerHandler(UserOperations.operationCodes.MODIFY_USER, UserOperations.handleModifyUser);
|
||||
operationHandler.registerHandler(UserOperations.operationCodes.DELETE_USER, UserOperations.handleDeleteUser);
|
||||
operationHandler.registerHandler(UserOperations.operationCodes.FIND_BY_ID, UserOperations.handleGetUserById);
|
||||
operationHandler.registerHandler(UserOperations.operationCodes.FIND_BY_EMAIL, UserOperations.handleGetUserByEmail);
|
||||
|
||||
// Register common operations inherited from OperationBase
|
||||
OperationBase.registerCommonOperations(operationHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
|
||||
export abstract class SocketCommunicatorBase {
|
||||
protected readonly ip: string;
|
||||
protected readonly port: number;
|
||||
protected readonly operationHandler: OperationHandler;
|
||||
protected handlerResult: ParsedMessage | null;
|
||||
|
||||
protected constructor(ip: string, port: number, operationHandler: OperationHandler) {
|
||||
this.ip = ip;
|
||||
this.port = port;
|
||||
this.operationHandler = operationHandler
|
||||
this.handlerResult = null;
|
||||
}
|
||||
|
||||
// Getter for the handler result
|
||||
getHandlerResult(): ParsedMessage | null {
|
||||
return this.handlerResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import { Socket } from 'net';
|
||||
import { privateEncrypt, privateDecrypt, generateKeyPairSync, createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
||||
import { MessageHandler, ParsedMessage } from '../message_handler';
|
||||
import { SocketCommunicatorBase } from './socket_communicator_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
import {constants} from "node:crypto";
|
||||
|
||||
const END_OF_MESSAGE = '<EOM>'; // Define a unique marker for end of message
|
||||
|
||||
export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
private readonly socket: Socket;
|
||||
private privateKey: string | null;
|
||||
private publicKey: string | null;
|
||||
private aesKey: Buffer | null;
|
||||
private aesIv: Buffer | null;
|
||||
private messageBuffer: string;
|
||||
|
||||
constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) {
|
||||
super(ip, port, operationHandler);
|
||||
this.socket = socket;
|
||||
this.privateKey = null;
|
||||
this.publicKey = null; // Client public key will be set later
|
||||
this.aesKey = null;
|
||||
this.aesIv = null;
|
||||
this.messageBuffer = ''; // Initialize the message buffer
|
||||
this.generateKeyPair(); // Generate RSA key pair for encryption
|
||||
}
|
||||
|
||||
// Generate RSA key pair (public and private keys)
|
||||
generateKeyPair(): void {
|
||||
const { privateKey, publicKey } = generateKeyPairSync('rsa', {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
||||
});
|
||||
this.privateKey = privateKey;
|
||||
this.publicKey = publicKey;
|
||||
console.log('RSA key pair generated.');
|
||||
}
|
||||
|
||||
// Send the server's public key to the client
|
||||
async sendPublicKey(): Promise<void> {
|
||||
if (!this.publicKey) {
|
||||
throw new Error('Public key is not available. Please generate RSA key pair.');
|
||||
}
|
||||
|
||||
const message = MessageHandler.formatMessage('SET_PUBLIC_KEY', { publicKey: this.publicKey });
|
||||
await this.writeToSocket(message + END_OF_MESSAGE);
|
||||
console.log('Public key sent to client.');
|
||||
}
|
||||
|
||||
// Generate AES key and IV, then send them to the client
|
||||
async sendAesKey(): Promise<void> {
|
||||
this.aesKey = randomBytes(32); // 256-bit AES key
|
||||
this.aesIv = randomBytes(16); // AES IV
|
||||
|
||||
const aesKeyBase64 = this.aesKey.toString('base64');
|
||||
const aesIvBase64 = this.aesIv.toString('base64');
|
||||
|
||||
const formattedMessage = MessageHandler.formatMessage('SET_AES_KEY', { aesKey: aesKeyBase64, aesIv: aesIvBase64 });
|
||||
const encryptedMessage = this.encryptWithRsa(Buffer.from(formattedMessage)).toString('base64');
|
||||
|
||||
await this.writeToSocket(encryptedMessage + END_OF_MESSAGE);
|
||||
console.log('AES key and IV sent to client.');
|
||||
}
|
||||
|
||||
// Encrypt a message with the server's private key (RSA encryption)
|
||||
private encryptWithRsa(message: Buffer): Buffer {
|
||||
const bufferMessage = Buffer.isBuffer(message) ? message : Buffer.from(message);
|
||||
if (!this.privateKey) throw new Error('Server private key not set.');
|
||||
|
||||
return privateEncrypt(
|
||||
{
|
||||
key: this.privateKey,
|
||||
padding: constants.RSA_PKCS1_PADDING, // PKCS1 padding
|
||||
},
|
||||
bufferMessage
|
||||
);
|
||||
}
|
||||
|
||||
// Decrypt AES-encrypted messages
|
||||
private decryptWithAes(encryptedMessage: string): string {
|
||||
if (!this.aesKey || !this.aesIv) {
|
||||
throw new Error('AES key not set.');
|
||||
}
|
||||
|
||||
const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv);
|
||||
let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64'));
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||
return decrypted.toString('utf-8');
|
||||
}
|
||||
|
||||
// Encrypt a message with AES
|
||||
private encryptWithAes(message: string): string {
|
||||
if (!this.aesKey || !this.aesIv) {
|
||||
throw new Error('AES key or IV is not set.');
|
||||
}
|
||||
const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv);
|
||||
let encrypted = cipher.update(message, 'utf-8');
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
return encrypted.toString('base64');
|
||||
}
|
||||
|
||||
// Handle incoming chunks of data
|
||||
async handleIncomingChunk(data: Buffer): Promise<void> {
|
||||
const incomingMessage = data.toString();
|
||||
this.messageBuffer += incomingMessage;
|
||||
|
||||
// Check if the message ends with END_OF_MESSAGE
|
||||
if (this.messageBuffer.endsWith(END_OF_MESSAGE)) {
|
||||
// Remove the END_OF_MESSAGE marker and process the message
|
||||
const completeMessage = this.messageBuffer.slice(0, -END_OF_MESSAGE.length);
|
||||
|
||||
console.log(`\n\nComplete Message:\n${completeMessage}\n\n`);
|
||||
|
||||
this.handleIncomingMessage(completeMessage);
|
||||
|
||||
// Clear the message buffer after processing
|
||||
this.messageBuffer = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Send chunked message
|
||||
async sendChunkedMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<void> {
|
||||
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent);
|
||||
let outgoingMessage: string;
|
||||
|
||||
if (this.aesKey && this.aesIv) {
|
||||
outgoingMessage = this.encryptWithAes(message);
|
||||
} else {
|
||||
outgoingMessage = message;
|
||||
}
|
||||
|
||||
outgoingMessage += END_OF_MESSAGE; // Append end-of-message marker
|
||||
await this.writeToSocket(outgoingMessage);
|
||||
}
|
||||
|
||||
// Handle incoming message (decrypt with AES if available)
|
||||
handleIncomingMessage(incomingMessage: string): void {
|
||||
let messageToProcess = incomingMessage;
|
||||
|
||||
if (this.aesKey && this.aesIv) {
|
||||
messageToProcess = this.decryptWithAes(incomingMessage);
|
||||
}
|
||||
|
||||
this.handlerResult = this.operationHandler.handleOperation(messageToProcess);
|
||||
}
|
||||
|
||||
// Write message to socket
|
||||
private writeToSocket(message: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.write(message, (err: any) => {
|
||||
if (err) {
|
||||
console.error('Error sending message over TCP:', err);
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Socket as UdpSocket } from 'dgram';
|
||||
import { MessageHandler } from '../message_handler';
|
||||
import { SocketCommunicatorBase } from './socket_communicator_base';
|
||||
import {OperationHandler} from "../operations_base/operation_handler";
|
||||
|
||||
export class UdpSocketCommunicator extends SocketCommunicatorBase {
|
||||
private readonly socket: UdpSocket;
|
||||
|
||||
constructor(socket: UdpSocket, ip: string, port: number, operationHandler: OperationHandler) {
|
||||
super(ip, port, operationHandler);
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
// Handle incoming message (no decryption needed for UDP)
|
||||
handleIncomingMessage(incomingMessage: string): void {
|
||||
this.handlerResult = this.operationHandler.handleOperation(incomingMessage);
|
||||
}
|
||||
|
||||
// Send plain message over UDP (metaInfo as JSON and fileContent as Buffer)
|
||||
async sendMessage(operationCode: string, metaInfo?: any, fileContent?: Buffer): Promise<void> {
|
||||
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent);
|
||||
|
||||
await this.sendUdpMessage(message);
|
||||
}
|
||||
|
||||
// Helper method to wrap socket.send in a Promise for async/await support
|
||||
private sendUdpMessage(message: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.send(message, this.port, this.ip, (err: any) => {
|
||||
if (err) {
|
||||
console.error('Error sending UDP message:', err);
|
||||
return reject(err);
|
||||
}
|
||||
console.log(`Plain message sent to ${this.ip}:${this.port} (UDP)`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import net, { Socket } from 'net';
|
||||
import path from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
import { ConnectionManager } from './network/connection_manager';
|
||||
import {OperationHandler} from "./network/operations_base/operation_handler";
|
||||
import {GeneralOperations} from "./network/operations_custom/general_operations";
|
||||
import {TcpServerCommunicator} from "./network/socket_communicator/tcp_server_communicator";
|
||||
import {CeoOperations} from "./network/operations_custom/ceo_operations";
|
||||
import {AuthOperations} from "./network/operations_custom/auth_operations";
|
||||
import {DepartmentOperations} from "./network/operations_custom/department_operations";
|
||||
import {KeyOperations} from "./network/operations_custom/key_operations";
|
||||
import {UserOperations} from "./network/operations_custom/user_operations";
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, './config/.env') });
|
||||
|
||||
export class TcpServer {
|
||||
private readonly connectionManager: ConnectionManager;
|
||||
private readonly operationHandler: OperationHandler;
|
||||
private readonly port: number;
|
||||
private readonly host: string;
|
||||
|
||||
constructor(host: string, port: number) {
|
||||
this.connectionManager = new ConnectionManager();
|
||||
this.operationHandler = OperationHandler.getInstance();
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
|
||||
this.operationHandler.loadPlugin(new GeneralOperations());
|
||||
this.operationHandler.loadPlugin(new CeoOperations());
|
||||
this.operationHandler.loadPlugin(new AuthOperations());
|
||||
this.operationHandler.loadPlugin(new DepartmentOperations());
|
||||
this.operationHandler.loadPlugin(new KeyOperations());
|
||||
this.operationHandler.loadPlugin(new UserOperations());
|
||||
|
||||
}
|
||||
|
||||
// Start the TCP server
|
||||
public start(): void {
|
||||
const tcpServer = net.createServer();
|
||||
|
||||
// Handle incoming connections
|
||||
tcpServer.on('connection', (socket: Socket) => {
|
||||
const ip = socket.remoteAddress || 'unknown';
|
||||
const port = socket.remotePort || 0;
|
||||
|
||||
const clientId = `${ip}:${port}`; // Use IP and port to identify the client
|
||||
console.log(`Client connected: ${clientId}`);
|
||||
|
||||
const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler);
|
||||
this.connectionManager.addConnection(ip, port, tcpCommunicator);
|
||||
|
||||
// Generate RSA key pair and start key exchange
|
||||
tcpCommunicator.generateKeyPair();
|
||||
tcpCommunicator.sendPublicKey()
|
||||
.then(() => tcpCommunicator.sendAesKey())
|
||||
.then(() => console.log('Public key and AES key sent successfully.'))
|
||||
.catch(err => {
|
||||
console.error(`Error during key exchange with client ${clientId}:`, err);
|
||||
socket.end(); // Close the connection in case of any error
|
||||
});
|
||||
|
||||
// Handle incoming data in chunks
|
||||
socket.on('data', async (data: Buffer) => {
|
||||
await this.handleData(data, ip, port);
|
||||
});
|
||||
|
||||
// Handle client disconnect
|
||||
socket.on('end', () => {
|
||||
console.log(`Client disconnected: ${clientId}`);
|
||||
this.connectionManager.removeCommunicator(ip, port);
|
||||
});
|
||||
|
||||
// Handle socket errors
|
||||
socket.on('error', (err: Error) => {
|
||||
console.error(`Error from client ${clientId}: ${err.message}`);
|
||||
this.connectionManager.removeCommunicator(ip, port);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle server errors
|
||||
tcpServer.on('error', (err: Error) => {
|
||||
console.error(`TCP server error: ${err.message}`);
|
||||
});
|
||||
|
||||
// Start listening for connections
|
||||
tcpServer.listen(this.port, this.host, () => {
|
||||
console.log(`TCP server listening on ${this.host}:${this.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle incoming data from a client
|
||||
private async handleData(data: Buffer, ip: string, port: number): Promise<void> {
|
||||
const clientId = `${ip}:${port}`;
|
||||
|
||||
// Retrieve the communicator associated with this connection
|
||||
const communicator = this.connectionManager.getCommunicator(ip, port) as TcpServerCommunicator;
|
||||
if (!communicator) {
|
||||
console.error(`No communicator found for ${clientId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle incoming chunked message via communicator
|
||||
await communicator.handleIncomingChunk(data);
|
||||
|
||||
// Fetch and process result if available
|
||||
const handlerResult = communicator.getHandlerResult();
|
||||
if (handlerResult) {
|
||||
try {
|
||||
await communicator.sendChunkedMessage(
|
||||
handlerResult.operationCode,
|
||||
handlerResult.metaInfo,
|
||||
handlerResult.fileContent
|
||||
);
|
||||
console.log(`Response sent to ${clientId}`);
|
||||
} catch (err) {
|
||||
console.error(`Failed to send response to ${clientId}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import dgram, { RemoteInfo } from 'dgram';
|
||||
import path from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
import { UdpSocketCommunicator } from "./network/socket_communicator/udp_socket_communicator";
|
||||
import { OperationHandler } from "./network/operations_base/operation_handler";
|
||||
import { GeneralOperations } from "./network/operations_custom/general_operations";
|
||||
|
||||
export class UdpServer {
|
||||
private readonly udpServer: dgram.Socket;
|
||||
private readonly operationHandler: OperationHandler;
|
||||
private readonly host: string;
|
||||
private readonly port: number
|
||||
|
||||
constructor(host: string, port: number) {
|
||||
this.udpServer = dgram.createSocket('udp4');
|
||||
this.operationHandler = OperationHandler.getInstance();
|
||||
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
|
||||
// Load only the GeneralOperations into the operation handler
|
||||
this.operationHandler.loadPlugin(new GeneralOperations());
|
||||
}
|
||||
|
||||
// Start the UDP server
|
||||
public start(): void {
|
||||
this.udpServer.on('message', this.handleUdpMessages.bind(this));
|
||||
this.udpServer.on('error', this.handleError);
|
||||
this.udpServer.on('listening', this.handleListening.bind(this));
|
||||
|
||||
// Bind the server to the UDP port and host
|
||||
this.udpServer.bind(this.port, this.host);
|
||||
}
|
||||
|
||||
// Handle incoming UDP messages
|
||||
private async handleUdpMessages(msg: Buffer, rinfo: RemoteInfo): Promise<void> {
|
||||
const ip = rinfo.address;
|
||||
const port = rinfo.port;
|
||||
|
||||
console.log(`Received message from ${ip}:${port}`);
|
||||
|
||||
// Create a temporary communicator for the incoming message
|
||||
const communicator = new UdpSocketCommunicator(this.udpServer, ip, port, this.operationHandler);
|
||||
|
||||
// Process the incoming message using the communicator
|
||||
communicator.handleIncomingMessage(msg.toString());
|
||||
|
||||
const communicatorResult = communicator.getHandlerResult();
|
||||
if (communicatorResult) {
|
||||
// Send response back to the client using the temporary communicator
|
||||
await communicator.sendMessage(communicatorResult.operationCode, communicatorResult.metaInfo);
|
||||
} else {
|
||||
console.error(`No handler result for ${ip}:${port}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle UDP server errors
|
||||
private handleError(err: Error): void {
|
||||
console.error(`UDP server error:\n${err.stack}`);
|
||||
}
|
||||
|
||||
// Handle when the UDP server starts listening
|
||||
private handleListening(): void {
|
||||
const address = this.udpServer.address();
|
||||
console.log(`UDP server listening on ${address.address}:${address.port}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user