From 74db2010d9fdb95c17f97769f3f51635b918a315 Mon Sep 17 00:00:00 2001 From: Max Bischof <106820326+bischofmax@users.noreply.github.com> Date: Mon, 17 Feb 2025 12:09:28 +0100 Subject: [PATCH] BC-8345 - Add validation if value contains < directly followed by a string (#3584) --- controllers/courses.js | 5 ++- locales/de.json | 1 + locales/en.json | 1 + locales/es.json | 1 + locales/uk.json | 1 + static/scripts/administration/school.js | 8 +++++ static/scripts/course.js | 7 +++++ static/scripts/courses.js | 4 --- .../scripts/helpers/openingTagValidation.js | 20 ++++++++++++ static/scripts/homework/edit.js | 8 +++++ static/scripts/settings.js | 7 +++++ static/scripts/topicEdit.js | 31 +++++++++++++++++++ views/courses/create-course.hbs | 1 + views/courses/edit-course.hbs | 1 + 14 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 static/scripts/course.js create mode 100644 static/scripts/helpers/openingTagValidation.js diff --git a/controllers/courses.js b/controllers/courses.js index b7dfeeb7ac..c9235d7876 100644 --- a/controllers/courses.js +++ b/controllers/courses.js @@ -603,7 +603,7 @@ router.post('/', (req, res, next) => { api(req) .post('/courses/', { - json: req.body, // TODO: sanitize + json: { ...req.body, name: req.body.name.trim() }, // TODO: sanitize }) .then((course) => { createEventsForCourse(req, res, course).then(() => { @@ -882,10 +882,9 @@ router.patch('/:courseId', async (req, res, next) => { // so temporarily add yourself to the list of teachers req.body.teacherIds.push(currentUserId); } - await deleteEventsForCourse(req, res, courseId); await api(req).patch(`/courses/${courseId}`, { - json: req.body, + json: { ...req.body, name: req.body.name.trim() }, }); // due to eventual consistency we need to get the course again from server // instead of using the response from patch diff --git a/locales/de.json b/locales/de.json index 4bd317af23..70a5372c5f 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1747,6 +1747,7 @@ "noTopicFoundWithCode": "Es wurde kein Thema für diesen Code gefunden.", "pageMoved": "Diese Seite wurde verschoben.", "passwordsAreDifferent": "Passwörter stimmen nicht überein.", + "containsOpeningTag": "Bitte Leerzeichen nach Kleiner-als-Zeichen einfügen.", "hasLowerCase": "Passwort muss Kleinbuchstaben enthalten.", "hasUpperCase": "Passwort muss Großbuchstaben enthalten.", "hasNumber": "Passwort muss eine Zahl enthalten.", diff --git a/locales/en.json b/locales/en.json index d5ffbdefcb..8cc099587e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1747,6 +1747,7 @@ "noTopicFoundWithCode": "No topic was found for this code.", "pageMoved": "This page has been moved.", "passwordsAreDifferent": "Passwords do not match.", + "containsOpeningTag": "Please insert a space after the less-than sign.", "hasLowerCase": "Password must contain lowercase letters.", "hasUpperCase": "Password must contain uppercase letters.", "hasNumber": "Password must contain a number.", diff --git a/locales/es.json b/locales/es.json index bd582d447d..41ff11c7bc 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1747,6 +1747,7 @@ "noTopicFoundWithCode": "No se ha encontrado ningún tema para este código.", "pageMoved": "Esta página se ha movido.", "passwordsAreDifferent": "Las contraseñas no coinciden.", + "containsOpeningTag": "Inserte un espacio después del signo menos-que.", "hasLowerCase": "El contraseña debe contener letras minúsculas.", "hasUpperCase": "El contraseña debe contener letras mayúsculas.", "hasNumber": "La contraseña debe contener un número.", diff --git a/locales/uk.json b/locales/uk.json index 05441fd81f..5d41593fa7 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -146,6 +146,7 @@ "noTopicFoundWithCode": "Не знайдено теми для цього коду.", "pageMoved": "Цю сторінку переміщено.", "passwordsAreDifferent": "Паролі не збігаються.", + "containsOpeningTag": "Будь ласка, вставте пробіл після знаку менше.", "hasLowerCase": "Пароль повинен містити малі літери.", "hasUpperCase": "Пароль повинен містити великі літери.", "hasNumber": "Пароль повинен містити цифру.", diff --git a/static/scripts/administration/school.js b/static/scripts/administration/school.js index d7bc7168d1..1754fadaed 100644 --- a/static/scripts/administration/school.js +++ b/static/scripts/administration/school.js @@ -1,3 +1,5 @@ +import validateInputOnOpeningTag from '../helpers/openingTagValidation'; + function transformToBase64(imageSrc) { const img = new Image(); const canvas = document.querySelector('#logo-canvas'); @@ -96,3 +98,9 @@ if (messengerInput && messengerSubOptions) { setMessengerSubOptionsViability(event.target.checked); }); } + +$(document).ready(() => { + const schoolName = document.getElementsByName('name')[0]; + + if (schoolName) schoolName.addEventListener('keyup', () => validateInputOnOpeningTag(schoolName)); +}); diff --git a/static/scripts/course.js b/static/scripts/course.js new file mode 100644 index 0000000000..5a4857ced8 --- /dev/null +++ b/static/scripts/course.js @@ -0,0 +1,7 @@ +import validateInputOnOpeningTag from './helpers/openingTagValidation'; + +$(document).ready(() => { + const courseName = document.getElementsByName('name')[0]; + + if (courseName) courseName.addEventListener('keyup', () => validateInputOnOpeningTag(courseName)); +}); diff --git a/static/scripts/courses.js b/static/scripts/courses.js index 62b3eb10c7..f349aeca5f 100644 --- a/static/scripts/courses.js +++ b/static/scripts/courses.js @@ -11,10 +11,6 @@ $(document).ready(() => { $('.safari-workaround').show(); } - $('.js-course-name-input').change(function courseNameInput() { - $(this).val($(this).val().trim()); - }); - $('.btn-hidden-toggle').click(function hiddenToggle(e) { e.stopPropagation(); e.preventDefault(); diff --git a/static/scripts/helpers/openingTagValidation.js b/static/scripts/helpers/openingTagValidation.js new file mode 100644 index 0000000000..f2c911f044 --- /dev/null +++ b/static/scripts/helpers/openingTagValidation.js @@ -0,0 +1,20 @@ +function containsOpeningTagFollowedByString(input) { + const regex = /<[^<\s]+/; + const result = regex.test(input); + + return result; +}; + +export default function validateInputOnOpeningTag(input) { + // Firefox needs blur event to trigger validation + input.blur(); + input.focus(); + + if (containsOpeningTagFollowedByString(input.value)) { + input.setCustomValidity($t('global.text.containsOpeningTag')); + } else { + input.setCustomValidity(''); + } + + input.reportValidity(); +}; diff --git a/static/scripts/homework/edit.js b/static/scripts/homework/edit.js index 4aec15b98e..71a7955335 100644 --- a/static/scripts/homework/edit.js +++ b/static/scripts/homework/edit.js @@ -1,6 +1,10 @@ +import validateInputOnOpeningTag from '../helpers/openingTagValidation'; + const moment = require('moment-timezone'); const Mousetrap = require('../mousetrap/mousetrap'); + + window.addEventListener('DOMContentLoaded', () => { const lang = $('html').attr('lang'); $.datetimepicker.setLocale(lang || 'de'); @@ -131,4 +135,8 @@ window.addEventListener('DOMContentLoaded', () => { window.history.back(); } }); + + const taskName = document.getElementsByName('name')[0]; + + if (taskName) taskName.addEventListener('keyup', () => validateInputOnOpeningTag(taskName)); }); diff --git a/static/scripts/settings.js b/static/scripts/settings.js index 3fc0e28b63..11bdbb683b 100644 --- a/static/scripts/settings.js +++ b/static/scripts/settings.js @@ -1,5 +1,6 @@ import './pwd.js'; import { validatePassword, validateConfirmationPassword } from './helpers/passwordValidations'; +import validateInputOnOpeningTag from './helpers/openingTagValidation'; $(document).ready(function() { @@ -11,6 +12,12 @@ $(document).ready(function() { if (password) password.addEventListener('keyup', () => validatePassword(password)); if (confirm_password) confirm_password.addEventListener('keyup', () => validateConfirmationPassword(password, confirm_password)); + const firstName = document.getElementsByName('firstName')[0]; + if (firstName) firstName.addEventListener('keyup', () => validateInputOnOpeningTag(firstName)); + + const lastName = document.getElementsByName('lastName')[0]; + if (lastName) lastName.addEventListener('keyup', () => validateInputOnOpeningTag(lastName)); + // TODO: replace with something cooler var reloadSite = function() { delete_cookie("notificationPermission"); diff --git a/static/scripts/topicEdit.js b/static/scripts/topicEdit.js index 17435a08ba..6045b71f94 100644 --- a/static/scripts/topicEdit.js +++ b/static/scripts/topicEdit.js @@ -7,6 +7,37 @@ import { arrayMove, SortableContainer, SortableElement, SortableHandle } from 'r import shortid from 'shortid'; import ckeditorConfig from './ckeditor/ckeditor-config'; import showFallbackImageOnError from './helpers/showFallbackImageOnError'; +import validateInputOnOpeningTag from './helpers/openingTagValidation'; + +$(document).ready(() => { + const lessonName = document.getElementsByName('name')[0]; + if (lessonName) lessonName.addEventListener('keyup', () => validateInputOnOpeningTag(lessonName)); + + document.querySelectorAll('[name^="contents["][name$="[title]"]') + .forEach((element) => element.addEventListener('keyup', () => validateInputOnOpeningTag(element))); + + // Observers if new content blocks are added to the page and ads the validation to the title input + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.addedNodes.length) { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1) { + const inputs = node.querySelectorAll('[name^="contents["][name$="[title]"]'); + + inputs.forEach((input) => { + input.addEventListener('keyup', () => validateInputOnOpeningTag(input)); + }); + } + }); + } + }); + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); +}); /** * A wrapper for each block including a title field, remove, sortable, ... diff --git a/views/courses/create-course.hbs b/views/courses/create-course.hbs index 694e294271..35504bcd5e 100644 --- a/views/courses/create-course.hbs +++ b/views/courses/create-course.hbs @@ -12,6 +12,7 @@ + diff --git a/views/courses/edit-course.hbs b/views/courses/edit-course.hbs index 91eb01fcc3..8cfe0ffecb 100644 --- a/views/courses/edit-course.hbs +++ b/views/courses/edit-course.hbs @@ -11,6 +11,7 @@ +