Skip to content
Commits on Source (20)
{
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
},
"requireConfigFile": false
},
"extends": [
"eslint:recommended",
......@@ -14,7 +15,7 @@
"plugin:react/recommended",
"airbnb",
"plugin:prettier/recommended",
"prettier/react"
"prettier"
],
"settings": {
"import/resolver": {
......
......@@ -39,7 +39,7 @@ stages:
- !reference [.rules-map, on-dev]
- !reference [.rules-map, not-on-semantic-release-commit]
- !reference [.rules-map, on-branch]
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.7.3
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.11.0
cache:
key:
files:
......@@ -117,14 +117,14 @@ build-docker:
# `release` stage: `new release`, testing prerelease`, `merge-to-dev`, `tag *`
###############################################################################
# Create the release versions on `$STABLE_BRANCH`
new release: {extends: '.semantic-release:stable'}
new release: { extends: '.semantic-release:stable' }
# Create the prereleases versions on `$TESTING_BRANCH`
# update `.releaserc.js` variable `betaBranch`
testing prerelease: {extends: '.semantic-release:testing'}
testing prerelease: { extends: '.semantic-release:testing' }
# Avoid regression and update `version` of package*.json in `$DEV_BRANCH`
merge-to-dev: {extends: '.git:merge-to', variables: {GIT_MERGE_TARGET: $DEV_BRANCH}}
merge-to-dev: { extends: '.git:merge-to', variables: { GIT_MERGE_TARGET: $DEV_BRANCH } }
## tag contribution branches with a more stable name than `git-${CI_COMMIT_SHORT_SHA}`
tag contrib branch:
......
......@@ -6,16 +6,16 @@
meteor-base@1.5.1 # Packages every Meteor app needs to have
mobile-experience@1.1.0 # Packages for a great mobile UX
mongo@1.15.0 # The database Meteor supports right now
mongo@1.16.5 # The database Meteor supports right now
jquery # Wrapper package for npm-installed jquery
reactive-var@1.0.11 # Reactive variable for tracker
tracker@1.2.0 # Meteor's client-side reactive programming library
reactive-var@1.0.12 # Reactive variable for tracker
tracker@1.3.1 # Meteor's client-side reactive programming library
standard-minifier-css@1.8.1 # CSS minifier run for production mode
standard-minifier-js@2.8.0 # JS minifier run for production mode
standard-minifier-css@1.9.0 # CSS minifier run for production mode
standard-minifier-js@2.8.1 # JS minifier run for production mode
es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers
ecmascript@0.16.2 # Enable ECMAScript2015+ syntax in app code
typescript@4.5.4 # Enable TypeScript syntax in .ts and .tsx modules
ecmascript@0.16.6 # Enable ECMAScript2015+ syntax in app code
typescript@4.9.4 # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.5.0 # Server-side component of the `meteor shell` command
svelte:compiler
......@@ -23,12 +23,12 @@ rdb:svelte-meteor-data
static-html@1.3.2
dburles:factory
johanbrook:publication-collector
accounts-base@2.2.3
accounts-base@2.2.7
aldeed:collection2
aldeed:schema-index
mdg:validated-method
meteortesting:mocha
accounts-password@2.3.1
accounts-password@2.3.4
alanning:roles
universe:i18n
percolate:find-from-publication
......
METEOR@2.7.3
METEOR@2.11.0
accounts-base@2.2.3
accounts-oauth@1.4.1
accounts-password@2.3.1
accounts-base@2.2.7
accounts-oauth@1.4.2
accounts-password@2.3.4
alanning:roles@3.4.0
aldeed:collection2@3.5.0
aldeed:schema-index@3.0.0
allow-deny@1.1.1
autoupdate@1.8.0
babel-compiler@7.9.0
babel-compiler@7.10.3
babel-runtime@1.5.1
base64@1.0.12
binary-heap@1.0.11
......@@ -14,27 +14,27 @@ blaze-tools@1.1.3
boilerplate-generator@1.7.1
caching-compiler@1.2.2
caching-html-compiler@1.2.1
callback-hook@1.4.0
check@1.3.1
dburles:factory@1.1.0
ddp@1.4.0
ddp-client@2.5.0
callback-hook@1.5.0
check@1.3.2
dburles:factory@1.3.0
ddp@1.4.1
ddp-client@2.6.1
ddp-common@1.4.0
ddp-rate-limiter@1.1.0
ddp-server@2.5.0
diff-sequence@1.1.1
ddp-rate-limiter@1.1.1
ddp-server@2.6.0
diff-sequence@1.1.2
dynamic-import@0.7.2
ecmascript@0.16.2
ecmascript@0.16.6
ecmascript-runtime@0.8.0
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
ejson@1.1.2
email@2.2.1
ejson@1.1.3
email@2.2.4
eoleteam:accounts-keycloak@2.1.0
eoleteam:keycloak-oauth@2.2.0
es5-shim@4.8.0
fetch@0.1.1
geojson-utils@1.0.10
fetch@0.1.3
geojson-utils@1.0.11
hot-code-push@1.0.4
html-tools@1.1.3
htmljs@1.1.1
......@@ -45,55 +45,55 @@ johanbrook:publication-collector@1.1.0
jquery@3.0.0
launch-screen@1.3.0
localstorage@1.2.0
logging@1.3.1
logging@1.3.2
mdg:validated-method@1.2.0
meteor@1.10.0
meteor@1.11.1
meteor-base@1.5.1
meteortesting:browser-tests@1.3.5
meteortesting:mocha@2.0.3
meteortesting:browser-tests@1.5.1
meteortesting:mocha@2.1.0
meteortesting:mocha-core@8.1.2
minifier-css@1.6.0
minifier-js@2.7.4
minimongo@1.8.0
minifier-css@1.6.2
minifier-js@2.7.5
minimongo@1.9.2
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.8
modules@0.18.0
modules-runtime@0.13.0
mongo@1.15.0
modern-browsers@0.1.9
modules@0.19.0
modules-runtime@0.13.1
mongo@1.16.5
mongo-decimal@0.1.3
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@4.3.1
oauth@2.1.2
oauth2@1.3.1
npm-mongo@4.14.0
oauth@2.2.0
oauth2@1.3.2
ordered-dict@1.1.0
percolate:find-from-publication@0.2.1
promise@0.12.0
promise@0.12.2
raix:eventemitter@1.0.0
random@1.2.0
random@1.2.1
rate-limit@1.0.9
rdb:svelte-meteor-data@0.3.1
react-fast-refresh@0.2.3
reactive-var@1.0.11
react-fast-refresh@0.2.6
reactive-var@1.0.12
reload@1.3.1
retry@1.1.0
routepolicy@1.1.1
service-configuration@1.3.0
service-configuration@1.3.1
sha@1.0.9
shell-server@0.5.0
socket-stream-client@0.5.0
spacebars-compiler@1.3.1
standard-minifier-css@1.8.1
standard-minifier-js@2.8.0
standard-minifier-css@1.9.0
standard-minifier-js@2.8.1
static-html@1.3.2
svelte:compiler@3.46.4
templating-tools@1.2.2
tmeasday:check-npm-versions@1.0.2
tracker@1.2.0
typescript@4.5.4
underscore@1.0.10
tracker@1.3.1
typescript@4.9.4
underscore@1.0.12
universe:i18n@1.32.6
url@1.3.2
webapp@1.13.1
webapp-hashing@1.1.0
webapp@1.13.4
webapp-hashing@1.1.1
# The tag here should match the Meteor version of your app, per .meteor/release
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.7.3
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.11.0
# Copy app package.json and package-lock.json into container
#COPY ./app/package*.json $APP_SOURCE_FOLDER/
......@@ -14,7 +14,7 @@ RUN bash $SCRIPTS_FOLDER/build-meteor-bundle.sh
# Rather than Node 8 latest (Alpine), you can also use the specific version of Node expected by your Meteor release, per https://docs.meteor.com/changelog.html
FROM hub.eole.education/proxyhub/library/node:14.19.3-alpine
FROM hub.eole.education/proxyhub/library/node:14.21.3-alpine
ENV APP_BUNDLE_FOLDER /opt/bundle
ENV SCRIPTS_FOLDER /docker
......@@ -38,7 +38,7 @@ RUN bash $SCRIPTS_FOLDER/build-meteor-npm-dependencies.sh --build-from-source
# Start another Docker stage, so that the final image doesn’t contain the layer with the build dependencies
# See previous FROM line; this must match
FROM hub.eole.education/proxyhub/library/node:14.19.3-alpine
FROM hub.eole.education/proxyhub/library/node:14.21.3-alpine
ENV APP_BUNDLE_FOLDER /opt/bundle
ENV SCRIPTS_FOLDER /docker
......
# Changelog
## [1.8.0](https://gitlab.mim-libre.fr/alphabet/mezig/compare/release/1.7.1...release/1.8.0) (2023-04-18)
### Features
* **input validation:** check content of string inputs ([90b6c45](https://gitlab.mim-libre.fr/alphabet/mezig/commit/90b6c45c48fbb418c63ce3de566d7dfe7868e538))
### Bug Fixes
* **audit:** sanitize html content before display ([ff22521](https://gitlab.mim-libre.fr/alphabet/mezig/commit/ff22521245eca45298133ff2496f77be1a7ed40e))
* **skills:** refactor updateSkills method into an internal function ([ca35a26](https://gitlab.mim-libre.fr/alphabet/mezig/commit/ca35a26d9ad4549b2fa3f676d04868cfcb4d0779))
### Continuous Integration
* **eslint:** rename import for prettier lib ([173801c](https://gitlab.mim-libre.fr/alphabet/mezig/commit/173801c02b3380857ed92b90ee9fd219ccf70b88))
### [1.7.1](https://gitlab.mim-libre.fr/alphabet/mezig/compare/release/1.7.0...release/1.7.1) (2023-01-30)
......
......@@ -5,6 +5,8 @@ import { Roles } from 'meteor/alanning:roles';
import i18n from 'meteor/universe:i18n';
import Mezigs from './mezigs';
import getFavicon from '../getFavicon';
import updateSkills from '../skills/server/methods';
import { validateString } from '../utils';
Meteor.methods({
'mezigs.checkProfile': function checkProfile() {
......@@ -35,6 +37,26 @@ export const getFedId = new ValidatedMethod({
},
});
const validateMezig = (data) => {
validateString(data.publicName);
if (data.firstName) validateString(data.firstName);
if (data.lastName) validateString(data.lastName);
if (data.email) validateString(data.email);
if (data.username) validateString(data.username);
if (data.profilPic) validateString(data.profilPic);
if (data.biography) validateString(data.biography);
if (data.skills) {
data.skills.forEach((skill) => validateString(skill));
}
if (data.links) {
data.links.forEach((link) => {
validateString(link.label);
validateString(link.URL);
if (link.favicon) validateString(link.favicon);
});
}
};
export const createMezig = new ValidatedMethod({
name: 'mezigs.createMezig',
validate: new SimpleSchema({
......@@ -46,6 +68,7 @@ export const createMezig = new ValidatedMethod({
throw new Meteor.Error('api.mezigs.methods.createMezig.notLoggedIn', i18n.__('api.notLoggedIn'));
}
try {
validateMezig(data);
Mezigs.insert(data);
} catch (error) {
if (error.code === 11000) {
......@@ -116,8 +139,6 @@ export const updateMezig = new ValidatedMethod({
}
});
Meteor.call('skills.updateSkills', { skillsToAdd, skillsToDelete });
// get favicon for links
// https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop
const links = await Promise.all(
......@@ -133,6 +154,9 @@ export const updateMezig = new ValidatedMethod({
);
const finalData = { ...data, links };
validateMezig(finalData);
updateSkills({ skillsToAdd, skillsToDelete });
let mezigData = null;
try {
mezigData = Mezigs.update({ _id: mezigId }, { $set: { ...finalData } });
......@@ -176,7 +200,7 @@ export const removeMezig = new ValidatedMethod({
if (myzig === undefined) {
throw new Meteor.Error('api.mezigs.methods.removeMezig.notFound', i18n.__('api.mezigs.notFound'));
}
Meteor.call('skills.updateSkills', { skillsToDelete: myzig.skills });
updateSkills({ skillsToDelete: myzig.skills });
return Mezigs.remove({ _id: mezigId });
},
});
......
import { Meteor } from 'meteor/meteor';
import Skills from './skills';
Meteor.methods({
// eslint-disable-next-line meteor/audit-argument-checks
'skills.updateSkills': function updateSkills(skillsToUpdate) {
const skillsToIncrement = skillsToUpdate.skillsToAdd || [];
const skillsToDecrement = skillsToUpdate.skillsToDelete || [];
if (skillsToIncrement.length > 0) {
skillsToIncrement.forEach((skill) => {
const skillFind = Skills.findOne({ name: skill.toString() });
if (!skillFind) return Skills.insert({ name: skill.toString(), count: 1 });
return Skills.update({ _id: skillFind._id }, { $inc: { count: 1 } });
});
}
if (skillsToDecrement.length > 0) {
skillsToDecrement.forEach((skill) => {
const skillFind = Skills.findOne({ name: skill.toString() });
if (!skillFind)
throw new Meteor.Error('api.skills.methods.updateSkills', `Unknown skill to remove : ${skill.toString()}`);
if (skillFind.count === 1) return Skills.remove({ name: skill.toString() });
return Skills.update({ name: skill.toString() }, { $inc: { count: -1 } });
});
}
},
});
import { Meteor } from 'meteor/meteor';
import Skills from '../skills';
function updateSkills(skillsToUpdate) {
const skillsToIncrement = skillsToUpdate.skillsToAdd || [];
const skillsToDecrement = skillsToUpdate.skillsToDelete || [];
if (skillsToIncrement.length > 0) {
skillsToIncrement.forEach((skill) => {
const skillFind = Skills.findOne({ name: skill.toString() });
if (!skillFind) return Skills.insert({ name: skill.toString(), count: 1 });
return Skills.update({ _id: skillFind._id }, { $inc: { count: 1 } });
});
}
if (skillsToDecrement.length > 0) {
skillsToDecrement.forEach((skill) => {
const skillFind = Skills.findOne({ name: skill.toString() });
if (!skillFind)
throw new Meteor.Error('api.skills.methods.updateSkills', `Unknown skill to remove : ${skill.toString()}`);
if (skillFind.count === 1) return Skills.remove({ name: skill.toString() });
return Skills.update({ name: skill.toString() }, { $inc: { count: -1 } });
});
}
}
export default updateSkills;
......@@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor';
import { Factory } from 'meteor/dburles:factory';
import Skills from '../skills';
import './publications';
import '../methods';
import updateSkills from './methods';
describe('skills', function () {
describe('mutators', function () {
......@@ -26,7 +26,7 @@ describe('skills', function () {
const skillsToAdd = ['test1', 'test4'];
const skillsToDelete = ['test2', 'test3'];
Meteor.call('skills.updateSkills', { skillsToAdd, skillsToDelete });
updateSkills({ skillsToAdd, skillsToDelete });
const skill1Find = Skills.findOne({ name: 'test1' });
const skill2Find = Skills.findOne({ name: 'test2' });
const skill3Find = Skills.findOne({ name: 'test3' });
......@@ -51,7 +51,7 @@ describe('skills', function () {
() => {
const skillsToAdd = [];
const skillsToDelete = ['test1'];
Meteor.call('skills.updateSkills', { skillsToAdd, skillsToDelete });
updateSkills({ skillsToAdd, skillsToDelete });
},
Meteor.Error,
'api.skills.methods.updateSkills',
......
......@@ -111,3 +111,23 @@ export function updateSkillsCollection() {
console.log(`...end updating ${Skills.find({}).count()} skills.`);
}
}
const regValidateStrict = /[<>"'&]/g;
const regValidate = /((<|%3C|&lt;)script)|(('|"|%22|%27) *on[a-z_]+ *(=|%3D))/gi;
/** Check a string for malicious content */
export const validateString = (content, strict = false) => {
if (content.length > 50000) {
throw new Meteor.Error('api.utils.functions.validateString.tooLong', 'api.utils.stringTooLong');
}
/** strict forbids any of the following characters : < > " ' &
otherwise, forbid script tags and pattern like " onload=... */
const scriptRegex = strict ? regValidateStrict : regValidate;
if (content.match(scriptRegex) !== null) {
throw new Meteor.Error(
'api.utils.functions.validateString.error',
strict ? 'api.utils.badCharsDetected' : 'api.utils.scriptDetected',
);
}
return content;
};
<script>
import sanitizeHtml from 'sanitize-html';
export let itemLabel;
export let highlighted;
</script>
<li class="autocomplete-items" class:autocomplete-active={highlighted} on:click>{@html itemLabel}</li>
<li class="autocomplete-items" class:autocomplete-active={highlighted} on:click>{@html sanitizeHtml(itemLabel)}</li>
<style>
li.autocomplete-items {
......
......@@ -4,7 +4,7 @@
import { link as routerLink, navigate } from 'svelte-routing';
import { _ } from 'svelte-i18n';
import Mezigs from '../../api/mezigs/mezigs';
import Skills from "../../api/skills/skills"
import Skills from '../../api/skills/skills';
import Spinner from '../components/Spinner.svelte';
import EditTableLinks from '../components/EditTableLinks.svelte';
......@@ -56,7 +56,7 @@
if (sub.ready()) {
return Skills.find({}).fetch();
}
return []
return [];
});
const maxSkillsCar = 32;
......@@ -109,10 +109,10 @@
email: email || null,
profileChecked: true,
};
Meteor.call('mezigs.updateMezig', { mezigId: $currentMezig._id, data: userData }, (err) => {
if (err) {
error = err.message;
error = err.reason ? $_(err.reason) : err.message;
errorDialog.setOpen(true);
} else {
// set loading to true to permit reload from api
......
......@@ -40,6 +40,11 @@
"ncloud": "Nextcloud URL",
"advancedPersonalPage": "Advanced personal page"
}
},
"utils": {
"scriptDetected": "Forbidden content has been detected",
"badCharsDetected": "Following characters are Forbidden: < > & ' \"",
"stringTooLong": "String is too long (max 50k)"
}
},
"ui": {
......
......@@ -40,6 +40,11 @@
"ncloud": "URL Nextcloud",
"advancedPersonalPage": "Page personnelle avancée"
}
},
"utils": {
"scriptDetected": "Du contenu non autorisé a été détecté",
"badCharsDetected": "Les caractères suivants sont interdits: < > & ' \"",
"stringTooLong": "Chaîne trop longue (50k maximum)"
}
},
"ui": {
......
This diff is collapsed.
{
"name": "mezig",
"private": true,
"version": "1.7.1",
"version": "1.8.0",
"license": "EUPL-1.2",
"description": "Online biography",
"author": "EOLE/PCLL <team@eole.education> - DINUM",
......@@ -15,37 +15,41 @@
"visualize": "meteor --production --extra-packages bundle-visualizer"
},
"dependencies": {
"@babel/runtime": "^7.17.8",
"@smui/button": "^4.2.0",
"@smui/chips": "^4.2.0",
"@smui/common": "^4.2.0",
"@smui/data-table": "^4.2.0",
"@smui/dialog": "^4.2.0",
"@smui/form-field": "^4.2.0",
"@smui/icon-button": "^4.2.0",
"@smui/list": "^4.2.0",
"@smui/menu": "^4.2.0",
"@smui/menu-surface": "^4.2.0",
"@smui/select": "^4.2.0",
"@smui/snackbar": "^4.2.0",
"@smui/switch": "^4.2.0",
"@smui/textfield": "^4.2.0",
"axios": "^0.24.0",
"bcrypt": "^5.0.1",
"chai": "^4.3.6",
"@babel/core": "^7.21.0",
"@babel/runtime": "^7.21.0",
"@smui/button": "^6.1.4",
"@smui/chips": "^6.1.4",
"@smui/common": "^6.1.4",
"@smui/data-table": "^6.2.0",
"@smui/dialog": "^6.1.4",
"@smui/form-field": "^6.1.4",
"@smui/icon-button": "^6.1.4",
"@smui/list": "^6.1.4",
"@smui/menu": "^6.2.0",
"@smui/menu-surface": "^6.1.4",
"@smui/select": "^6.2.0",
"@smui/snackbar": "^6.1.4",
"@smui/switch": "^6.1.4",
"@smui/textfield": "^6.1.4",
"axios": "^1.3.4",
"bcrypt": "^5.1.0",
"chai": "^4.3.7",
"dom-parser": "^0.1.6",
"jquery": "^3.5.1",
"meteor-node-stubs": "^1.2.1",
"moment": "^2.29.3",
"simpl-schema": "^1.10.2",
"eslint-plugin-n": "^15.6.1",
"jquery": "^3.6.4",
"meteor-node-stubs": "^1.2.5",
"moment": "^2.29.4",
"sanitize-html": "^2.10.0",
"simpl-schema": "^1.13.1",
"sortablejs": "^1.15.0",
"svelte": "^3.46.4",
"svelte-i18n": "^3.3.13",
"svelte-infinite-scroll": "^1.5.2",
"svelte-material-icons": "^1.0.3",
"svelte": "^3.56.0",
"svelte-i18n": "^3.6.0",
"svelte-infinite-scroll": "^2.0.1",
"svelte-material-icons": "^2.1.0",
"svelte-routing": "^1.6.0",
"svelte-simple-datatables": "^0.1.27",
"svelte-spinner": "^2.0.2"
"svelte-simple-datatables": "^0.2.3",
"svelte-spinner": "^2.0.2",
"typescript": "^4.9.5"
},
"meteor": {
"mainModule": {
......@@ -57,25 +61,25 @@
"extends": "@meteorjs/eslint-config-meteor"
},
"devDependencies": {
"@babel/eslint-parser": "^7.5.4",
"@meteorjs/eslint-config-meteor": "^1.0.5",
"babel-eslint": "^10.1.0",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-prettier": "^6.11.0",
"eslint-config-standard": "^14.1.1",
"eslint": "^8.35.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.7.0",
"eslint-config-standard": "^17.0.0",
"eslint-import-resolver-meteor": "^0.4.0",
"eslint-plugin-i18n": "^2.0.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx": "^0.1.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-meteor": "^7.2.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-standard": "^4.0.1",
"prettier": "^2.6.2",
"prettier-plugin-svelte": "^2.7.0"
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-standard": "^5.0.0",
"prettier": "^2.8.4",
"prettier-plugin-svelte": "^2.9.0"
}
}
......@@ -10,7 +10,6 @@ import '../imports/api/importFakeData';
import '../imports/api/appsettings/appsettings';
import '../imports/api/appsettings/server/publications';
import '../imports/api/skills/server/publications';
import '../imports/api/skills/methods';
import { ServiceConfiguration } from 'meteor/service-configuration';
import SimpleSchema from 'simpl-schema';
......