Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Popup Validations #548

Closed
Closed
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Guidefox helps app owners build knowledge and user-experience oriented apps. It
- Helper links
- Hints

To see a demo, [click here](https://guidefox-demo.bluewavelabs.ca/). If you have any questions, you can use the [Guidefox forum](https://github.com/bluewave-labs/guidefox/discussions).

The source code is available under GNU AGPLv3. If you would like to support us, please consider giving it a ⭐ and click on "watch" so you can latest news from us.

![guidefox](https://github.com/user-attachments/assets/46d912c9-339a-4044-979b-338557f28949)
Expand Down Expand Up @@ -48,7 +50,7 @@ Make sure docker and git is installed
## Server Installation

1. Make sure Docker is installed to your machine where the server will run.
2. Make sure git is installed to your machine Git.
2. Make sure git is installed to your machine.
3. Make sure nginx is installed.

4. Clone GitHub Repository
Expand Down Expand Up @@ -99,11 +101,11 @@ server {
}
```

6. Create a symbolic link to enable the configuration:
6. [Optional] Create a symbolic link to enable the configuration:

`sudo ln -s /etc/nginx/sites-available/guidefox /etc/nginx/sites-enabled/`

7. Install Certbot and its Nginx plugin:
7. [Optional] Install Certbot and its Nginx plugin:

`sudo apt install certbot python3-certbot-nginx`

Expand Down Expand Up @@ -237,17 +239,17 @@ For running tests in windows installing `win-node-env` module is recommended

After setting up the project, copy and paste the script that can be found in the Code tab of the Settings. Modify Api Base URL to point out to the url of tour backend server. The code snippet can also be found here:

`
```
window.bwApiBaseUrl = 'https://guidefox-demo.bluewavelabs.ca/api/';
window.bwAgentBaseUrl = 'https://cdn.jsdelivr.net/gh/bluewave-labs/[email protected].0/jsAgent/';
window.bwAgentBaseUrl = 'https://cdn.jsdelivr.net/gh/bluewave-labs/[email protected].2/jsAgent/';

var s=document.createElement("script");
s.type="text/javascript";
s.async=false;
s.onerror=()=>{console.log("onboard not loaded");};
s.src = window.bwAgentBaseUrl + '/main.js';
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(s);
`
```

We are working on a browser extention to move this code there to improve the user experience.

Expand All @@ -270,7 +272,7 @@ We pride ourselves on building strong connections with contributors at every lev
Also check other developer and contributor-friendly projects of BlueWave:

- [Checkmate](https://github.com/bluewave-labs/checkmate), a server and infrastructure monitoring tool
- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom), an secure file sharing application, aka dataroom.
- [BlueWave HRM](https://github.com/bluewave-labs/bluewave-hrm), a complete Human Resource Management platform.
- [DataHall](https://github.com/bluewave-labs/datahall), an secure file sharing application, aka dataroom.
- [Headcount](https://github.com/bluewave-labs/headcount), a complete Human Resource Management platform.
- [VerifyWise](https://github.com/bluewave-labs/verifywise), the first open source AI governance platform.

147 changes: 0 additions & 147 deletions backend/src/controllers/popup.controller.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,9 @@
const popupService = require("../service/popup.service");
const { internalServerError } = require("../utils/errors.helper");
const { validateCloseButtonAction, validateRepetitionOption } = require("../utils/guide.helper");
const {
validatePopupSize,
validateUrl,
validateRelativeUrl,
} = require("../utils/popup.helper");
const { checkColorFieldsFail } = require("../utils/guide.helper");

class PopupController {
async addPopup(req, res) {
const userId = req.user.id;
const {
popupSize,
closeButtonAction,
repetitionType,
headerBackgroundColor,
headerColor,
textColor,
buttonBackgroundColor,
buttonTextColor,
actionUrl,
url,
} = req.body;

if (!popupSize || !closeButtonAction) {
return res.status(400).json({
errors: [{ msg: "popupSize and closeButtonAction are required" }],
});
}

if (
!validatePopupSize(popupSize) ||
!validateCloseButtonAction(closeButtonAction) ||
!validateRepetitionOption(repetitionType)
) {
return res.status(400).json({
errors: [{ msg: "Invalid value for popupSize or closeButtonAction or repetitionType" }],
});
}

if (actionUrl) {
try {
validateUrl(actionUrl, "actionUrl");
} catch (err) {
return res.status(400).json({ errors: [{ msg: err.message }] });
}
}

if (url) {
try {
validateRelativeUrl(url, "url");
} catch (err) {
return res.status(400).json({ errors: [{ msg: err.message }] });
}
}


const colorFields = {
headerBackgroundColor,
headerColor,
textColor,
buttonBackgroundColor,
buttonTextColor,
};
const colorCheck = checkColorFieldsFail(colorFields, res);
if (colorCheck) {
return colorCheck;
}

try {
const newPopupData = { ...req.body, createdBy: userId };
Expand All @@ -86,19 +22,13 @@ class PopupController {
async deletePopup(req, res) {
try {
const { id } = req.params;

if (Number.isNaN(Number(id)) || id.trim() === "") {
return res.status(400).json({ errors: [{ msg: "Invalid id" }] });
}

const deletionResult = await popupService.deletePopup(id);

if (!deletionResult) {
return res.status(400).json({
errors: [{ msg: "Popup with the specified id does not exist" }],
});
}

res
.status(200)
.json({ message: `Popup with ID ${id} deleted successfully` });
Expand All @@ -114,71 +44,6 @@ class PopupController {
async editPopup(req, res) {
try {
const { id } = req.params;
const {
popupSize,
closeButtonAction,
repetitionType,
headerBackgroundColor,
headerColor,
textColor,
buttonBackgroundColor,
buttonTextColor,
actionUrl,
url,
} = req.body;

if (!popupSize || !closeButtonAction) {
return res.status(400).json({
errors: [{ msg: "popupSize and closeButtonAction are required" }],
});
}

if (!validatePopupSize(popupSize)) {
return res
.status(400)
.json({ errors: [{ msg: "Invalid value for popupSize" }] });
}

if (!validateCloseButtonAction(closeButtonAction)) {
return res
.status(400)
.json({ errors: [{ msg: "Invalid value for closeButtonAction" }] });
}

if (!validateRepetitionOption(repetitionType)) {
return res
.status(400)
.json({ errors: [{ msg: "Invalid value for repetition" }] });
}

if (actionUrl) {
try {
validateUrl(actionUrl, "actionUrl");
} catch (err) {
return res.status(400).json({ errors: [{ msg: err.message }] });
}
}

if (url) {
try {
validateRelativeUrl(url, "url");
} catch (err) {
return res.status(400).json({ errors: [{ msg: err.message }] });
}
}

const colorFields = {
headerBackgroundColor,
headerColor,
textColor,
buttonBackgroundColor,
buttonTextColor,
};
const colorCheck = checkColorFieldsFail(colorFields, res);
if (colorCheck) {
return colorCheck;
}

const updatedPopup = await popupService.updatePopup(id, req.body);
res.status(200).json(updatedPopup);
} catch (err) {
Expand Down Expand Up @@ -220,11 +85,6 @@ class PopupController {
async getPopupById(req, res) {
try {
const { id } = req.params;

if (Number.isNaN(Number(id)) || id.trim() === "") {
return res.status(400).json({ errors: [{ msg: "Invalid popup ID" }] });
}

const popup = await popupService.getPopupById(id);

if (!popup) {
Expand All @@ -243,13 +103,6 @@ class PopupController {
async getPopupByUrl(req, res) {
try {
const { url } = req.body;

if (!url || typeof url !== "string") {
return res
.status(400)
.json({ errors: [{ msg: "URL is missing or invalid" }] });
}

const popup = await popupService.getPopupByUrl(url);
res.status(200).json({ popup });
} catch (error) {
Expand Down
12 changes: 7 additions & 5 deletions backend/src/routes/popup.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ const popupController = require("../controllers/popup.controller");
const authenticateJWT = require("../middleware/auth.middleware");
const settings = require('../../config/settings');
const accessGuard = require('../middleware/accessGuard.middleware');
const { addOrUpdatePopupValidation, deleteOrGetPopupByIdValidation, getPopupByUrlValidation } = require("../utils/popup.helper");
const { handleValidationErrors } = require("../middleware/validation.middleware");

const router = express.Router();
const teamPermissions = settings.team.permissions;

router.post("/add_popup", authenticateJWT, accessGuard(teamPermissions.popups), popupController.addPopup);
router.delete("/delete_popup/:id", authenticateJWT, accessGuard(teamPermissions.popups), popupController.deletePopup);
router.put("/edit_popup/:id", authenticateJWT, accessGuard(teamPermissions.popups), popupController.editPopup);
router.post("/add_popup", authenticateJWT, accessGuard(teamPermissions.popups), addOrUpdatePopupValidation, handleValidationErrors,popupController.addPopup);
router.delete("/delete_popup/:id", authenticateJWT, accessGuard(teamPermissions.popups),deleteOrGetPopupByIdValidation,handleValidationErrors, popupController.deletePopup);
router.put("/edit_popup/:id", authenticateJWT, accessGuard(teamPermissions.popups), addOrUpdatePopupValidation, handleValidationErrors, popupController.editPopup);
router.get("/all_popups", authenticateJWT, popupController.getAllPopups);
router.get("/popups", authenticateJWT, popupController.getPopups);
router.get("/get_popup/:id", authenticateJWT, popupController.getPopupById);
router.get("/get_popup_by_url", popupController.getPopupByUrl);
router.get("/get_popup/:id", authenticateJWT, deleteOrGetPopupByIdValidation,handleValidationErrors, popupController.getPopupById);
router.get("/get_popup_by_url", getPopupByUrlValidation, handleValidationErrors ,popupController.getPopupByUrl);

module.exports = router;
73 changes: 73 additions & 0 deletions backend/src/utils/popup.helper.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,74 @@
const { body, param } = require('express-validator');
const settings = require('../../config/settings');
const hexColorPattern = /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/;

const addOrUpdatePopupValidation = [
body('popupSize')
.notEmpty()
.withMessage('Popup size is required')
.bail()
.isIn(settings.popup.size)
.withMessage('Invalid value for Popup size'),
body('closeButtonAction').notEmpty()
.withMessage('Close Button Action is required')
.bail()
.isIn(settings.popup.action)
.withMessage('Invalid close button action'),
body('repetitionType')
.notEmpty()
.withMessage('Repetition type is required')
.bail()
.isIn(settings.popup.repetition)
.withMessage('Invalid repetition type'),
body('url')
.custom((value, {req}) => {
const needsUrl = ['open url', 'open url in a new tab'].includes(req.body.closeButtonAction);
return !needsUrl || (needsUrl && value)
})
.withMessage('URL is required when close button action is set to open URL')
.bail()
.custom((value) => {
if (!value) return true;
const url = new URL(value)
return ['http:', 'https:'].includes(url.protocol) || value.startsWith('/');
})
.withMessage('Invalid URL. URL must start with / or use HTTP or HTTPS protocol'),
body('actionUrl')
.custom((value) => {
if(!value) return true;
try {
const url = new URL(value);
return ['http:', 'https:'].includes(url.protocol)
}catch {
return false
}
})
.withMessage('Invalid URL. Action URL must use HTTP or HTTPS protocol'),
body('headerBackgroundColor')
.optional()
.matches(hexColorPattern)
.withMessage('Header background color must be a valid hex color code'),
body('headerColor')
.optional()
.matches(hexColorPattern)
.withMessage('Header color must be a valid hex color code'),
body('textColor')
.optional()
.matches(hexColorPattern)
.withMessage('Text color must be a valid hex color code'),
body('buttonBackgroundColor')
.optional()
.matches(hexColorPattern)
.withMessage('Button background color must be a valid hex color code'),
body('buttonTextColor')
.optional()
.matches(hexColorPattern)
.withMessage('Button text color must be a valid hex color code')
]

const deleteOrGetPopupByIdValidation = [param('id').notEmpty().trim().isInt().withMessage("Invalid popup id")]

const getPopupByUrlValidation = [body('url').notEmpty().isString().withMessage('URL is missing or invalid')]

const validatePopupSize = (value) => {
const validSizes = settings.popup.size;
Expand Down Expand Up @@ -36,6 +106,9 @@ const validateUrl = (value, fieldName) => {
};

module.exports = {
addOrUpdatePopupValidation,
deleteOrGetPopupByIdValidation,
getPopupByUrlValidation,
validatePopupSize,
validatePopupSizeWrapper,
validateUrl,
Expand Down