diff --git a/.github/workflows/E2E_CI.yaml b/.github/workflows/E2E_CI.yaml new file mode 100644 index 0000000..e583831 --- /dev/null +++ b/.github/workflows/E2E_CI.yaml @@ -0,0 +1,134 @@ +name: E2E Build + +on: + pull_request + +jobs: + + extract_metadata: + runs-on: ubuntu-latest + name: Extract supported_features + outputs: + supported-features: ${{ steps.supported-features.outputs.value }} + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18.x' + - name: extract supported features + id: supported-features + run: echo "value=$(node -p -e "require('./px_metadata.json').supported_features?.join(' or ') || ''")" >> "$GITHUB_OUTPUT" + + + CI: + runs-on: ubuntu-latest + timeout-minutes: 60 + needs: + - extract_metadata + + steps: + + - name: Checkout Repo + uses: actions/checkout@v2 + + - name: Build local cluster + run: ./ci_files/build_cluster.sh + + - name: Set up Docker + uses: docker/setup-buildx-action@v1 + + - name: Build Sample-site Docker image + run: | + docker build -t localhost:5001/node-sample-site:1.0.0 . && docker images && docker push localhost:5001/node-sample-site:1.0.0 + env: + DOCKER_BUILDKIT: 1 + + + - name: install helm + run: | + curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null + sudo apt-get install apt-transport-https --yes + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list + sudo apt-get update + sudo apt-get install helm=3.12.1-1 + + - name: Clone helm charts repo - mock-collector + uses: actions/checkout@v2 + with: + repository: PerimeterX/connect-helm-charts + token: ${{ secrets.CONNECT_PULL_TOKEN }} + ref: mock-collector-0.1.0 + path: ./deploy_charts/mock-collector + + - name: Clone helm charts repo - enforcer-tests + uses: actions/checkout@v2 + with: + repository: PerimeterX/connect-helm-charts + token: ${{ secrets.CONNECT_PULL_TOKEN }} + ref: enforcer-spec-tests-0.6.0 + path: ./deploy_charts/enforcer-spec-tests + + + - name: Clone helm charts repo - sample-site + uses: actions/checkout@v2 + with: + repository: PerimeterX/connect-helm-charts + token: ${{ secrets.CONNECT_PULL_TOKEN }} + ref: sample-site-0.1.0 + path: ./deploy_charts/sample-site + + + - name: deploy sample site + run: | + helm install sample-site ./deploy_charts/sample-site/charts/sample-site --set image.name=localhost:5001/node-sample-site --set image.tag=1.0.0 --set imagePullPolicy=Always --set collectorURL=http://mock-collector-mock-collector:3001 --wait + + - name: Set up Google Cloud SDK + id: 'auth' + uses: 'google-github-actions/auth@v1' + with: + credentials_json: '${{ secrets.GCR_SA_KEY }}' + + - name: Configure Docker credentials + run: | + gcloud auth configure-docker gcr.io + + - name: pull mock collector image + run: | + docker pull gcr.io/px-docker-repo/connecteam/mock-collector:1.0.2 && \ + docker tag gcr.io/px-docker-repo/connecteam/mock-collector:1.0.2 localhost:5001/mock-collector:1.0.2 && \ + docker push localhost:5001/mock-collector:1.0.2 && \ + docker images + + - name: deploy mock collector + run: | + helm install mock-collector ./deploy_charts/mock-collector/charts/mock-collector --set image.repository=localhost:5001/mock-collector --set image.tag=1.0.2 --set imagePullPolicy=Always --wait + + - run: kubectl get pods + + - name: pull enforcer tests image + run: | + docker pull gcr.io/px-docker-repo/connecteam/enforcer-specs-tests:1.1.0 && \ + docker tag gcr.io/px-docker-repo/connecteam/enforcer-specs-tests:1.1.0 localhost:5001/enforcer-spec-tests:1.1.0 && \ + docker push localhost:5001/enforcer-spec-tests:1.1.0 && \ + docker images + + - name: run enforcer tests + run: | + helm install enforcer-spec-tests ./deploy_charts/enforcer-spec-tests/charts/enforcer-spec-tests --set image.repository=localhost:5001/enforcer-spec-tests --set image.tag=1.1.0 --set imagePullPolicy=Always \ + --set internalMockCollectorURL=http://mock-collector-mock-collector:3001 \ + --set appID=PXnEpdw6lS \ + --set siteURL=http://sample-site-sample-site:3000 \ + --set cookieSecret=${{ secrets.TEST_COOKIE_SECRET }} \ + --set supportedFeatures="${{ needs.extract_metadata.outputs.supported-features }}" \ + --set-file enforcerMetadataContent=./px_metadata.json + + - name: wait until test is over + run: ./ci_files/wait-for-job.sh + env: + JOB_NAME: enforcer-spec-tests + + - name: get tests results + if: ${{ always() }} + run: kubectl logs job/enforcer-spec-tests \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9c3586d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# create static files and configs +FROM node:16-slim + +WORKDIR /workspace +COPY ./demo-site/shared_config.json . +COPY ./demo-site/templates templates +COPY ./demo-site/utils utils +COPY ./demo-site/scripts scripts +COPY ./demo-site/servers/nodejs/package.json servers/nodejs/package.json +RUN cd servers/nodejs && npm install +COPY ./demo-site/servers/nodejs servers/nodejs + +RUN node scripts/create_static_files.js + +WORKDIR /workspace/servers/nodejs + +COPY ./ perimeterx-node-express +RUN npm install ./perimeterx-node-express + +ARG ENABLE_TEST_ENDPOINTS=true +ARG PX_APP_ID="" +ARG PX_AUTH_TOKEN="" +ARG PX_COOKIE_SECRET="" + +ENV ENABLE_TEST_ENDPOINTS=${ENABLE_TEST_ENDPOINTS} +ENV PX_APP_ID=${PX_APP_ID} +ENV PX_AUTH_TOKEN=${PX_AUTH_TOKEN} +ENV PX_COOKIE_SECRET=${PX_COOKIE_SECRET} + +EXPOSE 3000 +CMD ["node","app.js"] diff --git a/ci_files/build_cluster.sh b/ci_files/build_cluster.sh new file mode 100755 index 0000000..b48715f --- /dev/null +++ b/ci_files/build_cluster.sh @@ -0,0 +1,57 @@ +#!/bin/sh +set -o errexit + +# 2. Create registry container unless it already exists +reg_name='kind-registry' +reg_port='5001' +if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then + docker run \ + -d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" \ + registry:2 +fi + +# 3. Create kind cluster with containerd registry config dir enabled +cat <> /dev/null 2>&1 + success=$? + if [ $success -eq 0 ] + then + exit 0; + fi + + echo "checking for failure" + kubectl wait --for=condition=failed -n $ns job/$job --timeout=0s >> /dev/null 2>&1 + fail=$? + if [ $fail -eq 0 ] + then + exit 1 + fi + + sleep 5 +done diff --git a/demo-site/scripts/create_static_files.js b/demo-site/scripts/create_static_files.js new file mode 100644 index 0000000..000ee92 --- /dev/null +++ b/demo-site/scripts/create_static_files.js @@ -0,0 +1,105 @@ +//region imports +const fs = require('fs'); +const path = require('path'); +const sharedConfig = require('../shared_config.json'); +const { forEachServer, normalizeEnforcerName } = require("../utils/utils"); +const { CONFIG_FILE_NAME } = require('../utils/constants'); +//endregion + +const TEMPLATE_INDICATOR = sharedConfig.site_config.template_indicator; +const REPLACE_VARIABLE_REGEX = /\${[A-Za-z1-9_]*}/gi; + +const main = () => { + const publicTemplateDir = path.join(__dirname, "..", sharedConfig.site_config.public_template_dir); + forEachServer((serverName, serverPath, serverConfig) => { + const publicDir = createPublicDirForServer(serverPath, serverConfig); + if (!publicDir) { + console.error(`Couldn't create public dir for ${serverName} server`); + process.exit(1); + } + + if (!copyStaticFiles(publicTemplateDir, serverConfig, serverName, publicDir)) { + console.error(`Could not copy static files for ${normalizeEnforcerName(serverName)}. Skipping...`); + } else { + console.log(`Successfully created static files for ${normalizeEnforcerName(serverName)}`); + } + }); +}; + +const copyStaticFiles = (publicTemplateDir, serverConfig, serverName, publicDir) => { + const replaceVariableMaps = createReplacementVariableMaps(serverConfig, serverName); + if (!replaceVariableMaps) { + return false; + } + + const fileNames = fs.readdirSync(publicTemplateDir); + for (const fileName of fileNames) { + copyFileToServerDirectory(fileName, publicTemplateDir, replaceVariableMaps, publicDir); + } + return true; +}; + +const copyFileToServerDirectory = (fileName, publicTemplateDir, replaceVariableMaps, publicDir) => { + if (fileName.includes(TEMPLATE_INDICATOR)) { + copyTemplateToServerDirectory(fileName, publicTemplateDir, replaceVariableMaps, publicDir); + } else { + copyStaticFileToServerDirectory(fileName, publicTemplateDir, publicDir); + } +}; + +const copyTemplateToServerDirectory = (fileName, publicTemplateDir, replaceVariableMaps, publicDir) => { + const template = fs.readFileSync(path.join(publicTemplateDir, fileName)).toString(); + for (const { templateIndicatorReplacement, replacementMap } of replaceVariableMaps) { + const fileContents = fillInTemplate(template, replacementMap); + const newFileName = fileName.replace(TEMPLATE_INDICATOR, templateIndicatorReplacement); + fs.writeFileSync(path.join(publicDir, newFileName), fileContents); + } +}; + +const copyStaticFileToServerDirectory = (fileName, publicTemplateDir, publicDir) => { + const fileContents = fs.readFileSync(path.join(publicTemplateDir, fileName)).toString(); + fs.writeFileSync(path.join(publicDir, fileName), fileContents); +}; + +const fillInTemplate = (template, replaceVariableMap) => { + return template.replace(REPLACE_VARIABLE_REGEX, (matched) => replaceVariableMap[matched]); +} + +const createReplacementVariableMaps = (config, enforcerName) => { + const appId = config.enforcer_credentials.px_app_id; + if (appId == null || appId.length === 0) { + console.error(`No px_app_id found in ${enforcerName}/${CONFIG_FILE_NAME}!`); + return null; + } + const appIdSubstr = appId.substr(2); + return [ + createReplacementInfo("", appId, `//client.px-cloud.net/${appId}/main.min.js`, enforcerName), + createReplacementInfo(".firstparty", appId, `/${appIdSubstr}/init.js`, enforcerName) + ]; +}; + +const createReplacementInfo = (templateIndicatorReplacement, appId, sensorSrcUrl, enforcerName) => { + return { + templateIndicatorReplacement, + replacementMap: { + "${app_id}": appId, + "${sensor_src_url}": sensorSrcUrl, + "${enforcer_name}": normalizeEnforcerName(enforcerName) + } + }; +}; + +const createPublicDirForServer = (serverPath, config) => { + if (!config.site_config || !config.site_config.public_output_dir) { + console.error(`No property "public_output_dir" in ${serverPath}/${CONFIG_FILE_NAME}: ${config}`); + return null; + } + + const publicFilesPath = path.join(serverPath, config.site_config.public_output_dir); + if (!fs.existsSync(publicFilesPath)) { + fs.mkdirSync(publicFilesPath); + } + return publicFilesPath; +}; + +main(); diff --git a/demo-site/servers/nodejs/app.js b/demo-site/servers/nodejs/app.js new file mode 100644 index 0000000..ee36eab --- /dev/null +++ b/demo-site/servers/nodejs/app.js @@ -0,0 +1,202 @@ +const path = require('path'); +const axios = require('axios'); +const express = require('express'); +const cookieParser = require('cookie-parser'); +const formData = require("express-form-data"); +const perimeterx = require('perimeterx-node-express'); + +const pxConfigJson = require('./px_config.json'); + +const PORT = 3000; + +var PxMiddleware; +var PxCdMiddleware; +let pxConfig; +var PxCdInterval; +var pxInstance; + +const main = () => { + pxConfig = initializeConfigs(); + + const app = initializeApp(); + if (process.env.ENABLE_TEST_ENDPOINTS === "true") { + setAdditionalActivityHandler(pxConfig); + setCustomParam(pxConfig); + } + setPxMiddleware(app); + setRoutes(app); + setTestEndpoints(app); + setStaticRoutes(app); + + const server = app.listen(PORT, '0.0.0.0', function () { + console.log(`NodeJS sample site is listening on port ${PORT}!`) + }); + + process.on('SIGINT', () => { + console.log('Closing http server...'); + server.close(); + process.exit(0); + }); +} + +const initializeApp = () => { + const app = express(); + app.use(cookieParser()); + app.use(express.json()); + app.use(express.urlencoded()); + // support form-data/multipart bodies + app.use(formData.parse()); + app.use(formData.format()); + app.use(formData.stream()); + app.use(formData.union()); + app.use((req, res, next) => { + console.log(req.method, req.path); + next(); + }) + return app; +} + +const setPxMiddleware = (app) => { + pxInstance = perimeterx.new(pxConfig) + PxMiddleware = pxInstance.middleware; + app.use(PxMiddlewareWrap); + + if (pxInstance.cdEnforcer) { + PxCdMiddleware = pxInstance.cdMiddleware; + PxCdInterval = pxInstance.cdEnforcer.setIntervalId; + app.use(PxCdMiddleware); + } + app.use((req, res, next) => { + for (const [name, value] of Object.entries(req.headers)) { + res.setHeader(name, value); + } + next(); + }); +} + +const initializeConfigs = () => { + return addEnvConfigs(pxConfigJson); +} + +const setAdditionalActivityHandler = (pxConfig) => { + pxConfig['px_additional_activity_handler'] = (pxCtx) => { + const { uri, pxde, pxdeVerified, score } = pxCtx; + axios.post(pxConfig.px_backend_url + "/additional" + uri, { + _pxde: pxde, + pxdeVerified: pxdeVerified, + pxScore: score + }).catch((e) => console.log(e.message)); + }; +} + +const setCustomParam = (pxConfig) => { + pxConfig['px_enrich_custom_parameters'] = (px_context, px_config)=>{ + let customParams = []; + for (let i = 1; i < 3; i++) { + let param_key = `custom_param${i}`; + let value = `test${i}`; + customParams[param_key] = value; + } + for (let i = 3; i < 7; i++) { + let param_key = `custom_param${i}`; + let value = i; + customParams[param_key] = value; + } + for (let i = 7; i <= 12; i++) { + let param_key = `custom_param${i}`; + let value = null; + customParams[param_key] = value; + } + return customParams; + }; +} + +const setTestEndpoints = (app) => { + app.post('/config', function (req, res, next) { + if(process.env.ENABLE_TEST_ENDPOINTS === 'false'){ + return res.sendStatus(404); + } + let newConfig = req.body; + // merge new config into pxConfig + Object.assign(pxConfig, newConfig) + if (pxConfig['px_csp_enabled']) { + clearInterval(PxCdInterval); + } + var pxInstance = perimeterx.new(pxConfig); + PxMiddleware = pxInstance.middleware; + setAdditionalActivityHandler(pxConfig); + setCustomParam(pxConfig) + res.sendStatus(200); + }); + + app.get('/supported-features', function (req, res, next) { + if(process.env.ENABLE_TEST_ENDPOINTS === 'false'){ + return res.sendStatus(404); + } + const supportedFeatures = require('perimeterx-node-express/px_metadata.json'); + return res.json(supportedFeatures); + }); + + app.get('/test-app-credentials', function (req, res){ + if (process.env.ENABLE_TEST_ENDPOINTS === 'false'){ + return res.sendStatus(404); + } + const test_app_credentials = { + "px_app_id": pxConfig.px_app_id, + "px_cookie_secret": pxConfig.px_cookie_secret + }; + return res.json(test_app_credentials) + }); +} + +const setRoutes = (app) => { + app.get('/', function (req, res) { + res.sendFile(__dirname + '/public/index' + getRequiredSuffix() + '.html'); + }) + + app.post('/login', function (req, res, next) { + const loginSuccessful = req.body.username === 'pxUser' && req.body.password === '1234'; + res.pxLoginSuccessful = loginSuccessful; + if (loginSuccessful) { + res.sendFile(__dirname + '/public/profile' + getRequiredSuffix() + '.html'); + } else { + res.status(301).redirect('/'); + } + }); + + app.get('/logout', function (req, res) { + res.redirect('/'); + }); + +} + +const setStaticRoutes = (app) => { + app.use(express.static(path.join(__dirname, 'public'))); +} + +const addEnvConfigs = (config) => { + const envConfigs = { + "px_app_id" : process.env.PX_APP_ID, + "px_cookie_secret" : process.env.PX_COOKIE_SECRET, + } + for (const key in envConfigs){ + if (!envConfigs[key] || config[key] !== ""){ + delete envConfigs[key]; + } + } + Object.assign(config, envConfigs); + return config; +}; + +const getRequiredSuffix = () => { + return pxConfig.px_first_party_enabled ? ".firstparty" : ""; +}; + +const PxMiddlewareWrap = (req, res, next) => { + if (pxConfig.px_filter_by_route && pxConfig.px_filter_by_route.includes(req.path)){ + return next(); + } + PxMiddleware(req, res, next); +}; + +main(); \ No newline at end of file diff --git a/demo-site/servers/nodejs/package.json b/demo-site/servers/nodejs/package.json new file mode 100644 index 0000000..b39c2da --- /dev/null +++ b/demo-site/servers/nodejs/package.json @@ -0,0 +1,27 @@ +{ + "name": "nodejs", + "version": "1.0.0", + "description": "", + "main": "app.js", + "scripts": { + "start": "node app.js", + "dev": "nodemon app.js", + "test": "echo \"Error: no test specified\" && exit 1", + "cover": "nyc node app.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^0.21.1", + "body-parser": "^1.19.0", + "cookie-parser": "^1.4.3", + "express": "^4.14.0", + "express-form-data": "^2.0.17", + "perimeterx-node-core": "^3.11.0" + }, + "devDependencies": { + "mocha": "^5.2.0", + "nodemon": "^2.0.7", + "nyc": "^13.2.0" + } +} diff --git a/demo-site/servers/nodejs/px_config.json b/demo-site/servers/nodejs/px_config.json new file mode 100644 index 0000000..d8b6ff8 --- /dev/null +++ b/demo-site/servers/nodejs/px_config.json @@ -0,0 +1,87 @@ +{ +"px_advanced_blocking_response_enabled": true, +"px_app_id": "", +"px_auth_token": "", +"px_blocking_score": 70, +"px_bypass_monitor_header": "", +"px_cookie_secret": "", +"px_css_ref": "style.css", +"px_custom_block_page_url": "", +"px_custom_cookie_header": "x-px-cookies", +"px_custom_logo": "https://storage.googleapis.com/perimeterx-logos/primary_logo_red_cropped.png", +"px_enforced_routes": [], +"px_filter_by_extension": [ +".css", +".bmp", +".tif", +".ttf", +".docx", +".woff2", +".js", +".pict", +".tiff", +".eot", +".xlsx", +".jpg", +".csv", +".eps", +".woff", +".xls", +".jpeg", +".doc", +".ejs", +".otf", +".pptx", +".gif", +".pdf", +".swf", +".svg", +".ps", +".ico", +".pls", +".midi", +".svgz", +".class", +".png", +".ppt", +".mid", +".webp", +".jar", +".json", +".xml" +], +"px_filter_by_route": [ +"/supported-features", +"/test-app-credentials", +"/config" +], +"px_first_party_enabled": true, +"px_ip_headers": [], +"px_js_ref": "index.js", +"px_logger_severity": "debug", +"px_login_credentials_extraction": [ +{ +"path": "/login", +"method": "post", +"sent_through": "body", +"user_field": "username", +"pass_field": "password" +} +], +"px_login_credentials_extraction_enabled": true, +"px_max_activity_batch_size": 1, +"px_max_buffer_len": 1, +"px_module_enabled": true, +"px_module_mode": "active_blocking", +"px_proxy_url": "", +"px_risk_cookie_max_iterations": 5000, +"px_risk_cookie_max_length": 2048, +"px_risk_cookie_min_iterations": 1, +"px_s2s_timeout": 1000, +"px_sensitive_headers": [ +"cookie", +"cookies" +], +"px_sensitive_routes": [], +"px_user_agent_max_length": 8528 +} \ No newline at end of file diff --git a/demo-site/shared_config.json b/demo-site/shared_config.json new file mode 100644 index 0000000..3e1bbf4 --- /dev/null +++ b/demo-site/shared_config.json @@ -0,0 +1,83 @@ +{ + "site_config": { + "public_template_dir": "./templates/static_files", + "template_indicator": ".template" + }, + "enforcer_config": { + "px_module_enabled": true, + "px_first_party_enabled": true, + "px_blocking_score": 100, + "px_module_mode": "active_blocking", + "px_sensitive_headers": ["cookie", "cookies"], + "px_sensitive_routes": [], + "px_enforced_routes": [], + "px_ip_headers": [], + "px_custom_logo": "https://storage.googleapis.com/perimeterx-logos/primary_logo_red_cropped.png", + "px_css_ref": "style.css", + "px_js_ref": "index.js", + "px_logger_severity": "debug", + "px_s2s_timeout": 1000, + "px_filter_by_route": [ + "/supported-features", + "/test-app-credentials", + "/config" + ], + "px_advanced_blocking_response_enabled": true, + "px_proxy_url": "", + "px_max_activity_batch_size": 1, + "px_max_buffer_len": 1, + "px_bypass_monitor_header": "", + "px_custom_block_page_url": "", + "px_filter_by_extension": [ + ".css", + ".bmp", + ".tif", + ".ttf", + ".docx", + ".woff2", + ".js", + ".pict", + ".tiff", + ".eot", + ".xlsx", + ".jpg", + ".csv", + ".eps", + ".woff", + ".xls", + ".jpeg", + ".doc", + ".ejs", + ".otf", + ".pptx", + ".gif", + ".pdf", + ".swf", + ".svg", + ".ps", + ".ico", + ".pls", + ".midi", + ".svgz", + ".class", + ".png", + ".ppt", + ".mid", + ".webp", + ".jar", + ".json", + ".xml" + ], + "px_user_agent_max_length": 8528, + "px_risk_cookie_max_length": 2048, + "px_risk_cookie_max_iterations": 5000, + "px_login_credentials_extraction_enabled": true, + "px_login_credentials_extraction": [{ + "path": "/login", + "method": "post", + "sent_through": "body", + "user_field": "username", + "pass_field": "password" + }] + } +} \ No newline at end of file diff --git a/demo-site/templates/static_files/index.template.html b/demo-site/templates/static_files/index.template.html new file mode 100644 index 0000000..9b57590 --- /dev/null +++ b/demo-site/templates/static_files/index.template.html @@ -0,0 +1,44 @@ + + + + PerimeterX ${enforcer_name} SDK Sample + + + + + +
+
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/demo-site/templates/static_files/profile.template.html b/demo-site/templates/static_files/profile.template.html new file mode 100644 index 0000000..7eb0efb --- /dev/null +++ b/demo-site/templates/static_files/profile.template.html @@ -0,0 +1,39 @@ + + + + PerimeterX Login Successful + + + + + +
+
+

Hello, pxUser


+ +
+ +
+ + +
+ +
+ + \ No newline at end of file diff --git a/demo-site/templates/static_files/style.css b/demo-site/templates/static_files/style.css new file mode 100644 index 0000000..e218f83 --- /dev/null +++ b/demo-site/templates/static_files/style.css @@ -0,0 +1,115 @@ +body { + background-color: #848484; +} + +.container { + margin-top:150px; +} + +.box { + margin: 0 auto; + padding-top:20px; + text-align: center; + color: white; + width:350px; + background-color: black; + border-radius: 12px; + height: 350px; + box-shadow: 3px 3px 3px #ED1C24; +} + +.login-box { + margin: 0 auto; + padding-top:20px; + text-align: center; + color: white; + background-color: black; + border-radius: 12px; + box-shadow: 3px 3px 3px #ED1C24; +} + +#logout { + margin-top:120px; +} + +#logout a { + color:black; + text-decoration: none; +} + +.form-signin +{ + max-width: 330px; + padding: 15px; + margin: 0 auto; +} +.form-signin .form-signin-heading, .form-signin .checkbox +{ + margin-bottom: 10px; +} +.form-signin .checkbox +{ + font-weight: normal; +} +.form-signin .form-control +{ + position: relative; + font-size: 16px; + height: auto; + padding: 10px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.form-signin .form-control:focus +{ + z-index: 2; +} +.form-signin input[type="text"] +{ + margin-bottom: -1px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} +.form-signin input[type="password"] +{ + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.account-wall +{ + margin-top: 20px; + padding: 40px 0px 20px 0px; + background-color: black; + -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); + box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); + border-radius: 12px; +} +.login-title +{ + color: white; + font-size: 18px; + font-weight: 400; + display: block; +} +.profile-img +{ + width: 96px; + height: 96px; + margin: 0 auto 10px; + display: block; + -moz-border-radius: 50%; + -webkit-border-radius: 50%; + border-radius: 50%; +} +.need-help +{ + margin-top: 10px; +} +.new-account +{ + display: block; + margin-top: 10px; +} \ No newline at end of file diff --git a/demo-site/utils/constants.js b/demo-site/utils/constants.js new file mode 100644 index 0000000..dd055c4 --- /dev/null +++ b/demo-site/utils/constants.js @@ -0,0 +1,41 @@ +// region imports +const path = require('path'); +// endregion + +const JSON_SPACING = 4; + +const SERVER_CONFIG_FILE_NAME = 'px_config.json'; +const SERVER_CONFIG_INC_FILE_NAME = "config.inc.json"; +const CONFIG_FILE_NAME = 'px_config.json'; + +const SERVERS_DIRECTORY_NAME = "servers"; +const SERVERS_DIRECTORY_PATH = path.join(__dirname, `../${SERVERS_DIRECTORY_NAME}`); + +const FIRST_PARTY_STATIC_FILE_SUFFIX = ".firstparty"; +const THIRD_PARTY_STATIC_FILE_SUFFIX = ""; + +const PX_METADATA_FILE_NAME = "px_metadata.json"; + +const INDEX_ROUTE = "/"; +const LOGIN_ROUTE = "/login"; +const LOGOUT_ROUTE = "/logout"; + +const EXPECTED_USERNAME = "pxUser"; +const EXPECTED_PASSWORD = "1234"; + +module.exports = { + JSON_SPACING, + CONFIG_FILE_NAME, + SERVER_CONFIG_FILE_NAME, + SERVER_CONFIG_INC_FILE_NAME, + SERVERS_DIRECTORY_NAME, + SERVERS_DIRECTORY_PATH, + FIRST_PARTY_STATIC_FILE_SUFFIX, + THIRD_PARTY_STATIC_FILE_SUFFIX, + PX_METADATA_FILE_NAME, + INDEX_ROUTE, + LOGIN_ROUTE, + LOGOUT_ROUTE, + EXPECTED_USERNAME, + EXPECTED_PASSWORD +}; \ No newline at end of file diff --git a/demo-site/utils/utils.js b/demo-site/utils/utils.js new file mode 100644 index 0000000..7e05ed1 --- /dev/null +++ b/demo-site/utils/utils.js @@ -0,0 +1,115 @@ +// region imports +const fs = require('fs'); +const path = require('path'); +const process = require('process'); +const readline = require('readline'); +const { SERVERS_DIRECTORY_PATH, SERVER_CONFIG_FILE_NAME, SERVER_CONFIG_INC_FILE_NAME, JSON_SPACING } = require('./constants'); +// endregion + +const forEachServer = async (callback) => { + const serverDirectories = fs.readdirSync(SERVERS_DIRECTORY_PATH); + for (const serverName of serverDirectories) { + const serverPath = getServerAbsolutePath(serverName); + const serverConfig = getServerConfig(serverPath); + + if (!serverConfig) { + console.error("Couldn't get configs for", serverName); + continue; + } + + await callback(serverName, serverPath, serverConfig); + } +}; + +const getServerConfig = (serverPath) => { + if (!path.isAbsolute(serverPath)) { + serverPath = getServerAbsolutePath(serverPath); + } + const configJson = loadJson(path.join(serverPath, `${SERVER_CONFIG_FILE_NAME}`)); + if (configJson) { + return configJson; + } + + const configIncJson = loadJson(path.join(serverPath, `${SERVER_CONFIG_INC_FILE_NAME}`)); + if (configIncJson) { + console.log(`No ${serverPath}/${SERVER_CONFIG_FILE_NAME}, using ${SERVER_CONFIG_INC_FILE_NAME} instead!`); + return configIncJson; + } + + console.error(`No ${serverPath}/${SERVER_CONFIG_FILE_NAME} or ${SERVER_CONFIG_INC_FILE_NAME} files found!`); + return null; +}; + +const getServerAbsolutePath = (serverDir) => { + return path.join(SERVERS_DIRECTORY_PATH, serverDir); +}; + +const loadJson = (path) => { + if (fs.existsSync(path)) { + return JSON.parse(fs.readFileSync(path)); + } + return null; +} + +const saveJson = (filename, jsonObject) => { + fs.writeFileSync(filename, JSON.stringify(jsonObject, null, JSON_SPACING)); +} + +const sortObjectAlphabeticallyByKey = (object) => { + return Object.keys(object).sort().reduce((obj, key) => { + obj[key] = object[key]; + return obj; + }, {}); +}; + +const getUserInput = (query) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + return new Promise((resolve) => rl.question(query + " ", (ans) => { + rl.close(); + resolve(ans); + })); +}; + +const capitalize = (string) => { + return string.charAt(0).toUpperCase() + string.slice(1); +}; + +const normalizeEnforcerName = (enforcerName) => { + return enforcerName.split(/[_\.\-]/g).map(capitalize).join(' '); +}; + +const allEnvVariablesExist = (requiredEnvVariables) => { + if (!requiredEnvVariables || !Array.isArray(requiredEnvVariables)) { + return false; + } + + const messages = []; + for (const variableName of requiredEnvVariables) { + if (!process.env[variableName]) { + messages.push(`You must define environment variable ${variableName}`); + } + } + + if (messages.length === 0) { + return true; + } + console.log(messages.join("\n")); + return false; +} + +module.exports = { + forEachServer, + getServerConfig, + getServerAbsolutePath, + loadJson, + saveJson, + sortObjectAlphabeticallyByKey, + getUserInput, + capitalize, + normalizeEnforcerName, + allEnvVariablesExist +}; \ No newline at end of file diff --git a/px_metadata.json b/px_metadata.json index dba065e..392c83f 100644 --- a/px_metadata.json +++ b/px_metadata.json @@ -6,7 +6,6 @@ "batched_activities", "block_activity", "block_page_captcha", - "block_page_hard_block", "block_page_rate_limit", "block_page_js_challenge", "bypass_monitor_header", @@ -42,5 +41,21 @@ "sensitive_headers", "telemetry_command", "vid_extraction" + ], + "excluded_tests": [ + "test_additional_activity_handler_with_score_from_risk", + "test_additional_activity_handler_with_score_from_cookie", + "test_advanced_blocking_response", + "test_preflight_request_returns_custom_preflight_handler_response", + "test_pxde_extraction_s2s", + "test_pxde_extraction_unverified", + "test_pxde_extraction_verified", + "test_pxhd_should_return_on_set_cookie_header_when_received_from_risk", + "test_pxhd_should_be_on_set_cookie_even_if_domain_is_none", + "test_vid_extraction_on_first_party_xhr", + "test_block_activity_cookie_origin", + "test_page_requested_activity_cookie_origin", + "test_risk_api_validate_cookie_origin", + "test_risk_cookie_valid_cookie_with_user_agent_bigger_than_max_length" ] } \ No newline at end of file