diff --git a/server/package-lock.json b/server/package-lock.json index 11e1829b4..a1a392b18 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -51,6 +51,7 @@ "@types/gamedig": "^5.0.3", "@types/jest": "^30.0.0", "@types/jmespath": "^0.15.2", + "@types/jsdom": "^27.0.0", "@types/jsonwebtoken": "9.0.10", "@types/mjml": "^4.7.4", "@types/multer": "^2.0.0", @@ -4453,6 +4454,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsdom": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-27.0.0.tgz", + "integrity": "sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4637,6 +4650,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", diff --git a/server/package.json b/server/package.json index 91ba82988..2bf898c94 100755 --- a/server/package.json +++ b/server/package.json @@ -63,6 +63,7 @@ "@types/gamedig": "^5.0.3", "@types/jest": "^30.0.0", "@types/jmespath": "^0.15.2", + "@types/jsdom": "^27.0.0", "@types/jsonwebtoken": "9.0.10", "@types/mjml": "^4.7.4", "@types/multer": "^2.0.0", diff --git a/server/src/app.js b/server/src/app.js index d782d07eb..c83726a49 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -1,16 +1,14 @@ import express from "express"; import path from "path"; -import { responseHandler } from "./middleware/v1/responseHandler.js"; import cors from "cors"; import helmet from "helmet"; import compression from "compression"; import cookieParser from "cookie-parser"; -import languageMiddleware from "./middleware/v1/languageMiddleware.js"; import swaggerUi from "swagger-ui-express"; -import { handleErrors } from "./middleware/v1/handleErrors.js"; +import { handleErrors } from "./middleware/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; -import { generalApiLimiter } from "./middleware/v1/rateLimiter.js"; -import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js"; +import { generalApiLimiter } from "./middleware/rateLimiter.js"; +import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; @@ -19,9 +17,6 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op // Static files app.use(express.static(frontendPath)); - // Response handler - app.use(responseHandler); - app.use( cors({ origin: allowedOrigin, @@ -62,7 +57,6 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op }, }) ); - app.use(languageMiddleware(services.stringService, services.translationService, services.settingsService)); // Swagger UI app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec)); @@ -73,7 +67,7 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op }); // Main app routes - setupRoutes(app, controllers); + setupRoutes(app, controllers, services); // FE routes app.get("*", (req, res) => { diff --git a/server/src/config/routes.ts b/server/src/config/routes.ts index 303dd4eb7..024387c25 100644 --- a/server/src/config/routes.ts +++ b/server/src/config/routes.ts @@ -1,5 +1,5 @@ -import { verifyJWT } from "../middleware/v1/verifyJWT.js"; -import { authApiLimiter } from "../middleware/v1/rateLimiter.js"; +import { createVerifyJWT } from "../middleware/verifyJWT.js"; +import { authApiLimiter } from "../middleware/rateLimiter.js"; import AuthRoutes from "../routes/v1/authRoute.js"; import InviteRoutes from "../routes/v1/inviteRoute.js"; @@ -15,19 +15,20 @@ import NotificationRoutes from "../routes/v1/notificationRoute.js"; import IncidentRoutes from "../routes/v1/incidentRoute.js"; -export const setupRoutes = (app: any, controllers: Record) => { +export const setupRoutes = (app: any, controllers: Record, services: Record) => { + const verifyJWT = createVerifyJWT(services.settingsService); // V1 - const authRoutes = new AuthRoutes(controllers.authController); + const authRoutes = new AuthRoutes(controllers.authController, verifyJWT); const monitorRoutes = new MonitorRoutes(controllers.monitorController); const settingsRoutes = new SettingsRoutes(controllers.settingsController); const checkRoutes = new CheckRoutes(controllers.checkController); - const inviteRoutes = new InviteRoutes(controllers.inviteController); + const inviteRoutes = new InviteRoutes(controllers.inviteController, verifyJWT); const maintenanceWindowRoutes = new MaintenanceWindowRoutes(controllers.maintenanceWindowController); const queueRoutes = new QueueRoutes(controllers.queueController); const logRoutes = new LogRoutes(controllers.logController); - const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController); + const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController, verifyJWT); const notificationRoutes = new NotificationRoutes(controllers.notificationController); - const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController); + const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController, verifyJWT); const incidentRoutes = new IncidentRoutes(controllers.incidentController); app.use("/api/v1/auth", authApiLimiter, authRoutes.getRouter()); diff --git a/server/src/config/services.ts b/server/src/config/services.ts index e3587c662..de4d5115f 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -1,6 +1,4 @@ -import ServiceRegistry from "../service/system/serviceRegistry.js"; import TranslationService from "../service/system/translationService.js"; -import StringService from "../service/system/stringService.js"; import MongoDB from "../db/MongoDB.js"; import NetworkService from "../service/infrastructure/networkService.js"; import EmailService from "../service/infrastructure/emailService.js"; @@ -46,7 +44,7 @@ import { games, GameDig } from "gamedig"; import jmespath from "jmespath"; // DB Modules -import { NormalizeData, NormalizeDataUptimeDetails } from "../utils/dataUtils.js"; +import { NormalizeData } from "../utils/dataUtils.js"; import { GenerateAvatarImage } from "../utils/imageProcessing.js"; import { ParseBoolean } from "../utils/utils.js"; @@ -100,7 +98,6 @@ export type InitializedSerivces = { //v1 settingsService: any; translationService: any; - stringService: any; db: any; networkService: any; emailService: any; @@ -140,21 +137,16 @@ export const initializeServices = async ({ envSettings: any; settingsService: any; }): Promise => { - const serviceRegistry = new ServiceRegistry({ logger }); - (ServiceRegistry as any).instance = serviceRegistry; - const translationService = new TranslationService(logger); await translationService.initialize(); - const stringService = new StringService(translationService); - // Create DB - const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); - const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService, AppSettings }); - const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); + const inviteModule = new InviteModule({ InviteToken, crypto }); + const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, AppSettings }); + const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean }); const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow }); const notificationModule = new NotificationModule({ Notification: NotificationModel, Monitor }); - const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto, stringService }); + const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto }); const settingsModule = new SettingsModule({ AppSettings }); const incidentModule = new IncidentModule({ logger, Incident, Monitor, User }); @@ -195,7 +187,6 @@ export const initializeServices = async ({ http, Docker, net, - stringService, settingsService, }); const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger); @@ -205,7 +196,6 @@ export const initializeServices = async ({ db, logger, errorService, - stringService, incidentsRepository, }); @@ -261,7 +251,6 @@ export const initializeServices = async ({ emailService, settingsService, logger, - stringService, jwt, errorService, jobQueue: superSimpleQueue, @@ -281,14 +270,11 @@ export const initializeServices = async ({ }); const maintenanceWindowService = new MaintenanceWindowService({ db, - settingsService, - stringService, errorService, monitorsRepository, }); const monitorService = new MonitorService({ jobQueue: superSimpleQueue, - stringService, emailService, papaparse, logger, @@ -304,7 +290,6 @@ export const initializeServices = async ({ //v1 settingsService, translationService, - stringService, db, networkService, emailService, @@ -335,9 +320,5 @@ export const initializeServices = async ({ incidentsRepository, }; - Object.values(services).forEach((service) => { - ServiceRegistry.register(service.serviceName, service); - }); - return services; }; diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index fcb731283..2ebb113ae 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -41,13 +41,6 @@ class MonitorController { return MonitorController.SERVICE_NAME; } - async verifyTeamAccess(teamId: string, monitorId: string) { - const monitor = await this.monitorService.getMonitorById({ teamId, monitorId }); - if (monitor.teamId !== teamId) { - throw new AppError({ message: "Access denied", status: 403 }); - } - } - getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => { try { await getCertificateParamValidation.validateAsync(req.params); diff --git a/server/src/db/modules/inviteModule.js b/server/src/db/modules/inviteModule.js index 444e51efd..e3438dc3b 100755 --- a/server/src/db/modules/inviteModule.js +++ b/server/src/db/modules/inviteModule.js @@ -1,10 +1,11 @@ +import { AppError } from "@/utils/AppError.js"; + const SERVICE_NAME = "inviteModule"; class InviteModule { - constructor({ InviteToken, crypto, stringService }) { + constructor({ InviteToken, crypto }) { this.InviteToken = InviteToken; this.crypto = crypto; - this.stringService = stringService; } requestInviteToken = async (userData) => { @@ -27,7 +28,7 @@ class InviteModule { token, }); if (invite === null) { - throw new Error(this.stringService.authInviteNotFound); + throw new AppError({ message: "Invite token not found", status: 404 }); } return invite; } catch (error) { @@ -42,7 +43,7 @@ class InviteModule { token, }); if (invite === null) { - throw new Error(this.stringService.authInviteNotFound); + throw new Error("Invite not found"); } return invite; } catch (error) { diff --git a/server/src/db/modules/recoveryModule.js b/server/src/db/modules/recoveryModule.js index cc22306a3..b2f9296a6 100755 --- a/server/src/db/modules/recoveryModule.js +++ b/server/src/db/modules/recoveryModule.js @@ -1,11 +1,10 @@ const SERVICE_NAME = "recoveryModule"; class RecoveryModule { - constructor({ User, RecoveryToken, crypto, stringService }) { + constructor({ User, RecoveryToken, crypto }) { this.User = User; this.RecoveryToken = RecoveryToken; this.crypto = crypto; - this.stringService = stringService; } requestRecoveryToken = async (email) => { @@ -32,7 +31,7 @@ class RecoveryModule { if (recoveryToken !== null) { return recoveryToken; } else { - throw new Error(this.stringService.dbTokenNotFound); + throw new Error("Recovery token not found"); } } catch (error) { error.service = SERVICE_NAME; @@ -50,7 +49,7 @@ class RecoveryModule { const user = await this.User.findOne({ email: recoveryToken.email }); if (user === null) { - throw new Error(this.stringService.dbUserNotFound); + throw new Error("User not found"); } const match = await user.comparePassword(newPassword); diff --git a/server/src/db/modules/statusPageModule.js b/server/src/db/modules/statusPageModule.js index 152f9efc5..78a632cb8 100755 --- a/server/src/db/modules/statusPageModule.js +++ b/server/src/db/modules/statusPageModule.js @@ -1,14 +1,9 @@ -// import { NormalizeData } from "../../../utils/dataUtils.js"; -// import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -// import StringService from "../../../service/system/stringService.js"; - const SERVICE_NAME = "statusPageModule"; class StatusPageModule { - constructor({ StatusPage, NormalizeData, stringService, AppSettings }) { + constructor({ StatusPage, NormalizeData, AppSettings }) { this.StatusPage = StatusPage; this.NormalizeData = NormalizeData; - this.stringService = stringService; this.AppSettings = AppSettings; } @@ -31,7 +26,7 @@ class StatusPageModule { if (error?.code === 11000) { // Handle duplicate URL errors error.status = 400; - error.message = this.stringService.statusPageUrlNotUnique; + error.message = "Status page URL must be unique"; } error.service = SERVICE_NAME; error.method = "createStatusPage"; @@ -91,7 +86,7 @@ class StatusPageModule { try { const preliminaryStatusPage = await this.StatusPage.findOne({ url }); if (!preliminaryStatusPage) { - const error = new Error(this.stringService.statusPageNotFound); + const error = new Error("Status page not found"); error.status = 404; throw error; } @@ -253,7 +248,7 @@ class StatusPageModule { }, ]); if (!statusPageQuery.length) { - const error = new Error(this.stringService.statusPageNotFound); + const error = new Error("Status page not found"); error.status = 404; throw error; } diff --git a/server/src/db/modules/userModule.js b/server/src/db/modules/userModule.js index 83b3b15f9..0051702b6 100755 --- a/server/src/db/modules/userModule.js +++ b/server/src/db/modules/userModule.js @@ -2,12 +2,11 @@ const SERVICE_NAME = "userModule"; const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key class UserModule { - constructor({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }) { + constructor({ User, Team, GenerateAvatarImage, ParseBoolean }) { this.User = User; this.Team = Team; this.GenerateAvatarImage = GenerateAvatarImage; this.ParseBoolean = ParseBoolean; - this.stringService = stringService; } checkSuperadmin = async () => { @@ -53,7 +52,7 @@ class UserModule { return await this.User.findOne({ _id: newUser._id }).select("-password").select("-profileImage"); // .select() doesn't work with create, need to save then find } catch (error) { if (error.code === DUPLICATE_KEY_CODE) { - error.message = this.stringService.dbUserExists; + error.message = "User already exists"; } error.service = SERVICE_NAME; error.method = "insertUser"; @@ -66,7 +65,7 @@ class UserModule { // We can strip the hash before returning the user const user = await this.User.findOne({ email: email }).select("-profileImage"); if (!user) { - throw new Error(this.stringService.dbUserNotFound); + throw new Error("User not found"); } return user; } catch (error) { @@ -117,7 +116,7 @@ class UserModule { try { const deletedUser = await this.User.findByIdAndDelete(userId); if (!deletedUser) { - throw new Error(this.stringService.dbUserNotFound); + throw new Error("User not found"); } return deletedUser; } catch (error) { diff --git a/server/src/locales/en.json b/server/src/locales/en.json deleted file mode 100755 index 1fbb25f79..000000000 --- a/server/src/locales/en.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "dontHaveAccount": "Don't have account", - "email": "E-mail", - "forgotPassword": "Forgot Password", - "password": "password", - "signUp": "Sign up", - "submit": "Submit", - "title": "Title", - "continue": "Continue", - "enterEmail": "Enter your email", - "authLoginTitle": "Log In", - "authLoginEnterPassword": "Enter your password", - "commonPassword": "Password", - "commonBack": "Back", - "authForgotPasswordTitle": "Forgot password?", - "authForgotPasswordResetPassword": "Reset password", - "createPassword": "Create your password", - "createAPassword": "Create a password", - "authRegisterAlreadyHaveAccount": "Already have an account?", - "commonAppName": "BlueWave Uptime", - "authLoginEnterEmail": "Enter your email", - "authRegisterTitle": "Create an account", - "authRegisterStepOneTitle": "Create your account", - "authRegisterStepOneDescription": "Enter your details to get started", - "authRegisterStepTwoTitle": "Set up your profile", - "authRegisterStepTwoDescription": "Tell us more about yourself", - "authRegisterStepThreeTitle": "Almost done!", - "authRegisterStepThreeDescription": "Review your information", - "authForgotPasswordDescription": "No worries, we'll send you reset instructions.", - "authForgotPasswordSendInstructions": "Send instructions", - "authForgotPasswordBackTo": "Back to", - "authCheckEmailTitle": "Check your email", - "authCheckEmailDescription": "We sent a password reset link to {{email}}", - "authCheckEmailResendEmail": "Resend email", - "authCheckEmailBackTo": "Back to", - "goBackTo": "Go back to", - "authCheckEmailDidntReceiveEmail": "Didn't receive the email?", - "authCheckEmailClickToResend": "Click to resend", - "authSetNewPasswordTitle": "Set new password", - "authSetNewPasswordDescription": "Your new password must be different from previously used passwords.", - "authSetNewPasswordNewPassword": "New password", - "authSetNewPasswordConfirmPassword": "Confirm password", - "confirmPassword": "Confirm your password", - "authSetNewPasswordResetPassword": "Reset password", - "authSetNewPasswordBackTo": "Back to", - "authPasswordMustBeAtLeast": "Must be at least", - "authPasswordCharactersLong": "8 characters long", - "authPasswordMustContainAtLeast": "Must contain at least", - "authPasswordSpecialCharacter": "one special character", - "authPasswordOneNumber": "one number", - "authPasswordUpperCharacter": "one upper character", - "authPasswordLowerCharacter": "one lower character", - "authPasswordConfirmAndPassword": "Confirm password and password", - "authPasswordMustMatch": "must match", - "friendlyError": "Something went wrong...", - "unknownError": "An unknown error occurred", - "unauthorized": "Unauthorized access", - "authAdminExists": "Admin already exists", - "authInviteNotFound": "Invite not found", - "unknownService": "Unknown service", - "noAuthToken": "No auth token provided", - "invalidAuthToken": "Invalid auth token", - "expiredAuthToken": "Token expired", - "noRefreshToken": "No refresh token provided", - "invalidRefreshToken": "Invalid refresh token", - "expiredRefreshToken": "Refresh token expired", - "requestNewAccessToken": "Request new access token", - "invalidPayload": "Invalid payload", - "verifyOwnerNotFound": "Document not found", - "verifyOwnerUnauthorized": "Unauthorized access", - "insufficientPermissions": "Insufficient permissions", - "dbUserExists": "User already exists", - "dbUserNotFound": "User not found", - "dbTokenNotFound": "Token not found", - "dbResetPasswordBadMatch": "New password must be different from old password", - "dbFindMonitorById": "Monitor with id ${monitorId} not found", - "dbDeleteChecks": "No checks found for monitor with id ${monitorId}", - "authIncorrectPassword": "Incorrect password", - "authUnauthorized": "Unauthorized access", - "monitorGetById": "Monitor not found", - "monitorGetByUserId": "No monitors found for user", - "jobQueueWorkerClose": "Error closing worker", - "jobQueueDeleteJob": "Job not found in queue", - "jobQueueObliterate": "Error obliterating queue", - "pingCannotResolve": "No response", - "statusPageNotFound": "Status page not found", - "statusPageUrlNotUnique": "Status page url must be unique", - "dockerFail": "Failed to fetch Docker container information", - "dockerNotFound": "Docker container not found", - "portFail": "Failed to connect to port", - "alertCreate": "Alert created successfully", - "alertGetByUser": "Got alerts successfully", - "alertGetByMonitor": "Got alerts by Monitor successfully", - "alertGetById": "Got alert by Id successfully", - "alertEdit": "Alert edited successfully", - "alertDelete": "Alert deleted successfully", - "authCreateUser": "User created successfully", - "authLoginUser": "User logged in successfully", - "authLogoutUser": "User logged out successfully", - "authUpdateUser": "User updated successfully", - "authCreateRecoveryToken": "Recovery token created successfully", - "authVerifyRecoveryToken": "Recovery token verified successfully", - "authResetPassword": "Password reset successfully", - "authAdminCheck": "Admin check completed successfully", - "authDeleteUser": "User deleted successfully", - "authTokenRefreshed": "Auth token is refreshed", - "authGetAllUsers": "Got all users successfully", - "inviteIssued": "Invite sent successfully", - "inviteVerified": "Invite verified successfully", - "checkCreate": "Check created successfully", - "checkGet": "Got checks successfully", - "checkDelete": "Checks deleted successfully", - "checkUpdateTtl": "Checks TTL updated successfully", - "monitorGetAll": "Got all monitors successfully", - "monitorStatsById": "Got monitor stats by Id successfully", - "monitorGetByIdSuccess": "Got monitor by Id successfully", - "monitorGetByTeamId": "Got monitors by Team Id successfully", - "monitorGetByUserIdSuccess": "Got monitor for ${userId} successfully", - "monitorCreate": "Monitor created successfully", - "bulkMonitorsCreate": "Monitors created successfully", - "monitorDelete": "Monitor deleted successfully", - "monitorEdit": "Monitor edited successfully", - "monitorCertificate": "Got monitor certificate successfully", - "monitorDemoAdded": "Successfully added demo monitors", - "queueGetMetrics": "Got metrics successfully", - "queueGetJobs": "Got jobs successfully", - "queueAddJob": "Job added successfully", - "queueObliterate": "Queue obliterated", - "jobQueueDeleteJobSuccess": "Job removed successfully", - "jobQueuePauseJob": "Job paused successfully", - "jobQueueResumeJob": "Job resumed successfully", - "maintenanceWindowGetById": "Got Maintenance Window by Id successfully", - "maintenanceWindowCreate": "Maintenance Window created successfully", - "maintenanceWindowGetByTeam": "Got Maintenance Windows by Team successfully", - "maintenanceWindowDelete": "Maintenance Window deleted successfully", - "maintenanceWindowEdit": "Maintenance Window edited successfully", - "pingSuccess": "Success", - "getAppSettings": "Got app settings successfully", - "updateAppSettings": "Updated app settings successfully", - "statusPageByUrl": "Got status page by url successfully", - "statusPageCreate": "Status page created successfully", - "newTermsAdded": "New terms added to POEditor", - "dockerSuccess": "Docker container status fetched successfully", - "portSuccess": "Port connected successfully", - "monitorPause": "Monitor paused successfully", - "monitorResume": "Monitor resumed successfully", - "statusPageDelete": "Status page deleted successfully", - "statusPageUpdate": "Status page updated successfully", - "statusPageByTeamId": "Got status pages by team id successfully", - "httpNetworkError": "Network error", - "httpNotJson": "Response data is not json", - "httpExpectedValueFail": "Expected value did not match", - "httpJsonPathError": "Failed to parse json data", - "httpEmptyResult": "Result is empty", - "httpMatchSuccess": "Response data match successfully", - "httpMatchFail": "Failed to match response data", - "httpJsonPathFail": "JSONPath did not match expected value", - "webhookSendSuccess": "Webhook notification sent successfully", - "telegramRequiresBotTokenAndChatId": "Telegram notifications require both botToken and chatId", - "webhookUrlRequired": "Webhook URL is required", - "platformRequired": "Platform is required", - "testNotificationFailed": "Failed to send test notification", - "monitorUpAlert": "Uptime Alert: One of your monitors is back online.\n📌 Monitor: {monitorName}\n📅 Time: {time}\n⚠️ Status: UP\n📟 Status Code: {code}\n\u200B\n", - "monitorDownAlert": "Downtime Alert: One of your monitors went offline.\n📌 Monitor: {monitorName}\n📅 Time: {time}\n⚠️ Status: DOWN\n📟 Status Code: {code}\n\u200B\n", - "sendTestEmail": "Test email sent successfully", - "errorForValidEmailAddress": "A valid recipient email address is required.", - "testEmailSubject": "Test E-mail from Checkmate", - "monitorSummaryByTeamId": "Got monitors and summary by team id successfully", - "monitorExportSuccess": "Monitors exported successfully", - "gameListSuccess": "Got game list successfully", - "groupsByTeamId": "Got groups by team id successfully", - "discordNotification": { - "monitor": "Monitor", - "status": "Status", - "statusCode": "Status Code", - "time": "Time", - "up": "UP", - "down": "DOWN", - "checkmate": "Checkmate", - "url": "URL", - "unknown": "Unknown", - "uptimeAlert": "Uptime Alert: Your monitor {monitorName} is back online", - "downtimeAlert": "Downtime Alert: Your monitor {monitorName} went offline" - } -} diff --git a/server/src/locales/en.json.bak b/server/src/locales/en.json.bak deleted file mode 100755 index eaef58eef..000000000 --- a/server/src/locales/en.json.bak +++ /dev/null @@ -1,147 +0,0 @@ -{ - "dontHaveAccount": "Don't have account", - "email": "E-mail", - "forgotPassword": "Forgot Password", - "password": "password", - "signUp": "Sign up", - "submit": "Submit", - "title": "Title", - "continue": "Continue", - "enterEmail": "Enter your email", - "authLoginTitle": "Log In", - "authLoginEnterPassword": "Enter your password", - "commonPassword": "Password", - "commonBack": "Back", - "authForgotPasswordTitle": "Forgot password?", - "authForgotPasswordResetPassword": "Reset password", - "createPassword": "Create your password", - "createAPassword": "Create a password", - "authRegisterAlreadyHaveAccount": "Already have an account?", - "commonAppName": "BlueWave Uptime", - "authLoginEnterEmail": "Enter your email", - "authRegisterTitle": "Create an account", - "authRegisterStepOneTitle": "Create your account", - "authRegisterStepOneDescription": "Enter your details to get started", - "authRegisterStepTwoTitle": "Set up your profile", - "authRegisterStepTwoDescription": "Tell us more about yourself", - "authRegisterStepThreeTitle": "Almost done!", - "authRegisterStepThreeDescription": "Review your information", - "authForgotPasswordDescription": "No worries, we'll send you reset instructions.", - "authForgotPasswordSendInstructions": "Send instructions", - "authForgotPasswordBackTo": "Back to", - "authCheckEmailTitle": "Check your email", - "authCheckEmailDescription": "We sent a password reset link to {{email}}", - "authCheckEmailResendEmail": "Resend email", - "authCheckEmailBackTo": "Back to", - "goBackTo": "Go back to", - "authCheckEmailDidntReceiveEmail": "Didn't receive the email?", - "authCheckEmailClickToResend": "Click to resend", - "authSetNewPasswordTitle": "Set new password", - "authSetNewPasswordDescription": "Your new password must be different from previously used passwords.", - "authSetNewPasswordNewPassword": "New password", - "authSetNewPasswordConfirmPassword": "Confirm password", - "confirmPassword": "Confirm your password", - "authSetNewPasswordResetPassword": "Reset password", - "authSetNewPasswordBackTo": "Back to", - "authPasswordMustBeAtLeast": "Must be at least", - "authPasswordCharactersLong": "8 characters long", - "authPasswordMustContainAtLeast": "Must contain at least", - "authPasswordSpecialCharacter": "one special character", - "authPasswordOneNumber": "one number", - "authPasswordUpperCharacter": "one upper character", - "authPasswordLowerCharacter": "one lower character", - "authPasswordConfirmAndPassword": "Confirm password and password", - "authPasswordMustMatch": "must match", - "friendlyError": "Something went wrong...", - "unknownError": "An unknown error occurred", - "unauthorized": "Unauthorized access", - "authAdminExists": "Admin already exists", - "authInviteNotFound": "Invite not found", - "unknownService": "Unknown service", - "noAuthToken": "No auth token provided", - "invalidAuthToken": "Invalid auth token", - "expiredAuthToken": "Token expired", - "noRefreshToken": "No refresh token provided", - "invalidRefreshToken": "Invalid refresh token", - "expiredRefreshToken": "Refresh token expired", - "requestNewAccessToken": "Request new access token", - "invalidPayload": "Invalid payload", - "verifyOwnerNotFound": "Document not found", - "verifyOwnerUnauthorized": "Unauthorized access", - "insufficientPermissions": "Insufficient permissions", - "dbUserExists": "User already exists", - "dbUserNotFound": "User not found", - "dbTokenNotFound": "Token not found", - "dbResetPasswordBadMatch": "New password must be different from old password", - "dbFindMonitorById": "Monitor with id ${monitorId} not found", - "dbDeleteChecks": "No checks found for monitor with id ${monitorId}", - "authIncorrectPassword": "Incorrect password", - "authUnauthorized": "Unauthorized access", - "monitorGetById": "Monitor not found", - "monitorGetByUserId": "No monitors found for user", - "jobQueueWorkerClose": "Error closing worker", - "jobQueueDeleteJob": "Job not found in queue", - "jobQueueObliterate": "Error obliterating queue", - "pingCannotResolve": "No response", - "statusPageNotFound": "Status page not found", - "statusPageUrlNotUnique": "Status page url must be unique", - "dockerFail": "Failed to fetch Docker container information", - "dockerNotFound": "Docker container not found", - "portFail": "Failed to connect to port", - "alertCreate": "Alert created successfully", - "alertGetByUser": "Got alerts successfully", - "alertGetByMonitor": "Got alerts by Monitor successfully", - "alertGetById": "Got alert by Id successfully", - "alertEdit": "Alert edited successfully", - "alertDelete": "Alert deleted successfully", - "authCreateUser": "User created successfully", - "authLoginUser": "User logged in successfully", - "authLogoutUser": "User logged out successfully", - "authUpdateUser": "User updated successfully", - "authCreateRecoveryToken": "Recovery token created successfully", - "authVerifyRecoveryToken": "Recovery token verified successfully", - "authResetPassword": "Password reset successfully", - "authAdminCheck": "Admin check completed successfully", - "authDeleteUser": "User deleted successfully", - "authTokenRefreshed": "Auth token is refreshed", - "authGetAllUsers": "Got all users successfully", - "inviteIssued": "Invite sent successfully", - "inviteVerified": "Invite verified successfully", - "checkCreate": "Check created successfully", - "checkGet": "Got checks successfully", - "checkDelete": "Checks deleted successfully", - "checkUpdateTtl": "Checks TTL updated successfully", - "monitorGetAll": "Got all monitors successfully", - "monitorStatsById": "Got monitor stats by Id successfully", - "monitorGetByIdSuccess": "Got monitor by Id successfully", - "monitorGetByTeamId": "Got monitors by Team Id successfully", - "monitorGetByUserIdSuccess": "Got monitor for ${userId} successfully", - "monitorCreate": "Monitor created successfully", - "monitorDelete": "Monitor deleted successfully", - "monitorEdit": "Monitor edited successfully", - "monitorCertificate": "Got monitor certificate successfully", - "monitorDemoAdded": "Successfully added demo monitors", - "queueGetMetrics": "Got metrics successfully", - "queueAddJob": "Job added successfully", - "queueObliterate": "Queue obliterated", - "jobQueueDeleteJobSuccess": "Job removed successfully", - "jobQueuePauseJob": "Job paused successfully", - "jobQueueResumeJob": "Job resumed successfully", - "maintenanceWindowGetById": "Got Maintenance Window by Id successfully", - "maintenanceWindowCreate": "Maintenance Window created successfully", - "maintenanceWindowGetByTeam": "Got Maintenance Windows by Team successfully", - "maintenanceWindowDelete": "Maintenance Window deleted successfully", - "maintenanceWindowEdit": "Maintenance Window edited successfully", - "pingSuccess": "Success", - "getAppSettings": "Got app settings successfully", - "updateAppSettings": "Updated app settings successfully", - "statusPageByUrl": "Got status page by url successfully", - "statusPageCreate": "Status page created successfully", - "newTermsAdded": "New terms added to POEditor", - "dockerSuccess": "Docker container status fetched successfully", - "portSuccess": "Port connected successfully", - "monitorPause": "Monitor paused successfully", - "monitorResume": "Monitor resumed successfully", - "statusPageDelete": "Status page deleted successfully", - "statusPageUpdate": "Status page updated successfully" -} diff --git a/server/src/locales/tr.json b/server/src/locales/tr.json deleted file mode 100755 index cc1ef4c64..000000000 --- a/server/src/locales/tr.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "dontHaveAccount": "Hesabınız yok mu", - "email": "E-posta", - "forgotPassword": "Parolamı Unuttum", - "password": "Parola", - "signUp": "Kayıt ol", - "submit": "Gönder", - "title": "Başlık", - "continue": "Devam et", - "enterEmail": "E-posta adresinizi girin", - "authLoginTitle": "Giriş Yap", - "authLoginEnterPassword": "Parolanızı girin", - "commonPassword": "Parola", - "commonBack": "Geri", - "authForgotPasswordTitle": "Parolanı mi unuttun?", - "authForgotPasswordResetPassword": "Parola sıfırla", - "createPassword": "Parolanızı oluşturun", - "createAPassword": "Bir parola oluşturun", - "authRegisterAlreadyHaveAccount": "Zaten hesabınız var mı?", - "commonAppName": "BlueWave Uptime", - "authLoginEnterEmail": "E-posta adresinizi girin", - "authRegisterTitle": "Hesap oluştur", - "authRegisterStepOneTitle": "Hesabınızı oluşturun", - "authRegisterStepOneDescription": "Başlamak için bilgilerinizi girin", - "authRegisterStepTwoTitle": "Profilinizi ayarlayın", - "authRegisterStepTwoDescription": "Kendiniz hakkında daha fazla bilgi verin", - "authRegisterStepThreeTitle": "Neredeyse bitti!", - "authRegisterStepThreeDescription": "Bilgilerinizi gözden geçirin", - "authForgotPasswordDescription": "Endişelenmeyin, size sıfırlama talimatlarını göndereceğiz.", - "authForgotPasswordSendInstructions": "Talimatları gönder", - "authForgotPasswordBackTo": "Geri dön", - "authCheckEmailTitle": "E-postanızı kontrol edin", - "authCheckEmailDescription": "{{email}} adresine bir şifre sıfırlama bağlantısı gönderdik", - "authCheckEmailResendEmail": "E-postayı yeniden gönder", - "authCheckEmailBackTo": "Geri dön", - "goBackTo": "Geri dön", - "authCheckEmailDidntReceiveEmail": "E-postayı almadınız mı?", - "authCheckEmailClickToResend": "Yeniden göndermek için tıklayın", - "authSetNewPasswordTitle": "Yeni şifre belirleyin", - "authSetNewPasswordDescription": "Yeni şifreniz, daha önce kullanılan şifrelerden farklı olmalıdır.", - "authSetNewPasswordNewPassword": "Yeni şifre", - "authSetNewPasswordConfirmPassword": "Parolayı onayla", - "confirmPassword": "Parolanızı onaylayın", - "authSetNewPasswordResetPassword": "Parolayı sıfırla", - "authSetNewPasswordBackTo": "Geri dön", - "authPasswordMustBeAtLeast": "En az", - "authPasswordCharactersLong": "8 karakter uzunluğunda olmalı", - "authPasswordMustContainAtLeast": "En az içermeli", - "authPasswordSpecialCharacter": "bir özel karakter", - "authPasswordOneNumber": "bir rakam", - "authPasswordUpperCharacter": "bir büyük harf", - "authPasswordLowerCharacter": "bir küçük harf", - "authPasswordConfirmAndPassword": "Onay şifresi ve şifre", - "authPasswordMustMatch": "eşleşmelidir", - "friendlyError": "Bir şeyler yanlış gitti...", - "unknownError": "Bilinmeyen bir hata oluştu", - "unauthorized": "Yetkisiz erişim", - "authAdminExists": "Yönetici zaten mevcut", - "authInviteNotFound": "Davet bulunamadı", - "unknownService": "Bilinmeyen servis", - "noAuthToken": "Kimlik doğrulama belirteci sağlanmadı", - "invalidAuthToken": "Geçersiz kimlik doğrulama belirteci", - "expiredAuthToken": "Belirteç süresi doldu", - "noRefreshToken": "Yenileme belirteci sağlanmadı", - "invalidRefreshToken": "Geçersiz yenileme belirteci", - "expiredRefreshToken": "Yenileme belirteci süresi doldu", - "requestNewAccessToken": "Yeni erişim belirteci isteyin", - "invalidPayload": "Geçersiz veri", - "verifyOwnerNotFound": "Belge bulunamadı", - "verifyOwnerUnauthorized": "Yetkisiz erişim", - "insufficientPermissions": "Yetersiz izinler", - "dbUserExists": "Kullanıcı zaten mevcut", - "dbUserNotFound": "Kullanıcı bulunamadı", - "dbTokenNotFound": "Belirteç bulunamadı", - "dbResetPasswordBadMatch": "Yeni şifre eski şifreden farklı olmalıdır", - "dbFindMonitorById": "${monitorId} kimlikli monitör bulunamadı", - "dbDeleteChecks": "${monitorId} kimlikli monitör için kontrol bulunamadı", - "authIncorrectPassword": "Geçersiz parola", - "authUnauthorized": "Yetkisiz erişim", - "monitorGetById": "Monitör bulunamadı", - "monitorGetByUserId": "Kullanıcı için monitör bulunamadı", - "jobQueueWorkerClose": "İşçi kapatılırken hata oluştu", - "jobQueueDeleteJob": "İş kuyrukta bulunamadı", - "jobQueueObliterate": "Kuyruk yok edilirken hata oluştu", - "pingCannotResolve": "Yanıt yok", - "statusPageNotFound": "Durum sayfası bulunamadı", - "statusPageUrlNotUnique": "Durum sayfası URL'si benzersiz olmalıdır", - "dockerFail": "Docker konteyner bilgisi alınamadı", - "dockerNotFound": "Docker konteyner bulunamadı", - "portFail": "Porta bağlanılamadı", - "alertCreate": "Uyarı başarıyla oluşturuldu", - "alertGetByUser": "Uyarılar başarıyla alındı", - "alertGetByMonitor": "Monitöre göre uyarılar başarıyla alındı", - "alertGetById": "Kimliğe göre uyarı başarıyla alındı", - "alertEdit": "Uyarı başarıyla düzenlendi", - "alertDelete": "Uyarı başarıyla silindi", - "authCreateUser": "Kullanıcı başarıyla oluşturuldu", - "authLoginUser": "Kullanıcı başarıyla giriş yaptı", - "authLogoutUser": "Kullanıcı başarıyla çıkış yaptı", - "authUpdateUser": "Kullanıcı başarıyla güncellendi", - "authCreateRecoveryToken": "Kurtarma belirteci başarıyla oluşturuldu", - "authVerifyRecoveryToken": "Kurtarma belirteci başarıyla doğrulandı", - "authResetPassword": "Şifre başarıyla sıfırlandı", - "authAdminCheck": "Yönetici kontrolü başarıyla tamamlandı", - "authDeleteUser": "Kullanıcı başarıyla silindi", - "authTokenRefreshed": "Kimlik doğrulama belirteci yenilendi", - "authGetAllUsers": "Tüm kullanıcılar başarıyla alındı", - "inviteIssued": "Davet başarıyla gönderildi", - "inviteVerified": "Davet başarıyla doğrulandı", - "checkCreate": "Kontrol başarıyla oluşturuldu", - "checkGet": "Kontroller başarıyla alındı", - "checkDelete": "Kontroller başarıyla silindi", - "checkUpdateTtl": "Kontrol TTL başarıyla güncellendi", - "monitorGetAll": "Tüm monitörler başarıyla alındı", - "monitorStatsById": "Kimliğe göre monitör istatistikleri başarıyla alındı", - "monitorGetByIdSuccess": "Kimliğe göre monitör başarıyla alındı", - "monitorGetByTeamId": "Takım kimliğine göre monitörler başarıyla alındı", - "monitorGetByUserIdSuccess": "${userId} için monitör başarıyla alındı", - "monitorCreate": "Monitör başarıyla oluşturuldu", - "monitorDelete": "Monitör başarıyla silindi", - "monitorEdit": "Monitör başarıyla düzenlendi", - "monitorCertificate": "Monitör sertifikası başarıyla alındı", - "monitorDemoAdded": "Demo monitörler başarıyla eklendi", - "queueGetMetrics": "Metrikler başarıyla alındı", - "queueAddJob": "İş başarıyla eklendi", - "queueObliterate": "Kuyruk yok edildi", - "jobQueueDeleteJobSuccess": "İş başarıyla kaldırıldı", - "jobQueuePauseJob": "İş başarıyla duraklatıldı", - "jobQueueResumeJob": "İş başarıyla devam ettirildi", - "maintenanceWindowGetById": "Kimliğe göre bakım penceresi başarıyla alındı", - "maintenanceWindowCreate": "Bakım penceresi başarıyla oluşturuldu", - "maintenanceWindowGetByTeam": "Takıma göre bakım pencereleri başarıyla alındı", - "maintenanceWindowDelete": "Bakım penceresi başarıyla silindi", - "maintenanceWindowEdit": "Bakım penceresi başarıyla düzenlendi", - "pingSuccess": "Başarılı", - "getAppSettings": "Uygulama ayarları başarıyla alındı", - "updateAppSettings": "Uygulama ayarları başarıyla güncellendi", - "statusPageByUrl": "URL'ye göre durum sayfası başarıyla alındı", - "statusPageCreate": "Durum sayfası başarıyla oluşturuldu", - "dockerSuccess": "Docker konteyner durumu başarıyla alındı", - "portSuccess": "Porta başarıyla bağlanıldı", - "newTermsAdded": "POEditor'a yeni terimler eklendi", - "monitorPause": "Monitör başarıyla duraklatıldı", - "monitorResume": "Monitör başarıyla devam ettirildi", - "monitorDelete2": "" -} diff --git a/server/src/middleware/handleErrors.ts b/server/src/middleware/handleErrors.ts new file mode 100755 index 000000000..1fffc9a8c --- /dev/null +++ b/server/src/middleware/handleErrors.ts @@ -0,0 +1,20 @@ +import type { NextFunction, Request, Response } from "express"; +import { logger } from "@/utils/logger.js"; + +const handleErrors = (error: any, req: Request, res: Response, next: NextFunction) => { + const status = error.status || 500; + const message = error.message || "Server error"; + const service = error.service || "unknownService"; + logger.error({ + message: message, + service: service, + method: error.method, + stack: error.stack, + }); + res.status(status).json({ + status, + msg: message, + }); +}; + +export { handleErrors }; diff --git a/server/src/middleware/isAllowed.ts b/server/src/middleware/isAllowed.ts new file mode 100755 index 000000000..21690ee38 --- /dev/null +++ b/server/src/middleware/isAllowed.ts @@ -0,0 +1,29 @@ +import type { Request, Response, NextFunction } from "express"; +const SERVICE_NAME = "allowedRoles"; +import { AppError } from "@/utils/AppError.js"; +import type { UserRole } from "@/types/index.js"; + +const isAllowed = (allowedRoles: UserRole[]) => { + return (req: Request, res: Response, next: NextFunction) => { + try { + const user = req.user; + if (!user) { + throw new AppError({ message: "Unauthorized", status: 403, service: SERVICE_NAME }); + } + const userRoles = req.user?.role || []; + + // Check if the user has the required role + if (userRoles.some((role) => allowedRoles.includes(role))) { + next(); + return; + } else { + throw new AppError({ message: "Unauthorized", status: 403, service: SERVICE_NAME }); + } + } catch (error) { + next(error); + return; + } + }; +}; + +export { isAllowed }; diff --git a/server/src/middleware/v1/rateLimiter.js b/server/src/middleware/rateLimiter.ts similarity index 86% rename from server/src/middleware/v1/rateLimiter.js rename to server/src/middleware/rateLimiter.ts index 70b080d23..99655149d 100644 --- a/server/src/middleware/v1/rateLimiter.js +++ b/server/src/middleware/rateLimiter.ts @@ -1,7 +1,7 @@ import rateLimit from "express-rate-limit"; export const generalApiLimiter = rateLimit({ - window: 60 * 1000, + windowMs: 60 * 1000, limit: 600, standardHeaders: true, legacyHeaders: false, @@ -9,7 +9,7 @@ export const generalApiLimiter = rateLimit({ }); export const authApiLimiter = rateLimit({ - window: 60 * 1000, + windowMs: 60 * 1000, limit: 15, standardHeaders: true, legacyHeaders: false, diff --git a/server/src/middleware/sanitization.ts b/server/src/middleware/sanitization.ts new file mode 100644 index 000000000..be971a298 --- /dev/null +++ b/server/src/middleware/sanitization.ts @@ -0,0 +1,59 @@ +import type { Request, Response, NextFunction } from "express"; +import DOMPurify from "isomorphic-dompurify"; + +export const sanitizeInput = (input: any, options = {}) => { + if (typeof input !== "string") { + return input; + } + + // Default configuration - remove all HTML tags and attributes + const defaultConfig = { + ALLOWED_TAGS: [] as string[], + ALLOWED_ATTR: [] as string[], + KEEP_CONTENT: true, + ...options, + }; + + return DOMPurify.sanitize(input, defaultConfig); +}; + +export const sanitizeObject = (obj: Record, options = {}): Record => { + if (typeof obj !== "object" || obj === null) { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map((item) => sanitizeObject(item, options)); + } + + const sanitized: Record = {}; + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "string") { + sanitized[key] = sanitizeInput(value, options); + } else if (typeof value === "object" && value !== null) { + sanitized[key] = sanitizeObject(value, options); + } else { + sanitized[key] = value; + } + } + + return sanitized; +}; + +export const sanitizeBody = (options = {}): ((req: Request, res: Response, next: NextFunction) => void) => { + return (req: Request, res: Response, next: NextFunction) => { + if (req.body && typeof req.body === "object") { + req.body = sanitizeObject(req.body, options); + } + next(); + }; +}; + +export const sanitizeQuery = (options = {}): ((req: Request, res: Response, next: NextFunction) => void) => { + return (req: Request, res: Response, next: NextFunction) => { + if (req.query && typeof req.query === "object") { + req.query = sanitizeObject(req.query, options); + } + next(); + }; +}; diff --git a/server/src/middleware/v1/handleErrors.js b/server/src/middleware/v1/handleErrors.js deleted file mode 100755 index 0a30b0420..000000000 --- a/server/src/middleware/v1/handleErrors.js +++ /dev/null @@ -1,22 +0,0 @@ -import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/system/serviceRegistry.js"; -import StringService from "../../service/system/stringService.js"; - -const handleErrors = (error, req, res, next) => { - const status = error.status || 500; - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - const message = error.message || stringService.authIncorrectPassword; - const service = error.service || stringService.unknownService; - logger.error({ - message: message, - service: service, - method: error.method, - stack: error.stack, - }); - res.error({ - status, - msg: message, - }); -}; - -export { handleErrors }; diff --git a/server/src/middleware/v1/isAllowed.js b/server/src/middleware/v1/isAllowed.js deleted file mode 100755 index 1d0de42b4..000000000 --- a/server/src/middleware/v1/isAllowed.js +++ /dev/null @@ -1,57 +0,0 @@ -import jwt from "jsonwebtoken"; -const TOKEN_PREFIX = "Bearer "; -const SERVICE_NAME = "allowedRoles"; -import ServiceRegistry from "../../service/system/serviceRegistry.js"; -import StringService from "../../service/system/stringService.js"; -import SettingsService from "../../service/system/settingsService.js"; - -const isAllowed = (allowedRoles) => { - return (req, res, next) => { - const token = req.headers["authorization"]; - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - // If no token is pressent, return an error - if (!token) { - const error = new Error(stringService.noAuthToken); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - - // If the token is improperly formatted, return an error - if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(stringService.invalidAuthToken); - error.status = 400; - error.service = SERVICE_NAME; - next(error); - return; - } - // Parse the token - try { - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings(); - var decoded = jwt.verify(parsedToken, jwtSecret); - const userRoles = decoded.role; - - // Check if the user has the required role - if (userRoles.some((role) => allowedRoles.includes(role))) { - next(); - return; - } else { - const error = new Error(stringService.insufficientPermissions); - error.status = 403; - error.service = SERVICE_NAME; - next(error); - return; - } - } catch (error) { - error.status = 401; - error.method = "isAllowed"; - error.service = SERVICE_NAME; - next(error); - return; - } - }; -}; - -export { isAllowed }; diff --git a/server/src/middleware/v1/languageMiddleware.js b/server/src/middleware/v1/languageMiddleware.js deleted file mode 100755 index 819858a21..000000000 --- a/server/src/middleware/v1/languageMiddleware.js +++ /dev/null @@ -1,27 +0,0 @@ -import { logger } from "../../utils/logger.js"; - -const languageMiddleware = (stringService, translationService) => async (req, res, next) => { - try { - const acceptLanguage = req.headers["accept-language"] || "en"; - const language = acceptLanguage.split(",")[0].slice(0, 2).toLowerCase(); - - translationService.setLanguage(language); - stringService.setLanguage(language); - - next(); - } catch (error) { - logger.error({ - message: error.message, - service: "languageMiddleware", - }); - const acceptLanguage = req.headers["accept-language"] || "en"; - const language = acceptLanguage.split(",")[0].slice(0, 2).toLowerCase(); - - translationService.setLanguage(language); - stringService.setLanguage(language); - - next(); - } -}; - -export default languageMiddleware; diff --git a/server/src/middleware/v1/responseHandler.js b/server/src/middleware/v1/responseHandler.js deleted file mode 100755 index 5169e7160..000000000 --- a/server/src/middleware/v1/responseHandler.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Middleware that adds standardized response methods to the Express response object. - * This allows for consistent API responses throughout the application. - * - * @param {import('express').Request} req - Express request object - * @param {import('express').Response} res - Express response object - * @param {import('express').NextFunction} next - Express next middleware function - */ -const responseHandler = (req, res, next) => { - /** - * Sends a standardized success response - * - * @param {Object} options - Success response options - * @param {number} [options.status=200] - HTTP status code - * @param {string} [options.msg="OK"] - Success message - * @param {*} [options.data=null] - Response data payload - * @returns {Object} Express response object - */ - res.success = ({ status = 200, msg = "OK", data = null, headers = {} }) => { - // Set custom headers if provided - Object.entries(headers).forEach(([key, value]) => { - res.set(key, value); - }); - - return res.status(status).json({ - success: true, - msg: msg, - data: data, - }); - }; - - /** - * Sends a standardized error response - * - * @param {Object} options - Error response options - * @param {number} [options.status=500] - HTTP status code - * @param {string} [options.msg="Internal server error"] - Error message - * @param {*} [options.data=null] - Additional error data (if any) - * @returns {Object} Express response object - */ - res.error = ({ status = 500, msg = "Internal server error", data = null }) => { - return res.status(status).json({ - success: false, - msg, - data, - }); - }; - - /** - * Sends a raw file response (for CSV, PDF, etc.) - * @param {Object} options - * @param {Buffer|string} options.data - The file content - * @param {Object} options.headers - Headers to set (e.g. Content-Type, Content-Disposition) - * @param {number} [options.status=200] - HTTP status code - */ - res.file = ({ data, headers = {}, status = 200 }) => { - Object.entries(headers).forEach(([key, value]) => { - res.setHeader(key, value); - }); - return res.status(status).send(data); - }; - - next(); -}; - -export { responseHandler }; diff --git a/server/src/middleware/v1/sanitization.js b/server/src/middleware/v1/sanitization.js deleted file mode 100644 index a89325de1..000000000 --- a/server/src/middleware/v1/sanitization.js +++ /dev/null @@ -1,85 +0,0 @@ -import { JSDOM } from "jsdom"; -import DOMPurify from "isomorphic-dompurify"; - -// Initialize DOMPurify with jsdom -const window = new JSDOM("").window; -const purify = DOMPurify(window); - -/** - * Sanitizes user input to prevent XSS attacks - * @param {string} input - The input string to sanitize - * @param {Object} options - Sanitization options - * @returns {string} The sanitized string - */ -export const sanitizeInput = (input, options = {}) => { - if (typeof input !== "string") { - return input; - } - - // Default configuration - remove all HTML tags and attributes - const defaultConfig = { - ALLOWED_TAGS: [], - ALLOWED_ATTR: [], - KEEP_CONTENT: true, - ...options, - }; - - return purify.sanitize(input, defaultConfig); -}; - -/** - * Sanitizes an object recursively - * @param {Object} obj - The object to sanitize - * @param {Object} options - Sanitization options - * @returns {Object} The sanitized object - */ -export const sanitizeObject = (obj, options = {}) => { - if (typeof obj !== "object" || obj === null) { - return obj; - } - - if (Array.isArray(obj)) { - return obj.map((item) => sanitizeObject(item, options)); - } - - const sanitized = {}; - for (const [key, value] of Object.entries(obj)) { - if (typeof value === "string") { - sanitized[key] = sanitizeInput(value, options); - } else if (typeof value === "object" && value !== null) { - sanitized[key] = sanitizeObject(value, options); - } else { - sanitized[key] = value; - } - } - - return sanitized; -}; - -/** - * Express middleware for sanitizing request body - * @param {Object} options - Sanitization options - * @returns {Function} Express middleware function - */ -export const sanitizeBody = (options = {}) => { - return (req, res, next) => { - if (req.body && typeof req.body === "object") { - req.body = sanitizeObject(req.body, options); - } - next(); - }; -}; - -/** - * Express middleware for sanitizing query parameters - * @param {Object} options - Sanitization options - * @returns {Function} Express middleware function - */ -export const sanitizeQuery = (options = {}) => { - return (req, res, next) => { - if (req.query && typeof req.query === "object") { - req.query = sanitizeObject(req.query, options); - } - next(); - }; -}; diff --git a/server/src/middleware/v1/verifyJWT.ts b/server/src/middleware/v1/verifyJWT.ts deleted file mode 100755 index b3211d7b6..000000000 --- a/server/src/middleware/v1/verifyJWT.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { NextFunction, Request, Response } from "express"; - -import jwt from "jsonwebtoken"; -import ServiceRegistry from "../../service/system/serviceRegistry.js"; -import SettingsService from "../../service/system/settingsService.js"; -import StringService from "../../service/system/stringService.js"; -const SERVICE_NAME = "verifyJWT"; -const TOKEN_PREFIX = "Bearer "; - -const verifyJWT = (req: Request, res: Response, next: NextFunction) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - const token = req.headers["authorization"]; - // Make sure a token is provided - if (!token) { - const error: any = new Error(stringService.noAuthToken); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - // Make sure it is properly formatted - if (!token.startsWith(TOKEN_PREFIX)) { - const error: any = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token - error.status = 401; - error.service = SERVICE_NAME; - error.method = "verifyJWT"; - next(error); - return; - } - - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - // Verify the token's authenticity - const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings(); - jwt.verify(parsedToken, jwtSecret, (err: any, decoded: any) => { - if (err) { - const errorMessage = err.name === "TokenExpiredError" ? stringService.expiredAuthToken : stringService.invalidAuthToken; - err.details = { msg: errorMessage }; - err.status = 401; - err.service = SERVICE_NAME; - err.method = "verifyJWT"; - next(err); - return; - } else { - // Token is valid, carry on - req.user = decoded; - next(); - } - }); -}; - -export { verifyJWT }; diff --git a/server/src/middleware/v1/verifyOwnership.js b/server/src/middleware/v1/verifyOwnership.js deleted file mode 100755 index 6fcc84f60..000000000 --- a/server/src/middleware/v1/verifyOwnership.js +++ /dev/null @@ -1,59 +0,0 @@ -import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/system/serviceRegistry.js"; -import StringService from "../../service/system/stringService.js"; -import { ObjectId } from "mongodb"; - -const SERVICE_NAME = "verifyOwnership"; - -const verifyOwnership = (Model, paramName) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - return async (req, res, next) => { - const userId = req.user._id; - let documentId = req.params[paramName]; - - try { - if (typeof documentId === "string") { - documentId = ObjectId.createFromHexString(documentId); - } - const doc = await Model.findById(documentId); - //If the document is not found, return a 404 error - if (!doc) { - logger.error({ - message: stringService.verifyOwnerNotFound, - service: SERVICE_NAME, - method: "verifyOwnership", - }); - const error = new Error(stringService.verifyOwnerNotFound); - error.status = 404; - throw error; - } - - // Special case for User model, as it will not have a `userId` field as other docs will - if (Model.modelName === "User") { - if (userId.toString() !== doc._id.toString()) { - const error = new Error(stringService.verifyOwnerUnauthorized); - error.status = 403; - throw error; - } - next(); - return; - } - - // If the userID does not match the document's userID, return a 403 error - if (userId.toString() !== doc.userId.toString()) { - const error = new Error("Unauthorized"); - error.status = 403; - throw error; - } - next(); - return; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "verifyOwnership"; - next(error); - return; - } - }; -}; - -export { verifyOwnership }; diff --git a/server/src/middleware/v1/verifySuperAdmin.js b/server/src/middleware/v1/verifySuperAdmin.js deleted file mode 100755 index 41ac0a3e0..000000000 --- a/server/src/middleware/v1/verifySuperAdmin.js +++ /dev/null @@ -1,66 +0,0 @@ -const jwt = require("jsonwebtoken"); -const logger = require("../../utils/logger.js"); -const SERVICE_NAME = "verifyAdmin"; -const TOKEN_PREFIX = "Bearer "; -import ServiceRegistry from "../service/serviceRegistry.js"; -import SettingsService from "../service/settingsService.js"; -import StringService from "../service/stringService.js"; -/** - * Verifies the JWT token - * @function - * @param {express.Request} req - * @param {express.Response} res - * @param {express.NextFunction} next - * @returns {express.Response} - */ -const verifySuperAdmin = (req, res, next) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - const token = req.headers["authorization"]; - // Make sure a token is provided - if (!token) { - const error = new Error(stringService.noAuthToken); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - // Make sure it is properly formatted - if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token - error.status = 400; - error.service = SERVICE_NAME; - error.method = "verifySuperAdmin"; - next(error); - return; - } - - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - // verify admin role is present - const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings(); - - jwt.verify(parsedToken, jwtSecret, (err, decoded) => { - if (err) { - logger.error({ - message: err.message, - service: SERVICE_NAME, - method: "verifySuperAdmin", - stack: err.stack, - details: stringService.invalidAuthToken, - }); - return res.status(401).json({ success: false, msg: stringService.invalidAuthToken }); - } - - if (decoded.role.includes("superadmin") === false) { - logger.error({ - message: stringService.invalidAuthToken, - service: SERVICE_NAME, - method: "verifySuperAdmin", - stack: err.stack, - }); - return res.status(401).json({ success: false, msg: stringService.unauthorized }); - } - next(); - }); -}; - -module.exports = { verifySuperAdmin }; diff --git a/server/src/middleware/v1/verifyTeamAccess.js b/server/src/middleware/v1/verifyTeamAccess.js deleted file mode 100644 index 60647bdc2..000000000 --- a/server/src/middleware/v1/verifyTeamAccess.js +++ /dev/null @@ -1,38 +0,0 @@ -const SERVICE_NAME = "verifyTeamAccess"; - -const verifyTeamAccess = (Model, paramName) => { - return async (req, res, next) => { - try { - const documentId = req.params[paramName]; - const doc = await Model.findById(documentId); - - if (!doc) { - const error = new Error("Document not found"); - error.status = 404; - throw error; - } - - if (!req?.user?.teamId || !doc.teamId) { - const error = new Error("Missing team information"); - error.status = 400; - throw error; - } - - if (req.user.teamId.toString() === doc.teamId.toString()) { - next(); - return; - } - - const error = new Error("Unauthorized"); - error.status = 403; - throw error; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "verifyTeamAccess"; - next(error); - return; - } - }; -}; - -export { verifyTeamAccess }; diff --git a/server/src/middleware/verifyJWT.ts b/server/src/middleware/verifyJWT.ts new file mode 100644 index 000000000..6c2b0a229 --- /dev/null +++ b/server/src/middleware/verifyJWT.ts @@ -0,0 +1,52 @@ +import { NextFunction, Request, Response } from "express"; + +import jwt from "jsonwebtoken"; +import { AppError } from "@/utils/AppError.js"; +import { Settings } from "@/types/settings.js"; + +const SERVICE_NAME = "verifyJWT"; +const TOKEN_PREFIX = "Bearer "; + +export const createVerifyJWT = (settingsService: { getSettings: () => Settings }) => { + return (req: Request, res: Response, next: NextFunction) => { + const token = req.headers["authorization"]; + // Make sure a token is provided + if (!token) { + const error = new AppError({ message: "No token provided", status: 401, service: SERVICE_NAME }); + next(error); + return; + } + // Make sure it is properly formatted + if (!token.startsWith(TOKEN_PREFIX)) { + const error = new AppError({ message: "Invalid token format", status: 401, service: SERVICE_NAME, method: "verifyJWT" }); + next(error); + return; + } + + const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); + // Verify the token's authenticity + const { jwtSecret } = settingsService.getSettings(); + if (!jwtSecret) { + const error = new AppError({ message: "JWT secret not configured", status: 500, service: SERVICE_NAME }); + next(error); + return; + } + jwt.verify(parsedToken, jwtSecret, (err: any, decoded: any) => { + if (err) { + const error = new AppError({ + message: "Failed to authenticate token", + details: err, + status: 401, + service: SERVICE_NAME, + method: "verifyJWT", + }); + next(error); + return; + } else { + // Token is valid, carry on + req.user = decoded; + next(); + } + }); + }; +}; diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/v1/authRoute.js index 63b4c4201..0eca250e5 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -1,18 +1,17 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; const upload = multer(); class AuthRoutes { - constructor(authController) { + constructor(authController, verifyJWT) { this.router = Router(); this.authController = authController; - this.initRoutes(); + this.initRoutes(verifyJWT); } - initRoutes() { + initRoutes(verifyJWT) { this.router.post("/register", upload.single("profileImage"), this.authController.registerUser); this.router.post("/login", this.authController.loginUser); diff --git a/server/src/routes/v1/checkRoute.js b/server/src/routes/v1/checkRoute.js index 6565425d4..2c34049bb 100755 --- a/server/src/routes/v1/checkRoute.js +++ b/server/src/routes/v1/checkRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class CheckRoutes { constructor(checkController) { diff --git a/server/src/routes/v1/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js index 2cf481e08..9ec470e53 100755 --- a/server/src/routes/v1/diagnosticRoute.js +++ b/server/src/routes/v1/diagnosticRoute.js @@ -1,14 +1,14 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class DiagnosticRoutes { - constructor(diagnosticController) { + constructor(diagnosticController, verifyJWT) { this.router = Router(); this.diagnosticController = diagnosticController; - this.initRoutes(); + this.initRoutes(verifyJWT); } - initRoutes() { + + initRoutes(verifyJWT) { this.router.get("/system", verifyJWT, isAllowed(["admin", "superadmin"]), this.diagnosticController.getSystemStats); } diff --git a/server/src/routes/v1/incidentRoute.js b/server/src/routes/v1/incidentRoute.js index 6825ce21e..f87ef17a7 100644 --- a/server/src/routes/v1/incidentRoute.js +++ b/server/src/routes/v1/incidentRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class IncidentRoutes { constructor(incidentController) { diff --git a/server/src/routes/v1/inviteRoute.js b/server/src/routes/v1/inviteRoute.js index a47550536..44ec0495f 100755 --- a/server/src/routes/v1/inviteRoute.js +++ b/server/src/routes/v1/inviteRoute.js @@ -1,15 +1,14 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class InviteRoutes { - constructor(inviteController) { + constructor(inviteController, verifyJWT) { this.router = Router(); this.inviteController = inviteController; - this.initRoutes(); + this.initRoutes(verifyJWT); } - initRoutes() { + initRoutes(verifyJWT) { this.router.post("/send", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.sendInviteEmail); this.router.post("/verify", this.inviteController.verifyInviteToken); this.router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.getInviteToken); diff --git a/server/src/routes/v1/logRoutes.js b/server/src/routes/v1/logRoutes.js index 43da8b75a..feabe669a 100755 --- a/server/src/routes/v1/logRoutes.js +++ b/server/src/routes/v1/logRoutes.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class LogRoutes { constructor(logController) { this.router = Router(); diff --git a/server/src/routes/v1/monitorRoute.ts b/server/src/routes/v1/monitorRoute.ts index 2e6db630d..4488a2c49 100755 --- a/server/src/routes/v1/monitorRoute.ts +++ b/server/src/routes/v1/monitorRoute.ts @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; import { fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; const upload = multer({ diff --git a/server/src/routes/v1/queueRoute.js b/server/src/routes/v1/queueRoute.js index c6cfa05da..59e1cb744 100755 --- a/server/src/routes/v1/queueRoute.js +++ b/server/src/routes/v1/queueRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class QueueRoutes { constructor(queueController) { this.router = Router(); diff --git a/server/src/routes/v1/settingsRoute.js b/server/src/routes/v1/settingsRoute.js index e88186af2..76556e923 100755 --- a/server/src/routes/v1/settingsRoute.js +++ b/server/src/routes/v1/settingsRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class SettingsRoutes { constructor(settingsController) { diff --git a/server/src/routes/v1/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js index 2c3b0b265..7d915cb86 100755 --- a/server/src/routes/v1/statusPageRoute.js +++ b/server/src/routes/v1/statusPageRoute.js @@ -1,16 +1,15 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; import multer from "multer"; const upload = multer(); class StatusPageRoutes { - constructor(statusPageController) { + constructor(statusPageController, verifyJWT) { this.router = Router(); this.statusPageController = statusPageController; - this.initRoutes(); + this.initRoutes(verifyJWT); } - initRoutes() { + initRoutes(verifyJWT) { this.router.get("/", this.statusPageController.getStatusPage); this.router.get("/team", verifyJWT, this.statusPageController.getStatusPagesByTeamId); diff --git a/server/src/service/business/incidentService.ts b/server/src/service/business/incidentService.ts index 82211bc4e..5ababc5b4 100644 --- a/server/src/service/business/incidentService.ts +++ b/server/src/service/business/incidentService.ts @@ -20,26 +20,23 @@ class IncidentService { private db: any; private logger: any; private errorService: any; - private stringService: any; + private: any; private incidentsRepository: IIncidentsRepository; constructor({ db, logger, errorService, - stringService, incidentsRepository, }: { db: any; logger: any; errorService: any; - stringService: any; incidentsRepository: IIncidentsRepository; }) { this.db = db; this.logger = logger; this.errorService = errorService; - this.stringService = stringService; this.incidentsRepository = incidentsRepository; } diff --git a/server/src/service/business/maintenanceWindowService.ts b/server/src/service/business/maintenanceWindowService.ts index bb82821cf..c1ba5b8d7 100644 --- a/server/src/service/business/maintenanceWindowService.ts +++ b/server/src/service/business/maintenanceWindowService.ts @@ -5,27 +5,11 @@ const SERVICE_NAME = "maintenanceWindowService"; class MaintenanceWindowService { static SERVICE_NAME = SERVICE_NAME; private db: any; - private settingsService: any; - private stringService: any; private errorService: any; private monitorsRepository: IMonitorsRepository; - constructor({ - db, - settingsService, - stringService, - errorService, - monitorsRepository, - }: { - db: any; - settingsService: any; - stringService: any; - errorService: any; - monitorsRepository: IMonitorsRepository; - }) { + constructor({ db, errorService, monitorsRepository }: { db: any; errorService: any; monitorsRepository: IMonitorsRepository }) { this.db = db; - this.settingsService = settingsService; - this.stringService = stringService; this.errorService = errorService; this.monitorsRepository = monitorsRepository; } diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index fee3a9ffc..e9e10de04 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -65,9 +65,7 @@ export interface IMonitorService { export class MonitorService implements IMonitorService { static SERVICE_NAME = SERVICE_NAME; - private db: any; private jobQueue: any; - private stringService: any; private emailService: any; private papaparse: any; private logger: any; @@ -80,7 +78,6 @@ export class MonitorService implements IMonitorService { constructor({ jobQueue, - stringService, emailService, papaparse, logger, @@ -92,7 +89,6 @@ export class MonitorService implements IMonitorService { statusPagesRepository, }: { jobQueue: any; - stringService: any; emailService: any; papaparse: any; logger: any; @@ -104,7 +100,6 @@ export class MonitorService implements IMonitorService { statusPagesRepository: IStatusPagesRepository; }) { this.jobQueue = jobQueue; - this.stringService = stringService; this.emailService = emailService; this.papaparse = papaparse; this.logger = logger; @@ -465,7 +460,7 @@ export class MonitorService implements IMonitorService { }; sendTestEmail = async ({ to }: { to: string }): Promise => { - const subject = this.stringService.testEmailSubject; + const subject = "Test email from Checkmate"; const context = { testName: "Monitoring System" }; const html = await this.emailService.buildEmail("testEmailTemplate", context); diff --git a/server/src/service/business/userService.ts b/server/src/service/business/userService.ts index 5f73508dd..f6f489b8e 100644 --- a/server/src/service/business/userService.ts +++ b/server/src/service/business/userService.ts @@ -3,6 +3,7 @@ import Team from "@/db/models/Team.js"; import type { User } from "@/types/index.js"; import bcrypt from "bcryptjs"; import { AppError } from "@/utils/AppError.js"; +import { ISuperSimpleQueue } from "../infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; const SERVICE_NAME = "userService"; @@ -12,10 +13,8 @@ class UserService { private emailService: any; private settingsService: any; private logger: any; - private stringService: any; private jwt: any; - private errorService: any; - private jobQueue: any; + private jobQueue: ISuperSimpleQueue; private crypto: any; private monitorsRepository: IMonitorsRepository; private usersRepository: IUsersRepository; @@ -28,9 +27,7 @@ class UserService { emailService, settingsService, logger, - stringService, jwt, - errorService, jobQueue, monitorsRepository, usersRepository, @@ -42,10 +39,9 @@ class UserService { emailService: any; settingsService: any; logger: any; - stringService: any; jwt: any; errorService: any; - jobQueue: any; + jobQueue: ISuperSimpleQueue; monitorsRepository: IMonitorsRepository; usersRepository: IUsersRepository; invitesRepository: IInvitesRepository; @@ -55,9 +51,7 @@ class UserService { this.emailService = emailService; this.settingsService = settingsService; this.logger = logger; - this.stringService = stringService; this.jwt = jwt; - this.errorService = errorService; this.jobQueue = jobQueue; this.crypto = crypto; this.monitorsRepository = monitorsRepository; @@ -144,7 +138,7 @@ class UserService { const match = await bcrypt.compare(password, user.password); if (match !== true) { - throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword); + throw new AppError({ message: "Incorrect password", service: SERVICE_NAME, status: 401 }); } // Remove password from user object. Should this be abstracted to DB layer? @@ -173,7 +167,7 @@ class UserService { // If not a match, throw a 403 // 403 instead of 401 to avoid triggering axios interceptor if (!match) { - throw this.errorService.createAuthorizationError(this.stringService.authIncorrectPassword); + throw new AppError({ message: "Incorrect current password", service: SERVICE_NAME, status: 403 }); } // If a match, update the password updates.password = updates.newPassword; @@ -234,23 +228,23 @@ class UserService { deleteUser = async (user: User) => { const email = user?.email; if (!email) { - throw this.errorService.createBadRequestError("No email in request"); + throw new AppError({ message: "No email in request", service: SERVICE_NAME, method: "deleteUser", status: 400 }); } const teamId = user?.teamId; const userId = user?.id; if (!teamId) { - throw this.errorService.createBadRequestError("No team ID in request"); + throw new AppError({ message: "No team ID in request", service: SERVICE_NAME, method: "deleteUser", status: 400 }); } if (!userId) { - throw this.errorService.createBadRequestError("No user ID in request"); + throw new AppError({ message: "No user ID in request", service: SERVICE_NAME, method: "deleteUser", status: 400 }); } const roles = user?.role; if (roles.includes("demo")) { - throw this.errorService.createBadRequestError("Demo user cannot be deleted"); + throw new AppError({ message: "Demo user cannot be deleted", service: SERVICE_NAME, method: "deleteUser", status: 400 }); } // 1. Find all the monitors associated with the team ID if superadmin @@ -262,7 +256,7 @@ class UserService { res?.length > 0 && (await Promise.all( res.map(async (monitor) => { - await this.jobQueue.deleteJob(monitor); + await this.jobQueue.deleteJob(monitor.id); }) )); } diff --git a/server/src/service/infrastructure/networkService.ts b/server/src/service/infrastructure/networkService.ts index e4e5972af..7db8fc6e2 100644 --- a/server/src/service/infrastructure/networkService.ts +++ b/server/src/service/infrastructure/networkService.ts @@ -53,10 +53,8 @@ class NetworkService implements INetworkService { private GameDig: any; private ping: any; private logger: any; - private http: any; private Docker: any; private net: any; - private stringService: any; private settingsService: any; private buildStatusResponse = ({ @@ -124,7 +122,6 @@ class NetworkService implements INetworkService { http, Docker, net, - stringService, settingsService, }: { axios: any; @@ -137,7 +134,6 @@ class NetworkService implements INetworkService { http: any; Docker: any; net: any; - stringService: any; settingsService: any; }) { this.TYPE_PING = "ping"; @@ -152,14 +148,12 @@ class NetworkService implements INetworkService { this.PING_ERROR = 5001; this.axios = axios; this.https = https; - this.http = http; this.jmespath = jmespath; this.GameDig = GameDig; this.ping = ping; this.logger = logger; this.Docker = Docker; this.net = net; - this.stringService = stringService; this.settingsService = settingsService; const cacheable = new CacheableLookup(); @@ -326,7 +320,7 @@ class NetworkService implements INetworkService { } else { httpResponse.code = 500; httpResponse.status = false; - httpResponse.message = this.stringService.httpExpectedValueFail; + httpResponse.message = "Expected value did not match"; return httpResponse; } } @@ -336,7 +330,7 @@ class NetworkService implements INetworkService { const isJson = contentType?.includes("application/json"); if (!isJson) { httpResponse.status = false; - httpResponse.message = this.stringService.httpNotJson; + httpResponse.message = "Response is not JSON"; return httpResponse; } try { @@ -353,7 +347,7 @@ class NetworkService implements INetworkService { } else { httpResponse.status = false; httpResponse.code = 500; - httpResponse.message = this.stringService.httpJsonPathFail; + httpResponse.message = "Expected value did not match"; httpResponse.extracted = extracted; return httpResponse; } @@ -365,14 +359,14 @@ class NetworkService implements INetworkService { } else { httpResponse.status = false; httpResponse.code = 500; - httpResponse.message = this.stringService.httpJsonPathFail; + httpResponse.message = "Expected value did not match"; httpResponse.extracted = extracted; return httpResponse; } } } catch { httpResponse.status = false; - httpResponse.message = this.stringService.httpJsonPathError; + httpResponse.message = "Error evaluating JSON path"; return httpResponse; } } @@ -486,7 +480,7 @@ class NetworkService implements INetworkService { dockerResponse.code = 404; dockerResponse.status = false; - dockerResponse.message = this.stringService.dockerNotFound; + dockerResponse.message = "Docker container not found"; return dockerResponse; } @@ -568,7 +562,7 @@ class NetworkService implements INetworkService { overrides: { code: 200, status: (response as { success?: boolean })?.success ?? false, - message: this.stringService.portSuccess, + message: "Port check successful", responseTime, }, }); @@ -576,7 +570,7 @@ class NetworkService implements INetworkService { if (error) { portResponse.code = this.NETWORK_ERROR; portResponse.status = false; - portResponse.message = this.stringService.portFail; + portResponse.message = "Port check failed"; return portResponse; } diff --git a/server/src/service/system/serviceRegistry.js b/server/src/service/system/serviceRegistry.js deleted file mode 100755 index 4e3e28464..000000000 --- a/server/src/service/system/serviceRegistry.js +++ /dev/null @@ -1,63 +0,0 @@ -const SERVICE_NAME = "ServiceRegistry"; - -class ServiceRegistry { - static SERVICE_NAME = SERVICE_NAME; - - constructor({ logger }) { - this.services = {}; - this.logger = logger; - } - - get serviceName() { - return ServiceRegistry.SERVICE_NAME; - } - - // Instance methods - register(name, service) { - this.logger.info({ - message: `Registering service ${name}`, - service: SERVICE_NAME, - method: "register", - }); - this.services[name] = service; - } - - get(name) { - if (!this.services[name]) { - this.logger.error({ - message: `Service ${name} is not registered`, - service: SERVICE_NAME, - method: "get", - }); - throw new Error(`Service ${name} is not registered`); - } - return this.services[name]; - } - - listServices() { - return Object.keys(this.services); - } - - static get(name) { - if (!ServiceRegistry.instance) { - throw new Error("ServiceRegistry not initialized"); - } - return ServiceRegistry.instance.get(name); - } - - static register(name, service) { - if (!ServiceRegistry.instance) { - throw new Error("ServiceRegistry not initialized"); - } - return ServiceRegistry.instance.register(name, service); - } - - static listServices() { - if (!ServiceRegistry.instance) { - throw new Error("ServiceRegistry not initialized"); - } - return ServiceRegistry.instance.listServices(); - } -} - -export default ServiceRegistry; diff --git a/server/src/service/system/stringService.js b/server/src/service/system/stringService.js deleted file mode 100755 index 231550be7..000000000 --- a/server/src/service/system/stringService.js +++ /dev/null @@ -1,452 +0,0 @@ -const SERVICE_NAME = "StringService"; - -class StringService { - static SERVICE_NAME = SERVICE_NAME; - - constructor(translationService) { - if (StringService.instance) { - return StringService.instance; - } - - this.translationService = translationService; - this._language = "en"; // default language - StringService.instance = this; - } - - get serviceName() { - return StringService.SERVICE_NAME; - } - - setLanguage(language) { - this._language = language; - } - - get language() { - return this._language; - } - - // Auth Messages - get dontHaveAccount() { - return this.translationService.getTranslation("dontHaveAccount"); - } - - get email() { - return this.translationService.getTranslation("email"); - } - - get forgotPassword() { - return this.translationService.getTranslation("forgotPassword"); - } - - get password() { - return this.translationService.getTranslation("password"); - } - - get signUp() { - return this.translationService.getTranslation("signUp"); - } - - get submit() { - return this.translationService.getTranslation("submit"); - } - - get title() { - return this.translationService.getTranslation("title"); - } - - get continue() { - return this.translationService.getTranslation("continue"); - } - - get enterEmail() { - return this.translationService.getTranslation("enterEmail"); - } - - get authLoginTitle() { - return this.translationService.getTranslation("authLoginTitle"); - } - - get authLoginEnterPassword() { - return this.translationService.getTranslation("authLoginEnterPassword"); - } - - get commonPassword() { - return this.translationService.getTranslation("commonPassword"); - } - - get commonBack() { - return this.translationService.getTranslation("commonBack"); - } - - get authForgotPasswordTitle() { - return this.translationService.getTranslation("authForgotPasswordTitle"); - } - - get authForgotPasswordResetPassword() { - return this.translationService.getTranslation("authForgotPasswordResetPassword"); - } - - get createPassword() { - return this.translationService.getTranslation("createPassword"); - } - - get createAPassword() { - return this.translationService.getTranslation("createAPassword"); - } - - get authRegisterAlreadyHaveAccount() { - return this.translationService.getTranslation("authRegisterAlreadyHaveAccount"); - } - - get commonAppName() { - return this.translationService.getTranslation("commonAppName"); - } - - get authLoginEnterEmail() { - return this.translationService.getTranslation("authLoginEnterEmail"); - } - - get authRegisterTitle() { - return this.translationService.getTranslation("authRegisterTitle"); - } - - get monitorGetAll() { - return this.translationService.getTranslation("monitorGetAll"); - } - - get monitorGetById() { - return this.translationService.getTranslation("monitorGetById"); - } - - get monitorGetByIdSuccess() { - return this.translationService.getTranslation("monitorGetByIdSuccess"); - } - - get monitorCreate() { - return this.translationService.getTranslation("monitorCreate"); - } - - get bulkMonitorsCreate() { - return this.translationService.getTranslation("bulkMonitorsCreate"); - } - - get monitorEdit() { - return this.translationService.getTranslation("monitorEdit"); - } - - get monitorDelete() { - return this.translationService.getTranslation("monitorDelete"); - } - - get monitorPause() { - return this.translationService.getTranslation("monitorPause"); - } - - get monitorResume() { - return this.translationService.getTranslation("monitorResume"); - } - - get monitorDemoAdded() { - return this.translationService.getTranslation("monitorDemoAdded"); - } - - get monitorStatsById() { - return this.translationService.getTranslation("monitorStatsById"); - } - - get monitorCertificate() { - return this.translationService.getTranslation("monitorCertificate"); - } - - // Maintenance Window Messages - get maintenanceWindowCreate() { - return this.translationService.getTranslation("maintenanceWindowCreate"); - } - - get maintenanceWindowGetById() { - return this.translationService.getTranslation("maintenanceWindowGetById"); - } - - get maintenanceWindowGetByTeam() { - return this.translationService.getTranslation("maintenanceWindowGetByTeam"); - } - - get maintenanceWindowDelete() { - return this.translationService.getTranslation("maintenanceWindowDelete"); - } - - get maintenanceWindowEdit() { - return this.translationService.getTranslation("maintenanceWindowEdit"); - } - - // Webhook Messages - get webhookUnsupportedPlatform() { - return this.translationService.getTranslation("webhookUnsupportedPlatform"); - } - - get webhookSendError() { - return this.translationService.getTranslation("webhookSendError"); - } - - get webhookSendSuccess() { - return this.translationService.getTranslation("webhookSendSuccess"); - } - - get telegramRequiresBotTokenAndChatId() { - return this.translationService.getTranslation("telegramRequiresBotTokenAndChatId"); - } - - get webhookUrlRequired() { - return this.translationService.getTranslation("webhookUrlRequired"); - } - - get platformRequired() { - return this.translationService.getTranslation("platformRequired"); - } - - get testNotificationFailed() { - return this.translationService.getTranslation("testNotificationFailed"); - } - - get monitorUpAlert() { - return this.translationService.getTranslation("monitorUpAlert"); - } - - get monitorDownAlert() { - return this.translationService.getTranslation("monitorDownAlert"); - } - - get discordNotification() { - return this.translationService.getTranslation("discordNotification"); - } - - getWebhookUnsupportedPlatform(platform) { - return this.translationService.getTranslation("webhookUnsupportedPlatform").replace("{platform}", platform); - } - - getWebhookSendError(platform) { - return this.translationService.getTranslation("webhookSendError").replace("{platform}", platform); - } - - getMonitorStatus(name, status, url) { - const translationKey = status === true ? "monitorStatusUp" : "monitorStatusDown"; - return this.translationService.getTranslation(translationKey).replace("{name}", name).replace("{url}", url); - } - - // Error Messages - get unknownError() { - return this.translationService.getTranslation("unknownError"); - } - - get friendlyError() { - return this.translationService.getTranslation("friendlyError"); - } - - get authIncorrectPassword() { - return this.translationService.getTranslation("authIncorrectPassword"); - } - - get unauthorized() { - return this.translationService.getTranslation("unauthorized"); - } - - get authAdminExists() { - return this.translationService.getTranslation("authAdminExists"); - } - - get authInviteNotFound() { - return this.translationService.getTranslation("authInviteNotFound"); - } - - get unknownService() { - return this.translationService.getTranslation("unknownService"); - } - - get noAuthToken() { - return this.translationService.getTranslation("noAuthToken"); - } - - get invalidAuthToken() { - return this.translationService.getTranslation("invalidAuthToken"); - } - - get expiredAuthToken() { - return this.translationService.getTranslation("expiredAuthToken"); - } - - // Queue Messages - get queueGetMetrics() { - return this.translationService.getTranslation("queueGetMetrics"); - } - - get queueGetJobs() { - return this.translationService.getTranslation("queueGetJobs"); - } - - get queueAddJob() { - return this.translationService.getTranslation("queueAddJob"); - } - - get queueObliterate() { - return this.translationService.getTranslation("queueObliterate"); - } - - // Job Queue Messages - get jobQueueDeleteJobSuccess() { - return this.translationService.getTranslation("jobQueueDeleteJobSuccess"); - } - - get jobQueuePauseJob() { - return this.translationService.getTranslation("jobQueuePauseJob"); - } - - get jobQueueResumeJob() { - return this.translationService.getTranslation("jobQueueResumeJob"); - } - - // Status Page Messages - get statusPageByUrl() { - return this.translationService.getTranslation("statusPageByUrl"); - } - - get statusPageCreate() { - return this.translationService.getTranslation("statusPageCreate"); - } - - get statusPageDelete() { - return this.translationService.getTranslation("statusPageDelete"); - } - - get statusPageUpdate() { - return this.translationService.getTranslation("statusPageUpdate"); - } - - get statusPageNotFound() { - return this.translationService.getTranslation("statusPageNotFound"); - } - - get statusPageByTeamId() { - return this.translationService.getTranslation("statusPageByTeamId"); - } - - get statusPageUrlNotUnique() { - return this.translationService.getTranslation("statusPageUrlNotUnique"); - } - - // Docker Messages - get dockerFail() { - return this.translationService.getTranslation("dockerFail"); - } - - get dockerNotFound() { - return this.translationService.getTranslation("dockerNotFound"); - } - - get dockerSuccess() { - return this.translationService.getTranslation("dockerSuccess"); - } - - // Port Messages - get portFail() { - return this.translationService.getTranslation("portFail"); - } - - get portSuccess() { - return this.translationService.getTranslation("portSuccess"); - } - - // Alert Messages - get alertCreate() { - return this.translationService.getTranslation("alertCreate"); - } - - get alertGetByUser() { - return this.translationService.getTranslation("alertGetByUser"); - } - - get alertGetByMonitor() { - return this.translationService.getTranslation("alertGetByMonitor"); - } - - get alertGetById() { - return this.translationService.getTranslation("alertGetById"); - } - - get alertEdit() { - return this.translationService.getTranslation("alertEdit"); - } - - get alertDelete() { - return this.translationService.getTranslation("alertDelete"); - } - - getDeletedCount(count) { - return this.translationService.getTranslation("deletedCount").replace("{count}", count); - } - - get pingSuccess() { - return this.translationService.getTranslation("pingSuccess"); - } - - get getAppSettings() { - return this.translationService.getTranslation("getAppSettings"); - } - - get httpNetworkError() { - return this.translationService.getTranslation("httpNetworkError"); - } - - get httpNotJson() { - return this.translationService.getTranslation("httpNotJson"); - } - - get httpJsonPathError() { - return this.translationService.getTranslation("httpJsonPathError"); - } - - get httpEmptyResult() { - return this.translationService.getTranslation("httpEmptyResult"); - } - - get httpMatchSuccess() { - return this.translationService.getTranslation("httpMatchSuccess"); - } - - get httpMatchFail() { - return this.translationService.getTranslation("httpMatchFail"); - } - - get updateAppSettings() { - return this.translationService.getTranslation("updateAppSettings"); - } - - get insufficientPermissions() { - return this.translationService.getTranslation("insufficientPermissions"); - } - - getDbFindMonitorById(monitorId) { - return this.translationService.getTranslation("dbFindMonitorById").replace("${monitorId}", monitorId); - } - - get dbUserExists() { - return this.translationService.getTranslation("dbUserExists"); - } - - get testEmailSubject() { - return this.translationService.getTranslation("testEmailSubject"); - } - - get verifyOwnerNotFound() { - return this.translationService.getTranslation("verifyOwnerNotFound"); - } - - get verifyOwnerUnauthorized() { - return this.translationService.getTranslation("verifyOwnerUnauthorized"); - } - - get dbUserNotFound() { - return this.translationService.getTranslation("dbUserNotFound"); - } -} - -export default StringService; diff --git a/server/src/utils/cookieHelpers.js b/server/src/utils/cookieHelpers.js deleted file mode 100644 index 7005b66ca..000000000 --- a/server/src/utils/cookieHelpers.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Get standardized cookie options for authentication tokens - * @param {Object} options - Additional cookie options - * @returns {Object} Cookie options object - */ -export const getAuthCookieOptions = (options = {}) => { - return { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 2 * 60 * 60 * 1000, // 2 hours (matches JWT TTL) - ...options, - }; -}; - -/** - * Clear cookie options for authentication tokens - * @returns {Object} Cookie clear options object - */ -export const getClearAuthCookieOptions = () => { - return { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - }; -}; diff --git a/server/src/utils/logger.js b/server/src/utils/logger.ts similarity index 50% rename from server/src/utils/logger.js rename to server/src/utils/logger.ts index 0917862fa..ac1cbbf7c 100755 --- a/server/src/utils/logger.js +++ b/server/src/utils/logger.ts @@ -1,46 +1,76 @@ -import { createLogger, format, transports } from "winston"; +import { createLogger, format, transports, Logger as WinstonLogger } from "winston"; +import type { Logform } from "winston"; import dotenv from "dotenv"; dotenv.config(); const SERVICE_NAME = "Logger"; +interface LogConfig { + message: string; + service?: string; + method?: string; + details?: Record; + stack?: string; +} + +interface LogEntry extends LogConfig { + level: string; + timestamp: string; +} + +interface EnvSettings { + logLevel?: string; +} + class Logger { static SERVICE_NAME = SERVICE_NAME; - constructor({ envSettings }) { + + private logger: WinstonLogger; + private envSettings: EnvSettings; + private logCache: LogEntry[]; + private maxCacheSize: number; + + constructor({ envSettings }: { envSettings: EnvSettings }) { this.envSettings = envSettings; this.logCache = []; this.maxCacheSize = 1000; - const consoleFormat = format.printf(({ level, message, service, method, details, timestamp, stack }) => { - if (message instanceof Object) { - message = JSON.stringify(message, null, 2); + const consoleFormat = format.printf((info: Logform.TransformableInfo) => { + const { level, service, method, details, timestamp, stack } = info; + let message = info.message as string; + let formattedMessage: string = message; + let formattedDetails: string | undefined; + + if (typeof message === "object" && message !== null) { + formattedMessage = JSON.stringify(message, null, 2); } - if (details instanceof Object) { - details = JSON.stringify(details, null, 2); + if (typeof details === "object" && details !== null) { + formattedDetails = JSON.stringify(details, null, 2); } + let msg = `${timestamp} ${level}:`; service && (msg += ` [${service}]`); method && (msg += `(${method})`); - message && (msg += ` ${message}`); - details && (msg += ` (details: ${details})`); + formattedMessage && (msg += ` ${formattedMessage}`); + formattedDetails && (msg += ` (details: ${formattedDetails})`); - if (typeof stack !== "undefined") { + if (typeof stack === "string") { const stackTrace = stack - ?.split("\n") + .split("\n") .slice(1) // Remove first line (error message) - .map((line) => { + .map((line: string) => { const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/); if (match) { return { function: match[1], file: match[2], - line: parseInt(match[3]), - column: parseInt(match[4]), + line: parseInt(match[3] ?? "0", 10), + column: parseInt(match[4] ?? "0", 10), }; } return line.trim(); }); - stack && (msg += ` (stack: ${JSON.stringify(stackTrace, null, 2)})`); + msg += ` (stack: ${JSON.stringify(stackTrace, null, 2)})`; } return msg; @@ -67,62 +97,31 @@ class Logger { return Logger.SERVICE_NAME; } - /** - * Logs an informational message. - * @param {Object} config - The configuration object. - * @param {string} config.message - The message to log. - * @param {string} config.service - The service name. - * @param {string} config.method - The method name. - * @param {Object} config.details - Additional details. - */ - info(config) { + info(config: LogConfig) { const logEntry = this.buildLogEntry("info", config); this.cacheLog(logEntry); this.logger.info(logEntry); } - /** - * Logs a warning message. - * @param {Object} config - The configuration object. - * @param {string} config.message - The message to log. - * @param {string} config.service - The service name. - * @param {string} config.method - The method name. - * @param {Object} config.details - Additional details. - */ - warn(config) { + warn(config: LogConfig) { const logEntry = this.buildLogEntry("warn", config); this.cacheLog(logEntry); this.logger.warn(logEntry); } - /** - * Logs an error message. - * @param {Object} config - The configuration object. - * @param {string} config.message - The message to log. - * @param {string} config.service - The service name. - * @param {string} config.method - The method name. - * @param {Object} config.details - Additional details. - */ - error(config) { + error(config: LogConfig) { const logEntry = this.buildLogEntry("error", config); this.cacheLog(logEntry); this.logger.error(logEntry); } - /** - * Logs a debug message. - * @param {Object} config - The configuration object. - * @param {string} config.message - The message to log. - * @param {string} config.service - The service name. - * @param {string} config.method - The method name. - * @param {Object} config.details - Additional details. - */ - debug(config) { + + debug(config: LogConfig) { const logEntry = this.buildLogEntry("debug", config); this.cacheLog(logEntry); this.logger.debug(logEntry); } - cacheLog(entry) { + cacheLog(entry: LogEntry) { this.logCache.push(entry); if (this.logCache.length > this.maxCacheSize) { this.logCache.shift(); @@ -133,7 +132,7 @@ class Logger { return this.logCache; } - buildLogEntry(level, config) { + buildLogEntry(level: string, config: LogConfig): LogEntry { return { level, message: config.message, @@ -151,3 +150,4 @@ export default Logger; // Legacy logger const logger = new Logger({ envSettings: { logLevel: "debug" } }); export { logger }; +export type { LogConfig, LogEntry };