From e770e25573e748612e6218651110c9ef5217b276 Mon Sep 17 00:00:00 2001 From: gets0ul Date: Fri, 20 Mar 2020 04:23:14 +0700 Subject: [PATCH 1/2] - change maskInviteEmails to postProcessInvites which will be post-processing on invite(s) with following constraints: 1. email field will be omitted from invite if the invite has defined userId 2. email field (if existed) will be masked UNLESS current user has admin permissions OR current user created this invite - also apply this post-processing when creating invite. --- src/routes/projectMemberInvites/create.js | 4 +- .../projectMemberInvites/create.spec.js | 12 +-- src/routes/projectMemberInvites/get.js | 2 +- src/routes/projectMemberInvites/get.spec.js | 37 ++++++++- src/routes/projectMemberInvites/list.js | 2 +- src/routes/projectMemberInvites/list.spec.js | 38 ++++++++- src/routes/projectMemberInvites/update.js | 4 +- src/routes/projects/get.js | 2 +- src/routes/projects/get.spec.js | 22 +++++ src/routes/projects/list.js | 18 +++-- src/routes/projects/list.spec.js | 11 ++- src/util.js | 37 +++++++-- src/util.spec.js | 81 +++++++++++++++++-- 13 files changed, 235 insertions(+), 35 deletions(-) diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 336ab12f..bdd8fb52 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -395,9 +395,9 @@ module.exports = [ }) )) .then((values) => { - const response = _.assign({}, { success: values }); + const response = _.assign({}, { success: util.postProcessInvites('$[*]', values, req) }); if (failed.length) { - res.status(403).json(_.assign({}, response, { failed })); + res.status(403).json(_.assign({}, response, { failed: util.postProcessInvites('$[*]', failed, req) })); } else { res.status(201).json(response); } diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js index cfb4f44b..bbfb3974 100644 --- a/src/routes/projectMemberInvites/create.spec.js +++ b/src/routes/projectMemberInvites/create.spec.js @@ -393,7 +393,7 @@ describe('Project Member Invite create', () => { should.exist(resJson); resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); - resJson.email.should.equal('hello@world.com'); + resJson.email.should.equal('h***o@w***d.com'); resJson.hashEmail.should.equal(md5('hello@world.com')); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); @@ -446,8 +446,8 @@ describe('Project Member Invite create', () => { resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); resJson.userId.should.equal(12345); - resJson.email.should.equal('hello@world.com'); - resJson.hashEmail.should.equal(md5('hello@world.com')); + should.not.exist(resJson.email); + should.not.exist(resJson.hashEmail); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); } @@ -563,7 +563,7 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); - resJson[0].email.should.equal('romit.choudhary@rivigo.com'); + resJson[0].email.should.equal('r***y@r***o.com'); resJson[0].message.should.equal('User with such email is already a member of the team.'); resJson.length.should.equal(1); server.services.pubsub.publish.neverCalledWith('project.member.invite.created').should.be.true; @@ -802,7 +802,7 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); - resJson[0].email.should.equal('duplicate_lowercase@test.com'); + resJson[0].email.should.equal('d***e@t***t.com'); resJson[0].message.should.equal('User with such email is already invited to this project.'); resJson.length.should.equal(1); done(); @@ -828,7 +828,7 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); - resJson[0].email.should.equal('DUPLICATE_UPPERCASE@test.com'); // email is masked + resJson[0].email.should.equal('D***E@t***t.com'); // email is masked resJson[0].message.should.equal('User with such email is already invited to this project.'); resJson.length.should.equal(1); done(); diff --git a/src/routes/projectMemberInvites/get.js b/src/routes/projectMemberInvites/get.js index 205e3113..05552f82 100644 --- a/src/routes/projectMemberInvites/get.js +++ b/src/routes/projectMemberInvites/get.js @@ -114,7 +114,7 @@ module.exports = [ return invite; }) )) - .then(invite => res.json(util.maskInviteEmails('$[*].email', invite, req))) + .then(invite => res.json(util.postProcessInvites('$.email', invite, req))) .catch(next); }, ]; diff --git a/src/routes/projectMemberInvites/get.spec.js b/src/routes/projectMemberInvites/get.spec.js index 532221c6..294657a7 100644 --- a/src/routes/projectMemberInvites/get.spec.js +++ b/src/routes/projectMemberInvites/get.spec.js @@ -55,7 +55,7 @@ describe('GET Project Member Invite', () => { const invite2 = models.ProjectMemberInvite.create({ id: 2, userId: testUtil.userIds.copilot, - email: null, + email: 'test@topcoder.com', projectId: project1.id, role: 'copilot', createdBy: 1, @@ -80,6 +80,15 @@ describe('GET Project Member Invite', () => { }).then((p) => { project2 = p; + // create members + const pm2 = models.ProjectMember.create({ + userId: testUtil.userIds.romit, + projectId: project2.id, + role: 'copilot', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }); // create invite 3 const invite3 = models.ProjectMemberInvite.create({ id: 3, @@ -104,7 +113,7 @@ describe('GET Project Member Invite', () => { status: INVITE_STATUS.ACCEPTED, }); - return Promise.all([invite3, invite4]); + return Promise.all([pm2, invite3, invite4]); }); return Promise.all([p1, p2]) .then(() => done()); @@ -206,6 +215,7 @@ describe('GET Project Member Invite', () => { const resJson = res.body; should.exist(resJson); should.exist(resJson.projectId); + should.not.exist(resJson.email); resJson.id.should.be.eql(2); resJson.userId.should.be.eql(testUtil.userIds.copilot); resJson.status.should.be.eql(INVITE_STATUS.PENDING); @@ -237,5 +247,28 @@ describe('GET Project Member Invite', () => { } }); }); + + it('should return the invite with masked email if user get not his/her own invitation by email', (done) => { + request(server) + .get(`/v5/projects/${project2.id}/invites/3`) + .set({ + Authorization: `Bearer ${testUtil.jwts.romit}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + should.exist(resJson.projectId); + resJson.id.should.be.eql(3); + resJson.email.should.be.eql('t***t@t***r.com'); + resJson.status.should.be.eql(INVITE_STATUS.PENDING); + done(); + } + }); + }); }); }); diff --git a/src/routes/projectMemberInvites/list.js b/src/routes/projectMemberInvites/list.js index a8a6dcfd..7c531930 100644 --- a/src/routes/projectMemberInvites/list.js +++ b/src/routes/projectMemberInvites/list.js @@ -103,7 +103,7 @@ module.exports = [ return invites; }) )) - .then(invites => res.json(util.maskInviteEmails('$[*].email', invites, req))) + .then(invites => res.json(util.postProcessInvites('$[*]', invites, req))) .catch(next); }, ]; diff --git a/src/routes/projectMemberInvites/list.spec.js b/src/routes/projectMemberInvites/list.spec.js index 1ea32bef..68faa61a 100644 --- a/src/routes/projectMemberInvites/list.spec.js +++ b/src/routes/projectMemberInvites/list.spec.js @@ -81,6 +81,16 @@ describe('GET Project Member Invites', () => { }).then((p) => { project2 = p; + // create members + const pm2 = models.ProjectMember.create({ + userId: testUtil.userIds.romit, + projectId: project2.id, + role: 'copilot', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }); + // create invite 3 const invite3 = models.ProjectMemberInvite.create({ id: 3, @@ -105,7 +115,7 @@ describe('GET Project Member Invites', () => { status: INVITE_STATUS.ACCEPTED, }); - return Promise.all([invite3, invite4]); + return Promise.all([pm2, invite3, invite4]); }); return Promise.all([p1, p2]) .then(() => done()); @@ -209,6 +219,7 @@ describe('GET Project Member Invites', () => { resJson.length.should.be.eql(1); // check invitations _.filter(resJson, inv => inv.id === 2).length.should.be.eql(1); + should.not.exist(resJson[0].email); done(); } }); @@ -253,6 +264,31 @@ describe('GET Project Member Invites', () => { resJson.length.should.be.eql(1); // check invitations _.filter(resJson, inv => inv.id === 3).length.should.be.eql(1); + resJson[0].email.should.be.eql('test@topcoder.com'); + done(); + } + }); + }); + + it('should return the invite with masked email if user get not his/her own invitation by email', (done) => { + request(server) + .get(`/v5/projects/${project2.id}/invites`) + .set({ + Authorization: `Bearer ${testUtil.jwts.romit}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.be.an('array'); + resJson.length.should.be.eql(1); + // check invitations + _.filter(resJson, inv => inv.id === 3).length.should.be.eql(1); + resJson[0].email.should.be.eql('t***t@t***r.com'); done(); } }); diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 20184c60..490b61ae 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -122,11 +122,11 @@ module.exports = [ }; return util .addUserToProject(req, member) - .then(() => res.json(util.maskInviteEmails('$.email', updatedInvite, req))) + .then(() => res.json(util.postProcessInvites('$.email', updatedInvite, req))) .catch(err => next(err)); }); } - return res.json(util.maskInviteEmails('$.email', updatedInvite, req)); + return res.json(util.postProcessInvites('$.email', updatedInvite, req)); }); }) .catch(next); diff --git a/src/routes/projects/get.js b/src/routes/projects/get.js index a8aee1a4..fbb18481 100644 --- a/src/routes/projects/get.js +++ b/src/routes/projects/get.js @@ -186,7 +186,7 @@ module.exports = [ req.log.debug('Project found in ES'); return result; }).then((project) => { - res.status(200).json(util.maskInviteEmails('$.invites[?(@.email)]', project, req)); + res.status(200).json(util.postProcessInvites('$.invites[?(@.email)]', project, req)); }) .catch(err => next(err)); }, diff --git a/src/routes/projects/get.spec.js b/src/routes/projects/get.spec.js index 1a3ab773..8f220e3a 100644 --- a/src/routes/projects/get.spec.js +++ b/src/routes/projects/get.spec.js @@ -526,6 +526,28 @@ describe('GET Project', () => { }); }); + it('should not return "email" for any invite which has userId field', (done) => { + request(server) + .get(`/v5/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.invites.length.should.be.eql(1); + resJson.invites[0].should.have.property('userId'); + should.not.exist(resJson.invites[0].email); + done(); + } + }); + }); + it('should only return "members.role" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) .get(`/v5/projects/${project1.id}?fields=members.role`) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index 135ab145..4dcafca8 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -629,15 +629,18 @@ module.exports = [ // so we don't want DB to return unrelated data, ref issue #450 if (_.intersection(_.keys(filters), SUPPORTED_FILTERS).length > 0) { req.log.debug('Don\'t fallback to DB because some filters are defined.'); - return util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', result, req)); + return util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', result, req)); } return retrieveProjectsFromDB(req, criteria, sort, req.query.fields) - .then(r => util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', r, req))); + .then(r => util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', r, req))); } req.log.debug('Projects found in ES'); // set header - return util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', result, req)); + return util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', result, req)); }) .catch(err => next(err)); } @@ -655,14 +658,17 @@ module.exports = [ // so we don't want DB to return unrelated data, ref issue #450 if (_.intersection(_.keys(filters), SUPPORTED_FILTERS).length > 0) { req.log.debug('Don\'t fallback to DB because some filters are defined.'); - return util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', result, req)); + return util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', result, req)); } return retrieveProjectsFromDB(req, criteria, sort, req.query.fields) - .then(r => util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', r, req))); + .then(r => util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', r, req))); } req.log.debug('Projects found in ES'); - return util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', result, req)); + return util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', result, req)); }) .catch(err => next(err)); }, diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js index a23995a8..6c5a3bff 100644 --- a/src/routes/projects/list.spec.js +++ b/src/routes/projects/list.spec.js @@ -173,8 +173,8 @@ describe('LIST Project', () => { let project1; let project2; let project3; - before(function inner(done) { - this.timeout(10000); + before((done) => { + // this.timeout(10000); testUtil.clearDb() .then(() => testUtil.clearES()) .then(() => { @@ -376,6 +376,10 @@ describe('LIST Project', () => { const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(2); + resJson[0].invites[0].should.have.property('userId'); + should.not.exist(resJson[0].invites[0].email); + resJson[1].invites[0].should.have.property('userId'); + should.not.exist(resJson[1].invites[0].email); done(); } }); @@ -1070,6 +1074,9 @@ describe('LIST Project', () => { should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); + resJson[0].invites.should.have.lengthOf(1); + resJson[0].invites[0].should.have.property('userId'); + should.not.exist(resJson[0].invites[0].email); done(); } }); diff --git a/src/util.js b/src/util.js index 2accedbc..ff104255 100644 --- a/src/util.js +++ b/src/util.js @@ -639,7 +639,11 @@ _.assignIn(util, { } }, /** - * Mask email in the fields defined by `jsonPath` in the `data`. + * Post-process given invite(s) with following constraints: + * - email field will be omitted from invite if the invite has defined userId + * - email field (if existed) will be masked UNLESS current user has admin permissions OR current user created this invite + * + * Email to be masked is found in the fields defined by `jsonPath` in the `data`. * Immutable - doesn't modify data, but creates a clone. * * @param {String} jsonPath jsonpath string @@ -648,24 +652,45 @@ _.assignIn(util, { * * @return {Object} data has been processed */ - maskInviteEmails: (jsonPath, data, req) => { + postProcessInvites: (jsonPath, data, req) => { // clone data to avoid mutations const dataClone = _.cloneDeep(data); const isAdmin = util.hasPermission({ topcoderRoles: [USER_ROLE.TOPCODER_ADMIN] }, req.authUser); + const currentUserEmail = req.authUser.email; if (isAdmin) { // even though we didn't make any changes to the data, return a clone here for consistency return dataClone; } + const postProcessInvite = (invite) => { + if (!_.has(invite, 'email')) { + return invite; + } + let email; + if (!invite.userId) { + // mask email if non-admin or not own invite + email = isAdmin || invite.email === currentUserEmail ? invite.email : util.maskEmail(invite.email); + } else { + // userId is defined, no email field returned + email = null; + } + _.assign(invite, { email }); + if (!invite.email && _.has(invite, 'hashEmail')) { + _.assign(invite, { hashEmail: null }); + } + return invite; + }; + jp.apply(dataClone, jsonPath, (value) => { if (_.isObject(value)) { - _.assign(value, { email: util.maskEmail(value.email) }); - return value; + // data contains nested invite object + return postProcessInvite(value); } - // isString or null - return util.maskEmail(value); + // data is single invite object + // value is string or null + return postProcessInvite(dataClone).email; }); return dataClone; diff --git a/src/util.spec.js b/src/util.spec.js index 80dc973a..78b174cb 100644 --- a/src/util.spec.js +++ b/src/util.spec.js @@ -43,7 +43,7 @@ describe('Util method', () => { }); }); - describe('maskInviteEmails', () => { + describe('postProcessInvites', () => { it('should mask emails when passing data like for a project list endpoint for non-admin user', () => { const list = [ { @@ -68,7 +68,7 @@ describe('Util method', () => { const res = { authUser: { userId: 2 }, }; - util.maskInviteEmails('$..invites[?(@.email)]', list, res).should.deep.equal(list2); + util.postProcessInvites('$..invites[?(@.email)]', list, res).should.deep.equal(list2); }); it('should mask emails when passing data like for a project details endpoint for non-admin user', () => { @@ -91,7 +91,7 @@ describe('Util method', () => { const res = { authUser: { userId: 2 }, }; - util.maskInviteEmails('$..invites[?(@.email)]', detail, res).should.deep.equal(detail2); + util.postProcessInvites('$..invites[?(@.email)]', detail, res).should.deep.equal(detail2); }); it('should mask emails when passing data like for a single invite endpoint for non-admin user', () => { @@ -114,7 +114,7 @@ describe('Util method', () => { const res = { authUser: { userId: 2 }, }; - util.maskInviteEmails('$.success[?(@.email)]', detail, res).should.deep.equal(detail2); + util.postProcessInvites('$.success[?(@.email)]', detail, res).should.deep.equal(detail2); }); it('should NOT mask emails when passing data like for a single invite endpoint for admin user', () => { @@ -137,7 +137,78 @@ describe('Util method', () => { const res = { authUser: { userId: 2, roles: ['administrator'] }, }; - util.maskInviteEmails('$..email', detail, res).should.deep.equal(detail2); + util.postProcessInvites('$.success[?(@.email)]', detail, res).should.deep.equal(detail2); + }); + + it('should NOT mask emails when passing data like for a single invite endpoint for user\'s own invite', () => { + const detail = { + success: [ + { + id: 1, + email: 'abcd@aaaa.com', + }, + ], + }; + const detail2 = { + success: [ + { + id: 1, + email: 'abcd@aaaa.com', + }, + ], + }; + const res = { + authUser: { userId: 2, email: 'abcd@aaaa.com' }, + }; + util.postProcessInvites('$.success[?(@.email)]', detail, res).should.deep.equal(detail2); + }); + + it('should NOT mask emails when passing data like for a project details endpoint for user\'s own invite', () => { + const detail = { + id: 1, + invites: [{ + id: 2, + email: 'abcd@aaaa.com', + }, + ], + }; + const detail2 = { + id: 1, + invites: [{ + id: 2, + email: 'abcd@aaaa.com', + }, + ], + }; + const res = { + authUser: { userId: 2, email: 'abcd@aaaa.com' }, + }; + util.postProcessInvites('$.invites[?(@.email)]', detail, res).should.deep.equal(detail2); + }); + + it('should not return emails for invite with defined userId', () => { + const detail = { + id: 1, + invites: [{ + id: 2, + email: 'abcd@aaaa.com', + userId: 33, + }, + ], + }; + const detail2 = { + id: 1, + invites: [{ + id: 2, + email: null, + userId: 33, + }, + ], + }; + const res = { + authUser: { userId: 2 }, + }; + util.postProcessInvites('$..invites[?(@.email)]', detail, res).should.deep.equal(detail2); }); }); From 96a3855103d56174d7967679392443ed44fe3971 Mon Sep 17 00:00:00 2001 From: gets0ul Date: Sat, 21 Mar 2020 19:28:06 +0700 Subject: [PATCH 2/2] Fixes after feedback. --- src/routes/projectMemberInvites/create.js | 2 +- .../projectMemberInvites/create.spec.js | 10 +++-- src/routes/projectMemberInvites/get.spec.js | 39 ++----------------- src/routes/projectMemberInvites/list.spec.js | 38 +----------------- src/routes/projects/list.spec.js | 13 +++++-- src/util.js | 4 +- src/util.spec.js | 4 ++ 7 files changed, 29 insertions(+), 81 deletions(-) diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index bdd8fb52..a06e1bed 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -397,7 +397,7 @@ module.exports = [ .then((values) => { const response = _.assign({}, { success: util.postProcessInvites('$[*]', values, req) }); if (failed.length) { - res.status(403).json(_.assign({}, response, { failed: util.postProcessInvites('$[*]', failed, req) })); + res.status(403).json(_.assign({}, response, { failed })); } else { res.status(201).json(response); } diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js index bbfb3974..17f4422d 100644 --- a/src/routes/projectMemberInvites/create.spec.js +++ b/src/routes/projectMemberInvites/create.spec.js @@ -393,7 +393,7 @@ describe('Project Member Invite create', () => { should.exist(resJson); resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); - resJson.email.should.equal('h***o@w***d.com'); + resJson.email.should.equal('hello@world.com'); resJson.hashEmail.should.equal(md5('hello@world.com')); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); @@ -495,6 +495,8 @@ describe('Project Member Invite create', () => { resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); resJson.userId.should.equal(40051331); + should.not.exist(resJson.email); + should.not.exist(resJson.hashEmail); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); } @@ -563,7 +565,7 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); - resJson[0].email.should.equal('r***y@r***o.com'); + resJson[0].email.should.equal('romit.choudhary@rivigo.com'); resJson[0].message.should.equal('User with such email is already a member of the team.'); resJson.length.should.equal(1); server.services.pubsub.publish.neverCalledWith('project.member.invite.created').should.be.true; @@ -802,7 +804,7 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); - resJson[0].email.should.equal('d***e@t***t.com'); + resJson[0].email.should.equal('duplicate_lowercase@test.com'); resJson[0].message.should.equal('User with such email is already invited to this project.'); resJson.length.should.equal(1); done(); @@ -828,7 +830,7 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); - resJson[0].email.should.equal('D***E@t***t.com'); // email is masked + resJson[0].email.should.equal('DUPLICATE_UPPERCASE@test.com'); resJson[0].message.should.equal('User with such email is already invited to this project.'); resJson.length.should.equal(1); done(); diff --git a/src/routes/projectMemberInvites/get.spec.js b/src/routes/projectMemberInvites/get.spec.js index 294657a7..6bcb88ec 100644 --- a/src/routes/projectMemberInvites/get.spec.js +++ b/src/routes/projectMemberInvites/get.spec.js @@ -80,15 +80,6 @@ describe('GET Project Member Invite', () => { }).then((p) => { project2 = p; - // create members - const pm2 = models.ProjectMember.create({ - userId: testUtil.userIds.romit, - projectId: project2.id, - role: 'copilot', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); // create invite 3 const invite3 = models.ProjectMemberInvite.create({ id: 3, @@ -113,7 +104,7 @@ describe('GET Project Member Invite', () => { status: INVITE_STATUS.ACCEPTED, }); - return Promise.all([pm2, invite3, invite4]); + return Promise.all([invite3, invite4]); }); return Promise.all([p1, p2]) .then(() => done()); @@ -215,7 +206,8 @@ describe('GET Project Member Invite', () => { const resJson = res.body; should.exist(resJson); should.exist(resJson.projectId); - should.not.exist(resJson.email); + should.not.exist(resJson.email); + should.not.exist(resJson.hashEmail); resJson.id.should.be.eql(2); resJson.userId.should.be.eql(testUtil.userIds.copilot); resJson.status.should.be.eql(INVITE_STATUS.PENDING); @@ -240,35 +232,12 @@ describe('GET Project Member Invite', () => { should.exist(resJson); should.exist(resJson.projectId); resJson.id.should.be.eql(3); - resJson.email.should.be.eql('test@topcoder.com'); + resJson.email.should.be.eql('t***t@t***r.com'); // masked resJson.hashEmail.should.be.eql(md5('test@topcoder.com')); resJson.status.should.be.eql(INVITE_STATUS.PENDING); done(); } }); }); - - it('should return the invite with masked email if user get not his/her own invitation by email', (done) => { - request(server) - .get(`/v5/projects/${project2.id}/invites/3`) - .set({ - Authorization: `Bearer ${testUtil.jwts.romit}`, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - const resJson = res.body; - should.exist(resJson); - should.exist(resJson.projectId); - resJson.id.should.be.eql(3); - resJson.email.should.be.eql('t***t@t***r.com'); - resJson.status.should.be.eql(INVITE_STATUS.PENDING); - done(); - } - }); - }); }); }); diff --git a/src/routes/projectMemberInvites/list.spec.js b/src/routes/projectMemberInvites/list.spec.js index 68faa61a..10631634 100644 --- a/src/routes/projectMemberInvites/list.spec.js +++ b/src/routes/projectMemberInvites/list.spec.js @@ -81,16 +81,6 @@ describe('GET Project Member Invites', () => { }).then((p) => { project2 = p; - // create members - const pm2 = models.ProjectMember.create({ - userId: testUtil.userIds.romit, - projectId: project2.id, - role: 'copilot', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); - // create invite 3 const invite3 = models.ProjectMemberInvite.create({ id: 3, @@ -115,7 +105,7 @@ describe('GET Project Member Invites', () => { status: INVITE_STATUS.ACCEPTED, }); - return Promise.all([pm2, invite3, invite4]); + return Promise.all([invite3, invite4]); }); return Promise.all([p1, p2]) .then(() => done()); @@ -264,31 +254,7 @@ describe('GET Project Member Invites', () => { resJson.length.should.be.eql(1); // check invitations _.filter(resJson, inv => inv.id === 3).length.should.be.eql(1); - resJson[0].email.should.be.eql('test@topcoder.com'); - done(); - } - }); - }); - - it('should return the invite with masked email if user get not his/her own invitation by email', (done) => { - request(server) - .get(`/v5/projects/${project2.id}/invites`) - .set({ - Authorization: `Bearer ${testUtil.jwts.romit}`, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - const resJson = res.body; - should.exist(resJson); - resJson.should.be.an('array'); - resJson.length.should.be.eql(1); - // check invitations - _.filter(resJson, inv => inv.id === 3).length.should.be.eql(1); - resJson[0].email.should.be.eql('t***t@t***r.com'); + resJson[0].email.should.be.eql('t***t@t***r.com'); // masked done(); } }); diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js index 6c5a3bff..60bf3701 100644 --- a/src/routes/projects/list.spec.js +++ b/src/routes/projects/list.spec.js @@ -64,6 +64,12 @@ const data = [ email: 'test@topcoder.com', status: 'pending', }, + { + id: 2, + email: 'hello@world.com', + status: 'pending', + createdBy: 1, + }, ], phases: [ @@ -173,8 +179,8 @@ describe('LIST Project', () => { let project1; let project2; let project3; - before((done) => { - // this.timeout(10000); + before(function inner(done) { + this.timeout(10000); testUtil.clearDb() .then(() => testUtil.clearES()) .then(() => { @@ -1074,9 +1080,10 @@ describe('LIST Project', () => { should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); - resJson[0].invites.should.have.lengthOf(1); + resJson[0].invites.should.have.lengthOf(2); resJson[0].invites[0].should.have.property('userId'); should.not.exist(resJson[0].invites[0].email); + resJson[0].invites[1].email.should.equal('h***o@w***d.com'); done(); } }); diff --git a/src/util.js b/src/util.js index ff104255..42c72913 100644 --- a/src/util.js +++ b/src/util.js @@ -657,7 +657,7 @@ _.assignIn(util, { const dataClone = _.cloneDeep(data); const isAdmin = util.hasPermission({ topcoderRoles: [USER_ROLE.TOPCODER_ADMIN] }, req.authUser); - const currentUserEmail = req.authUser.email; + const currentUserId = req.authUser.userId; if (isAdmin) { // even though we didn't make any changes to the data, return a clone here for consistency @@ -671,7 +671,7 @@ _.assignIn(util, { let email; if (!invite.userId) { // mask email if non-admin or not own invite - email = isAdmin || invite.email === currentUserEmail ? invite.email : util.maskEmail(invite.email); + email = isAdmin || invite.createdBy === currentUserId ? invite.email : util.maskEmail(invite.email); } else { // userId is defined, no email field returned email = null; diff --git a/src/util.spec.js b/src/util.spec.js index 78b174cb..03a0693b 100644 --- a/src/util.spec.js +++ b/src/util.spec.js @@ -146,6 +146,7 @@ describe('Util method', () => { { id: 1, email: 'abcd@aaaa.com', + createdBy: 2, }, ], }; @@ -154,6 +155,7 @@ describe('Util method', () => { { id: 1, email: 'abcd@aaaa.com', + createdBy: 2, }, ], }; @@ -169,6 +171,7 @@ describe('Util method', () => { invites: [{ id: 2, email: 'abcd@aaaa.com', + createdBy: 2, }, ], }; @@ -177,6 +180,7 @@ describe('Util method', () => { invites: [{ id: 2, email: 'abcd@aaaa.com', + createdBy: 2, }, ], };