From f25239815ed447f9a551337aa4db0f4eabc4b09e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 15 Dec 2024 18:57:58 +0100 Subject: [PATCH 01/10] fix: xss dom security issue --- src/shared/utils/url.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/shared/utils/url.js b/src/shared/utils/url.js index bd2599d36..25e7ba6a0 100644 --- a/src/shared/utils/url.js +++ b/src/shared/utils/url.js @@ -14,7 +14,13 @@ import { BUCKETS } from 'utils/challenge-listing/buckets'; */ export function getCurrentUrl() { if (isomorphy.isServerSide()) return null; - return window.location.href; + const url = window.location.href; + + if (typeof url === 'string' && url.startsWith('http')) { + return url; + } + + return null; } /** From 8e86304defa64181d1d2d6c9da4d8f63dcc1d24e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Dec 2024 16:41:55 +0100 Subject: [PATCH 02/10] fix: lint --- src/shared/utils/url.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/utils/url.js b/src/shared/utils/url.js index 25e7ba6a0..6245ff277 100644 --- a/src/shared/utils/url.js +++ b/src/shared/utils/url.js @@ -15,7 +15,7 @@ import { BUCKETS } from 'utils/challenge-listing/buckets'; export function getCurrentUrl() { if (isomorphy.isServerSide()) return null; const url = window.location.href; - + if (typeof url === 'string' && url.startsWith('http')) { return url; } From 7777ccf91a543ee158c6eb4f877698fe61ba9362 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Dec 2024 22:43:41 +0100 Subject: [PATCH 03/10] fix: default user privilege --- Dockerfile | 4 ++++ automated-smoke-test/Dockerfile | 3 +++ 2 files changed, 7 insertions(+) diff --git a/Dockerfile b/Dockerfile index 7691feebd..123609baf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,10 +4,14 @@ FROM node:8.11.2 LABEL app="Community App" version="1.0" +RUN useradd -m -s /bin/bash appuser WORKDIR /opt/app COPY . . +RUN chown -R appuser:appuser /opt/app +USER appuser + ################################################################################ # Receiving of build arguments. diff --git a/automated-smoke-test/Dockerfile b/automated-smoke-test/Dockerfile index b228769e6..a5ad40ec6 100644 --- a/automated-smoke-test/Dockerfile +++ b/automated-smoke-test/Dockerfile @@ -1,4 +1,5 @@ FROM node:10.17.0-stretch +RUN useradd -m -s /bin/bash appuser RUN apt update RUN apt install sudo RUN sudo apt-get update; sudo apt-get install -y openjdk-8-jre openjdk-8-jre-headless openjdk-8-jdk openjdk-8-jdk-headless; @@ -26,6 +27,8 @@ RUN printf '#!/bin/sh\nXvfb :99 -screen 0 1280x1024x24 &\nexec "$@"\n' > /tmp/en COPY . /automated-smoke-test WORKDIR /automated-smoke-test +RUN chown -R appuser:appuser /automated-smoke-test +USER appuser RUN npm install RUN ./node_modules/.bin/webdriver-manager update --versions.chrome=="$(google-chrome -version)" ENTRYPOINT ["/docker-entrypoint.sh"] From d89f6d15a4e1a2fec5767ea065781fdfbe09f549 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 18 Dec 2024 17:24:14 +0200 Subject: [PATCH 04/10] PM-197 - XSS poor validation error handling --- src/shared/components/Contentful/Article/Article.jsx | 2 +- src/shared/components/Gigs/GigApply/index.jsx | 6 +++--- src/shared/components/TopcoderHeader/Auth/index.jsx | 2 +- src/shared/components/tc-communities/AccessDenied/index.jsx | 2 +- src/shared/components/tc-communities/Footer/index.jsx | 4 ++-- src/shared/components/tc-communities/Header/index.jsx | 6 +++--- src/shared/containers/Dashboard/index.jsx | 2 +- src/shared/containers/challenge-detail/index.jsx | 2 +- src/shared/containers/tc-communities/Loader.jsx | 2 +- src/shared/utils/tc.js | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/shared/components/Contentful/Article/Article.jsx b/src/shared/components/Contentful/Article/Article.jsx index d15b08c26..772a0afb0 100644 --- a/src/shared/components/Contentful/Article/Article.jsx +++ b/src/shared/components/Contentful/Article/Article.jsx @@ -139,7 +139,7 @@ class Article extends React.Component { } = this.state || {}; let shareUrl; if (isomorphy.isClientSide()) { - shareUrl = encodeURIComponent(window.location.href); + shareUrl = encodeURIComponent(`${window.location.origin}${window.location.pathname}`); } const description = htmlToText.fromString( ReactDOMServer.renderToString(markdown(fields.content)), diff --git a/src/shared/components/Gigs/GigApply/index.jsx b/src/shared/components/Gigs/GigApply/index.jsx index a36919c2b..53d6b557b 100644 --- a/src/shared/components/Gigs/GigApply/index.jsx +++ b/src/shared/components/Gigs/GigApply/index.jsx @@ -36,7 +36,7 @@ export default function GigApply(props) { recruitProfile, auth, } = props; - const retUrl = window.location.href; + const retUrl = encodeURIComponent(`${window.location.origin}${window.location.pathname}`); const duration = getCustomField(job.custom_fields, 'Duration'); const isPlaced = _.find(_.isEmpty(recruitProfile) ? [] : recruitProfile.custom_fields, { field_id: 12 }); const fetchSkills = useMemo(() => _.debounce((inputValue, callback) => { @@ -353,9 +353,9 @@ export default function GigApply(props) {

You must be a Topcoder member to apply!

- Login + Login
-

Not a member? Register here.

+

Not a member? Register here.

diff --git a/src/shared/components/TopcoderHeader/Auth/index.jsx b/src/shared/components/TopcoderHeader/Auth/index.jsx index ab8c237b6..eee206a73 100644 --- a/src/shared/components/TopcoderHeader/Auth/index.jsx +++ b/src/shared/components/TopcoderHeader/Auth/index.jsx @@ -28,7 +28,7 @@ export default function Auth({ column }) { className="tc-btn-sm tc-btn-default" href={`${config.URL.AUTH}/member?utm_source=community-app-main`} onClick={(event) => { - const retUrl = encodeURIComponent(window.location.href); + const retUrl = encodeURIComponent(`${window.location.origin}${window.location.pathname}`); window.location = `${config.URL.AUTH}/member?retUrl=${retUrl}&utm_source=community-app-main`; event.preventDefault(); }} diff --git a/src/shared/components/tc-communities/AccessDenied/index.jsx b/src/shared/components/tc-communities/AccessDenied/index.jsx index a5730d6d9..32952604e 100644 --- a/src/shared/components/tc-communities/AccessDenied/index.jsx +++ b/src/shared/components/tc-communities/AccessDenied/index.jsx @@ -50,7 +50,7 @@ export default function AccessDenied(props) { className="tc-btn-md tc-btn-primary" href={`${config.URL.AUTH}/member?utm_source=${communityId}`} onClick={(event) => { - const retUrl = encodeURIComponent(window.location.href); + const retUrl = encodeURIComponent(`${window.location.origin}${window.location.pathname}`); window.location = `${config.URL.AUTH}/member?retUrl=${retUrl}&utm_source=${communityId}`; event.preventDefault(); }} diff --git a/src/shared/components/tc-communities/Footer/index.jsx b/src/shared/components/tc-communities/Footer/index.jsx index 8f0809a15..4299238d2 100644 --- a/src/shared/components/tc-communities/Footer/index.jsx +++ b/src/shared/components/tc-communities/Footer/index.jsx @@ -56,7 +56,7 @@ function Footer({ - - - - - )} -
- -

Account Role

-
-
- Access to Topcoder tools and applications are based on your account - role. If you change this setting, you will be required to sign out - of your account and login. -
-
-
- - - -
-
-
-
- - ); -}; - -MyPrimaryRole.propTypes = { - user: PT.shape().isRequired, - tokenV3: PT.string.isRequired, - updatePrimaryRole: PT.func.isRequired, -}; - - -export default withRouter(MyPrimaryRole); diff --git a/src/shared/components/Settings/Account/MyPrimaryRole/styles.scss b/src/shared/components/Settings/Account/MyPrimaryRole/styles.scss deleted file mode 100644 index 787ff5d4d..000000000 --- a/src/shared/components/Settings/Account/MyPrimaryRole/styles.scss +++ /dev/null @@ -1,254 +0,0 @@ -@import "../../style"; -@import "~styles/mixins"; - -.hide { - display: none; -} - -.form-container-default { - display: flex; - flex-direction: column; - - .form-default { - display: block; - - @include upto-sm { - display: none; - } - } - - .form-mobile { - display: none; - - @include upto-sm { - display: block; - - .row { - display: flex; - flex-direction: column; - } - } - } - - input { - @include roboto-regular; - - height: 40px; - font-size: 15px; - line-height: 20px; - font-weight: 400; - color: $tc-black; - border: 1px solid $tc-gray-20; - border-radius: $corner-radius * 2 $corner-radius * 2 $corner-radius * 2 $corner-radius * 2; - margin-bottom: 0; - } - - .form-field { - background: white; - color: black; - - &:disabled { - color: #b7b7b7; - } - - &.grey { - background-color: #fcfcfc; - color: #151516; - } - } -} - -.form-container { - padding: $pad-xxxxl; - background-color: $color-tc-white; - border-radius: 4px; - margin: $margin-sm 0 $margin-xxxxl 0; - - .account-form { - display: flex; - justify-content: space-between; - width: 100%; - - @media (max-width: 768px) { - flex-direction: column; - } - } -} - -.form-title { - @include barlow-semi-bold; - - font-size: 20px; - line-height: 22px; - color: inherit; - text-transform: uppercase; - padding-bottom: $pad-xxxxl; -} - -.form-content { - display: flex; - flex-wrap: wrap; - align-items: flex-start; -} - -.form-label { - flex: 0 0 calc(50% - 13px); - padding-right: 230px; - - @include roboto-regular; - - font-size: 16px; - line-height: 26px; - color: inherit; -} - -.form-body { - flex: 0 0 calc(50% + 13px); -} - -@include xs-to-md { - .form-container { - padding: $pad-xxl $pad-lg; - } - - .form-title { - @include barlow-semi-bold; - - font-size: 20px; - line-height: 22px; - padding-bottom: $pad-xxl; - } - - .form-label { - flex: 1 1 100%; - padding: 0; - margin-bottom: $margin-xxl; - font-size: 14px; - line-height: 20px; - } - - .form-body { - flex: 1 1 100%; - } - - .form-footer { - margin: 0; - } -} - -.nagModal { - display: flex; - flex-direction: column; - margin: 32px; - - @include xs-to-md { - flex-direction: column; - margin-top: 24px; - } - - .header { - display: flex; - align-items: flex-start; - justify-content: space-between; - border-bottom: 2px solid #e9e9e9; - - @include xs-to-md { - margin-top: 24px; - text-align: center; - } - - .title { - @include barlow-bold; - - color: $tco-black; - font-size: 22px; - font-weight: 600; - line-height: 26px; - margin-bottom: 12px; - text-transform: uppercase; - - @include xs-to-md { - text-align: center; - } - } - - .icon { - cursor: pointer; - } - } - - .description { - @include roboto-regular; - - font-weight: 400; - color: $tco-black; - font-size: 16px; - line-height: 24px; - margin-top: 24px; - - strong { - font-weight: 700; - } - - .badgeWrap { - display: flex; - justify-content: center; - margin-bottom: 12px; - } - - span span { - color: #137d60; - font-weight: bold; - } - } -} - -.container { - box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2); - border-radius: 8px; - min-width: 600px; - max-width: 700px; - - @include xs-to-sm { - width: 90%; - min-width: unset; - } -} - -.overlay { - background-color: #0c0c0c; - opacity: 0.85; -} - -.actionButtons { - display: flex; - align-items: center; - justify-content: flex-end; - margin-top: 32px; - padding-top: 24px; - border-top: 2px solid #e9e9e9; - - .primaryBtn { - background-color: #137d60; - border-radius: 24px; - color: #fff; - font-size: 13px; - font-weight: bolder; - text-decoration: none; - text-transform: uppercase; - line-height: 32px; - padding: 0 20px; - border: none; - outline: none; - display: flex; - - &:hover { - box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); - background-color: #0ab88a; - } - - @include xs-to-sm { - margin-bottom: 20px; - } - } -} diff --git a/src/shared/components/Settings/Account/Security/Modal/index.jsx b/src/shared/components/Settings/Account/Security/Modal/index.jsx deleted file mode 100644 index 3e33c982e..000000000 --- a/src/shared/components/Settings/Account/Security/Modal/index.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useEffect } from 'react'; -import PT from 'prop-types'; -import DiceImage from 'assets/images/account/security/dicelogosmall.png'; -import CloseButton from 'assets/images/account/security/green-close.svg'; - -import './style.scss'; - -export default function DiceModal({ - children, onCancel, leftButtonName, leftButtonDisabled, leftButtonClick, - rightButtonName, rightButtonDisabled, rightButtonClick, rightButtonHide, -}) { - useEffect(() => { - document.body.style.overflow = 'hidden'; - return () => { document.body.style.overflow = 'unset'; }; - }, []); - - return ( - ( - -
event.stopPropagation()} - > -
-
- diceid -
-
DICE ID authenticator setup
- -
-
-
- {children} -
-
-
-
- {leftButtonName} -
- {!rightButtonHide && ( -
- {rightButtonName} -
- )} -
-
-
- - ) - ); -} -DiceModal.defaultProps = { - children: null, - leftButtonDisabled: false, - rightButtonDisabled: false, - rightButtonHide: false, -}; -DiceModal.propTypes = { - children: PT.node, - onCancel: PT.func.isRequired, - leftButtonName: PT.string.isRequired, - leftButtonDisabled: PT.bool, - leftButtonClick: PT.func.isRequired, - rightButtonName: PT.string.isRequired, - rightButtonDisabled: PT.bool, - rightButtonClick: PT.func.isRequired, - rightButtonHide: PT.bool, -}; diff --git a/src/shared/components/Settings/Account/Security/Modal/style.scss b/src/shared/components/Settings/Account/Security/Modal/style.scss deleted file mode 100644 index 1f972a52e..000000000 --- a/src/shared/components/Settings/Account/Security/Modal/style.scss +++ /dev/null @@ -1,162 +0,0 @@ -@import "../../../style"; -@import "~styles/mixins"; - -.overlay { - background: #0c0c0c; - border: none; - height: 100%; - left: 0; - opacity: 0.85; - outline: none; - position: fixed; - top: 0; - width: 100%; - z-index: 950; -} - -.container { - background: #fff; - position: fixed; - max-width: 1000px; - height: 752px; - max-height: 99%; - top: 50%; - left: 50%; - padding: 32px; - box-shadow: 0 0 1px 5px rgba(0, 0, 0, 0.2); - border-radius: 8px; - overflow: auto; - transform: translate(-50%, -50%); - z-index: 951; - display: flex; - flex-direction: column; - align-items: flex-start; - - .header { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - margin-bottom: 24px; - - .icon-wrapper { - display: flex; - align-items: center; - justify-content: center; - width: 150px; - height: 40px; - background: #fff; - border-radius: 4px; - margin-right: $margin-md; - - img { - display: block; - } - } - - .title { - @include barlow-semi-bold; - - flex: 1; - font-size: 22px; - line-height: 26px; - color: $tco-black; - text-transform: uppercase; - } - - .close-button { - cursor: pointer; - height: 15px; - width: 15px; - } - } - - .body { - flex: 1; - width: 100%; - display: flex; - flex-direction: column; - margin-bottom: 24px; - } - - .divider { - width: 100%; - min-height: 2px; - background-color: #e9e9e9; - border-radius: 1px; - margin-bottom: 24px; - } - - .footer { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - width: 100%; - - .left-button { - @include roboto-bold; - - text-align: center; - margin: 0; - height: 48px; - font-size: 16px; - line-height: 21px; - letter-spacing: 0.008em; - text-transform: uppercase; - color: $color-turq-160; - padding: 12px 24px; - background: $tc-white; - border: 2px solid $color-turq-160; - border-radius: 50px; - cursor: pointer; - - &:focus { - outline: none; - } - - &:focus-visible { - outline: none; - } - - &.disabled { - background: #f4f4f4; - color: #767676; - border: none; - line-height: 24px; - pointer-events: none; - } - } - - .right-button { - @include roboto-bold; - - text-align: center; - margin: 0; - height: 48px; - font-size: 16px; - line-height: 24px; - letter-spacing: 0.008em; - text-transform: uppercase; - color: $tc-white; - padding: 12px 24px; - background: $color-turq-160; - border-radius: 50px; - cursor: pointer; - - &:focus { - outline: none; - } - - &:focus-visible { - outline: none; - } - - &.disabled { - background: #f4f4f4; - color: #767676; - pointer-events: none; - } - } - } -} diff --git a/src/shared/components/Settings/Account/Security/VerificationListener/index.jsx b/src/shared/components/Settings/Account/Security/VerificationListener/index.jsx deleted file mode 100644 index 26e0ee9e5..000000000 --- a/src/shared/components/Settings/Account/Security/VerificationListener/index.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useEffect, useCallback } from 'react'; -import PT from 'prop-types'; - -export default function VerificationListener({ - event, callback, origin, type, onProcessing, startType, -}) { - const messageHandler = useCallback((e) => { - if (e.origin === origin && e.data && e.data.type) { - if (e.data.type === startType) { - onProcessing(); - } else if (e.data.type === type) { - callback(e.data); - } - } - }, [origin, type, startType]); - - useEffect(() => { - window.addEventListener(event, messageHandler); - return () => window.removeEventListener(event, messageHandler); - }, [event, messageHandler]); - - return false; -} - -VerificationListener.propTypes = { - event: PT.string.isRequired, - callback: PT.func.isRequired, - origin: PT.string.isRequired, - type: PT.string.isRequired, - onProcessing: PT.func.isRequired, - startType: PT.string.isRequired, -}; diff --git a/src/shared/components/Settings/Account/Security/index.jsx b/src/shared/components/Settings/Account/Security/index.jsx deleted file mode 100644 index 796cf1beb..000000000 --- a/src/shared/components/Settings/Account/Security/index.jsx +++ /dev/null @@ -1,430 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import PT from 'prop-types'; -import _ from 'lodash'; -import { config } from 'topcoder-react-utils'; -import QRCode from 'react-qr-code'; -import { SettingBannerV2 as Collapse } from 'components/Settings/SettingsBanner'; -import Tooltip from 'components/Tooltip'; -import MfaImage from 'assets/images/account/security/mfa.svg'; -import DiceLogo from 'assets/images/account/security/dicelogo.png'; -import DiceLogoBig from 'assets/images/account/security/dicelogobig.png'; -import GooglePlay from 'assets/images/account/security/google-play.png'; -import AppleStore from 'assets/images/account/security/apple-store.svg'; -import UnsuccessfulIcon from 'assets/images/account/security/unsuccessful.svg'; -import TooltipInfo from 'assets/images/tooltip-info.svg'; -import Modal from './Modal'; -import VerificationListener from './VerificationListener'; - - -import './styles.scss'; - -export default function Security({ - usermfa, updateUser2fa, updateUserDice, getNewDiceConnection, - getDiceConnection, tokenV3, handle, emailAddress, -}) { - const [setupStep, setSetupStep] = useState(-1); - const [isConnVerifyRunning, setIsConnVerifyRunning] = useState(false); - const [connVerifyCounter, setConnVerifyCounter] = useState(0); - const [isVerificationProcessing, setIsVerificationProcessing] = useState(false); - const diceVerifyUrl = config.DICE_VERIFY_URL; - - const diceTip = ( -
-

Please reach out to support@topcoder.com for deactivating Dice ID

-
- ); - const useInterval = (callback, delay) => { - const savedCallback = useRef(); - - useEffect(() => { - savedCallback.current = callback; - }, [callback]); - - // eslint-disable-next-line consistent-return - useEffect(() => { - function tick() { - savedCallback.current(); - } - if (delay !== null) { - const id = setInterval(tick, delay); - return () => clearInterval(id); - } - setConnVerifyCounter(0); - }, [delay]); - }; - - const getMfaOption = () => { - const mfaEnabled = _.get(usermfa, 'user2fa.mfaEnabled'); - if (mfaEnabled) return true; - return false; - }; - - const getDiceOption = () => { - const diceEnabled = _.get(usermfa, 'user2fa.diceEnabled'); - if (diceEnabled) return true; - return false; - }; - - const mfaChecked = getMfaOption(); - const diceChecked = getDiceOption(); - const userId = _.get(usermfa, 'user2fa.userId'); - const diceConnection = _.get(usermfa, 'diceConnection'); - - const onUpdateMfaOption = () => { - if (usermfa.updatingUser2fa) { - return; - } - updateUser2fa(userId, !mfaChecked, tokenV3); - }; - - const goToConnection = () => { - if (mfaChecked && !usermfa.gettingNewDiceConnection) { - getNewDiceConnection(userId, tokenV3); - } - setSetupStep(1); - setIsConnVerifyRunning(true); - }; - - const getConnectionAccepted = () => { - if (diceConnection.accepted) return true; - return false; - }; - - const openSetup = () => { - setSetupStep(0); - }; - - const closeSetup = () => { - setSetupStep(-1); - setIsVerificationProcessing(false); - }; - - const verifyConnection = () => { - if (getConnectionAccepted() || usermfa.diceConnectionError) { - setIsConnVerifyRunning(false); - } else if (!usermfa.gettingDiceConnection && diceConnection.id) { - if (connVerifyCounter >= 36) { - closeSetup(); - } else { - getDiceConnection(userId, diceConnection.id, tokenV3); - setConnVerifyCounter(connVerifyCounter + 1); - } - } - }; - - const goToVerification = () => { - if (!getConnectionAccepted()) { - return; - } - setSetupStep(2); - }; - - const verificationCallback = (data) => { - if (data.success) { - const userEmail = _.get(data, 'user.profile.Email'); - if (!_.isUndefined(userEmail) && _.lowerCase(userEmail) === _.lowerCase(emailAddress)) { - updateUserDice(userId, true, tokenV3); - setSetupStep(3); - } else { - setSetupStep(4); - } - } else { - setSetupStep(4); - } - }; - - const onStartProcessing = () => { - setIsVerificationProcessing(true); - }; - - useInterval(verifyConnection, setupStep === 1 && isConnVerifyRunning ? 5000 : null); - - const getVerificationStepTitle = () => { - if (isVerificationProcessing) { - return 'Processing...'; - } - return 'STEP 3 OF 3'; - }; - - const getVerificationStepText = () => { - if (isVerificationProcessing) { - return 'Please wait while your credentials are validated.'; - } - return 'Scan the following DICE ID QR Code in your DICE ID mobile application to confirm your credential.'; - }; - - const setupStepNodes = [ - -
-
- STEP 1 OF 3 -
-
- First, please download the DICE ID App from the - Google Play Store or the iOS App Store. -
-
-
- Google Play Store - -
-
- - -
-
-
- After you have downloaded and installed the mobile app, - make sure to complete the configuration process. - When ready, click next below. -
-
-
, - -
-
- STEP 2 OF 3 -
-
- Scan the following DICE ID QR Code in your DICE ID mobile application. -
-
- {diceConnection.connection - ? - : 'Loading'} -
-
- Once the connection is established, the service will offer you a Verifiable Credential. -
Press the ACCEPT button in your DICE ID App. -
If you DECLINE the invitation, please try again after 5 minutes. -
-
-
, - {}} - rightButtonHide - > -
-
- {getVerificationStepTitle()} -
-
- {getVerificationStepText()} -
-