diff --git a/__tests__/shared/utils/__snapshots__/markdown.js.snap b/__tests__/shared/utils/__snapshots__/markdown.js.snap index 449da8d15c..5dc7969857 100644 --- a/__tests__/shared/utils/__snapshots__/markdown.js.snap +++ b/__tests__/shared/utils/__snapshots__/markdown.js.snap @@ -1203,7 +1203,7 @@ Array [ A list item with a code block:
- <code goes here>
+ <code>
@@ -1294,7 +1294,7 @@ end tell
ampersands and angle brackets. For example, this:
,
- <div class="footer">
+ <div>
© 2004 Foo Corporation
</div>
diff --git a/src/shared/components/Contentful/PasswordScreen/index.jsx b/src/shared/components/Contentful/PasswordScreen/index.jsx
deleted file mode 100644
index c574520af5..0000000000
--- a/src/shared/components/Contentful/PasswordScreen/index.jsx
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * High order component that apply front-side password protection
- * before loading a Contentful Viewport. It uses sessionStorage for working.
- */
-import PT from 'prop-types';
-import React from 'react';
-import Viewport from 'components/Contentful/Viewport';
-import TextInput from 'components/GUIKit/TextInput';
-
-import './style.scss';
-
-export default class PasswordScreen extends React.Component {
- state = {};
-
- constructor(props) {
- super(props);
-
- this.onSubmit = this.onSubmit.bind(this);
- this.onPasswordInput = this.onPasswordInput.bind(this);
- }
-
- onSubmit() {
- const { password } = this.props;
- this.setState((state) => {
- const { inputVal } = state;
- return {
- authorized: password === inputVal,
- errorMsg: password === inputVal ? '' : 'Password incorrect',
- };
- });
- }
-
- onPasswordInput(inputVal) {
- const update = {
- inputVal,
- };
- if (!inputVal) update.errorMsg = '';
- this.setState(update);
- }
-
- render() {
- const {
- authorized, errorMsg, inputVal,
- } = this.state;
- const {
- viewPortId, preview, spaceName, environment, baseUrl, title, btnText, content,
- } = this.props;
- return authorized ? (
-
- ) : (
-
-
- {title || 'GET ACCESS WITH PASSWORD'}
- Please enter the password you were provided
- this.onPasswordInput(val)}
- errorMsg={errorMsg}
- required
- type="password"
- onEnterKey={this.onSubmit}
- />
-
-
-
-
- {
- content ? (
-
- ) : null
- }
-
- );
- }
-}
-
-PasswordScreen.defaultProps = {
- preview: false,
- spaceName: null,
- environment: null,
- baseUrl: '',
- title: 'GET ACCESS WITH PASSWORD',
- btnText: 'SUBMIT',
- content: null,
-};
-
-PasswordScreen.propTypes = {
- password: PT.string.isRequired,
- viewPortId: PT.string.isRequired,
- preview: PT.bool,
- spaceName: PT.string,
- environment: PT.string,
- baseUrl: PT.string,
- title: PT.string,
- btnText: PT.string,
- content: PT.shape(),
-};
diff --git a/src/shared/components/Contentful/PasswordScreen/style.scss b/src/shared/components/Contentful/PasswordScreen/style.scss
deleted file mode 100644
index 74d6ddfd05..0000000000
--- a/src/shared/components/Contentful/PasswordScreen/style.scss
+++ /dev/null
@@ -1,51 +0,0 @@
-@import "~styles/mixins";
-@import "~components/Contentful/default";
-@import "~components/GUIKit/Assets/Styles/default";
-
-.wrapper {
- background-color: #e9e9e9;
- padding: 86px 0 121px 0;
- min-height: 100vh;
-
- .container {
- text-align: center;
- border-radius: 10px;
- max-width: 544px;
- max-height: 371px;
- margin: 0 auto;
- padding: 31px 65px;
- background-color: #fff;
-
- @include gui-kit-headers;
- @include gui-kit-content;
-
- h4 {
- margin-bottom: 5px;
- }
-
- .hint {
- font-size: 14px;
- margin-bottom: 30px;
- }
-
- .cta {
- margin: 50px auto 29px auto;
- }
-
- .submit {
- outline: none;
-
- @include primary-green;
- @include md;
-
- &:disabled,
- &:hover:disabled {
- background-color: #e9e9e9 !important;
- border: none !important;
- text-decoration: none !important;
- color: #fafafb !important;
- box-shadow: none !important;
- }
- }
- }
-}
diff --git a/src/shared/components/Contentful/Route.jsx b/src/shared/components/Contentful/Route.jsx
index 4b88d43585..0d9dccaf47 100644
--- a/src/shared/components/Contentful/Route.jsx
+++ b/src/shared/components/Contentful/Route.jsx
@@ -12,7 +12,6 @@ import PT from 'prop-types';
import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import Viewport from 'components/Contentful/Viewport';
-import PasswordScreen from 'components/Contentful/PasswordScreen';
import { isomorphy, config } from 'topcoder-react-utils';
import cookies from 'browser-cookies';
import { removeTrailingSlash } from 'utils/url';
@@ -57,7 +56,7 @@ function ChildRoutesLoader(props) {
{
// eslint-disable-next-line no-nested-ternary
fields.viewport
- ? (!fields.password ? (
+ ? (
- ) : (
-
- )
) :
}
diff --git a/src/shared/containers/Profile.jsx b/src/shared/containers/Profile.jsx
index af011788f8..cbc047eecd 100644
--- a/src/shared/containers/Profile.jsx
+++ b/src/shared/containers/Profile.jsx
@@ -135,6 +135,20 @@ class ProfileContainer extends React.Component {
const title = `${handleParam} | Community Profile | Topcoder`;
const description = `Meet Topcoder member ${handleParam} and view their skills and development and design activity. You can also see wins and tenure with Topcoder.`;
+ const {
+ copilot,
+ externalAccounts,
+ externalLinks,
+ challenges,
+ skills,
+ stats,
+ lookupData,
+ badges,
+ meta,
+ tcAcademyCertifications,
+ tcAcademyCourses,
+ } = this.props;
+
return (
) :
}
@@ -154,9 +180,8 @@ class ProfileContainer extends React.Component {
}
ProfileContainer.defaultProps = {
- achievements: null,
copilot: false,
- country: '',
+ challenges: null,
externalAccounts: null,
externalLinks: null,
info: null,
@@ -166,12 +191,15 @@ ProfileContainer.defaultProps = {
meta: null,
memberGroups: null,
auth: {},
+ badges: {},
+ tcAcademyCertifications: [],
+ tcAcademyCourses: [],
};
ProfileContainer.propTypes = {
- achievements: PT.arrayOf(PT.shape()),
+ badges: PT.shape(),
+ challenges: PT.arrayOf(PT.shape()),
copilot: PT.bool,
- country: PT.string,
externalAccounts: PT.shape(),
externalLinks: PT.arrayOf(PT.shape()),
handleParam: PT.string.isRequired,
@@ -188,14 +216,14 @@ ProfileContainer.propTypes = {
lookupData: PT.shape().isRequired,
meta: PT.shape(),
auth: PT.shape(),
+ tcAcademyCertifications: PT.arrayOf(PT.shape()),
+ tcAcademyCourses: PT.arrayOf(PT.shape()),
};
const mapStateToProps = (state, ownProps) => ({
challenges: state.members[ownProps.match.params.handle]
? state.members[ownProps.match.params.handle].userMarathons : null,
- achievements: state.profile.achievements,
copilot: state.profile.copilot,
- country: state.profile.country,
externalAccounts: state.profile.externalAccounts,
externalLinks: state.profile.externalLinks,
handleParam: ownProps.match.params.handle,
diff --git a/src/shared/containers/ProfileStats.jsx b/src/shared/containers/ProfileStats.jsx
index 56ae4ce25e..527e149224 100644
--- a/src/shared/containers/ProfileStats.jsx
+++ b/src/shared/containers/ProfileStats.jsx
@@ -81,6 +81,13 @@ class ProfileStatsContainer extends React.Component {
handleParam,
track,
subTrack,
+ stats,
+ tab,
+ setTab,
+ info,
+ statsDistribution,
+ statsHistory,
+ isAlreadyLoadChallenge,
} = this.props;
if (loadingError || !isValidTrack(track, subTrack)) {
@@ -99,7 +106,16 @@ class ProfileStatsContainer extends React.Component {
isLoading ?
: (
)
}
diff --git a/src/shared/containers/Toastr/index.jsx b/src/shared/containers/Toastr/index.jsx
index ba313e9de8..4c68fcf82b 100644
--- a/src/shared/containers/Toastr/index.jsx
+++ b/src/shared/containers/Toastr/index.jsx
@@ -78,7 +78,13 @@ class ExtendedReduxToastr extends ReduxToastr {
inMemory={this.toastrFired}
addToMemory={() => this._addToMemory(item.id)}
item={mergedItem}
- {...this.props}
+ toastrs={this.props.toastrs}
+ preventDuplicates={this.props.preventDuplicates}
+ position={this.props.position}
+ transitionIn={this.props.transitionIn}
+ transitionOut={this.props.transitionOut}
+ progressBar={this.props.progressBar}
+ showCloseButton={this.props.showCloseButton}
/>
{item.options && item.options.attention
&& (
diff --git a/src/shared/utils/markdown.js b/src/shared/utils/markdown.js
index 5e0883bf04..1b23da60d2 100644
--- a/src/shared/utils/markdown.js
+++ b/src/shared/utils/markdown.js
@@ -13,6 +13,7 @@ import { Button, PrimaryButton, SecondaryButton } from 'topcoder-react-ui-kit';
import { Link } from 'topcoder-react-utils';
import hljs from 'highlight.js';
import ReactHtmlParser from 'react-html-parser';
+import xss from 'xss';
import sub from 'markdown-it-sub';
import sup from 'markdown-it-sup';
import 'highlight.js/styles/github.css';
@@ -133,6 +134,11 @@ const customComponents = {
MMLeaderboard: attrs => ({ type: MMLeaderboard, props: attrs }),
};
+const unsafeHtmlTags = [
+ 'script', 'style', 'iframe', 'object', 'embed', 'applet', 'base',
+ 'form', 'meta', 'frame', 'frameset', 'marquee', 'svg',
+];
+
/**
* The following functions are only used internally and should not need to be
* changed for new components.
@@ -165,6 +171,24 @@ function getProps(token, key) {
return normalizeProps(res);
}
+/**
+ * Check if the tag is safe to render.
+ * @param {String} tag
+ * @returns
+ */
+function checkForSafeTag(tag) {
+ return !unsafeHtmlTags.includes(tag);
+}
+
+/**
+ * Sanitize content
+ * @param {String} content
+ * @returns
+ */
+function sanitizeContent(content) {
+ return xss(content);
+}
+
/**
* Renders tokens with zero nesting.
* @param {Object} tokens
@@ -184,7 +208,7 @@ function renderToken(tokens, index, md) {
return renderTokens(token.children, 0, md);
/* eslint-enable no-use-before-define */
case 'text':
- return token.content;
+ return sanitizeContent(token.content);
case 'fence':
return Highlighter({
codeString: token.content,
@@ -204,9 +228,9 @@ function renderToken(tokens, index, md) {
}
default:
return React.createElement(
- token.tag,
+ checkForSafeTag(token.tag) ? token.tag : 'div',
getProps(token, index),
- token.content || undefined,
+ sanitizeContent(token.content) || undefined,
);
}
}
@@ -232,7 +256,7 @@ function renderTokens(tokens, startFrom, md) {
} else if (level === 0) {
if (token.nesting === 1) {
output.push(React.createElement(
- token.tag,
+ checkForSafeTag(token.tag) ? token.tag : 'div',
getProps(token, pos),
renderTokens(tokens, 1 + pos, md),
));
@@ -252,11 +276,11 @@ function renderTokens(tokens, startFrom, md) {
}
props = normalizeProps(props);
if (selfClosing) {
- output.push(React.createElement(tag, { key: pos, ...props }));
+ output.push(React.createElement(checkForSafeTag(tag) ? tag : 'div', { key: pos, ...props }));
} else {
level += 1;
output.push(React.createElement(
- tag,
+ checkForSafeTag(tag) ? tag : 'div',
{ key: pos, ...props },
renderTokens(tokens, pos + 1, md),
));