BACKEND DONE FOR ALL APPS

This commit is contained in:
andrei-mihnea-cerbu
2024-10-21 18:34:45 +03:00
parent 4157ca9e7d
commit ab1eaec413
349 changed files with 17199 additions and 14015 deletions
+6
View File
@@ -0,0 +1,6 @@
HOST=0.0.0.0
UDP_PORT=41234
TCP_PORT=41233
CEO_EMAIL=ceo@domain.com
CEO_PASSWORD=ceo_password
-5
View File
@@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
-1
View File
@@ -1 +0,0 @@
package.json
-12
View File
@@ -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>
-12
View File
@@ -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>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="FLOW" />
</component>
</project>
-8
View File
@@ -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
View File
@@ -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>
-8
View File
@@ -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>
-45
View File
@@ -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.
-1811
View File
File diff suppressed because it is too large Load Diff
-30
View File
@@ -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"
}
}
-70
View File
@@ -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);
-3
View File
@@ -1,3 +0,0 @@
{
"apiKey": "uc_api"
}
-12
View File
@@ -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
}
}
-7
View File
@@ -1,7 +0,0 @@
{
"id": "7c0ef9d5-23ed-47a6-bfb4-fdcfce436904",
"name": "CEO",
"email": "ceo@yourfirm.com",
"password": "b85cfc54e8dda76e72d7bde7b10ce30a",
"department": "CEO"
}
-10
View File
@@ -1,10 +0,0 @@
{
"1": {
"name": "CEO",
"key": "6f444243063271cc1ea3ccb3621dfebc352fd40e2f58eb6c97b26b7e34fde7a1"
},
"2": {
"name": "HR",
"key": "2c480e5e3fdaca10d4d838b1b16fb44ce0f57238cb4343a839e065f8d6e41dc7"
}
}
-91
View File
@@ -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
};
-9
View File
@@ -1,9 +0,0 @@
[
{
"id": "59c712d9-e6dd-438f-aec9-31ca2ae750e6",
"name": "Andrei Cerbu",
"email": "a@c.com",
"password": "5447045dc3cfaafdb49b365e31dad41eb88616b0b1449930e55fd3a70bd8cee4",
"department": "HR"
}
]
-38
View File
@@ -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;
-16
View File
@@ -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;
-15
View File
@@ -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;
-10
View File
@@ -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;
-25
View File
@@ -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;
-15
View File
@@ -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;
-21
View File
@@ -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;
-85
View File
@@ -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;
-63
View File
@@ -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;
-196
View File
@@ -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;
-210
View File
@@ -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;
-21
View File
@@ -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
BIN
View File
Binary file not shown.
-5
View File
@@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
-12
View File
@@ -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>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>
-8
View File
@@ -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
View File
@@ -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>
-6
View File
@@ -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>
-32
View File
@@ -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.
-55
View File
@@ -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}`);
});
-6
View File
@@ -1,6 +0,0 @@
{
"id": "deee8837-3549-44d5-af56-fb7532505574",
"name": "Andrei",
"email": "admin@yourfirm.com",
"password": "password"
}
-11
View File
@@ -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;
-22
View File
@@ -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
-8
View File
@@ -1,8 +0,0 @@
const Joi = require('joi');
const loginModel = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().required()
});
module.exports = loginModel;
-7
View File
@@ -1,7 +0,0 @@
const loginModelSchema = require('./login');
const schemas = {
loginModelSchema
}
module.exports = schemas;
-1151
View File
File diff suppressed because it is too large Load Diff
-22
View File
@@ -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"
}
}
-110
View File
@@ -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;
}
-118
View File
@@ -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;
}
-25
View File
@@ -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;
}
}
-50
View File
@@ -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>
-59
View File
@@ -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

-38
View File
@@ -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.');
}
});
});
-199
View File
@@ -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');
});
});
-41
View File
@@ -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;
-46
View File
@@ -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;
+2088
View File
File diff suppressed because it is too large Load Diff
+31
View File
@@ -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"
}
}
+15
View File
@@ -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
}
+116
View File
@@ -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.' };
}
}
}
+164
View File
@@ -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.' };
}
}
}
+36
View File
@@ -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;
+235
View File
@@ -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.' };
}
}
}
+25
View File
@@ -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');
+46
View File
@@ -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;
}
}
+65
View File
@@ -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();
});
});
}
}
+121
View File
@@ -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);
}
}
}
}
+67
View File
@@ -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}`);
}
}
+110
View File
@@ -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. */
}
}