Where to implement XSS prevention in symfony-based REST API and Vue.js front-end - rest

I'm building an application that requires html tags to be allowed for user comments in Vue.js.
I wan't to allow users to input a certain selection of HTML tags(p, i, ul, li) and escape/sanitize other like script or div.
Right now I see three ways of dealing with this issue:
On rendering the content with Vue.js
Before sending the response in Symfony(I'm using JMS Serializer)
Upon receiving request to the API
Personally I think that we could save the data to database with tags like script or div, and just sanitize them before sending a response.
Basically my question is where should I implement the prevention and should I allow tags like script into my database?

If you're using v-html to render the comments, then there's always the possibility of XSS. Strict HTML sanitization can mitigate the risk, but you never know.
The only surefire way to prevent XSS is to never use v-html or innerHTML. This means you'll have to parse the HTML (using DOMParser) and render the comments manually.
For something like this it will be easier if you write the render function manually so you have full control over how the comment content will be rendered – only render the HTML tags you choose. Whitelist instead of blacklist.
Don't render user-defined HTML attributes.
HTML sanitization won't be necessary on the server because the HTML will never be rendered as-is in the browser, but you can still sanitize it if you want to trim the fat beforehand.
Here's a basic example:
Vue.component('comment-content', {
functional: true,
props: {
html: {},
allowedElements: {
default: () => ['p', 'i', 'b', 'ul', 'li'],
},
},
render(h, ctx) {
const { html, allowedElements } = ctx.props;
const renderNode = node => {
switch (node.nodeType) {
case Node.TEXT_NODE: return renderTextNode(node);
case Node.ELEMENT_NODE: return renderElementNode(node);
}
};
const renderTextNode = node => {
return node.nodeValue;
};
const renderElementNode = node => {
const tag = node.tagName.toLowerCase();
if (allowedElements.includes(tag)) {
const children = [...node.childNodes].map(node => renderNode(node));
return h(tag, children);
}
};
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return [...doc.body.childNodes].map(node => renderNode(node));
},
});
new Vue({
el: '#app',
data: {
html: `
<p>Paragraph</p>
<ul>
<li>One <script>alert('Hacked')<\/script></li>
<li onmouseover="alert('Hacked')">Two</li>
<li style="color: red">Three <b>bold</b> <i>italic</i></li>
<li>Four <img src="javascript:alert('Hacked')"></li>
</ul>
<section>This element isn't allowed</section>
<p>Last paragraph</p>
`,
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<comment-content :html="html"></comment-content>
</div>

Related

Mapbox auto fill complete address

I use mapbox tools for my autofill place address autocomplete on my project Symfony
I want to know how can i extract full complete address in autofill, i have 2 inputs one for search and one hidden for get full/complete address
<mapbox-address-autofill>
{{ form_widget( form.address, {
'attr': {
'class': 'form-control form-control-solid font-weight-bold',
'placeholder': 'Adresse de départ',
'required': 'required',
'autocomplete': 'address-line1'
}
} ) }}
{{ form_widget( form.address_value, {
'attr': {
'autocomplete': 'full-address'
}
}) }}
</mapbox-address-autofill>
I have this but with tag 'full-addresse' 'complete' 'place_name'
No one workn if you have any solution for get full address to persist this in php Symfony project
"full_address" is a property of the feature object that is returned from a retrieve event, but does not automatically map to any HTML form field autocomplete value. The only object properties that get mapped to HTML elements are the ones corresponding to WHATWG standards, i.e.:
'street-address'
'address-line1'
'address-line2'
'address-line3'
'address-level1'
'address-level2'
'address-level3'
'address-level4'
'country'
'country-name'
'postal-code'
I'm not familiar with PHP, but a way to do this in Javascript would be something like:
const autofill = document.querySelector('mapbox-address-autofill');
const targetInput = document.getElementById('myTargetInput');
autofill.addEventListener('retrieve', (event) => {
const featureCollection = event.detail;
const feature = featureCollection[0];
const fullAddress = feature.properties.full_address;
targetInput.value = fullAddress;
});

React|Rest API: Storing form data into an object on the REST API

I've set up a react web application that's currently listing all "Employees" from a mongodb.
I'm now trying to "add" employees to the database through a react frontend form.
I've managed to pass the data from the form to the application but I'm unsure of the process I need to go through to actually get that data solidified into an object and stored in the api.
Please excuse my code, it's disgusting as this is my first week learning react(honestly with little js knowledge, that's another story) and I've just patched together like 20 tutorials....
Here's my Form class:
class Form extends React.Component {
state = {
fullname: '',
}
change = e => {
this.setState({
[e.target.name]: e.target.value
});
}
onSubmit = e => {
e.preventDefault();
this.props.onSubmit(this.state)
this.setState({
fullname: ''
})
}
render() {
return <div>
<form>
<input name="fullname" placeholder="Full Name" value={this.state.fullname} onChange={e => this.change(e)} />
<button onClick={e => this.onSubmit(e)}>Submit</button>
</form>
</div>
}
}
and my Listing(?) class:
class EmployeeList extends React.Component {
constructor(props) {
super(props);
this.state = {employee: []};
this.EmployeeList = this.EmployeeList.bind(this)
this.componentDidMount = this.componentDidMount.bind(this)
}
componentDidMount() {
this.EmployeeList();
}
EmployeeList() {
fetch('/api/employees').then(function(data){
return data.json();
}).then( json => {
this.setState({
employee: json
});
console.log(json);
});
}
onSubmit = fields => {
console.log('app component got: ', fields)
}
render() {
//return a mapped array of employees
const employees = this.state.employee.map((item, i) => {
return <div className="row">
<span className="col-sm-6">{item.fullname}</span>
<span className="col-sm-2" id={item.action1}></span>
<span className="col-sm-2" id={item.action2}></span>
<span className="col-sm-2" id={item.action3}></span>
</div>
});
return <div>
<Form onSubmit={fields => this.onSubmit(fields)}/>
<div className="container">
<div className="row">
<div className="col-sm-6 bg-warning"><h3>Full Name</h3></div>
<div className="col-sm-2 bg-success"><h3>Action 1</h3></div>
<div className="col-sm-2 bg-success"><h3>Action 2</h3></div>
<div className="col-sm-2 bg-success"><h3>Action 3</h3></div>
</div>
</div>
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{ employees }</div>
</div>
</div>
}
}
I've managed to pass the data to the listing app evident by
onSubmit = fields => {
console.log('app component got: ', fields)
}
But how can I go about making a post request to store this data I send into an object on the db? And then also reload the page so that the new list of all employee's is shown?
Thanks so much for your time!
You can use fetch API to make POST request as well. Second parameter is the config object wherein you can pass the required request configurations.
fetch('url', {
method: 'post',
body: JSON.stringify({
name: fields.fullname
})
})
.then(response) {
response.json();
}
.then( json => {
this.setState({
employee: json
});
});
Additional Request Configs which can be used :
method - GET, POST, PUT, DELETE, HEAD
url - URL of the request
headers - associated Headers object
referrer - referrer of the request
mode - cors, no-cors, same-origin
credentials - should cookies go with the request? omit, same-origin
redirect - follow, error, manual
integrity - subresource integrity value
cache - cache mode (default, reload, no-cache)

How to search collection in meteor with more parameters

I need help with searching the meteor collection with more parameters.
I am using search query and filters to see certain objects from a collection. The problem is that I want client to load whole collection and then reactively change what the user sees, but only changing the subscribe, not calling server again. Thill now search query + one filter is working okay, but only if I call server every time something changes. Now in my code below you can see that I am doing it with if else elements, but that is not a good way. Any suggestion will help. Thank you.
Template.jobs.onCreated( function showOnCreate() {
Meteor.subscribe('Jobs');
this.searchQuery = new ReactiveVar('');
this.remoteQuery = new ReactiveVar(false);
this.typeQuery = new ReactiveVar(false);
});
Template.jobs.helpers({
job: () => {
query = Template.instance().searchQuery.get();
remoteQuery = Template.instance().remoteQuery.get();
typeQuery = Template.instance().typeQuery.get();
let regex = new RegExp( query, 'i' );
// **************************
// the problem starts here
// **************************
if (Router.current().params.slug) {
const companyJobs = Company.findOne({slug: Router.current().params.slug}).jobs;
if ( companyJobs !== undefined) {
return Meteor.subscribe('Jobs', {'_id': { '$in': companyJobs }});
}
return false
} else if (Router.current().params.slug === undefined && remoteQuery === true ) {
return Job.find({ $or: [ { Name: regex }, { Description: regex }, ] , Remote: true, positionType: [],});
} else if (typeQuery = '') {
return Job.find({ $or: [ { Name: regex }, { Description: regex }, ] , positionType: typeQuery, });
},
// -------*****************************
employer: () => {
if (Router.current().params.slug === undefined) {
Meteor.subscribe('Companies');
return 'Poslodavac: ' + Company.findOne({_id: Template.currentData().CompanyId}).Name;
}
return false
},
jobUrl: () => {
Meteor.subscribe('Companies');
companySlug = Company.findOne({_id: Template.currentData().CompanyId}).slug;
return ('/company/' + companySlug + '/job/' );
}
});
Template.jobs.events({
'click .positionType': (event, templateInstance) => {
if (Template.instance().remoteQuery.get().lenght > 1){
Template.instance().typeQuery.set(Template.instance().remoteQuery.get().push(event.target.value));
console.log(Template.instance().remoteQuery.get())
} else {
console.log(Template.instance().remoteQuery.get())
console.log('ggggggg')
Template.instance().typeQuery.set(event.target.value);
}
},
'click #remoteFriendly': (event, templateInstance) => {
Template.instance().remoteQuery.set(!Template.instance().remoteQuery.get());
},
});
Html tempalte with filters:
<template name="jobs" >
<div>
<p>Filteri:</p>
<span>
<input type="checkbox" id="remoteFriendly" name="remote"> <span for="remoteFriendly"> Remote friendly? </span>
</span>
<span>
<p>Tip pozicije:</p>
<input type="checkbox" class="positionType" id="1" value="Programiranje" > <span for="1"> Programiranje </span>
<input type="checkbox" class="positionType" id="2" value="Dizajn" > <span for="2"> Dizajn </span>
<input type="checkbox" class="positionType" id="3" value="Marketing" > <span for="3"> Marketing </span>
<input type="checkbox" class="positionType" id="4" value="Ostalo" > <span for="4"> Ostalo </span>
</span>
</div>
{{#each job}}
<div style="border: 0.1rem solid black; margin: 1cm; padding: 5px; max-width: 420px;" > <!-- OVO JE PRIVREMENI STIL, OBRISATI-->
<p> Posao: {{Name}} <br> Opis: {{Description}}</p>
<p> {{employer}} </p>
<p>Remote friendly?: {{Remote}}</p>
<p>Tip pozicije: {{positionType}}</p>
<p> Saznajte vise OVDE</p>
</div>
{{/each}}
<p id="nesto"></p>
</template>
Welcome to SO!
You seem to be confused between Pub/Sub and Collection.find.
You should first realize that the 2 are different mechanisms, which provide different functionalities.
Pub/Sub indeed sends data from your Server into your Client's Minimongo database. But this data is not displayed yet.
Collection.find is used on your Server against your actual MongoDB, and on your Client against your local Minimongo DB.
Therefore on your client, once you have correctly subscribed to your server publication (typically at app level or template level / in onCreated hook), you can directly call Jobs.find in your helpers (or anywhere else) to get your documents, without having to change the subscription (unless the latter needs new parameters).
There should be nothing wrong with your commented code:
return Job.find({'_id': { '$in': companyJobs }});
In general, avoid any expensive computation in helpers (like Meteor.subscribe), as helpers may be executed many times without you noticing it. Your Meteor.subscribe('Companies') should also go to template level (i.e. in onCreated hook).
Therefore, instead of doing your if / else conditions in your helper, simply do it once at your template level. To account for your need to use a value from another document in another collection, why not just passing directly the company's slug as an argument to your Jobs subscription, and performing the computation Server-side? Or even just subscribing to everything, as your current initial subscription seems to do.
Then your helper will just use Jobs.find, which queries against your Client's local minimongo DB, leaving your Server unbothered.

ng-click and ngRouting on mobile devices

I am completely new to Angularjs and haven’t been doing any code for ages. I started setting up my website again with Angularjs. I have a main page and an about page, to which the user gets via ngRoute on ng-click (or hitting space). Once on the about page, the user can go back by clicking somewhere on the page and so on.
App.js
var app = angular.module("MyApp", ["ngRoute"]);
app.config(function($locationProvider, $routeProvider) {
$routeProvider
.when("/teaser", {
controller:"teaserCtrl",
templateUrl:'teaser.html'
})
.when("/about", {
controller:"aboutCtrl",
templateUrl: "about.html"
})
.otherwise({
redirectTo:"/teaser"
})
});
app.controller("mainCtrl", function($scope, $http, $location) {
$scope.v = {
inverted: false,
display: true,
offwhite: true,
}
$scope.$on("space", function() {
if ($scope.v.teaser) {
$location.path("/about")
$scope.v.teaser = false
} else {
$location.path("/teaser")
$scope.v.teaser = true
}
$scope.$apply()
})
$scope.goHome = function(){
$scope.$broadcast("goHome")
}
});
app.directive("ngMobileClick", [function () {
return function (scope, clickElement, attrs) {
clickElement.bind("touchstart click", function (e) {
e.preventDefault();
e.stopPropagation();
scope.$apply(attrs["ngMobileClick"]);
});
}
}])
HTML
<body ng-controller="mainCtrl as main" ng-mobile-click="goHome()" ng-class="{inverted: v.inverted, bg: v.offwhite}" space>
<div class="content" ng-view ng-hide="v.display"></div>
//My body code
<script ng-init="sw = 'some website'; id="about.html" type="text/ng-template">
<div class="about">
<p class="text" ng-click="next(); $event.stopPropagation()">
<p>some text</p>
<br>
<a ng-href="{{mail}}" ng-click="$event.stopPropagation()">some address</a>
</p>
</div>
</script>
</body>
The code for the about page is written into a script and it has hyperlinks (ng-href). Now my issue: As you can see, I changed my ng-click to ng-mobile-click for the body-section. If I also change it in the script for the hyperlinks, something weird is happening which I can’t really figure out (links change to hover color, but still no redirection to the ng-href.
For the desktop version, the click is triggering ngRoute, but I can also click the links. For the mobile version this is not possible any more. Any ideas how I can fix this? I know, there is no hovering possible, but somehow I need to detect the hyperlinks also on mobile devices without being redirected to the main-page.
As I said: this is my first try with Angularjs and I haven’t done any code for a while, please be as clear as possible!
There is another controller for teaser/about which I haven’t put here, as well as the directive for the keyup.
Any ideas or suggestions? Thank you so much in advance!

Nette - {link!} macro can't handle already existing GET params in URL

I Use Nette 2 in my project and I also use .latte template system with AJAX.
Now I have jquery function (in template) that should generate GET request on the same destination but it should add some GET parameters after it.
This destination is initially rendered using one GET parameter, then some actions are made and during some of them AJAX loads some information from the same destination (just adds a couple of GET parameters).
Now I generate AJAX URL using .latte {link!} macro (exclamation mark stands for signal). This is now able to generate new URL with GET params appended to original one. But append is badly parsed, because there is &amp%3B in the URL instead of just &.
I have this code in my template:
{block content}
<h1 n:block=title>New Rule</h1>
{snippet rulesForm}
{form newRuleForm}
{$form}
<script type="text/javascript">
{foreach $form['rule']->containers as $i => $rule}
{include #jsCallback, id => $i, input => $rule['table']->name, link => tableChange}
{include #jsCallback, id => $i, input => $rule['column']->name, link => tableChange}
{/foreach}
</script>
{/form}
{/snippet}
{/block}
{define #jsCallback}
$('#{$control["newRuleForm"]['rule'][$id][$input]->htmlId}').on('change', function(){
alert('{$link}');
$.nette.ajax({
type: 'GET',
url: '{link {$link}!}',
data: {
'value': $(this).val(),
}
});
});
{/define}
How can I fix this problem so I can generate link which appends GET parameters correctly?
Thanks.
Best approach is to avoid generating inline Javascript. You can mark all those form controls by CSS class (eg. special-input), then you don't have to generate Javascript code in Latte iteration.
{snippet rulesForm}
<div data-link-table="{link tableChange!}"></div>
...
{/snippet}
$('.special-input').on('change', function () {
$.nette.ajax({
type: 'GET',
url: $('[data-link-table]').data('linkTable'),
data: {
value: $(this).val()
}
});
});
Maybe noescape modifier?
$.nette.ajax({
type: 'GET',
url: '{link {$link|noescape}!}',
data: {
'value': $(this).val(),
}
});