diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 5f3d3e4bd..cd9284479 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -25,7 +25,7 @@ const { getTokenFromHeaders } = require("../utils/utils"); * @param {Object} payload * @returns {String} */ -const issueToken = async (payload, appSettings) => { +const issueToken = (payload, appSettings) => { try { const tokenTTL = appSettings.jwtTTL ? appSettings.jwtTTL : "2h"; return jwt.sign(payload, appSettings.jwtSecret, { expiresIn: tokenTTL }); @@ -74,7 +74,6 @@ const registerController = async (req, res, next) => { const appSettings = await req.settingsService.getSettings(); const token = issueToken(userForToken, appSettings); - req.emailService .buildAndSendEmail( "welcomeEmailTemplate", @@ -137,7 +136,7 @@ const loginController = async (req, res, next) => { // Happy path, return token const appSettings = req.settingsService.getSettings(); - const token = await issueToken(userWithoutPassword, appSettings); + const token = issueToken(userWithoutPassword, appSettings); // reset avatar image userWithoutPassword.avatarImage = user.avatarImage; @@ -186,7 +185,8 @@ const userEditController = async (req, res, next) => { // Get token from headers const token = getTokenFromHeaders(req.headers); // Get email from token - const { email } = jwt.verify(token, process.env.JWT_SECRET); + const { jwtSecret } = req.settingsService.getSettings(); + const { email } = jwt.verify(token, jwtSecret); // Add user email to body for DB operation req.body.email = email; // Get user @@ -236,12 +236,13 @@ const inviteController = async (req, res, next) => { } const inviteToken = await req.db.requestInviteToken(req, res); + const { clientHost } = req.settingsService.getSettings(); req.emailService .buildAndSendEmail( "employeeActivationTemplate", { name: firstname, - link: `${process.env.CLIENT_HOST}/register/${inviteToken.token}`, + link: `${clientHost}/register/${inviteToken.token}`, }, req.body.email, "Welcome to Uptime Monitor" @@ -345,7 +346,8 @@ const recoveryRequestController = async (req, res, next) => { const recoveryToken = await req.db.requestRecoveryToken(req, res); const name = user.firstName; const email = req.body.email; - const url = `${process.env.CLIENT_HOST}/set-new-password/${recoveryToken.token}`; + const { clientHost } = req.settingsService.getSettings(); + const url = `${clientHost}/set-new-password/${recoveryToken.token}`; const msgId = await req.emailService.buildAndSendEmail( "passwordResetTemplate", diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js index bf30d9017..17507e0b2 100644 --- a/Server/controllers/checkController.js +++ b/Server/controllers/checkController.js @@ -163,7 +163,8 @@ const updateChecksTTL = async (req, res, next) => { try { // Get user's teamId const token = getTokenFromHeaders(req.headers); - const { teamId } = jwt.verify(token, process.env.JWT_SECRET); + const { jwtSecret } = req.settingsService.getSettings(); + const { teamId } = jwt.verify(token, jwtSecret); const ttl = parseInt(req.body.ttl, 10) * SECONDS_PER_DAY; await req.db.updateChecksTTL(teamId, ttl); return res.status(200).json({ diff --git a/Server/controllers/inviteController.js b/Server/controllers/inviteController.js index 1b8ab01f1..da9b2496a 100644 --- a/Server/controllers/inviteController.js +++ b/Server/controllers/inviteController.js @@ -39,12 +39,13 @@ const inviteController = async (req, res, next) => { } const inviteToken = await req.db.requestInviteToken({ ...req.body }); + const { clientHost } = req.settingsService.getSettings(); req.emailService .buildAndSendEmail( "employeeActivationTemplate", { name: firstname, - link: `${process.env.CLIENT_HOST}/register/${inviteToken.token}`, + link: `${clientHost}/register/${inviteToken.token}`, }, req.body.email, "Welcome to Uptime Monitor" diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 2dc577f19..5cda56634 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -345,7 +345,8 @@ const deleteMonitor = async (req, res, next) => { const deleteAllMonitors = async (req, res) => { try { const token = getTokenFromHeaders(req.headers); - const { teamId } = jwt.verify(token, process.env.JWT_SECRET); + const { jwtSecret } = req.settingsService.getSettings(); + const { teamId } = jwt.verify(token, jwtSecret); const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId); await monitors.forEach(async (monitor) => { await req.jobQueue.deleteJob(monitor); @@ -460,7 +461,9 @@ const pauseMonitor = async (req, res, next) => { const addDemoMonitors = async (req, res, next) => { try { const token = getTokenFromHeaders(req.headers); - const { _id, teamId } = jwt.verify(token, process.env.JWT_SECRET); + const { jwtSecret } = req.settingsService.getSettings(); + + const { _id, teamId } = jwt.verify(token, jwtSecret); const demoMonitors = await req.db.addDemoMonitors(_id, teamId); await demoMonitors.forEach(async (monitor) => { await req.jobQueue.addJob(monitor._id, monitor); diff --git a/Server/controllers/settingsController.js b/Server/controllers/settingsController.js index f4b5ae0f3..87e4edfc1 100644 --- a/Server/controllers/settingsController.js +++ b/Server/controllers/settingsController.js @@ -1,5 +1,6 @@ const { successMessages } = require("../utils/messages"); const SERVICE_NAME = "SettingsController"; +const { updateAppSettingsBodyValidation } = require("../validation/joi"); const getAppSettings = async (req, res, next) => { try { @@ -12,9 +13,37 @@ const getAppSettings = async (req, res, next) => { } catch (error) { error.service === undefined ? (error.service = SERVICE_NAME) : null; error.method === undefined ? (error.method = "getAppSettings") : null; + next(error); + } +}; + +const updateAppSettings = async (req, res, next) => { + try { + await updateAppSettingsBodyValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + + try { + const settings = await req.db.updateAppSettings(req.body); + return res.status(200).json({ + success: true, + msg: successMessages.UPDATE_APP_SETTINGS, + data: settings, + }); + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "updateAppSettings") : null; + next(error); } }; module.exports = { getAppSettings, + updateAppSettings, }; diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index a8f527391..268e62798 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -137,7 +137,10 @@ const { //**************************************** // AppSettings //**************************************** -const { getAppSettings } = require("./modules/settingsModule"); +const { + getAppSettings, + updateAppSettings, +} = require("./modules/settingsModule"); module.exports = { connect, @@ -187,4 +190,5 @@ module.exports = { getNotificationsByMonitorId, deleteNotificationsByMonitorId, getAppSettings, + updateAppSettings, }; diff --git a/Server/db/mongo/modules/settingsModule.js b/Server/db/mongo/modules/settingsModule.js index defc3e870..b7e6b765d 100644 --- a/Server/db/mongo/modules/settingsModule.js +++ b/Server/db/mongo/modules/settingsModule.js @@ -12,6 +12,22 @@ const getAppSettings = async () => { } }; +const updateAppSettings = async (newSettings) => { + try { + const settings = await AppSettings.findOneAndUpdate( + {}, + { $set: newSettings }, + { new: true } + ); + return settings; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateAppSettings"; + throw error; + } +}; + module.exports = { getAppSettings, + updateAppSettings, }; diff --git a/Server/index.js b/Server/index.js index c7974bb60..05eaeb617 100644 --- a/Server/index.js +++ b/Server/index.js @@ -126,12 +126,17 @@ const startApp = async () => { // Create services await connectDbAndRunServer(app, db); - const emailService = new EmailService(); - const networkService = new NetworkService(db, emailService); - const jobQueue = await JobQueue.createJobQueue(db, networkService); - const pageSpeedService = new PageSpeedService(); const settingsService = new SettingsService(); - settingsService.loadSettings(); + + await settingsService.loadSettings(); + const emailService = new EmailService(settingsService); + const networkService = new NetworkService(db, emailService); + const jobQueue = await JobQueue.createJobQueue( + db, + networkService, + settingsService + ); + const pageSpeedService = new PageSpeedService(); const cleanup = async () => { if (cleaningUp) { diff --git a/Server/middleware/isAllowed.js b/Server/middleware/isAllowed.js index c7d78785a..e141ad165 100644 --- a/Server/middleware/isAllowed.js +++ b/Server/middleware/isAllowed.js @@ -27,7 +27,8 @@ const isAllowed = (allowedRoles) => { // Parse the token try { const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - var decoded = jwt.verify(parsedToken, process.env.JWT_SECRET); + const { jwtSecret } = req.settingsService.getSettings(); + var decoded = jwt.verify(parsedToken, jwtSecret); const userRoles = decoded.role; // Check if the user has the required role diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index 9d50fe7ea..ffbcd55a5 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -35,7 +35,9 @@ const verifyJWT = (req, res, next) => { const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); // Verify the token's authenticity - jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => { + const { jwtSecret } = req.settingsService.getSettings(); + + jwt.verify(parsedToken, jwtSecret, (err, decoded) => { if (err) { return res .status(401) diff --git a/Server/middleware/verifySuperAdmin.js b/Server/middleware/verifySuperAdmin.js index e977a1591..cf8774c31 100644 --- a/Server/middleware/verifySuperAdmin.js +++ b/Server/middleware/verifySuperAdmin.js @@ -33,7 +33,9 @@ const verifySuperAdmin = (req, res, next) => { const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); // verify admin role is present - jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => { + const { jwtSecret } = req.settingsService.getSettings(); + + jwt.verify(parsedToken, jwtSecret, (err, decoded) => { if (err) { logger.error(errorMessages.INVALID_AUTH_TOKEN, { service: SERVICE_NAME, diff --git a/Server/models/AppSettings.js b/Server/models/AppSettings.js index 3a146e46f..79ede650a 100644 --- a/Server/models/AppSettings.js +++ b/Server/models/AppSettings.js @@ -51,15 +51,19 @@ const AppSettingsSchema = mongoose.Schema( }, systemEmailHost: { type: String, + default: "smtp.gmail.com", }, systemEmailPort: { type: Number, + default: 465, }, systemEmailAddress: { type: String, + default: "", }, - systemEmailAddress: { + systemEmailPassword: { type: String, + default: "", }, singleton: { type: Boolean, diff --git a/Server/routes/settingsRoute.js b/Server/routes/settingsRoute.js index 4c0acf1b8..834f68f26 100644 --- a/Server/routes/settingsRoute.js +++ b/Server/routes/settingsRoute.js @@ -4,5 +4,10 @@ const { isAllowed } = require("../middleware/isAllowed"); const Monitor = require("../models/Monitor"); router.get("/", isAllowed(["superadmin"]), settingsController.getAppSettings); +router.put( + "/", + isAllowed(["superadmin"]), + settingsController.updateAppSettings +); module.exports = router; diff --git a/Server/service/emailService.js b/Server/service/emailService.js index 27518484c..4295f33ab 100644 --- a/Server/service/emailService.js +++ b/Server/service/emailService.js @@ -13,7 +13,8 @@ class EmailService { /** * Constructs an instance of the EmailService, initializing template loaders and the email transporter. */ - constructor() { + constructor(settingsService) { + this.settingsService = settingsService; /** * Loads an email template from the filesystem. * @@ -54,15 +55,26 @@ class EmailService { * The email transporter used to send emails. * @type {Object} */ - this.transporter = nodemailer.createTransport({ - host: process.env.SYSTEM_EMAIL_HOST, - port: process.env.SYSTEM_EMAIL_PORT, - secure: true, // Use `true` for port 465, `false` for all other ports + + const { + systemEmailHost, + systemEmailPort, + systemEmailAddress, + systemEmailPassword, + } = this.settingsService.getSettings(); + + const emailConfig = { + host: systemEmailHost, + port: systemEmailPort, + secure: true, auth: { - user: process.env.SYSTEM_EMAIL_ADDRESS, - pass: process.env.SYSTEM_EMAIL_PASSWORD, + user: systemEmailAddress, + pass: systemEmailPassword, }, - }); + }; + console.log(emailConfig); + + this.transporter = nodemailer.createTransport(emailConfig); } /** diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 5a6d3e8e0..7566debd8 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -1,9 +1,6 @@ const { Queue, Worker, Job } = require("bullmq"); const QUEUE_NAME = "monitors"; -const connection = { - host: process.env.REDIS_HOST || "127.0.0.1", - port: process.env.REDIS_PORT || 6379, -}; + const JOBS_PER_WORKER = 5; const logger = require("../utils/logger"); const { errorMessages, successMessages } = require("../utils/messages"); @@ -15,13 +12,20 @@ class JobQueue { * @constructor * @throws {Error} */ - constructor(networkService) { + constructor(settingsService) { + const { redisHost, redisPort } = settingsService.getSettings(); + const connection = { + host: redisHost || "127.0.0.1", + port: redisPort || 6379, + }; + this.connection = connection; this.queue = new Queue(QUEUE_NAME, { connection, }); this.workers = []; this.db = null; this.networkService = null; + this.settingsService = settingsService; } /** @@ -31,8 +35,8 @@ class JobQueue { * @returns {Promise} - Returns a new JobQueue * */ - static async createJobQueue(db, networkService) { - const queue = new JobQueue(); + static async createJobQueue(db, networkService, settingsService) { + const queue = new JobQueue(settingsService); try { queue.db = db; queue.networkService = networkService; @@ -99,7 +103,7 @@ class JobQueue { } }, { - connection, + connection: this.connection, } ); return worker; diff --git a/Server/utils/messages.js b/Server/utils/messages.js index 14e1af6db..59fa53f49 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -101,6 +101,7 @@ const successMessages = { // App Settings GET_APP_SETTINGS: "Got app settings successfully", + UPDATE_APP_SETTINGS: "Updated app settings successfully", }; module.exports = { diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 0a83b6e74..7b26ea8e1 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -382,6 +382,26 @@ const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({ monitorId: joi.string().required(), }); +//**************************************** +// SettingsValidation +//**************************************** +const updateAppSettingsBodyValidation = joi.object({ + apiBaseUrl: joi.string().allow(""), + logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""), + clientHost: joi.string().allow(""), + jwtSecret: joi.string().allow(""), + dbType: joi.string().allow(""), + dbConnectionString: joi.string().allow(""), + redisHost: joi.string().allow(""), + redisPort: joi.number().allow(null, ""), + jwtTTL: joi.string().allow(""), + pagespeedApiKey: joi.string().allow(""), + systemEmailHost: joi.string().allow(""), + systemEmailPort: joi.number().allow(""), + systemEmailAddress: joi.string().allow(""), + systemEmailPassword: joi.string().allow(""), +}); + module.exports = { roleValidatior, loginValidation, @@ -432,4 +452,5 @@ module.exports = { createMaintenanceWindowBodyValidation, getMaintenanceWindowsByUserIdParamValidation, getMaintenanceWindowsByMonitorIdParamValidation, + updateAppSettingsBodyValidation, };