BACKEND DONE FOR ALL APPS
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
HOST=0.0.0.0
|
||||
UDP_PORT=41234
|
||||
TCP_PORT=41233
|
||||
|
||||
CEO_EMAIL=ceo@domain.com
|
||||
CEO_PASSWORD=ceo_password
|
||||
Generated
-5
@@ -1,5 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
-1
@@ -1 +0,0 @@
|
||||
package.json
|
||||
Generated
-12
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
-12
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<includedPredefinedLibrary name="Node.js Core" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="FLOW" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/backend.iml" filepath="$PROJECT_DIR$/.idea/backend.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="bin\www" type="NodeJSConfigurationType" path-to-js-file="bin/www" working-dir="$PROJECT_DIR$">
|
||||
<envs>
|
||||
<env name="DEBUG" value="backend:*" />
|
||||
</envs>
|
||||
<EXTENSION ID="com.jetbrains.nodejs.run.NodeStartBrowserRunConfigurationExtension">
|
||||
<browser url="http://localhost:3000/" />
|
||||
</EXTENSION>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,45 +0,0 @@
|
||||
General Overview
|
||||
-
|
||||
|
||||
The UC serves as the main logistic station for all the client/CEO applications which are used
|
||||
inside the corporation. This UC stores
|
||||
- credentials for the users and ceo;
|
||||
- registered departments, as well security levels and the encrypting/decrypting keys for the files;
|
||||
- backup schemes for each user, as well as the IP, as well as the IPs where each computer is located;
|
||||
|
||||
The main endpoints of the application are:
|
||||
|
||||
- <b>Users</b>:
|
||||
|
||||
- Can register/login in the system;
|
||||
- Update the information (username, email, password);
|
||||
- Change the working department (the client app will prompt the CEO password request to proceed);
|
||||
- Retrieve the full list of departments for the sign-up / 'change department' procedure;
|
||||
- Retrieve the full list of users and their IPs for sharing a file
|
||||
|
||||
|
||||
- <b>CEO</b>:
|
||||
|
||||
- Can log in the system;
|
||||
- Can change the email (the password can only be retrieved by the admin);
|
||||
- Delete users from the system;
|
||||
- Create / Delete departments, as well as managing the security levels of each department;
|
||||
- Retrieve the full list of users and their IPs for sharing a file
|
||||
|
||||
|
||||
- <b>Admin</b>:
|
||||
|
||||
- Reset the server to default state;
|
||||
- Retrieve the CEO login information, as well as a list of all users in the system;
|
||||
|
||||
|
||||
- <b>Backup Schemes</b>:
|
||||
|
||||
- At a request, will get the ID of a user, their IP and the backup directory structure to place it in the scheme,
|
||||
as well as the data size of the backup
|
||||
|
||||
|
||||
Other Features
|
||||
-
|
||||
The UC will also create a separate process which at a set interval of time will retrieve from the admin config, as
|
||||
well as from the CEO config, the ID so that it will be used to authenticate the users at their endpoints.
|
||||
Generated
-1811
File diff suppressed because it is too large
Load Diff
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "uc",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node ./src/app.js",
|
||||
"dev": "nodemon ./src/app.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Cerbu Andrei- Mihnea",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.8",
|
||||
"body-parser": "^1.20.2",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"cors": "^2.8.5",
|
||||
"cron": "^3.1.6",
|
||||
"crypto": "^1.0.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"express": "^4.19.1",
|
||||
"joi": "^17.12.2",
|
||||
"json": "^11.0.0",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.2.2",
|
||||
"nodemon": "^3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
const express = require('express');
|
||||
const {json} = require("body-parser");
|
||||
const cors = require('cors');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 5000;
|
||||
|
||||
app.use(cors({
|
||||
origin: '*', // Allow all origins
|
||||
methods: 'GET,POST,PUT,DELETE,PATCH', // Allow all methods
|
||||
allowedHeaders: '*', // Allow all headers
|
||||
credentials: true, // Enable credentials
|
||||
preflightContinue: false,
|
||||
optionsSuccessStatus: 204 // Some legacy browsers (IE11, various SmartTVs) choke on 204
|
||||
}));
|
||||
app.use(json());
|
||||
|
||||
const {apiKey} = require('./config.json');
|
||||
app.locals.apiKey = apiKey;
|
||||
|
||||
const {
|
||||
usersDB,
|
||||
departmentsDB,
|
||||
backupSchemesDB,
|
||||
ceoDB,
|
||||
adminDB
|
||||
} = require('./db/jsonDatabaseManager');
|
||||
|
||||
app.set('usersDB', usersDB);
|
||||
app.set('departmentsDB', departmentsDB);
|
||||
app.set('backupSchemesDB', backupSchemesDB);
|
||||
app.set('ceoDB', ceoDB);
|
||||
app.set('adminDB', adminDB);
|
||||
|
||||
app.use('/heartbeat', (req, res) => {
|
||||
return res.status(200).json({message: 'Server ap and running.'});
|
||||
})
|
||||
|
||||
const checkJson = require('./middlewares/checkJson');
|
||||
const apiKeyValidation = require('./middlewares/apiKeyValidation');
|
||||
|
||||
app.use(apiKeyValidation)
|
||||
app.use(checkJson)
|
||||
|
||||
const usersRouter = require('./routes/users');
|
||||
const adminRouter = require('./routes/admin');
|
||||
const ceoRouter = require('./routes/ceo');
|
||||
const backupSchemesRouter = require('./routes/backup_schemes');
|
||||
|
||||
app.use('/users', usersRouter);
|
||||
app.use('/admin', adminRouter);
|
||||
app.use('/ceo', ceoRouter);
|
||||
app.use('/backup_schemes', backupSchemesRouter);
|
||||
|
||||
app.use('/heartbeat', (req, res) => {
|
||||
return res.status(200).json({message: 'Server ap and running.'});
|
||||
})
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server running on http://localhost:${port}`);
|
||||
});
|
||||
|
||||
// Graceful shutdown function
|
||||
function gracefulShutdown() {
|
||||
console.log('\nShutting down!');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.on('SIGTERM', gracefulShutdown);
|
||||
process.on('SIGINT', gracefulShutdown);
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"apiKey": "uc_api"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"7c0ef9d5-23ed-47a6-bfb4-fdcfce436904": {
|
||||
"ip": "192.168.169.127",
|
||||
"directoryStructure": "{\"backup_dir\":{\"files\":[],\"fisiere\":{\"files\":[\"New Microsoft Excel Worksheet.xlsx\",\"New Microsoft Word Document.docx\"]}}}",
|
||||
"totalSize": 19363
|
||||
},
|
||||
"59c712d9-e6dd-438f-aec9-31ca2ae750e6": {
|
||||
"ip": "192.168.169.19",
|
||||
"directoryStructure": "{\"backup_dir\":{\"files\":[],\"fisiere\":{\"files\":[\"New Microsoft Excel Worksheet.xlsx\",\"New Microsoft Word Document.docx\"]}}}",
|
||||
"totalSize": 19363
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"id": "7c0ef9d5-23ed-47a6-bfb4-fdcfce436904",
|
||||
"name": "CEO",
|
||||
"email": "ceo@yourfirm.com",
|
||||
"password": "b85cfc54e8dda76e72d7bde7b10ce30a",
|
||||
"department": "CEO"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"1": {
|
||||
"name": "CEO",
|
||||
"key": "6f444243063271cc1ea3ccb3621dfebc352fd40e2f58eb6c97b26b7e34fde7a1"
|
||||
},
|
||||
"2": {
|
||||
"name": "HR",
|
||||
"key": "2c480e5e3fdaca10d4d838b1b16fb44ce0f57238cb4343a839e065f8d6e41dc7"
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
class JSONDatabase {
|
||||
constructor(filePath) {
|
||||
this.filePath = filePath;
|
||||
this.readFile = this.readFile.bind(this); // Bind readFile method to the instance
|
||||
this.writeFile = this.writeFile.bind(this); // Bind writeFile method to the instance
|
||||
|
||||
}
|
||||
|
||||
readFile() {
|
||||
if (!fs.existsSync(this.filePath)) {
|
||||
console.log('File does not exist, returning null:', this.filePath);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const data = fs.readFileSync(this.filePath, 'utf8');
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
console.error('Error reading file:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
writeFile(data) {
|
||||
try {
|
||||
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), { flag: 'w' });
|
||||
console.log('File written successfully.');
|
||||
} catch (error) {
|
||||
console.error('Error writing file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
isKeyValue(key, value) {
|
||||
const data = this.readFile();
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const item of data) {
|
||||
if (item[key] === value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
findIndexByKeyValueInArray(key, value) {
|
||||
const data = this.readFile();
|
||||
if (!data || !Array.isArray(data)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i][key] === value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
findByKeyValue(key, value) {
|
||||
const data = this.readFile();
|
||||
if (!data || !Array.isArray(data)) {
|
||||
return null; // Database read failed or data is not an array
|
||||
}
|
||||
|
||||
for (const item of data) {
|
||||
if (item[key] === value) {
|
||||
return item; // Return the object containing the specified key-value pair
|
||||
}
|
||||
}
|
||||
return null; // No matching key-value pair found
|
||||
}
|
||||
}
|
||||
|
||||
const usersDB = new JSONDatabase(path.join(__dirname, 'users.json'));
|
||||
const departmentsDB = new JSONDatabase(path.join(__dirname, 'departments.json'));
|
||||
const backupSchemesDB = new JSONDatabase(path.join(__dirname, 'backup_schemes.json'));
|
||||
const ceoDB = new JSONDatabase(path.join(__dirname, 'ceo.json'));
|
||||
const adminDB = new JSONDatabase(path.join(__dirname + '..', '..', '..', '..', 'frontend', 'config', 'credentials.json'));
|
||||
|
||||
module.exports = {
|
||||
usersDB,
|
||||
departmentsDB,
|
||||
backupSchemesDB,
|
||||
ceoDB,
|
||||
adminDB,
|
||||
JSONDatabase
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "59c712d9-e6dd-438f-aec9-31ca2ae750e6",
|
||||
"name": "Andrei Cerbu",
|
||||
"email": "a@c.com",
|
||||
"password": "5447045dc3cfaafdb49b365e31dad41eb88616b0b1449930e55fd3a70bd8cee4",
|
||||
"department": "HR"
|
||||
}
|
||||
]
|
||||
@@ -1,38 +0,0 @@
|
||||
const httpStatus = {
|
||||
// Informational
|
||||
CONTINUE: 100,
|
||||
SWITCHING_PROTOCOLS: 101,
|
||||
PROCESSING: 102,
|
||||
|
||||
// Success
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
ACCEPTED: 202,
|
||||
NO_CONTENT: 204,
|
||||
|
||||
// Redirection
|
||||
MOVED_PERMANENTLY: 301,
|
||||
FOUND: 302,
|
||||
SEE_OTHER: 303,
|
||||
NOT_MODIFIED: 304,
|
||||
TEMPORARY_REDIRECT: 307,
|
||||
|
||||
// Client Error
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
METHOD_NOT_ALLOWED: 405,
|
||||
CONFLICT: 409,
|
||||
GONE: 410,
|
||||
UNSUPPORTED_MEDIA_TYPE: 415,
|
||||
|
||||
// Server Error
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
NOT_IMPLEMENTED: 501,
|
||||
SERVICE_UNAVAILABLE: 503
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports = {httpStatus};
|
||||
@@ -1,11 +0,0 @@
|
||||
const {httpStatus, httpStatusMessages} = require('../helpers/httpResponses')
|
||||
function apiKeyValidation(req, res, next) {
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
|
||||
if (apiKey !== req.app.locals.apiKey) {
|
||||
return res.status(httpStatus.UNAUTHORIZED).json({message: 'Invalid API key'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = apiKeyValidation;
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
const {httpStatus} = require("../helpers/httpResponses");
|
||||
|
||||
function checkJson(req, res, next) {
|
||||
if(req.method === 'GET'){
|
||||
next()
|
||||
}
|
||||
|
||||
if (['POST', 'PUT', 'PATCH'].includes(req.method) && req.headers['content-type'] !== 'application/json') {
|
||||
return res.status(httpStatus.BAD_REQUEST).json({message: "Body missing in action."});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = checkJson;
|
||||
@@ -1,15 +0,0 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const ceoModifyModel = Joi.object({
|
||||
name: Joi.string().required().messages({
|
||||
'any.required': 'CEO name is required',
|
||||
'string.empty': 'CEO name must not be empty'
|
||||
}),
|
||||
email: Joi.string().email().required().messages({
|
||||
'any.required': 'CEO email is required',
|
||||
'string.empty': 'CEO email must not be empty',
|
||||
'string.email': 'CEO email must be a valid email address'
|
||||
})
|
||||
});
|
||||
|
||||
module.exports = ceoModifyModel;
|
||||
@@ -1,10 +0,0 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const ceoPasswordModel = Joi.object({
|
||||
password: Joi.string().required().messages({
|
||||
'any.required': 'Password is required',
|
||||
'string.empty': 'Password must not be empty'
|
||||
})
|
||||
});
|
||||
|
||||
module.exports = ceoPasswordModel;
|
||||
@@ -1,10 +0,0 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const departmentRegisterModel = Joi.object({
|
||||
name: Joi.string().required().messages({
|
||||
'any.required': 'Department name is required',
|
||||
'string.empty': 'Department name must not be empty'
|
||||
})
|
||||
});
|
||||
|
||||
module.exports = departmentRegisterModel
|
||||
@@ -1,23 +0,0 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const dirStructureModel = Joi.object({
|
||||
id: Joi.string().guid().required().messages({
|
||||
'any.required': 'ID is required',
|
||||
'string.empty': 'ID must not be empty',
|
||||
'string.guid': 'ID must be a valid GUID'
|
||||
}),
|
||||
ip: Joi.string().ip().required().messages({
|
||||
'string.ip': 'The IP address "{{#value}}" is not valid.',
|
||||
'any.required': 'IP address is required.'
|
||||
}),
|
||||
directoryStructure: Joi.string().required().messages({
|
||||
'any.required': 'Directory configuration is required',
|
||||
'string.empty': 'Directory configuration must not be empty'
|
||||
}),
|
||||
totalSize: Joi.number().required().messages({
|
||||
'any.required': 'Total space is required',
|
||||
'number.base': 'Total space must be a number'
|
||||
})
|
||||
});
|
||||
|
||||
module.exports = dirStructureModel
|
||||
@@ -1,11 +0,0 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const emailVerificationModel = Joi.object({
|
||||
email: Joi.string().email().required().messages({
|
||||
'string.empty': 'Email can\'t be empty.',
|
||||
'string.email': 'Invalid email format.',
|
||||
'any.required': 'Email is required'
|
||||
})
|
||||
});
|
||||
|
||||
module.exports = emailVerificationModel;
|
||||
@@ -1,25 +0,0 @@
|
||||
const usersRegisterModelSchema = require('./usersRegisterModel');
|
||||
const usersLoginModelSchema = require('./usersLoginModel');
|
||||
const usersModifyModelSchema = require('./usersModifyModel');
|
||||
const dirStructureModelSchema = require('./dirStructureModel');
|
||||
const departmentRegisterModelSchema = require('./departmentRegisterModel');
|
||||
const ceoModifyModelSchema = require('./ceoModifyModel');
|
||||
const emailVerificationSchema = require('./emailVerificationModel');
|
||||
const userDepartmentPatchSchema = require('./userDepartmentPatchModel');
|
||||
const ceoPasswordModelSchema = require('./ceoPasswordModel');
|
||||
const securityLevelsModelSchema = require('./securityLevelsModel');
|
||||
|
||||
module.exports = {
|
||||
schemas: {
|
||||
dirStructureModelSchema,
|
||||
usersRegisterModelSchema,
|
||||
usersLoginModelSchema,
|
||||
usersModifyModelSchema,
|
||||
departmentRegisterModelSchema,
|
||||
ceoModifyModelSchema,
|
||||
emailVerificationSchema,
|
||||
userDepartmentPatchSchema,
|
||||
ceoPasswordModelSchema,
|
||||
securityLevelsModelSchema
|
||||
},
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
const Joi = require("joi");
|
||||
|
||||
// Define a schema for the string values (department names)
|
||||
const nameSchema = Joi.string().required().messages({
|
||||
'string.empty': 'Name can\'t be empty',
|
||||
'any.required': 'Name is required'
|
||||
});
|
||||
|
||||
// Define the main schema for the security levels model
|
||||
const securityLevelsModel = Joi.object().pattern(
|
||||
Joi.number().integer().positive().messages({
|
||||
'number.base': 'Keys must be integers',
|
||||
'number.integer': 'Keys must be integers',
|
||||
'number.positive': 'Keys must be positive integers'
|
||||
}),
|
||||
nameSchema
|
||||
);
|
||||
|
||||
module.exports = securityLevelsModel;
|
||||
@@ -1,15 +0,0 @@
|
||||
const Joi = require("joi");
|
||||
|
||||
const UserDepartmentPatchModel = Joi.object({
|
||||
id: Joi.string().uuid().required().messages({
|
||||
'any.required': 'User ID is required',
|
||||
'string.empty': 'User ID must not be empty',
|
||||
'string.uuid': 'User ID must be a valid UUID'
|
||||
}),
|
||||
department: Joi.string().required().messages({
|
||||
'any.required': 'User ID is required',
|
||||
'string.empty': 'User ID must not be empty',
|
||||
})
|
||||
});
|
||||
|
||||
module.exports = UserDepartmentPatchModel;
|
||||
@@ -1,15 +0,0 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const usersLoginModel = Joi.object({
|
||||
email: Joi.string().email().required().messages({
|
||||
'any.required': 'Email is required',
|
||||
'string.empty': 'Email must not be empty',
|
||||
'string.email': 'Email must be a valid email address'
|
||||
}),
|
||||
password: Joi.string().required().messages({
|
||||
'any.required': 'Password is required',
|
||||
'string.empty': 'Password must not be empty'
|
||||
})
|
||||
});
|
||||
|
||||
module.exports = usersLoginModel;
|
||||
@@ -1,21 +0,0 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const usersModifyModel = Joi.object({
|
||||
name: Joi.string().required().messages({
|
||||
'any.required': 'User name is required',
|
||||
'string.empty': 'User name must not be empty'
|
||||
}),
|
||||
email: Joi.string().email().required().messages({
|
||||
'any.required': 'User email is required',
|
||||
'string.empty': 'User email must not be empty',
|
||||
'string.email': 'User email must be a valid email address'
|
||||
}),
|
||||
password: Joi.string().min(8).max(20).required().messages({
|
||||
'any.required': 'Password is required',
|
||||
'string.empty': 'Password must not be empty',
|
||||
'string.min': 'Password must be at least {#limit} characters long',
|
||||
'string.max': 'Password must be at most {#limit} characters long'
|
||||
})
|
||||
});
|
||||
|
||||
module.exports = usersModifyModel;
|
||||
@@ -1,25 +0,0 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const usersRegisterModel = Joi.object({
|
||||
name: Joi.string().required().messages({
|
||||
'any.required': 'User name is required',
|
||||
'string.empty': 'User name must not be empty'
|
||||
}),
|
||||
email: Joi.string().email().required().messages({
|
||||
'any.required': 'Email is required',
|
||||
'string.empty': 'Email must not be empty',
|
||||
'string.email': 'Email must be a valid email address'
|
||||
}),
|
||||
password: Joi.string().min(8).max(20).required().messages({
|
||||
'any.required': 'Password is required',
|
||||
'string.empty': 'Password must not be empty',
|
||||
'string.min': 'Password must be at least {#limit} characters long',
|
||||
'string.max': 'Password must be at most {#limit} characters long'
|
||||
}),
|
||||
department: Joi.string().required().messages({
|
||||
'any.required': 'Department ID is required',
|
||||
'string.empty': 'Department ID must not be empty'
|
||||
})
|
||||
});
|
||||
|
||||
module.exports = usersRegisterModel;
|
||||
@@ -1,85 +0,0 @@
|
||||
const checkForServerConnection = async () => {
|
||||
const pathToIpConfig = path.join(__dirname, '..', '..', 'ipConfig.json');
|
||||
await lockFile.lock(pathToIpConfig);
|
||||
try {
|
||||
await decryptFileInPlace(pathToIpConfig);
|
||||
const ipConfig = await fs.readFile(pathToIpConfig, 'utf-8');
|
||||
const { ip } = JSON.parse(ipConfig);
|
||||
const response = await fetch(`http://${ip}:5000/heartbeat`);
|
||||
if (response.ok) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
return false;
|
||||
} finally {
|
||||
await lockFile.unlock(pathToIpConfig);
|
||||
}
|
||||
};const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const {httpStatus} = require("../helpers/httpResponses");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function validateBody(req, res, next) {
|
||||
const apiKey = req.headers['authorization'];
|
||||
const adminDB = req.app.get('adminDB');
|
||||
const {id} = adminDB.readFile();
|
||||
|
||||
if(apiKey !== id){
|
||||
return res.status(httpStatus.UNAUTHORIZED).json(
|
||||
{message: "You are not authorized as an admin."})
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
router.get('/reset_server', validateBody, (req, res) => {
|
||||
const usersDB = req.app.get('usersDB');
|
||||
const departmentsDB = req.app.get('departmentsDB');
|
||||
const backupSchemesDB = req.app.get('backupSchemesDB');
|
||||
const ceoDB = req.app.get('ceoDB');
|
||||
|
||||
usersDB.writeFile([]);
|
||||
departmentsDB.writeFile({});
|
||||
backupSchemesDB.writeFile({});
|
||||
ceoDB.writeFile({
|
||||
id: uuidv4(),
|
||||
name: "CEO",
|
||||
email: "ceo@yourfirm.com",
|
||||
password: crypto.randomBytes(16).toString('hex'),
|
||||
department: "CEO"
|
||||
});
|
||||
|
||||
const ceoDepartment = {
|
||||
name: "CEO",
|
||||
key: crypto.randomBytes(32).toString('hex')
|
||||
|
||||
}
|
||||
|
||||
const departmentsJson = departmentsDB.readFile();
|
||||
departmentsJson[1] = ceoDepartment;
|
||||
departmentsDB.writeFile(departmentsJson);
|
||||
|
||||
res.status(httpStatus.OK).json({ message: "Resetting the server..." });
|
||||
});
|
||||
|
||||
router.get('/ceo', validateBody, (req, res) => {
|
||||
const ceoDB = req.app.get('ceoDB');
|
||||
res.status(httpStatus.OK).json({ message: "Retrieving CEO info...", data: ceoDB.readFile() }) // Corrected to readFile
|
||||
});
|
||||
|
||||
router.get('/users', validateBody, (req, res) => {
|
||||
const usersDB = req.app.get('usersDB');
|
||||
res.status(httpStatus.OK).json({ message: "Retrieving users info...", data: usersDB.readFile() }) // Corrected to readFile
|
||||
});
|
||||
|
||||
router.use((req, res) => {
|
||||
return res.status(httpStatus.NOT_FOUND).json({message: "Endpoint not found"});
|
||||
})
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,63 +0,0 @@
|
||||
const express = require('express');
|
||||
const {httpStatus} = require("../helpers/httpResponses");
|
||||
const {schemas} = require("../models/schemaMapper");
|
||||
const router = express.Router();
|
||||
|
||||
function validateBody(req, res, next) {
|
||||
let validationSchema = undefined;
|
||||
|
||||
if(req.path === '/' && req.method === 'PATCH'){
|
||||
validationSchema = schemas.dirStructureModelSchema
|
||||
}
|
||||
|
||||
if(validationSchema !== undefined){
|
||||
console.log(req.body);
|
||||
const {error} = validationSchema.validate(req.body);
|
||||
if(error){
|
||||
const errorMessage = error.details.map(detail => detail.message).join(', ');
|
||||
return res.status(httpStatus.BAD_REQUEST).json({ message: errorMessage });
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
router.patch('/', validateBody, (req, res) => {
|
||||
const backupSchemesDB = req.app.get('backupSchemesDB');
|
||||
const { id, ip, directoryStructure, totalSize } = req.body;
|
||||
|
||||
let backupSchemesJson = backupSchemesDB.readFile();
|
||||
backupSchemesJson[id] = {
|
||||
ip: ip,
|
||||
directoryStructure: directoryStructure,
|
||||
totalSize: totalSize
|
||||
}
|
||||
|
||||
backupSchemesDB.writeFile(backupSchemesJson);
|
||||
return res.status(httpStatus.OK).json({message: 'Backup schema updated'});
|
||||
});
|
||||
|
||||
router.get('/', validateBody, (req, res) => {
|
||||
const backupSchemesDB = req.app.get('backupSchemesDB');
|
||||
return res.status(httpStatus.OK).json({
|
||||
message: 'Backup Schemes fetched.',
|
||||
data: backupSchemesDB.readFile()
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:userId', (req, res) => {
|
||||
const userId = req.params.userId;
|
||||
const backupSchemesDB = req.app.get('backupSchemesDB');
|
||||
let backupSchemesJson = backupSchemesDB.readFile();
|
||||
const ip = backupSchemesJson[userId].ip;
|
||||
if (ip) {
|
||||
res.status(httpStatus.OK).json({message: "IP found", data: ip});
|
||||
} else {
|
||||
res.status(httpStatus.NOT_FOUND).send({message: "IP not found"});
|
||||
}
|
||||
});
|
||||
|
||||
router.use((req, res) => {
|
||||
return res.status(httpStatus.NOT_FOUND).json({message: "Endpoint not found"});
|
||||
})
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,196 +0,0 @@
|
||||
const express = require('express');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const {schemas} = require("../models/schemaMapper");
|
||||
const {httpStatus} = require("../helpers/httpResponses");
|
||||
const crypto = require("crypto");
|
||||
const {departmentsDB} = require("../db/jsonDatabaseManager");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function validateBody(req, res, next) {
|
||||
if(req.path !== '/login'){
|
||||
const ceoDB = req.app.get('ceoDB');
|
||||
const ceoPassword = req.headers['ceo_password'];
|
||||
const {password} = ceoDB.readFile()
|
||||
|
||||
if(ceoPassword !== password){
|
||||
return res.status(httpStatus.UNAUTHORIZED).json({message: 'Invalid CEO password.'});
|
||||
}
|
||||
}
|
||||
|
||||
let validationSchema = undefined;
|
||||
|
||||
switch(req.path){
|
||||
case '/set_security_levels':
|
||||
validationSchema = schemas.securityLevelsModelSchema;
|
||||
break;
|
||||
case '/login':
|
||||
validationSchema = schemas.usersLoginModelSchema;
|
||||
break;
|
||||
case '/departments':
|
||||
validationSchema = schemas.departmentRegisterModelSchema;
|
||||
break;
|
||||
case '/':
|
||||
if(req.method === 'PUT'){
|
||||
validationSchema = schemas.ceoModifyModelSchema;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
validationSchema = undefined;
|
||||
}
|
||||
|
||||
if(validationSchema !== undefined){
|
||||
const {error} = validationSchema.validate(req.body);
|
||||
if(error){
|
||||
const errorMessage = error.details.map(detail => detail.message).join(', ');
|
||||
return res.status(httpStatus.BAD_REQUEST).json({ message: errorMessage });
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
router.post('/login', validateBody, (req, res) => {
|
||||
const ceoDB = req.app.get('ceoDB');
|
||||
const { email, password } = req.body;
|
||||
|
||||
if(ceoDB.findIndexByKeyValueInArray('email', email) !== ceoDB.findIndexByKeyValueInArray('password', password)){
|
||||
return res.status(httpStatus.UNAUTHORIZED).json({message: 'Invalid credentials.'});
|
||||
}
|
||||
|
||||
return res.status(httpStatus.OK).json({message: 'Logged in.', data: ceoDB.readFile()});
|
||||
});
|
||||
|
||||
router.post('/set_security_levels', validateBody, (req, res) => {
|
||||
const departmentsDB = req.app.get('departmentsDB');
|
||||
const departmentsJson = departmentsDB.readFile();
|
||||
|
||||
const levelsJson = req.body;
|
||||
console.log(levelsJson);
|
||||
|
||||
const newJson = {};
|
||||
|
||||
Object.entries(levelsJson).forEach(([levelKey, departmentName]) => {
|
||||
let departmentId = -1;
|
||||
for (let deptKey in departmentsJson) {
|
||||
if (departmentsJson.hasOwnProperty(deptKey)) {
|
||||
if (departmentsJson[deptKey].name === departmentName) {
|
||||
departmentId = deptKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (departmentId !== -1) {
|
||||
newJson[levelKey] = {
|
||||
name: departmentName,
|
||||
key: departmentsJson[departmentId].key
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
departmentsDB.writeFile(newJson);
|
||||
return res.status(httpStatus.OK).json({message: 'Departments levels updated.'});
|
||||
});
|
||||
|
||||
router.delete('/users/:id', validateBody, (req, res) => {
|
||||
const usersDB = req.app.get('usersDB');
|
||||
const { id } = req.params;
|
||||
|
||||
let usersJson = usersDB.readFile();
|
||||
|
||||
const userIndex = usersJson.findIndex(user => user.id === id);
|
||||
if (userIndex === -1) {
|
||||
return res.status(httpStatus.NOT_FOUND).json({message: 'User not found in system.'});
|
||||
}
|
||||
|
||||
usersJson.splice(userIndex, 1);
|
||||
|
||||
|
||||
usersDB.writeFile(usersJson);
|
||||
|
||||
return res.status(httpStatus.OK).json({ message: 'User deleted successfully.' });
|
||||
});
|
||||
|
||||
router.post('/departments', validateBody, (req, res) => {
|
||||
const departmentsDB = req.app.get('departmentsDB');
|
||||
const { name } = req.body;
|
||||
console.log(name);
|
||||
|
||||
let jsonDepartments = departmentsDB.readFile();
|
||||
const nameExists = Object.values(jsonDepartments).some(department => department.name === name);
|
||||
if(nameExists){
|
||||
return res.status(httpStatus.CONFLICT).json({message: 'Department already in system.'})
|
||||
}
|
||||
|
||||
const isObjectEmpty = !Object.keys(jsonDepartments).length;
|
||||
const highestKey = isObjectEmpty ? 0 : Math.max(...Object.keys(jsonDepartments).map(Number));
|
||||
const nextKey = highestKey + 1;
|
||||
|
||||
jsonDepartments[nextKey] = {
|
||||
name: name,
|
||||
key: crypto.randomBytes(32).toString('hex')
|
||||
};
|
||||
departmentsDB.writeFile(jsonDepartments);
|
||||
|
||||
return res.status(httpStatus.CREATED).json({message: 'Department successfully created'});
|
||||
});
|
||||
|
||||
router.delete('/departments/:name', validateBody, (req, res) => {
|
||||
const departmentsDB = req.app.get('departmentsDB');
|
||||
let departmentsJson = departmentsDB.readFile();
|
||||
let foundName = false;
|
||||
const { name } = req.params;
|
||||
|
||||
console.log(name);
|
||||
|
||||
for (let key in departmentsJson) {
|
||||
if (departmentsJson.hasOwnProperty(key)) {
|
||||
const department = departmentsJson[key];
|
||||
if (department.name === name) {
|
||||
delete departmentsJson[key];
|
||||
foundName = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(foundName !== true){
|
||||
return res.status(httpStatus.NOT_FOUND).json({ message: 'Department not found.' });
|
||||
}
|
||||
|
||||
departmentsDB.writeFile(departmentsJson);
|
||||
return res.status(httpStatus.OK).json({ message: 'Department deleted successfully.' });
|
||||
});
|
||||
|
||||
router.put('/', validateBody, (req, res) => {
|
||||
const ceoDB = req.app.get('ceoDB');
|
||||
const { name, email } = req.body;
|
||||
const {id, department} = ceoDB.readFile()
|
||||
|
||||
const newCeo = {
|
||||
id: id,
|
||||
name: name,
|
||||
email: email,
|
||||
password: crypto.randomBytes(16).toString('hex'),
|
||||
department: department
|
||||
}
|
||||
|
||||
ceoDB.writeFile(newCeo);
|
||||
return res.status(httpStatus.OK).json({message: 'Information modified.', data: newCeo});
|
||||
});
|
||||
|
||||
router.get('/get_decrypt_keys', (req, res) => {
|
||||
return res.status(httpStatus.OK).json({
|
||||
message: 'Departments fetched.',
|
||||
data: departmentsDB.readFile()
|
||||
});
|
||||
});
|
||||
|
||||
router.use((req, res) => {
|
||||
return res.status(httpStatus.NOT_FOUND).json({message: "Endpoint not found"});
|
||||
})
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,210 +0,0 @@
|
||||
const express = require('express');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const {schemas} = require('../models/schemaMapper');
|
||||
const {httpStatus} = require("../helpers/httpResponses");
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function validateBody(req, res, next) {
|
||||
let validationSchema = undefined;
|
||||
|
||||
switch(req.path){
|
||||
case '/register':
|
||||
validationSchema = schemas.usersRegisterModelSchema;
|
||||
break;
|
||||
case '/login':
|
||||
validationSchema = schemas.usersLoginModelSchema;
|
||||
break;
|
||||
case '/validate_ceo_password':
|
||||
validationSchema = schemas.ceoPasswordModelSchema;
|
||||
break;
|
||||
case '/validate_email':
|
||||
validationSchema = schemas.emailVerificationSchema;
|
||||
break;
|
||||
case '/':
|
||||
if(req.method === 'PUT'){
|
||||
validationSchema = schemas.usersModifyModelSchema;
|
||||
}
|
||||
break;
|
||||
case '/change_department':
|
||||
validationSchema = schemas.userDepartmentPatchSchema;
|
||||
break;
|
||||
default:
|
||||
validationSchema = undefined;
|
||||
}
|
||||
|
||||
if(validationSchema !== undefined){
|
||||
console.log(req.body);
|
||||
const {error} = validationSchema.validate(req.body);
|
||||
if(error){
|
||||
const errorMessage = error.details.map(detail => detail.message).join(', ');
|
||||
return res.status(httpStatus.BAD_REQUEST).json({ message: errorMessage });
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
router.post('/register', validateBody, (req, res) => {
|
||||
const usersDB = req.app.get('usersDB');
|
||||
const usersJson = usersDB.readFile();
|
||||
|
||||
const { name, email, password, department } = req.body;
|
||||
const hashedPassword = crypto.createHash('sha256').update(password).digest('hex');
|
||||
|
||||
const newUser = {
|
||||
id: uuidv4(),
|
||||
name: name,
|
||||
email: email,
|
||||
password: hashedPassword,
|
||||
department: department
|
||||
}
|
||||
|
||||
usersJson.push(newUser);
|
||||
usersDB.writeFile(usersJson);
|
||||
|
||||
return res.status(httpStatus.CREATED).json({message: "User successfully created.", data: newUser});
|
||||
});
|
||||
|
||||
router.post('/login', validateBody, (req, res) => {
|
||||
const usersDB = req.app.get('usersDB');
|
||||
const { email, password } = req.body;
|
||||
const hashedPassword = crypto.createHash('sha256').update(password).digest('hex');
|
||||
|
||||
if(usersDB.readFile().length === 0){
|
||||
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({message: 'Internal server error.'});
|
||||
}
|
||||
|
||||
if(usersDB.findIndexByKeyValueInArray('email', email) !==
|
||||
usersDB.findIndexByKeyValueInArray('password', hashedPassword)){
|
||||
return res.status(httpStatus.UNAUTHORIZED).json({message: 'Invalid credentials.'});
|
||||
}
|
||||
|
||||
const userIndex = usersDB.findIndexByKeyValueInArray('email', email);
|
||||
|
||||
return res.status(httpStatus.OK).json({
|
||||
message: 'Logged in.',
|
||||
data: usersDB.readFile()[userIndex]
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/validate_ceo_password', validateBody, (req, res) => {
|
||||
const ceoDB = req.app.get('ceoDB');
|
||||
const { password } = req.body;
|
||||
|
||||
const ceoJson = ceoDB.readFile();
|
||||
if(ceoJson.password !== password){
|
||||
return res.status(httpStatus.UNAUTHORIZED).json({message: 'Incorrect CEO password'});
|
||||
}
|
||||
|
||||
return res.status(httpStatus.OK).json({message: 'Password verified.'})
|
||||
})
|
||||
|
||||
router.post('/validate_email', validateBody, (req, res) => {
|
||||
const usersDB = req.app.get('usersDB');
|
||||
const { email } = req.body;
|
||||
|
||||
if(usersDB.isKeyValue("email", email)){
|
||||
return res.status(httpStatus.CONFLICT).json({"message": "Email already in system."});
|
||||
}
|
||||
|
||||
return res.status(httpStatus.OK).json({"message": "Email is valid."});
|
||||
});
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const usersDB = req.app.get('usersDB');
|
||||
const ceoDB = req.app.get('ceoDB');
|
||||
|
||||
const ceoJson = ceoDB.readFile();
|
||||
let usersJson = usersDB.readFile();
|
||||
usersJson.push(ceoJson);
|
||||
|
||||
return res.status(httpStatus.OK).json({
|
||||
message: "Fetched users",
|
||||
data: usersJson
|
||||
})
|
||||
});
|
||||
|
||||
router.get('/departments', (req, res) => {
|
||||
const departmentsDB = req.app.get('departmentsDB');
|
||||
return res.status(httpStatus.OK).json({
|
||||
message: 'Departments fetched.',
|
||||
data: departmentsDB.readFile()
|
||||
});
|
||||
})
|
||||
|
||||
router.put('/', validateBody, (req, res) => {
|
||||
const usersDB = req.app.get('usersDB');
|
||||
let usersJson = usersDB.readFile();
|
||||
|
||||
const { name, email, password } = req.body;
|
||||
const userIndex = usersDB.findIndexByKeyValueInArray('email', email);
|
||||
if(userIndex === -1){
|
||||
return res.status(httpStatus.NOT_FOUND).json({message: 'User not found.'});
|
||||
}
|
||||
|
||||
const hashedPassword = crypto.createHash('sha256').update(password).digest('hex');
|
||||
let userInfo = usersJson[userIndex];
|
||||
userInfo = {
|
||||
id: userInfo.id,
|
||||
name: name,
|
||||
email: email,
|
||||
password: hashedPassword,
|
||||
department: userInfo.department
|
||||
}
|
||||
|
||||
usersJson[userIndex] = userInfo;
|
||||
usersDB.writeFile(usersJson);
|
||||
|
||||
return res.status(httpStatus.OK).json({message: 'Information modified'});
|
||||
});
|
||||
|
||||
router.patch('/change_department', validateBody, (req, res) => {
|
||||
const usersDB = req.app.get('usersDB');
|
||||
const { id, department } = req.body;
|
||||
|
||||
const userIndex = usersDB.findIndexByKeyValueInArray('id', id);
|
||||
if(userIndex === -1){
|
||||
return res.status(httpStatus.NOT_FOUND).json({message: "User not found."});
|
||||
}
|
||||
|
||||
let usersJson = usersDB.readFile();
|
||||
let userInfo = usersJson[userIndex];
|
||||
|
||||
userInfo = {
|
||||
id: userInfo.id,
|
||||
name: userInfo.name,
|
||||
email: userInfo.email,
|
||||
password: userInfo.password,
|
||||
department: department
|
||||
}
|
||||
|
||||
usersJson[userIndex] = userInfo
|
||||
usersDB.writeFile(usersJson);
|
||||
|
||||
return res.status(httpStatus.OK).json({message: "Department modified."});
|
||||
});
|
||||
|
||||
router.get('/get_decrypt_keys', (req, res) => {
|
||||
const ceoDB = req.app.get('ceoDB');
|
||||
const departmentsDB = req.app.get('departmentsDB');
|
||||
const ceoPassword = req.headers['ceo_password'];
|
||||
const {password} = ceoDB.readFile()
|
||||
|
||||
if(ceoPassword !== password){
|
||||
return res.status(httpStatus.UNAUTHORIZED).json({message: 'Invalid CEO password.'});
|
||||
}
|
||||
|
||||
return res.status(httpStatus.OK).json({
|
||||
message: 'Departments fetched.',
|
||||
data: departmentsDB.readFile()
|
||||
});
|
||||
});
|
||||
|
||||
router.use((req, res) => {
|
||||
return res.status(httpStatus.NOT_FOUND).json({message: "Endpoint not found"});
|
||||
})
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,21 +0,0 @@
|
||||
Taskuri
|
||||
-
|
||||
- create form which upon submission set's the ip location (also create a button which can change this one and have to
|
||||
create a IPC for retrieving the ip when making the requests).
|
||||
- create the encryption process when writing a file on disk / decryption process when reading a file form disk
|
||||
- configure endpoint for 'backup_schemes' completely
|
||||
- refactor the JS code from all the html to make it with .then statements and show the messages
|
||||
from the UC
|
||||
- finish the CEO interface
|
||||
- create process which will update the backup scheme with:
|
||||
|
||||
- IP
|
||||
- user_id
|
||||
- dir_schema
|
||||
- size
|
||||
|
||||
- create process which will fetch the large schema and encryption keys and compare it with the one on local;
|
||||
if is the same, hibernate; else, make requests for the files
|
||||
- create process which will get the user database and combine it with the ip and at the request will share a
|
||||
specific file
|
||||
- create process which will decrypt all the files from the backup scheme locally
|
||||
Binary file not shown.
Generated
-5
@@ -1,5 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
-12
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<includedPredefinedLibrary name="Node.js Core" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/frontend.iml" filepath="$PROJECT_DIR$/.idea/frontend.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="bin\www" type="NodeJSConfigurationType" path-to-js-file="bin/www" working-dir="$PROJECT_DIR$">
|
||||
<envs>
|
||||
<env name="DEBUG" value="frontend:*" />
|
||||
</envs>
|
||||
<EXTENSION ID="com.jetbrains.nodejs.run.NodeStartBrowserRunConfigurationExtension">
|
||||
<browser url="http://localhost:3000/" />
|
||||
</EXTENSION>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,32 +0,0 @@
|
||||
General overview
|
||||
-
|
||||
|
||||
For the use of the admin console, start with 'npm run start' the UI frontend.
|
||||
To see/modify the initial login credentials check the /config/config.json file.
|
||||
|
||||
WITHOUT SETTING THE CONFIG FILE, THE SERVER WILL NOT WORK AS INTENDED!
|
||||
(click on the 'Reset UC' when first configuring the server to bring the
|
||||
UC at the initial state)
|
||||
|
||||
Because the client and CEO apps are dependent on the Central Unit (UC), we strongly
|
||||
suggest you to make backup at a regular interval in case of hardware failing you will
|
||||
only have to deploy the server with the configurations already existing.
|
||||
|
||||
Important aspects:
|
||||
-
|
||||
|
||||
- The starting/stopping of the UC is handled by this 'Admin Console'. The server will not
|
||||
work if the server which servers as the 'Admin Console' is not running.
|
||||
- The main server will run at the port 5000, so make sure to configure your routers to
|
||||
enable routing to the server;
|
||||
- The client and CEO backup system of their applications will need access at ports 5001 and 5002,
|
||||
so also make sure to open those ports as well;
|
||||
- If the CEO wants to change the email address linked to the account, his password will be changed
|
||||
to a low-risk password, so make sure after any release to specify the account information;
|
||||
- Make sure that all the ports configurations are respected to have all the applications work properly
|
||||
|
||||
Closing thoughts
|
||||
-
|
||||
|
||||
We really hope that you will enjoy our application and for any issues, feel free to contact us at
|
||||
'username@ourdomain.com' for troubleshooting and other problems.
|
||||
@@ -1,55 +0,0 @@
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const loginRouter = require('./routers/login');
|
||||
const menuRouter = require('./routers/mainMenu');
|
||||
const path = require('path');
|
||||
const cookieParser = require('cookie-parser');
|
||||
|
||||
const checkSession = require('./middlewares/checkSession');
|
||||
|
||||
process.on('SIGINT', shutdown);
|
||||
process.on('SIGTERM', shutdown);
|
||||
|
||||
function shutdown() {
|
||||
console.log('Received kill signal, shutting down gracefully.');
|
||||
const serverProcess = app.get('serverProcess');
|
||||
if (serverProcess && typeof serverProcess.kill === 'function') {
|
||||
console.log('Shutting down server process...');
|
||||
serverProcess.kill(); // Send SIGTERM to server process
|
||||
|
||||
serverProcess.on('exit', () => {
|
||||
console.log('Server process terminated.');
|
||||
process.exit(0); // Exit main process cleanly
|
||||
});
|
||||
} else {
|
||||
console.log('No server process or cannot be killed, exiting.');
|
||||
process.exit(0); // Exit main process cleanly
|
||||
}
|
||||
}
|
||||
|
||||
const app = express()
|
||||
|
||||
app.set('serverProcess', null);
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(express.static(path.join(__dirname, 'public', 'html')));
|
||||
app.use('/css', express.static(path.join(__dirname, 'public', 'css')));
|
||||
app.use('/js', express.static(path.join(__dirname, 'public', 'js')));
|
||||
app.use('/images', express.static(path.join(__dirname, 'public', 'images')));
|
||||
|
||||
app.use(checkSession);
|
||||
|
||||
// Mount login and menu routers
|
||||
app.use('/login', loginRouter);
|
||||
app.use('/', menuRouter);
|
||||
|
||||
// Start the server
|
||||
const PORT = process.env.PORT || 80;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"id": "deee8837-3549-44d5-af56-fb7532505574",
|
||||
"name": "Andrei",
|
||||
"email": "admin@yourfirm.com",
|
||||
"password": "password"
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
const statusCodes = {
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
INTERNAL_SERVER_ERROR: 500
|
||||
};
|
||||
|
||||
module.exports = statusCodes;
|
||||
@@ -1,22 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const checkSession = (req, res, next) => {
|
||||
const configPath = path.join(__dirname, '..', 'config', 'credentials.json');
|
||||
const configFile = fs.readFileSync(configPath);
|
||||
const config = JSON.parse(configFile);
|
||||
|
||||
const userEmail = req.cookies.email;
|
||||
|
||||
if (req.path === '/login') {
|
||||
next();
|
||||
} else {
|
||||
if (userEmail && decodeURIComponent(userEmail) === config.email) {
|
||||
next();
|
||||
} else {
|
||||
res.redirect('/login');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = checkSession
|
||||
@@ -1,8 +0,0 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const loginModel = Joi.object({
|
||||
email: Joi.string().email().required(),
|
||||
password: Joi.string().required()
|
||||
});
|
||||
|
||||
module.exports = loginModel;
|
||||
@@ -1,7 +0,0 @@
|
||||
const loginModelSchema = require('./login');
|
||||
|
||||
const schemas = {
|
||||
loginModelSchema
|
||||
}
|
||||
|
||||
module.exports = schemas;
|
||||
Generated
-1151
File diff suppressed because it is too large
Load Diff
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "1.0.0",
|
||||
"description": "UI for the UC",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node app.js",
|
||||
"dev": "nodemon app.js"
|
||||
},
|
||||
"author": "Andrei Cerbu",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"express": "^4.19.2",
|
||||
"joi": "^17.12.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
body, html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
|
||||
background-image: url('/images/background.png');
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
}
|
||||
|
||||
.container {
|
||||
opacity: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header{
|
||||
color: #1B1A55;
|
||||
font-size: 4vh;
|
||||
text-transform: uppercase;
|
||||
line-height: 2.5rem;
|
||||
margin-bottom: 10rem;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
background-color: #535C91;
|
||||
opacity: 71;
|
||||
padding: 9vh 3vh 5vh;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.login-form-title{
|
||||
margin: 0 0 5vh 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-form-title h2{
|
||||
color: #FFFFFF;
|
||||
font-size: 5vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.login-form hr{
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.login-form-content{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
width: 70%;
|
||||
padding: 1rem;
|
||||
margin: 0.7rem;
|
||||
background-color: #1B1A55;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-weight: bold;
|
||||
width: 35%;
|
||||
font-size: 1rem;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
margin-top: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover{
|
||||
filter: brightness(85%);
|
||||
}
|
||||
|
||||
.login-form-footer{
|
||||
margin-top: 3vh;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
button[name="submit"] {
|
||||
background-color: #F44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button[name="signin"] {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
body, html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
|
||||
background-image: url('/images/background.png');
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
}
|
||||
.container {
|
||||
opacity: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.main_block{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #535C91;
|
||||
opacity: 71;
|
||||
padding: 2rem;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.header h1{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header img{
|
||||
margin: 0 3vw 0 5vw;
|
||||
width: 8vw;
|
||||
height: 8vw;
|
||||
}
|
||||
|
||||
.content{
|
||||
margin: 2rem 0 2rem 0;
|
||||
}
|
||||
|
||||
.content_buttons{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
button{
|
||||
margin: 0 1rem 0 1rem;
|
||||
width: 15vw;
|
||||
height: 10vh;
|
||||
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
outline: none;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover{
|
||||
filter: brightness(85%);
|
||||
}
|
||||
|
||||
.footer{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
button[name="logout"]{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 10vw;
|
||||
height: 5vh;
|
||||
background-color: #F44336;
|
||||
}
|
||||
|
||||
button[name="alert"]{
|
||||
margin: 0.7rem;
|
||||
background-color: #F44336;
|
||||
}
|
||||
|
||||
button[name="notification"]{
|
||||
margin: 0.7rem;
|
||||
background-color: #23BDEE;
|
||||
}
|
||||
|
||||
button[name="menu_button"]{
|
||||
margin: 0.7rem;
|
||||
background-color: #1B1A55;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
.fade-in {
|
||||
animation: fadeInAnimation 0.5s ease-in forwards;
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
animation: fadeOutAnimation 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeInAnimation {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOutAnimation {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/images/hard-disk.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" type="text/css" href="/css/login.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/transition.css">
|
||||
<script src="/js/login.js"></script>
|
||||
<script>
|
||||
function fadeIn() {
|
||||
document.querySelector('.container').classList.remove('fade-out');
|
||||
document.querySelector('.container').classList.add('fade-in');
|
||||
}
|
||||
|
||||
function fadeOut(destination) {
|
||||
const container = document.querySelector('.container');
|
||||
container.classList.remove('fade-in');
|
||||
container.classList.add('fade-out');
|
||||
|
||||
container.addEventListener('animationend', () => {
|
||||
window.location.href = destination;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>DO WE KNOW</h1>
|
||||
<h1>EACH OTHER?</h1>
|
||||
</div>
|
||||
<form id="loginForm" class="login-form">
|
||||
<div class="login-form-title">
|
||||
<h2>Login</h2>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="login-form-content">
|
||||
<input type="email" name="email" placeholder="Email">
|
||||
<input type="password" name="password" placeholder="Password">
|
||||
</div>
|
||||
<div class="login-form-footer">
|
||||
<button id="submit" type="submit" name="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,59 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/images/hard-disk.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/css/main_menu.css">
|
||||
<link rel="stylesheet" href="/css/transition.css">
|
||||
|
||||
<script src="/js/main_menu.js"></script>
|
||||
<script>
|
||||
function fadeIn() {
|
||||
document.querySelector('.container').classList.remove('fade-out');
|
||||
document.querySelector('.container').classList.add('fade-in');
|
||||
}
|
||||
|
||||
function fadeOut() {
|
||||
document.querySelector('.container').classList.remove('fade-in');
|
||||
document.querySelector('.container').classList.add('fade-out');
|
||||
// Redirect after fadeOut animation completes (adjust timeout as needed)
|
||||
setTimeout(() => {
|
||||
window.location.href = 'your_redirect_url.html';
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<title>Main Page</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="container">
|
||||
<div class="main_block">
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1>WELCOME BACK,</h1>
|
||||
<h1 id="username_field"></h1>
|
||||
<h2>Hope you have a productive day!</h2>
|
||||
</div>
|
||||
<img src="/images/user_1144760.png" alt="profile image">
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="content_buttons">
|
||||
<button id="start_uc" name="menu_button">Start UC</button>
|
||||
<button id="stop_uc" name="menu_button">Stop UC</button>
|
||||
</div>
|
||||
<div class="content_buttons">
|
||||
<button id="ceo_info" name="menu_button">Get CEO Info</button>
|
||||
<button id="users_info" name="menu_button">Get All Users</button>
|
||||
</div>
|
||||
<div class="content_buttons">
|
||||
<button id="reset_uc" name="menu_button">Reset UC</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button id="logout" name="logout">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 298 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB |
@@ -1,38 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
|
||||
loginForm.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(loginForm);
|
||||
const email = formData.get('email');
|
||||
const password = formData.get('password');
|
||||
|
||||
const data = { email, password };
|
||||
|
||||
try {
|
||||
const response = await fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.success) {
|
||||
document.cookie = `id=${result.user.id};`;
|
||||
document.cookie = `email=${result.user.email};`;
|
||||
document.cookie = `name=${result.user.name};`;
|
||||
|
||||
fadeOut('/');
|
||||
} else {
|
||||
alert('Incorrect credentials. Please try again.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during fetch:', error);
|
||||
alert('An error occurred. Please try again.');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,199 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
function getCookie(name) {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
if (cookie.startsWith(name + '=')) {
|
||||
return cookie.substring(name.length + 1, cookie.length);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = getCookie('name'); // Using 'name' to match your cookie structure
|
||||
const usernameField = document.getElementById('username_field');
|
||||
if (usernameField && username) {
|
||||
usernameField.textContent = username;
|
||||
}else{
|
||||
usernameField.textContent = 'Admin';
|
||||
}
|
||||
|
||||
// Define handler function
|
||||
function handleButtonClick(event) {
|
||||
console.log(`${event.target.id} button was clicked`);
|
||||
// Implement specific logic for each button here
|
||||
}
|
||||
|
||||
// Attach event handlers to specific buttons
|
||||
const startUcButton = document.getElementById('start_uc');
|
||||
const stopUcButton = document.getElementById('stop_uc');
|
||||
const ceoInfoButton = document.getElementById('ceo_info');
|
||||
const usersInfoButton = document.getElementById('users_info');
|
||||
const resetUcButton = document.getElementById('reset_uc');
|
||||
const logoutButton = document.getElementById('logout');
|
||||
|
||||
startUcButton.addEventListener('click', () => {
|
||||
console.log('Start UC Button pressed');
|
||||
fetch('/server', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ action: 'start' }),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => alert(data.message))
|
||||
.catch(error => console.error('Error:', error));
|
||||
});
|
||||
|
||||
stopUcButton.addEventListener('click', () => {
|
||||
console.log('Stop UC Button pressed');
|
||||
fetch('/server', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ action: 'stop' }),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => alert(data.message))
|
||||
.catch(error => console.error('Error:', error));
|
||||
});
|
||||
|
||||
ceoInfoButton.addEventListener('click', () => {
|
||||
console.log('Ceo Info Button pressed');
|
||||
|
||||
const domain = window.location.hostname;
|
||||
const endpoint = 'http://' + domain + ':5000/admin/ceo';
|
||||
const id = getCookie('id');
|
||||
|
||||
fetch(endpoint, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': id,
|
||||
'x-api-key': "uc_api"
|
||||
},
|
||||
})
|
||||
.then(async response => {
|
||||
if (!response.ok) {
|
||||
const responseData = await response.json();
|
||||
throw new Error(responseData.message);
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
})
|
||||
.then(data => {
|
||||
alert(data.message);
|
||||
|
||||
// Assuming 'data' is the property you want to download
|
||||
const toDownload = data.data;
|
||||
const jsonStr = JSON.stringify(toDownload, null, 2);
|
||||
const blob = new Blob([jsonStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'ceo.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
// Cleanup: remove the link and revoke the URL
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch(error => {
|
||||
alert(error)
|
||||
console.error('Error: '+ error)
|
||||
});
|
||||
})
|
||||
|
||||
usersInfoButton.addEventListener('click', () => {
|
||||
console.log('Users Info Button pressed');
|
||||
|
||||
const domain = window.location.hostname;
|
||||
const endpoint = 'http://' + domain + ':5000/admin/users';
|
||||
const id = getCookie('id');
|
||||
|
||||
fetch(endpoint, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': id,
|
||||
'x-api-key': "uc_api"
|
||||
},
|
||||
})
|
||||
.then(async response => {
|
||||
if (!response.ok) {
|
||||
const responseData = await response.json();
|
||||
throw new Error(responseData.message);
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
})
|
||||
.then(data => {
|
||||
alert(data.message);
|
||||
|
||||
const toDownload = data.data;
|
||||
const jsonStr = JSON.stringify(toDownload, null, 2);
|
||||
const blob = new Blob([jsonStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Create a temporary link to trigger the download
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'users.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
// Cleanup: remove the link and revoke the URL
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch(error => {
|
||||
alert(error)
|
||||
console.error('Error: '+ error)
|
||||
});
|
||||
})
|
||||
|
||||
resetUcButton.addEventListener('click', () => {
|
||||
console.log('Reset UC Button pressed');
|
||||
|
||||
const domain = window.location.hostname;
|
||||
const endpoint = 'http://' + domain + ':5000/admin/reset_server';
|
||||
const id = getCookie('id');
|
||||
|
||||
fetch(endpoint, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': id,
|
||||
'x-api-key': "uc_api"
|
||||
},
|
||||
})
|
||||
.then(async response => {
|
||||
if (!response.ok) {
|
||||
const responseData = await response.json();
|
||||
throw new Error(responseData.message);
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
})
|
||||
.then(data => alert(data.message))
|
||||
.catch(error => {
|
||||
alert(error)
|
||||
console.error('Error: '+ error)
|
||||
});
|
||||
})
|
||||
|
||||
logoutButton.addEventListener('click', () => {
|
||||
console.log('Logout button was clicked');
|
||||
|
||||
const deleteCookie = (name) => {
|
||||
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; Secure`;
|
||||
}
|
||||
|
||||
deleteCookie('email');
|
||||
deleteCookie('name');
|
||||
deleteCookie('id');
|
||||
|
||||
fadeOut('/login');
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const schemas = require('../models/schemas');
|
||||
const statusCodes = require('../helpers/status_codes');
|
||||
|
||||
const config = require('../config/credentials.json');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
res.sendFile('login.html', {root: path.join(__dirname, '..', 'public', 'html')});
|
||||
});
|
||||
|
||||
function validateLogin(req, res, next) {
|
||||
const {error, value} = schemas.loginModelSchema.validate(req.body);
|
||||
|
||||
if (error) {
|
||||
res.status(statusCodes.BAD_REQUEST).json({error: error.details});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
router.post('/', validateLogin, (req, res) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (email === config.email && password === config.password) {
|
||||
res.status(statusCodes.OK).json({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
user: {
|
||||
id: config.id,
|
||||
name: config.name,
|
||||
email: config.email
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(statusCodes.UNAUTHORIZED).json({message: 'Invalid email or password' });
|
||||
}
|
||||
});
|
||||
module.exports = router;
|
||||
@@ -1,46 +0,0 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const { fork } = require('child_process');
|
||||
const httpStatus = require('../helpers/status_codes');
|
||||
|
||||
const router = express.Router();
|
||||
const backendPath = path.join(__dirname, '..', '..', 'backend');
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
res.sendFile('main_menu.html', {root: path.join(__dirname, '..', 'public', 'html')});
|
||||
});
|
||||
|
||||
router.post('/server', async (req, res) => {
|
||||
const action = req.body.action;
|
||||
let serverProcess = req.app.get('serverProcess');
|
||||
|
||||
if (action === 'start') {
|
||||
if (serverProcess) {
|
||||
return res.status(httpStatus.BAD_REQUEST).send({message: 'Server is already running.'});
|
||||
}
|
||||
const serverPath = path.join(backendPath, 'src', 'app.js');
|
||||
serverProcess = fork(serverPath, [], { stdio: 'inherit' }); // Add stdio: 'inherit' to see child process logs in the parent process console
|
||||
|
||||
serverProcess.on('message', (msg) => {
|
||||
console.log('Message from server:', msg);
|
||||
});
|
||||
|
||||
serverProcess.on('close', (code, signal) => {
|
||||
console.log(`Server process exited with code ${code} and signal ${signal}`);
|
||||
req.app.set('serverProcess', null);
|
||||
});
|
||||
|
||||
req.app.set('serverProcess', serverProcess); // Update the serverProcess in the app
|
||||
res.status(httpStatus.OK).json({message: 'Server starting...'});
|
||||
} else if (action === 'stop') {
|
||||
if (!serverProcess) {
|
||||
return res.status(httpStatus.BAD_REQUEST).json({message: 'Server is not running.'});
|
||||
}
|
||||
serverProcess.kill('SIGTERM');
|
||||
res.status(httpStatus.OK).json({message: 'Server stopping...'});
|
||||
} else {
|
||||
res.status(httpStatus.BAD_REQUEST).json({message: 'Invalid action.'});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Generated
+2088
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "uc",
|
||||
"version": "1.0.0",
|
||||
"description": "A backend service with UDP heartbeat and TCP communication secured using Diffie-Hellman key exchange.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "ts-node src/index.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Cerbu Andrei-Mihnea",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"async-mutex": "^0.5.0",
|
||||
"better-sqlite3": "^11.3.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"lokijs": "^1.5.12",
|
||||
"ping": "^0.4.4",
|
||||
"sqlite3": "^5.1.7",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.11",
|
||||
"@types/lokijs": "^1.5.14",
|
||||
"@types/node": "^22.6.1",
|
||||
"@types/sqlite3": "^3.1.11",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user