fix(client): prevent data url render (#44658)

* fix: only render profile urls

* feat: warn user when submitting data url

* fix: prevent dataurls being saved to db

* fix: tests and imports

Not sure why jest didn't like the es imports, but they aren't necessary
so I dropped them.

* fix: check for url protocol
This commit is contained in:
Oliver Eyton-Williams
2022-01-04 06:35:40 +01:00
committed by GitHub
parent 418480782a
commit a726dd381f
4 changed files with 164 additions and 14 deletions

View File

@ -1,5 +1,6 @@
import debug from 'debug'; import debug from 'debug';
import { check } from 'express-validator'; import { check } from 'express-validator';
import isURL from 'validator/lib/isURL';
import { isValidUsername } from '../../../../utils/validate'; import { isValidUsername } from '../../../../utils/validate';
import { alertTypes } from '../../common/utils/flash.js'; import { alertTypes } from '../../common/utils/flash.js';
@ -164,10 +165,11 @@ function updateMyAbout(req, res, next) {
body: { name, location, about, picture } body: { name, location, about, picture }
} = req; } = req;
log(name, location, picture, about); log(name, location, picture, about);
return user.updateAttributes( // prevent dataurls from being stored
{ name, location, about, picture }, const update = isURL(picture, { require_protocol: true })
createStandardHandler(req, res, next) ? { name, location, about, picture }
); : { name, location, about };
return user.updateAttributes(update, createStandardHandler(req, res, next));
} }
function createUpdateMyUsername(app) { function createUpdateMyUsername(app) {

View File

@ -1,6 +1,7 @@
import { Image } from '@freecodecamp/react-bootstrap'; import { Image } from '@freecodecamp/react-bootstrap';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import isURL from 'validator/lib/isURL';
import { defaultUserImage } from '../../../../config/misc'; import { defaultUserImage } from '../../../../config/misc';
import DefaultAvatar from '../../assets/icons/default-avatar'; import DefaultAvatar from '../../assets/icons/default-avatar';
import { borderColorPicker } from '.'; import { borderColorPicker } from '.';
@ -26,9 +27,14 @@ function AvatarRenderer({
useEffect(() => { useEffect(() => {
const validationImage = document.createElement('img'); const validationImage = document.createElement('img');
validationImage.src = picture; // eslint-disable-next-line @typescript-eslint/naming-convention
validationImage.onload = onImageLoad; if (isURL(picture, { require_protocol: true })) {
validationImage.onerror = onImageError; validationImage.src = picture;
validationImage.onload = onImageLoad;
validationImage.onerror = onImageError;
} else {
setIsPictureValid(false);
}
}, [picture]); }, [picture]);
const isPlaceHolderImage = const isPlaceHolderImage =

View File

@ -23,11 +23,145 @@ exports[`<Profile/> renders correctly 1`] = `
<div <div
class="avatar-container default-border" class="avatar-container default-border"
> >
<img <svg
alt="profile.avatar" class="avatar default-avatar"
class="avatar img-responsive" height="500px"
src="string" version="1.1"
/> viewBox="0 0 500 500"
width="500px"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<title>
icons.avatar
</title>
<desc>
icons.avatar-2
</desc>
<g
fill="none"
fill-rule="evenodd"
>
<g
id="g"
>
<rect
fill="#D0D0D5"
height="500"
width="500"
/>
<path
d="m251.34 49c23.859 58.47 34.222 90.121 31.088 94.954-4.701 7.2493-15.381 32.924 0 50.968s77.487 6.439 92.029 23.897c14.542 17.458 12.196 68.184 14.542 135.56-22.154 0.54208-68.154 1.0837-138 1.6248l0.062-56h-0.124l0.062 56c-69.846-0.54109-115.85-1.0827-138-1.6248 2.3463-67.372 0-118.1 14.542-135.56 14.542-17.458 76.649-5.852 92.029-23.897s4.701-43.719 0-50.968c-3.134-4.8329 7.2285-36.484 31.088-94.954l0.13247 120h0.415z"
fill="#242440"
/>
<path
d="m250.13 185c47.577 0 66.872-66.034 66.872-90.434 0-42.286-29.773-76.566-66.5-76.566s-66.5 34.28-66.5 76.566c0 24.7 18.552 90.434 66.128 90.434z"
fill="#242440"
id="c"
stroke="#D0D0D5"
stroke-width="17"
/>
<path
d="m77.011 459c-19.341-119.95-29.011-183.79-29.011-191.53 0-11.605 6.2167-16.473 17.298-16.473h370.4c11.082 0 17.298 4.8681 17.298 16.473 0 7.7366-9.6704 71.579-29.011 191.53z"
fill="#5F5F8C"
stroke="#D0D0D5"
stroke-width="16"
/>
<rect
fill="#5F5F8C"
height="23"
stroke="#D0D0D5"
stroke-width="6"
width="339"
x="81"
y="459"
/>
<g
fill-rule="nonzero"
transform="translate(162 283)"
>
<ellipse
cx="88.5"
cy="79"
fill="#0A0A23"
rx="88.5"
ry="79"
/>
<g
transform="translate(20 40)"
>
<g
id="Group"
transform="translate(42.462 4)"
>
<g
fill="#fff"
id="e"
>
<path
d="m38.312 39.042c-3.9186-0.91476 12.174-18.263-16.43-39.034 0 0 3.7555 10.879-15.169 35.157-18.933 24.27 8.418 38.725 8.418 38.725s-12.834-6.24 2.0846-28.459c2.6734-4.0329 6.1663-7.6847 10.507-15.899 0 0 3.839 4.9441 1.834 15.671-2.9996 16.208 13.005 11.569 13.256 11.794 5.5895 6.0077-4.6307 16.564-5.2513 16.894-0.62061 0.32307 29.185-16.36 8.0083-41.469-1.4521 1.325-3.3338 7.5359-7.2564 6.6212z"
id="i"
/>
</g>
<g
fill="#000"
fill-opacity="0"
stroke="#000"
stroke-opacity="0"
>
<path
d="m38.312 39.042c-3.9186-0.91476 12.174-18.263-16.43-39.034 0 0 3.7555 10.879-15.169 35.157-18.933 24.27 8.418 38.725 8.418 38.725s-12.834-6.24 2.0846-28.459c2.6734-4.0329 6.1663-7.6847 10.507-15.899 0 0 3.839 4.9441 1.834 15.671-2.9996 16.208 13.005 11.569 13.256 11.794 5.5895 6.0077-4.6307 16.564-5.2513 16.894-0.62061 0.32307 29.185-16.36 8.0083-41.469-1.4521 1.325-3.3338 7.5359-7.2564 6.6212z"
/>
</g>
</g>
<g
id="b"
transform="translate(110.13)"
>
<g
fill="#fff"
id="d"
>
<path
d="m0.96996 0.62339c-0.47786 0.41439-0.95166 1.0162-0.95166 1.6215-0.0040664 1.045 1.3846 2.4611 3.9577 4.7889 10.713 9.1022 16.104 20.251 16.068 33.692-0.040843 14.875-5.7099 26.82-16.729 36.077-2.3158 1.8305-3.2674 3.2647-3.2715 4.4935 0 0.60537 0.4697 1.2324 0.94347 1.8341 0.44519 0.4216 1.3927 0.83962 2.0748 0.83962 2.5486 0.0071777 6.1183-2.6521 10.774-7.8266 9.0712-9.8085 13.172-20.64 13.401-35.4 0.21238-14.77-5.0319-24.784-15.308-35.126-3.6963-3.6935-6.7759-5.6141-8.8834-5.6177-0.68208 0-1.3927 0.20539-2.0748 0.62339z"
id="a"
/>
</g>
<g
fill="#000"
fill-opacity="0"
stroke="#000"
stroke-opacity="0"
>
<path
d="m0.96996 0.62339c-0.47786 0.41439-0.95166 1.0162-0.95166 1.6215-0.0040664 1.045 1.3846 2.4611 3.9577 4.7889 10.713 9.1022 16.104 20.251 16.068 33.692-0.040843 14.875-5.7099 26.82-16.729 36.077-2.3158 1.8305-3.2674 3.2647-3.2715 4.4935 0 0.60537 0.4697 1.2324 0.94347 1.8341 0.44519 0.4216 1.3927 0.83962 2.0748 0.83962 2.5486 0.0071777 6.1183-2.6521 10.774-7.8266 9.0712-9.8085 13.172-20.64 13.401-35.4 0.21238-14.77-5.0319-24.784-15.308-35.126-3.6963-3.6935-6.7759-5.6141-8.8834-5.6177-0.68208 0-1.3927 0.20539-2.0748 0.62339z"
/>
</g>
</g>
<g
fill="#fff"
id="h"
>
<path
d="m26.367 0.6342c0.47409 0.41439 0.9482 1.0162 0.9482 1.6215 0.004069 1.045-1.3855 2.4611-3.9603 4.7889-10.72 9.1022-16.111 20.251-16.078 33.692 0.04087 14.875 5.7136 26.82 16.74 36.077 2.3173 1.8305 3.2696 3.2647 3.2737 4.4935 0 0.60537-0.47001 1.2324-0.9441 1.8341-0.44548 0.4216-1.3896 0.83962-2.0762 0.83962-2.5503 0.0071777-6.1223-2.6521-10.782-7.8266-9.0773-9.8085-13.181-20.64-13.409-35.4-0.21252-14.77 5.0352-24.784 15.318-35.126 3.6987-3.6935 6.7844-5.6141 8.8892-5.6177 0.68253 0 1.3937 0.20539 2.0803 0.62339z"
id="f"
/>
</g>
<g
fill="#000"
fill-opacity="0"
stroke="#000"
stroke-opacity="0"
>
<path
d="m26.367 0.6342c0.47409 0.41439 0.9482 1.0162 0.9482 1.6215 0.004069 1.045-1.3855 2.4611-3.9603 4.7889-10.72 9.1022-16.111 20.251-16.078 33.692 0.04087 14.875 5.7136 26.82 16.74 36.077 2.3173 1.8305 3.2696 3.2647 3.2737 4.4935 0 0.60537-0.47001 1.2324-0.9441 1.8341-0.44548 0.4216-1.3896 0.83962-2.0762 0.83962-2.5503 0.0071777-6.1223-2.6521-10.782-7.8266-9.0773-9.8085-13.181-20.64-13.409-35.4-0.21252-14.77 5.0352-24.784 15.318-35.126 3.6987-3.6935 6.7844-5.6141 8.8892-5.6177 0.68253 0 1.3937 0.20539 2.0803 0.62339z"
/>
</g>
</g>
</g>
</g>
</g>
</svg>
</div> </div>
</div> </div>
</div> </div>

View File

@ -8,6 +8,7 @@ import {
import React, { Component } from 'react'; import React, { Component } from 'react';
import { TFunction, withTranslation } from 'react-i18next'; import { TFunction, withTranslation } from 'react-i18next';
import isURL from 'validator/lib/isURL';
import { FullWidthRow, Spacer } from '../helpers'; import { FullWidthRow, Spacer } from '../helpers';
import BlockSaveButton from '../helpers/form/block-save-button'; import BlockSaveButton from '../helpers/form/block-save-button';
import SoundSettings from './sound'; import SoundSettings from './sound';
@ -149,8 +150,15 @@ class AboutSettings extends Component<AboutProps, AboutState> {
handlePictureChange = (e: React.FormEvent<HTMLInputElement>) => { handlePictureChange = (e: React.FormEvent<HTMLInputElement>) => {
const value = (e.target as HTMLInputElement).value.slice(0); const value = (e.target as HTMLInputElement).value.slice(0);
this.validationImage.src = value; // eslint-disable-next-line @typescript-eslint/naming-convention
return this.setState(state => ({ if (isURL(value, { require_protocol: true })) {
this.validationImage.src = value;
} else {
this.setState({
isPictureUrlValid: false
});
}
this.setState(state => ({
formValues: { formValues: {
...state.formValues, ...state.formValues,
picture: value picture: value