From 83a6d017fc0188f6cdbaa9d1581c8e5c0a348bd0 Mon Sep 17 00:00:00 2001 From: M Fikri A Date: Fri, 19 Mar 2021 09:29:33 +0700 Subject: [PATCH 1/5] Implement Secure Identity Verification --- config/custom-environment-variables.js | 1 + docs/secure-identity-verification.md | 9 +++++++++ src/client/index.jsx | 25 ++++++++++++++++++++----- src/shared/reducers/index.js | 24 +++++++++++++++++++++++- 4 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 docs/secure-identity-verification.md diff --git a/config/custom-environment-variables.js b/config/custom-environment-variables.js index 34ffc3cb89..280e961b4f 100644 --- a/config/custom-environment-variables.js +++ b/config/custom-environment-variables.js @@ -99,6 +99,7 @@ module.exports = { RECRUITCRM_API_KEY: 'RECRUITCRM_API_KEY', GROWSURF_API_KEY: 'GROWSURF_API_KEY', SENDGRID_API_KEY: 'SENDGRID_API_KEY', + CHAMELEON_VERIFICATION_SECRET: 'CHAMELEON_VERIFICATION_SECRET', }, GROWSURF_CAMPAIGN_ID: 'GROWSURF_CAMPAIGN_ID', AUTH_CONFIG: { diff --git a/docs/secure-identity-verification.md b/docs/secure-identity-verification.md new file mode 100644 index 0000000000..4f5d6bb2a5 --- /dev/null +++ b/docs/secure-identity-verification.md @@ -0,0 +1,9 @@ +## Setup +1. Make sure you have a Chameleon Account and segment.com account +2. Integrate Chameleon Account with Segment. https://help.trychameleon.com/en/articles/1161770-installing-using-segment +3. Set Environment secret variable retrieved here https://app.trychameleon.com/settings/integrations/segment. Run the following command +`export CHAMELEON_VERIFICATION_SECRET=` +4. Run community app + +## Verification +1. When you reload the page you will notice in the network tab there will be 2 requests POST to https://api.segment.io/v1/i, one will send it to segment and one will send only to chameleon (with request payload `{ integrations: { All: false, Chameleon: true }}`) diff --git a/src/client/index.jsx b/src/client/index.jsx index b44745e76e..b879711ac2 100644 --- a/src/client/index.jsx +++ b/src/client/index.jsx @@ -23,9 +23,10 @@ const { setErrorsStore } = errors; * Performs AnalyticsJS identification of the user. * @param {Object} profile TC user profile. * @param {Array} roles User roles. + * @param {String} userIdHash Unique Hash per user. */ -function identify(profile, roles) { - analytics.identify(profile.userId, { +function identify(profile, roles, userIdHash) { + const payload = { avatar: profile.photoURL, createdAt: profile.createdAt, email: profile.email, @@ -39,7 +40,21 @@ function identify(profile, roles) { })), tracks: profile.tracks || [], username: profile.handle, - }); + }; + analytics.identify( + profile.userId, + payload, + { + integrations: { Chameleon: false }, + }, + ); + analytics.identify( + profile.userId, + { uid_hash: userIdHash, ...payload }, + { + integrations: { All: false, Chameleon: true }, + }, + ); } /** @@ -74,7 +89,7 @@ function authenticate(store) { }).then(({ tctV2, tctV3 }) => { const { auth } = store.getState(); if (auth.profile && !analyticsIdentitySet) { - identify(auth.profile, _.get(auth, 'user.roles')); + identify(auth.profile, _.get(auth, 'user.roles'), auth.userIdHash); analyticsIdentitySet = true; } if (auth.tokenV3 !== (tctV3 || null)) { @@ -85,7 +100,7 @@ function authenticate(store) { const userId = profile && profile.userId; const prevUserId = _.get(store.getState(), 'auth.profile.userId'); if (userId && userId !== prevUserId) { - identify(profile, _.get(auth, user.roles)); + identify(profile, _.get(auth, user.roles), auth.userIdHash); analyticsIdentitySet = true; } }); diff --git a/src/shared/reducers/index.js b/src/shared/reducers/index.js index 518f7de831..2a49d02df0 100644 --- a/src/shared/reducers/index.js +++ b/src/shared/reducers/index.js @@ -15,8 +15,9 @@ */ import _ from 'lodash'; +import crypto from 'crypto'; import { getCommunityId } from 'server/services/communities'; -import { redux } from 'topcoder-react-utils'; +import { redux, config, isomorphy } from 'topcoder-react-utils'; import { reducer as toastrReducer } from 'react-redux-toastr'; import { reducerFactory } from 'topcoder-react-lib'; import { getAuthTokens } from 'utils/tc'; @@ -113,6 +114,21 @@ function generateSsrOptions(req) { return res; } +/** + * Generate user id hash for secure Identity verification + * @param {Object} user + * @return {String} User Id Hash. + */ +function generateUserIdHash(user) { + const secret = _.get(config, 'SECRET.CHAMELEON_VERIFICATION_SECRET'); + const now = Math.floor(Date.now() / 1000); + + return [ + crypto.createHmac('sha256', secret).update(`${user.id}-${now}`).digest('hex'), + now, + ].join('-'); +} + export function factory(req) { return redux.resolveReducers({ standard: reducerFactory(req && generateSsrOptions(req)), @@ -125,6 +141,12 @@ export function factory(req) { page: pageFactory(req), }).then(resolvedReducers => redux.combineReducers((state) => { const res = { ...state }; + + const user = _.get(res, 'auth.user'); + if (user && isomorphy.isServerSide()) { + res.auth.userIdHash = generateUserIdHash(user); + } + if (req) { res.domain = `${req.protocol}://${req.headers.host || req.hostname}`; res.subdomainCommunity = getCommunityId(req.subdomains); From c416d4e77b8ee708edc16ead6e1458ad472cf329 Mon Sep 17 00:00:00 2001 From: M Fikri A Date: Fri, 19 Mar 2021 10:05:49 +0700 Subject: [PATCH 2/5] Use userId --- docs/secure-identity-verification.md | 6 +++++- src/shared/reducers/index.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/secure-identity-verification.md b/docs/secure-identity-verification.md index 4f5d6bb2a5..896bc450ad 100644 --- a/docs/secure-identity-verification.md +++ b/docs/secure-identity-verification.md @@ -6,4 +6,8 @@ 4. Run community app ## Verification -1. When you reload the page you will notice in the network tab there will be 2 requests POST to https://api.segment.io/v1/i, one will send it to segment and one will send only to chameleon (with request payload `{ integrations: { All: false, Chameleon: true }}`) +1. Log in to topcoder-dev account +2. Access http://local.topcoder-dev.com/challenges +3. You will notice in the network tab there will be 2 requests POST to https://api.segment.io/v1/i, one will send it to segment and one will send only to chameleon (with request payload `{ integrations: { All: false, Chameleon: true }}`) + +Repeat the proses and log in to different account and make sure the `uid_hash` is different for each different user. diff --git a/src/shared/reducers/index.js b/src/shared/reducers/index.js index 2a49d02df0..42fcb4d19c 100644 --- a/src/shared/reducers/index.js +++ b/src/shared/reducers/index.js @@ -124,7 +124,7 @@ function generateUserIdHash(user) { const now = Math.floor(Date.now() / 1000); return [ - crypto.createHmac('sha256', secret).update(`${user.id}-${now}`).digest('hex'), + crypto.createHmac('sha256', secret).update(`${user.userId}-${now}`).digest('hex'), now, ].join('-'); } From 38931560832f7fd4be377ab104688ccf7812444d Mon Sep 17 00:00:00 2001 From: Luiz Ricardo Rodrigues Date: Fri, 2 Apr 2021 04:52:01 -0300 Subject: [PATCH 3/5] ci: Deploy update-segment-script to Dev and Stag --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7d107a6dc6..a67f61b412 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -276,7 +276,7 @@ workflows: branches: only: - develop - - feature/recommender-sync-develop + - update-segment-script # This is alternate dev env for parallel testing - "build-test": context : org-global @@ -305,7 +305,7 @@ workflows: branches: only: - develop - - feature/recommender-sync-develop + - update-segment-script - "approve-smoke-test-on-staging": type: approval requires: From 48d046072cd5e72a4d9bad6b2da72a91a73ee1cc Mon Sep 17 00:00:00 2001 From: "Luiz R. Rodrigues" Date: Thu, 15 Apr 2021 06:54:39 -0300 Subject: [PATCH 4/5] Added CHAMELEON_VERIFICATION_SECRET to Dockerfile --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 97e99ee223..4ef22556d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,6 +52,7 @@ ARG MAILCHIMP_BASE_URL ARG NODE_CONFIG_ENV ARG OPEN_EXCHANGE_RATES_KEY ARG SEGMENT_IO_API_KEY +ARG CHAMELEON_VERIFICATION_SECRET ARG SERVER_API_KEY # TC M2M credentials for Community App server @@ -108,6 +109,7 @@ ENV MAILCHIMP_BASE_URL=$MAILCHIMP_BASE_URL ENV NODE_CONFIG_ENV=$NODE_CONFIG_ENV ENV OPEN_EXCHANGE_RATES_KEY=$OPEN_EXCHANGE_RATES_KEY ENV SEGMENT_IO_API_KEY=$SEGMENT_IO_API_KEY +ENV CHAMELEON_VERIFICATION_SECRET=$CHAMELEON_VERIFICATION_SECRET ENV SERVER_API_KEY=$SERVER_API_KEY # TC M2M credentials for Community App server From 91d2efd0d4de6076a143379c42960b701610b2b8 Mon Sep 17 00:00:00 2001 From: "Luiz R. Rodrigues" Date: Thu, 15 Apr 2021 07:51:58 -0300 Subject: [PATCH 5/5] Added missing build var --- build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sh b/build.sh index 32b4414968..e5e7fb59f4 100755 --- a/build.sh +++ b/build.sh @@ -32,6 +32,7 @@ docker build -t $TAG \ --build-arg NODE_CONFIG_ENV=$NODE_CONFIG_ENV \ --build-arg OPEN_EXCHANGE_RATES_KEY=$OPEN_EXCHANGE_RATES_KEY \ --build-arg SEGMENT_IO_API_KEY=$SEGMENT_IO_API_KEY \ + --build-arg CHAMELEON_VERIFICATION_SECRET=$CHAMELEON_VERIFICATION_SECRET \ --build-arg SERVER_API_KEY=$SERVER_API_KEY \ --build-arg TC_M2M_CLIENT_ID=$TC_M2M_CLIENT_ID \ --build-arg TC_M2M_CLIENT_SECRET=$TC_M2M_CLIENT_SECRET \