Skip to content

Commit 5d29438

Browse files
authored
Merge pull request #302 from mfikria/feature/fix-email-duplicated-invites
Implement dot notation email validation for gmail
2 parents cb3f60b + 57a2581 commit 5d29438

File tree

3 files changed

+108
-47
lines changed

3 files changed

+108
-47
lines changed

config/default.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,6 @@
5959
"inviteEmailSectionTitle": "Project Invitation",
6060
"connectUrl":"https://connect.topcoder-dev.com",
6161
"accountsAppUrl": "https://accounts.topcoder-dev.com",
62-
"MAX_REVISION_NUMBER": 100
62+
"MAX_REVISION_NUMBER": 100,
63+
"UNIQUE_GMAIL_VALIDATION": true
6364
}

src/routes/projectMemberInvites/create.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,30 @@ const addMemberValidations = {
2828
},
2929
};
3030

31+
/**
32+
* Helper method to check the uniqueness of two emails
33+
*
34+
* @param {String} email1 first email to compare
35+
* @param {String} email2 second email to compare
36+
* @param {Object} options the options
37+
*
38+
* @returns {Boolean} true if two emails are same
39+
*/
40+
const compareEmail = (email1, email2, options = { UNIQUE_GMAIL_VALIDATION: false }) => {
41+
if (options.UNIQUE_GMAIL_VALIDATION) {
42+
// email is gmail
43+
const emailSplit = /(^[\w.+-]+)(@gmail\.com|@googlemail\.com)$/g.exec(_.toLower(email1));
44+
if (emailSplit) {
45+
const address = emailSplit[1].replace('.', '');
46+
const emailDomain = emailSplit[2].replace('.', '\\.');
47+
const regexAddress = address.split('').join('\\.?');
48+
const regex = new RegExp(`${regexAddress}${emailDomain}`);
49+
return regex.test(_.toLower(email2));
50+
}
51+
}
52+
return _.toLower(email1) === _.toLower(email2);
53+
};
54+
3155
/**
3256
* Helper method to build promises for creating new invites in DB
3357
*
@@ -68,7 +92,8 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => {
6892
});
6993
// non-existent users we will invite them by email only
7094
const nonExistentUserEmails = invite.emails.filter(inviteEmail =>
71-
!_.find(existentUsers, { email: inviteEmail }),
95+
!_.find(existentUsers, existentUser =>
96+
compareEmail(existentUser.email, inviteEmail, { UNIQUE_GMAIL_VALIDATION: false })),
7297
);
7398

7499
// remove invites for users that are invited already
@@ -84,7 +109,8 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => {
84109

85110
// remove invites for users that are invited already
86111
_.remove(nonExistentUserEmails, email =>
87-
_.some(invites, i => _.toLower(i.email) === _.toLower(email)));
112+
_.some(invites, i =>
113+
compareEmail(i.email, email, { UNIQUE_GMAIL_VALIDATION: config.get('UNIQUE_GMAIL_VALIDATION') })));
88114
nonExistentUserEmails.forEach((email) => {
89115
const dataNew = _.clone(data);
90116

src/routes/projectMemberInvites/create.spec.js

Lines changed: 78 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe('Project Member Invite create', () => {
8787
}),
8888
models.ProjectMemberInvite.create({
8989
projectId: project1.id,
90-
email: 'duplicate_lowercase@gmail.com',
90+
email: 'duplicate_lowercase@test.com',
9191
role: PROJECT_MEMBER_ROLE.MANAGER,
9292
status: INVITE_STATUS.PENDING,
9393
createdBy: 1,
@@ -97,7 +97,27 @@ describe('Project Member Invite create', () => {
9797
}),
9898
models.ProjectMemberInvite.create({
9999
projectId: project1.id,
100-
email: 'DUPLICATE_UPPERCASE@gmail.com',
100+
email: 'DUPLICATE_UPPERCASE@test.com',
101+
role: PROJECT_MEMBER_ROLE.MANAGER,
102+
status: INVITE_STATUS.PENDING,
103+
createdBy: 1,
104+
updatedBy: 1,
105+
createdAt: '2016-06-30 00:33:07+00',
106+
updatedAt: '2016-06-30 00:33:07+00',
107+
}),
108+
models.ProjectMemberInvite.create({
109+
projectId: project1.id,
110+
email: 'with.dot@gmail.com',
111+
role: PROJECT_MEMBER_ROLE.MANAGER,
112+
status: INVITE_STATUS.PENDING,
113+
createdBy: 1,
114+
updatedBy: 1,
115+
createdAt: '2016-06-30 00:33:07+00',
116+
updatedAt: '2016-06-30 00:33:07+00',
117+
}),
118+
models.ProjectMemberInvite.create({
119+
projectId: project1.id,
120+
email: 'withoutdot@gmail.com',
101121
role: PROJECT_MEMBER_ROLE.MANAGER,
102122
status: INVITE_STATUS.PENDING,
103123
createdBy: 1,
@@ -664,33 +684,14 @@ describe('Project Member Invite create', () => {
664684
});
665685

666686
it('should return 201 and empty response when trying add already invited member by lowercase email', (done) => {
667-
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
668-
get: () => Promise.resolve({
669-
status: 200,
670-
data: {
671-
id: 'requesterId',
672-
version: 'v3',
673-
result: {
674-
success: true,
675-
status: 200,
676-
content: {
677-
success: [{
678-
roleName: USER_ROLE.COPILOT,
679-
}],
680-
},
681-
},
682-
},
683-
}),
684-
});
685-
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
686687
request(server)
687688
.post(`/v4/projects/${project1.id}/members/invite`)
688689
.set({
689690
Authorization: `Bearer ${testUtil.jwts.copilot}`,
690691
})
691692
.send({
692693
param: {
693-
emails: ['DUPLICATE_LOWERCASE@gmail.com'],
694+
emails: ['DUPLICATE_LOWERCASE@test.com'],
694695
role: 'customer',
695696
},
696697
})
@@ -703,40 +704,20 @@ describe('Project Member Invite create', () => {
703704
const resJson = res.body.result.content.success;
704705
should.exist(resJson);
705706
resJson.length.should.equal(0);
706-
server.services.pubsub.publish.neverCalledWith('project.member.invite.created').should.be.true;
707707
done();
708708
}
709709
});
710710
});
711711

712712
it('should return 201 and empty response when trying add already invited member by uppercase email', (done) => {
713-
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
714-
get: () => Promise.resolve({
715-
status: 200,
716-
data: {
717-
id: 'requesterId',
718-
version: 'v3',
719-
result: {
720-
success: true,
721-
status: 200,
722-
content: {
723-
success: [{
724-
roleName: USER_ROLE.COPILOT,
725-
}],
726-
},
727-
},
728-
},
729-
}),
730-
});
731-
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
732713
request(server)
733714
.post(`/v4/projects/${project1.id}/members/invite`)
734715
.set({
735716
Authorization: `Bearer ${testUtil.jwts.copilot}`,
736717
})
737718
.send({
738719
param: {
739-
emails: ['duplicate_uppercase@gmail.com'],
720+
emails: ['duplicate_uppercase@test.com'],
740721
role: 'customer',
741722
},
742723
})
@@ -749,12 +730,65 @@ describe('Project Member Invite create', () => {
749730
const resJson = res.body.result.content.success;
750731
should.exist(resJson);
751732
resJson.length.should.equal(0);
752-
server.services.pubsub.publish.neverCalledWith('project.member.invite.created').should.be.true;
753733
done();
754734
}
755735
});
756736
});
757737

738+
it('should return 201 and empty response when trying add already invited member by gmail email with dot',
739+
(done) => {
740+
request(server)
741+
.post(`/v4/projects/${project1.id}/members/invite`)
742+
.set({
743+
Authorization: `Bearer ${testUtil.jwts.copilot}`,
744+
})
745+
.send({
746+
param: {
747+
emails: ['WITHdot@gmail.com'],
748+
role: 'customer',
749+
},
750+
})
751+
.expect('Content-Type', /json/)
752+
.expect(201)
753+
.end((err, res) => {
754+
if (err) {
755+
done(err);
756+
} else {
757+
const resJson = res.body.result.content.success;
758+
should.exist(resJson);
759+
resJson.length.should.equal(0);
760+
done();
761+
}
762+
});
763+
});
764+
765+
it('should return 201 and empty response when trying add already invited member by gmail email without dot',
766+
(done) => {
767+
request(server)
768+
.post(`/v4/projects/${project1.id}/members/invite`)
769+
.set({
770+
Authorization: `Bearer ${testUtil.jwts.copilot}`,
771+
})
772+
.send({
773+
param: {
774+
emails: ['WITHOUT.dot@gmail.com'],
775+
role: 'customer',
776+
},
777+
})
778+
.expect('Content-Type', /json/)
779+
.expect(201)
780+
.end((err, res) => {
781+
if (err) {
782+
done(err);
783+
} else {
784+
const resJson = res.body.result.content.success;
785+
should.exist(resJson);
786+
resJson.length.should.equal(0);
787+
done();
788+
}
789+
});
790+
});
791+
758792
describe('Bus api', () => {
759793
let createEventSpy;
760794

0 commit comments

Comments
 (0)