diff --git a/config/constants/development.js b/config/constants/development.js
index 07d39092..55db9b79 100644
--- a/config/constants/development.js
+++ b/config/constants/development.js
@@ -51,7 +51,7 @@ module.exports = {
// duration to show the prompt saying user will be logged out, before actually logging out the user
IDLE_TIMEOUT_GRACE_MINUTES: 5,
MULTI_ROUND_CHALLENGE_TEMPLATE_ID: 'd4201ca4-8437-4d63-9957-3f7708184b07',
- UNIVERSAL_NAV_URL: '//uni-nav.topcoder-dev.com/v1/tc-universal-nav.js',
+ UNIVERSAL_NAV_URL: 'https://uni-nav.topcoder-dev.com/v1/tc-universal-nav.js',
HEADER_AUTH_URLS_HREF: `https://accounts-auth0.${DOMAIN}?utm_source=community-app-main`,
HEADER_AUTH_URLS_LOCATION: `https://accounts-auth0.${DOMAIN}?retUrl=%S&utm_source=community-app-main`,
SKILLS_V5_API_URL: `${API_V5}/standardized-skills/skills/autocomplete`,
diff --git a/config/constants/production.js b/config/constants/production.js
index 6e5e6e64..84fc03c0 100644
--- a/config/constants/production.js
+++ b/config/constants/production.js
@@ -48,7 +48,7 @@ module.exports = {
IDLE_TIMEOUT_MINUTES: 10,
IDLE_TIMEOUT_GRACE_MINUTES: 5,
MULTI_ROUND_CHALLENGE_TEMPLATE_ID: 'd4201ca4-8437-4d63-9957-3f7708184b07',
- UNIVERSAL_NAV_URL: '//uni-nav.topcoder.com/v1/tc-universal-nav.js',
+ UNIVERSAL_NAV_URL: 'https://uni-nav.topcoder.com/v1/tc-universal-nav.js',
HEADER_AUTH_URLS_HREF: `https://accounts-auth0.${DOMAIN}?utm_source=community-app-main`,
HEADER_AUTH_URLS_LOCATION: `https://accounts-auth0.${DOMAIN}?retUrl=%S&utm_source=community-app-main`,
SKILLS_V5_API_URL: `${API_V5}/standardized-skills/skills/autocomplete`,
diff --git a/config/env.js b/config/env.js
index afe5d437..fba3ab29 100644
--- a/config/env.js
+++ b/config/env.js
@@ -49,7 +49,7 @@ dotenvFiles.forEach(dotenvFile => {
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
// We also resolve them to make sure all tools using them work consistently.
-const appDirectory = fs.realpathSync(process.cwd())
+const appDirectory = process.cwd()
process.env.NODE_PATH = (process.env.NODE_PATH || '')
.split(path.delimiter)
.filter(folder => folder && !path.isAbsolute(folder))
diff --git a/config/paths.js b/config/paths.js
index dbd6246b..a55133df 100644
--- a/config/paths.js
+++ b/config/paths.js
@@ -6,7 +6,7 @@ const url = require('url')
// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637
-const appDirectory = fs.realpathSync(process.cwd())
+const appDirectory = process.cwd()
const resolveApp = relativePath => path.resolve(appDirectory, relativePath)
const envPublicUrl = process.env.PUBLIC_URL
diff --git a/config/webpack.config.js b/config/webpack.config.js
index d77bd35c..c5d2c025 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -27,9 +27,6 @@ const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false'
-// Check if TypeScript is setup
-const useTypeScript = fs.existsSync(paths.appTsConfig)
-
// style files regexes
const cssRegex = /\.css$/
const cssModuleRegex = /\.module\.css$/
@@ -257,7 +254,7 @@ module.exports = function (webpackEnv) {
// for React Native Web.
extensions: paths.moduleFileExtensions
.map(ext => `.${ext}`)
- .filter(ext => useTypeScript || !ext.includes('ts')),
+ .filter(ext => !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 496381d5..8eaf11db 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,5 +1,6 @@
# Use the base image with Node.js
FROM node:12
+RUN useradd -m -s /bin/bash appuser
ARG NODE_ENV
ARG BABEL_ENV
@@ -18,6 +19,9 @@ COPY . /challenge-engine-ui
# Set working directory for future use
WORKDIR /challenge-engine-ui
+RUN chown -R appuser:appuser /challenge-engine-ui
+USER appuser
+
# Install the dependencies from package.json
RUN echo "NODE ENV in Docker: $NODE_ENV"
RUN echo "BABEL ENV in Docker: $BABEL_ENV"
diff --git a/package.json b/package.json
index 1db5fcba..ed6c1a2f 100644
--- a/package.json
+++ b/package.json
@@ -38,9 +38,9 @@
"jwt-decode": "^2.2.0",
"lodash": "^4.17.11",
"mini-css-extract-plugin": "0.4.3",
- "moment": "^2.24.0",
- "moment-duration-format": "^2.2.2",
- "moment-timezone": "^0.5.34",
+ "moment": "^2.29.4",
+ "moment-duration-format": "^2.3.2",
+ "moment-timezone": "^0.5.43",
"node-sass": "^4.14.0",
"normalize-text": "^2.4.1",
"optimize-css-assets-webpack-plugin": "5.0.1",
diff --git a/scripts/build.js b/scripts/build.js
index eef55b65..60909f24 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -28,7 +28,7 @@ const printBuildError = require('react-dev-utils/printBuildError')
const measureFileSizesBeforeBuild =
FileSizeReporter.measureFileSizesBeforeBuild
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild
-const useYarn = fs.existsSync(paths.yarnLockFile)
+const useYarn = false
// These sizes are pretty large. We'll warn for bundles exceeding them.
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024
diff --git a/scripts/start.js b/scripts/start.js
index 71d668f7..57864185 100644
--- a/scripts/start.js
+++ b/scripts/start.js
@@ -32,7 +32,7 @@ const paths = require('../config/paths')
const configFactory = require('../config/webpack.config')
const createDevServerConfig = require('../config/webpackDevServer.config')
-const useYarn = fs.existsSync(paths.yarnLockFile)
+const useYarn = false
const isInteractive = process.stdout.isTTY
// Warn and crash if required files are missing
diff --git a/scripts/test.js b/scripts/test.js
index 9c17521c..520c3464 100644
--- a/scripts/test.js
+++ b/scripts/test.js
@@ -16,27 +16,8 @@ process.on('unhandledRejection', err => {
require('../config/env')
const jest = require('jest')
-const execSync = require('child_process').execSync
let argv = process.argv.slice(2)
-function isInGitRepository () {
- try {
- execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' })
- return true
- } catch (e) {
- return false
- }
-}
-
-function isInMercurialRepository () {
- try {
- execSync('hg --cwd . root', { stdio: 'ignore' })
- return true
- } catch (e) {
- return false
- }
-}
-
// Watch unless on CI, in coverage mode, or explicitly running all tests
if (
!process.env.CI &&
@@ -44,8 +25,7 @@ if (
argv.indexOf('--watchAll') === -1
) {
// https://github.com/facebook/create-react-app/issues/5210
- const hasSourceControl = isInGitRepository() || isInMercurialRepository()
- argv.push(hasSourceControl ? '--watch' : '--watchAll')
+ argv.push('--watchAll')
}
jest.run(argv)
diff --git a/src/components/ChallengeEditor/ChallengeView/index.js b/src/components/ChallengeEditor/ChallengeView/index.js
index c2da57eb..8b658549 100644
--- a/src/components/ChallengeEditor/ChallengeView/index.js
+++ b/src/components/ChallengeEditor/ChallengeView/index.js
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types'
import cn from 'classnames'
import { withRouter } from 'react-router-dom'
import styles from './ChallengeView.module.scss'
-import xss from 'xss'
import Track from '../../Track'
import NDAField from '../NDAField'
import UseSchedulingAPIField from '../UseSchedulingAPIField'
@@ -18,7 +17,6 @@ import ChallengeTotalField from '../ChallengeTotal-Field'
import Loader from '../../Loader'
import AssignedMemberField from '../AssignedMember-Field'
import { getResourceRoleByName } from '../../../util/tc'
-import { isBetaMode } from '../../../util/cookie'
import { loadGroupDetails } from '../../../actions/challenges'
import {
REVIEW_TYPES,
@@ -29,6 +27,7 @@ import {
} from '../../../config/constants'
import PhaseInput from '../../PhaseInput'
import CheckpointPrizesField from '../CheckpointPrizes-Field'
+import { isBetaMode } from '../../../util/localstorage'
const ChallengeView = ({
projectDetail,
@@ -114,10 +113,7 @@ const ChallengeView = ({
- Project:
-
+ Project: {projectDetail ? projectDetail.name : ''}
{selectedMilestone &&
diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js
index 0ce1ea9a..6f59b8c0 100644
--- a/src/components/ChallengeEditor/index.js
+++ b/src/components/ChallengeEditor/index.js
@@ -8,8 +8,6 @@ import moment from 'moment-timezone'
import { pick } from 'lodash/fp'
import { withRouter } from 'react-router-dom'
import { toastr } from 'react-redux-toastr'
-import xss from 'xss'
-
import {
VALIDATION_VALUE_TYPE,
PRIZE_SETS_TYPE,
@@ -69,11 +67,11 @@ import Tooltip from '../Tooltip'
import CancelDropDown from './Cancel-Dropdown'
import UseSchedulingAPIField from './UseSchedulingAPIField'
-import { isBetaMode } from '../../util/cookie'
import MilestoneField from './Milestone-Field'
import DiscussionField from './Discussion-Field'
import CheckpointPrizesField from './CheckpointPrizes-Field'
import { canChangeDuration } from '../../util/phase'
+import { isBetaMode } from '../../util/localstorage'
const theme = {
container: styles.modalContainer
@@ -1704,10 +1702,7 @@ class ChallengeEditor extends Component {
- Project:
-
+ Project: {projectDetail ? projectDetail.name : ''}
diff --git a/src/components/ChallengesComponent/index.js b/src/components/ChallengesComponent/index.js
index 6fa3adc0..a69bdd9a 100644
--- a/src/components/ChallengesComponent/index.js
+++ b/src/components/ChallengesComponent/index.js
@@ -10,7 +10,6 @@ import { CONNECT_APP_URL, PROJECT_ROLES } from '../../config/constants'
import { PrimaryButton } from '../Buttons'
import ChallengeList from './ChallengeList'
import styles from './ChallengesComponent.module.scss'
-import xss from 'xss'
import { checkReadOnlyRoles } from '../../util/tc'
const ChallengesComponent = ({
@@ -61,12 +60,9 @@ const ChallengesComponent = ({
{!dashboard &&
-
+
+ {activeProject ? activeProject.name : ''}
+
{activeProject && activeProject.id && (
(
diff --git a/src/components/ProjectCard/index.js b/src/components/ProjectCard/index.js
index c133a5fa..39016cb2 100644
--- a/src/components/ProjectCard/index.js
+++ b/src/components/ProjectCard/index.js
@@ -2,7 +2,6 @@ import React from 'react'
import PT from 'prop-types'
import { Link } from 'react-router-dom'
import cn from 'classnames'
-import xss from 'xss'
import styles from './ProjectCard.module.scss'
@@ -14,7 +13,7 @@ const ProjectCard = ({ projectName, projectId, selected, setActiveProject }) =>
className={cn(styles.projectName, { [styles.selected]: selected })}
onClick={() => setActiveProject(parseInt(projectId))}
>
-
+ {projectName}
)
diff --git a/src/config/constants.js b/src/config/constants.js
index 8542258a..b8917e2a 100644
--- a/src/config/constants.js
+++ b/src/config/constants.js
@@ -19,7 +19,6 @@ export const {
CP_TRACK_ID,
CHALLENGE_TYPE_ID,
MARATHON_TYPE_ID,
- SEGMENT_API_KEY,
MULTI_ROUND_CHALLENGE_TEMPLATE_ID,
UNIVERSAL_NAV_URL,
HEADER_AUTH_URLS_HREF,
diff --git a/src/index.js b/src/index.js
index 82fb6376..26460c85 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,41 +6,35 @@ import ReactDOM from 'react-dom'
import './styles/main.scss'
import 'react-redux-toastr/lib/css/react-redux-toastr.min.css'
import App from './App'
-import { SEGMENT_API_KEY, UNIVERSAL_NAV_URL } from './config/constants'
+import { UNIVERSAL_NAV_URL } from './config/constants'
ReactDOM.render(
, document.getElementById('root'))
-/* eslint-disable */
-if (!_.isEmpty(SEGMENT_API_KEY)) {
- !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e
-// eslint-disable-next-line no-unused-expressions
-!(function (n, t, e, a, c, i, o) {
-// eslint-disable-next-line no-unused-expressions, no-sequences
- ;(n['TcUnivNavConfig'] = c),
- (n[c] =
- n[c] ||
- function () {
- ;(n[c].q = n[c].q || []).push(arguments)
- }),
- (n[c].l = 1 * new Date())
+// SAST/open-redirect handling: make sure script hostname matches what we expect
+if ((new URL(UNIVERSAL_NAV_URL)).hostname.match(/uni-nav\.topcoder(-dev)?\.com$/i)) {
+ // eslint-disable-next-line no-unused-expressions
+ !(function (n, t, e, a, c, i, o) {
// eslint-disable-next-line no-unused-expressions, no-sequences
- ;(i = t.createElement(e)), (o = t.getElementsByTagName(e)[0])
- i.async = 1
- i.type = 'module'
- i.src = a
- o.parentNode.insertBefore(i, o)
-})(
- window,
- document,
- 'script',
- UNIVERSAL_NAV_URL,
- 'tcUniNav'
-)
+ ;(n['TcUnivNavConfig'] = c),
+ (n[c] =
+ n[c] ||
+ function () {
+ ;(n[c].q = n[c].q || []).push(arguments)
+ }),
+ (n[c].l = 1 * new Date())
+ // eslint-disable-next-line no-unused-expressions, no-sequences
+ ;(i = t.createElement(e)), (o = t.getElementsByTagName(e)[0])
+ i.async = 1
+ i.type = 'module'
+ i.src = a
+ o.parentNode.insertBefore(i, o)
+ })(
+ window,
+ document,
+ 'script',
+ UNIVERSAL_NAV_URL,
+ 'tcUniNav'
+ )
+}
//
diff --git a/src/routes.js b/src/routes.js
index 34e995da..01716670 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -17,11 +17,11 @@ import { saveToken } from './actions/auth'
import { loadChallengeDetails } from './actions/challenges'
import { connect } from 'react-redux'
import { checkAllowedRoles, checkOnlyReadOnlyRoles, checkReadOnlyRoles } from './util/tc'
-import { setCookie, removeCookie, isBetaMode } from './util/cookie'
import IdleTimer from 'react-idle-timer'
import modalStyles from './styles/modal.module.scss'
import ConfirmationModal from './components/Modal/ConfirmationModal'
import Users from './containers/Users'
+import { isBetaMode, removeFromLocalStorage, saveToLocalStorage } from './util/localstorage'
const { ACCOUNTS_APP_LOGIN_URL, IDLE_TIMEOUT_MINUTES, IDLE_TIMEOUT_GRACE_MINUTES, COMMUNITY_APP_URL } = process.env
@@ -94,9 +94,9 @@ class Routes extends React.Component {
getFreshToken().then((token) => {
this.props.saveToken(token)
}).catch((error) => {
- console.error(error)
- const redirectBackToUrl = window.location.origin + this.props.location.pathname
- window.location = ACCOUNTS_APP_LOGIN_URL + '?retUrl=' + redirectBackToUrl
+ console.error(error.message)
+ const redirectBackToUrl = encodeURIComponent(window.location.origin + this.props.location.pathname)
+ window.location = `${ACCOUNTS_APP_LOGIN_URL}?retUrl=${redirectBackToUrl}`
})
}
@@ -105,9 +105,9 @@ class Routes extends React.Component {
const params = new URLSearchParams(search)
if (!_.isEmpty(params.get('beta'))) {
if (params.get('beta') === 'true' && !isBetaMode()) {
- setCookie(BETA_MODE_COOKIE_TAG, 'true')
+ saveToLocalStorage(BETA_MODE_COOKIE_TAG, 'true')
} else if (params.get('beta') === 'false' && isBetaMode()) {
- removeCookie(BETA_MODE_COOKIE_TAG)
+ removeFromLocalStorage(BETA_MODE_COOKIE_TAG)
}
this.props.history.push(this.props.location.pathname)
}
diff --git a/src/util/cookie.js b/src/util/cookie.js
deleted file mode 100644
index b1242acb..00000000
--- a/src/util/cookie.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Provides Cookie related utility methods
- */
-
-import { BETA_MODE_COOKIE_TAG } from '../config/constants'
-
-/**
- * A function that get's a cookie
- */
-export function getCookie (name) {
- const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)')
- return v ? v[2] : undefined
-}
-
-/**
- * A function that set's a cookie
- */
-export function setCookie (name, value) {
- document.cookie = `${name}=${value}; path=/`
-}
-
-/**
- * A function that removes Cookie by setting expiry date to past
- */
-export function removeCookie (name) {
- document.cookie = `${name}=; path=/; expires=Thu, 18 Dec 2013 12:00:00 UTC;`
-}
-
-/**
- * A function that checks whether beta mode is enabled or not
- */
-export function isBetaMode () {
- return getCookie(BETA_MODE_COOKIE_TAG)
-}
diff --git a/src/util/localstorage.js b/src/util/localstorage.js
new file mode 100644
index 00000000..00fcd6d4
--- /dev/null
+++ b/src/util/localstorage.js
@@ -0,0 +1,61 @@
+import { BETA_MODE_COOKIE_TAG } from '../config/constants'
+
+/**
+ * Save an item to localStorage.
+ * @param {string} key - The key under which the data will be stored.
+ * @param {any} value - The data to store (will be stringified).
+ */
+export function saveToLocalStorage (key, value) {
+ if (!key || typeof key !== 'string') {
+ throw new Error('Key must be a valid string.')
+ }
+
+ try {
+ const jsonValue = JSON.stringify(value)
+ window.localStorage.setItem(key, jsonValue)
+ } catch (error) {
+ console.error('Failed to save to localStorage:', error)
+ }
+}
+
+/**
+* Get an item from localStorage.
+* @param {string} key - The key under which the data is stored.
+* @returns {any} - The parsed data from localStorage, or null if not found.
+*/
+export function getFromLocalStorage (key) {
+ if (!key || typeof key !== 'string') {
+ throw new Error('Key must be a valid string.')
+ }
+
+ try {
+ const jsonValue = window.localStorage.getItem(key)
+ return jsonValue ? JSON.parse(jsonValue) : null
+ } catch (error) {
+ console.error('Failed to retrieve from localStorage:', error)
+ return null
+ }
+}
+
+/**
+ * Remove an item from localStorage.
+ * @param {string} key - The key of the item to remove.
+ */
+export function removeFromLocalStorage (key) {
+ if (!key || typeof key !== 'string') {
+ throw new Error('Key must be a valid string.')
+ }
+
+ try {
+ window.localStorage.removeItem(key)
+ } catch (error) {
+ console.error('Failed to remove from localStorage:', error)
+ }
+}
+
+/**
+ * A function that checks whether beta mode is enabled or not
+ */
+export function isBetaMode () {
+ return getFromLocalStorage(BETA_MODE_COOKIE_TAG) === 'true'
+}
diff --git a/test-automation/Dockerfile b/test-automation/Dockerfile
deleted file mode 100644
index 2f5a74ed..00000000
--- a/test-automation/Dockerfile
+++ /dev/null
@@ -1,32 +0,0 @@
-FROM node:10.17.0-stretch
-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;
-RUN curl --silent --show-error --location --fail --retry 3 --output /tmp/google-chrome-stable_current_amd64.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
- && (sudo dpkg -i /tmp/google-chrome-stable_current_amd64.deb || sudo apt-get -fy install) \
- && rm -rf /tmp/google-chrome-stable_current_amd64.deb \
- && sudo sed -i 's|HERE/chrome"|HERE/chrome" --disable-setuid-sandbox --no-sandbox|g' \
- "/opt/google/chrome/google-chrome" \
- && google-chrome --version
-RUN export CHROMEDRIVER_RELEASE=$(curl --location --fail --retry 3 http://chromedriver.storage.googleapis.com/LATEST_RELEASE) \
- && curl --silent --show-error --location --fail --retry 3 --output /tmp/chromedriver_linux64.zip "http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_RELEASE/chromedriver_linux64.zip" \
- && cd /tmp \
- && unzip chromedriver_linux64.zip \
- && rm -rf chromedriver_linux64.zip \
- && sudo mv chromedriver /usr/local/bin/chromedriver \
- && sudo chmod +x /usr/local/bin/chromedriver \
- && chromedriver --version
-RUN sudo apt-get install -y libgconf-2-4
-RUN sudo apt-get install -y xvfb
-RUN sudo apt-get install -y jq
-ENV DISPLAY :99
-RUN printf '#!/bin/sh\nXvfb :99 -screen 0 1280x1024x24 &\nexec "$@"\n' > /tmp/entrypoint \
- && chmod +x /tmp/entrypoint \
- && sudo mv /tmp/entrypoint /docker-entrypoint.sh
-
-COPY . /test-automation
-WORKDIR /test-automation
-RUN npm install
-RUN ./node_modules/.bin/webdriver-manager update --versions.chrome=="$(google-chrome -version)"
-ENTRYPOINT ["/docker-entrypoint.sh"]
-CMD ["/bin/sh"]
\ No newline at end of file
diff --git a/test-automation/config/config.json b/test-automation/config/config.json
index 7ec0216c..744e51e5 100644
--- a/test-automation/config/config.json
+++ b/test-automation/config/config.json
@@ -1,12 +1,10 @@
{
"copilotRole": {
"email": "topcoderconnect+Copilot@gmail.com",
- "password": "appirio123",
"handle": "TCConnCopilot"
},
"copilotManagerRole": {
"email": "topcoderconnect+CopilotManager@gmail.com",
- "password": "appirio123",
"handle": "TCConCopilotMgr"
},
"givenUrl": "https://challenges.topcoder-dev.com/",