From 2f6ec4b8c9526fa0768fe8bdab13e70502b35cb2 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Mon, 11 May 2026 12:25:27 +0530 Subject: [PATCH 01/15] PM-4975: User Exist handles --- src/common/helper.js | 2 +- src/services/MemberService.js | 6 +++--- src/services/MemberTraitService.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 90cf857..2c5b51b 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -204,7 +204,7 @@ function checkIfExists (source, term) { async function getMemberByHandle (handle) { const ret = await prisma.member.findUnique({ where: { - handleLower: handle.trim().toLowerCase() + handleLower: handle.toLowerCase() }, include: { maxRating: true, phones: true } }) diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 83910b8..64c70a9 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -101,7 +101,7 @@ function omitMemberAttributes (currentUser, mb) { const hasSensitiveDataRole = helper.hasSensitiveDataRole(currentUser) const isM2M = currentUser && currentUser.isMachine const isSelf = currentUser && currentUser.handle && mb.handleLower && - currentUser.handle.trim().toLowerCase() === mb.handleLower.trim().toLowerCase() + currentUser.handle.toLowerCase() === mb.handleLower.toLowerCase() const canSeeIdentityVerified = isM2M || hasSensitiveDataRole || isSelf const canSeeRecentActivity = isM2M || hasSensitiveDataRole || isSelf const canSeeFullAddress = canManageMember || hasSensitiveDataRole @@ -207,7 +207,7 @@ async function getMemberData (handle, query, allowedFields = MEMBER_FIELDS) { const prismaFilter = { where: { - handleLower: handle.trim().toLowerCase() + handleLower: handle.toLowerCase() }, include: {} } @@ -251,7 +251,7 @@ async function getMember (currentUser, handle, query) { const hasSensitiveDataRole = helper.hasSensitiveDataRole(currentUser) const isM2M = currentUser && currentUser.isMachine const isSelf = currentUser && currentUser.handle && - currentUser.handle.trim().toLowerCase() === handle.trim().toLowerCase() + currentUser.handle.toLowerCase() === handle.toLowerCase() const canSeePhones = isM2M || hasSensitiveDataRole || isSelf const canSeeRecentActivity = isM2M || hasSensitiveDataRole || isSelf diff --git a/src/services/MemberTraitService.js b/src/services/MemberTraitService.js index bfaa0f2..936682a 100644 --- a/src/services/MemberTraitService.js +++ b/src/services/MemberTraitService.js @@ -206,7 +206,7 @@ async function getTraits (currentUser, handle, query) { const hasSensitiveDataRole = helper.hasSensitiveDataRole(currentUser) const isM2M = currentUser && currentUser.isMachine const isSelf = currentUser && currentUser.handle && - currentUser.handle.trim().toLowerCase() === handle.trim().toLowerCase() + currentUser.handle.toLowerCase() === handle.toLowerCase() // can read private personalisation info on a member const canReadPrivate = isM2M || hasSensitiveDataRole || isSelf From 76ca02001ab65b8a990fa4a189c2400fdd007113 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Mon, 11 May 2026 14:06:37 +0530 Subject: [PATCH 02/15] PM-4975: User Exist handles --- src/services/MemberService.js | 12 ++++++------ src/services/MemberTraitService.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 64c70a9..36c3fbd 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -345,7 +345,7 @@ getMember.schema = { handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() - }) + }).unknown(true) } /** @@ -586,7 +586,7 @@ getProfileCompleteness.schema = { query: Joi.object().keys({ fields: Joi.string(), toast: Joi.string() - }) + }).unknown(true) } /** @@ -610,7 +610,7 @@ getMemberUserIdSignature.schema = { currentUser: Joi.any(), query: Joi.object().keys({ type: Joi.string().valid('userflow').required() - }).required() + }).unknown(true).required() } /** @@ -778,7 +778,7 @@ updateMember.schema = { handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() - }), + }).unknown(true), data: Joi.object().keys({ handle: Joi.forbidden(), handleLower: Joi.forbidden(), @@ -925,7 +925,7 @@ updateHandle.schema = { handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() - }), + }).unknown(true), data: Joi.object().keys({ newHandle: Joi.string().required() }).required() @@ -988,7 +988,7 @@ verifyEmail.schema = { handle: Joi.string().required(), query: Joi.object().keys({ token: Joi.string().required() - }).required() + }).unknown(true).required() } /** diff --git a/src/services/MemberTraitService.js b/src/services/MemberTraitService.js index 936682a..1b99f96 100644 --- a/src/services/MemberTraitService.js +++ b/src/services/MemberTraitService.js @@ -284,7 +284,7 @@ getTraits.schema = { query: Joi.object().keys({ traitIds: Joi.string(), fields: Joi.string() - }) + }).unknown(true) } /** @@ -659,7 +659,7 @@ removeTraits.schema = { handle: Joi.string().required(), query: Joi.object().keys({ traitIds: Joi.string() // if not provided, then all member traits are removed - }) + }).unknown(true) } /** * This function is used to calculate a deduction to the skill score used in the talent search From 146736213e841f95d3a0a8cc82e3c49d930ab7ed Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Mon, 11 May 2026 15:17:01 +0530 Subject: [PATCH 03/15] PM-4975: User Exist handles --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4379e6e..b8fe8cf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,7 +67,7 @@ workflows: branches: only: - develop - - PM-4482 + - PM-4975 # Production builds are exectuted only on tagged commits to the # master branch. From b69dfb516e59edf53758e1932b36625280f41fb1 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Mon, 11 May 2026 16:03:39 +0530 Subject: [PATCH 04/15] PM-4975: User Exist handles --- src/common/helper.js | 2 +- src/services/MemberService.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 2c5b51b..90cf857 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -204,7 +204,7 @@ function checkIfExists (source, term) { async function getMemberByHandle (handle) { const ret = await prisma.member.findUnique({ where: { - handleLower: handle.toLowerCase() + handleLower: handle.trim().toLowerCase() }, include: { maxRating: true, phones: true } }) diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 36c3fbd..5ad1634 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -207,7 +207,7 @@ async function getMemberData (handle, query, allowedFields = MEMBER_FIELDS) { const prismaFilter = { where: { - handleLower: handle.toLowerCase() + handleLower: handle.trim().toLowerCase() }, include: {} } From 849989d608ca849d4862da7a1a247aeeaec550cf Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Tue, 12 May 2026 12:31:18 +0530 Subject: [PATCH 05/15] updates to User Exist handles --- src/common/helper.js | 7 ++++++- src/services/MemberService.js | 22 +++++++++++----------- src/services/MemberTraitService.js | 6 +++--- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 90cf857..7f216cf 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -201,7 +201,12 @@ function checkIfExists (source, term) { * @param {String} handle the member handle * @returns {Promise} the member of given handle */ -async function getMemberByHandle (handle) { +async function getMemberByHandle (handle) { + const normalizedHandle = handle.trim().toLowerCase() + if (!normalizedHandle) { + throw new errors.BadRequestError('Member handle must not be blank.') + } + const ret = await prisma.member.findUnique({ where: { handleLower: handle.trim().toLowerCase() diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 5ad1634..7e29d6e 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -342,7 +342,7 @@ async function getMember (currentUser, handle, query) { getMember.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true) @@ -416,7 +416,7 @@ async function getMemberSendgridEmails (currentUser, handle) { getMemberSendgridEmails.schema = { currentUser: Joi.any(), - handle: Joi.string().required() + handle: Joi.string().unsafe().required() } /** @@ -582,7 +582,7 @@ async function getProfileCompleteness (currentUser, handle, query) { getProfileCompleteness.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ fields: Joi.string(), toast: Joi.string() @@ -775,7 +775,7 @@ async function updateMember (currentUser, handle, query, data) { updateMember.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -922,7 +922,7 @@ async function updateHandle (currentUser, handle, query, data) { updateHandle.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -985,7 +985,7 @@ async function verifyEmail (currentUser, handle, query) { verifyEmail.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ token: Joi.string().required() }).unknown(true).required() @@ -1262,7 +1262,7 @@ async function updateIdentityRecords (userId, handle, email, timestamp) { uploadPhoto.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), files: Joi.object().keys({ photo: Joi.object().required() }).required() @@ -1270,7 +1270,7 @@ uploadPhoto.schema = { deleteMember.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), data: Joi.object().keys({ ticketUrl: Joi.string().uri().required() }).required() @@ -1309,7 +1309,7 @@ async function confirmProfileData (currentUser, handle) { confirmProfileData.schema = { currentUser: Joi.any(), - handle: Joi.string().required() + handle: Joi.string().unsafe().required() } /** @@ -1958,7 +1958,7 @@ async function downloadProfile (currentUser, handle) { downloadProfile.schema = { currentUser: Joi.any(), - handle: Joi.string().required() + handle: Joi.string().unsafe().required() } /** @@ -2206,7 +2206,7 @@ async function getMemberSkill (currentUser, handle, skillId) { getMemberSkill.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), skillId: Joi.string().uuid().required() } diff --git a/src/services/MemberTraitService.js b/src/services/MemberTraitService.js index 1b99f96..730016e 100644 --- a/src/services/MemberTraitService.js +++ b/src/services/MemberTraitService.js @@ -280,7 +280,7 @@ async function getTraits (currentUser, handle, query) { getTraits.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ traitIds: Joi.string(), fields: Joi.string() @@ -504,7 +504,7 @@ async function createTraits (currentUser, handle, data) { createTraits.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), data: Joi.array().items(Joi.object().keys({ traitId: Joi.string().valid(...TRAIT_IDS).required(), categoryName: Joi.string(), @@ -656,7 +656,7 @@ async function removeTraits (currentUser, handle, query) { removeTraits.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ traitIds: Joi.string() // if not provided, then all member traits are removed }).unknown(true) From 9f7c06ec348662d7f980c550b9a8d5575d4703f7 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Tue, 12 May 2026 13:33:56 +0530 Subject: [PATCH 06/15] updates to User Exist handles --- src/services/MemberService.js | 27 +++++++++++++++++---------- src/services/MemberTraitService.js | 13 ++++++++++--- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 7e29d6e..0238f81 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -262,6 +262,13 @@ async function getMember (currentUser, handle, query) { const threeMonthsAgo = new Date() threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3) +const handleSchema = Joi.any().custom((value, helpers) => { + if (typeof value !== 'string' || value.trim().length === 0) { + return helpers.error('any.invalid') + } + return value +}, 'handle validation').required() + // Conditionally add phones and recent activity to query if user has permission const modifiedQuery = { ...query } if (canSeePhones) { @@ -342,7 +349,7 @@ async function getMember (currentUser, handle, query) { getMember.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, query: Joi.object().keys({ fields: Joi.string() }).unknown(true) @@ -416,7 +423,7 @@ async function getMemberSendgridEmails (currentUser, handle) { getMemberSendgridEmails.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required() + handle: handleSchema, } /** @@ -582,7 +589,7 @@ async function getProfileCompleteness (currentUser, handle, query) { getProfileCompleteness.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, query: Joi.object().keys({ fields: Joi.string(), toast: Joi.string() @@ -775,7 +782,7 @@ async function updateMember (currentUser, handle, query, data) { updateMember.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -922,7 +929,7 @@ async function updateHandle (currentUser, handle, query, data) { updateHandle.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -985,7 +992,7 @@ async function verifyEmail (currentUser, handle, query) { verifyEmail.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, query: Joi.object().keys({ token: Joi.string().required() }).unknown(true).required() @@ -1262,7 +1269,7 @@ async function updateIdentityRecords (userId, handle, email, timestamp) { uploadPhoto.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, files: Joi.object().keys({ photo: Joi.object().required() }).required() @@ -1270,7 +1277,7 @@ uploadPhoto.schema = { deleteMember.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, data: Joi.object().keys({ ticketUrl: Joi.string().uri().required() }).required() @@ -1309,7 +1316,7 @@ async function confirmProfileData (currentUser, handle) { confirmProfileData.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required() + handle: handleSchema, } /** @@ -2206,7 +2213,7 @@ async function getMemberSkill (currentUser, handle, skillId) { getMemberSkill.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, skillId: Joi.string().uuid().required() } diff --git a/src/services/MemberTraitService.js b/src/services/MemberTraitService.js index 730016e..4bad096 100644 --- a/src/services/MemberTraitService.js +++ b/src/services/MemberTraitService.js @@ -278,9 +278,16 @@ async function getTraits (currentUser, handle, query) { return helper.convertBigIntDeep(result) } +const handleSchema = Joi.any().custom((value, helpers) => { + if (typeof value !== 'string' || value.trim().length === 0) { + return helpers.error('any.invalid') + } + return value +}, 'handle validation').required() + getTraits.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, query: Joi.object().keys({ traitIds: Joi.string(), fields: Joi.string() @@ -504,7 +511,7 @@ async function createTraits (currentUser, handle, data) { createTraits.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, data: Joi.array().items(Joi.object().keys({ traitId: Joi.string().valid(...TRAIT_IDS).required(), categoryName: Joi.string(), @@ -656,7 +663,7 @@ async function removeTraits (currentUser, handle, query) { removeTraits.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: handleSchema, query: Joi.object().keys({ traitIds: Joi.string() // if not provided, then all member traits are removed }).unknown(true) From 2184764f99d477cd81d974d7560732c12d727045 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Tue, 12 May 2026 13:48:17 +0530 Subject: [PATCH 07/15] Revert "updates to User Exist handles" This reverts commit 9f7c06ec348662d7f980c550b9a8d5575d4703f7. --- src/services/MemberService.js | 27 ++++++++++----------------- src/services/MemberTraitService.js | 13 +++---------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 0238f81..7e29d6e 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -262,13 +262,6 @@ async function getMember (currentUser, handle, query) { const threeMonthsAgo = new Date() threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3) -const handleSchema = Joi.any().custom((value, helpers) => { - if (typeof value !== 'string' || value.trim().length === 0) { - return helpers.error('any.invalid') - } - return value -}, 'handle validation').required() - // Conditionally add phones and recent activity to query if user has permission const modifiedQuery = { ...query } if (canSeePhones) { @@ -349,7 +342,7 @@ const handleSchema = Joi.any().custom((value, helpers) => { getMember.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true) @@ -423,7 +416,7 @@ async function getMemberSendgridEmails (currentUser, handle) { getMemberSendgridEmails.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required() } /** @@ -589,7 +582,7 @@ async function getProfileCompleteness (currentUser, handle, query) { getProfileCompleteness.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ fields: Joi.string(), toast: Joi.string() @@ -782,7 +775,7 @@ async function updateMember (currentUser, handle, query, data) { updateMember.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -929,7 +922,7 @@ async function updateHandle (currentUser, handle, query, data) { updateHandle.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -992,7 +985,7 @@ async function verifyEmail (currentUser, handle, query) { verifyEmail.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ token: Joi.string().required() }).unknown(true).required() @@ -1269,7 +1262,7 @@ async function updateIdentityRecords (userId, handle, email, timestamp) { uploadPhoto.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), files: Joi.object().keys({ photo: Joi.object().required() }).required() @@ -1277,7 +1270,7 @@ uploadPhoto.schema = { deleteMember.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), data: Joi.object().keys({ ticketUrl: Joi.string().uri().required() }).required() @@ -1316,7 +1309,7 @@ async function confirmProfileData (currentUser, handle) { confirmProfileData.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required() } /** @@ -2213,7 +2206,7 @@ async function getMemberSkill (currentUser, handle, skillId) { getMemberSkill.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), skillId: Joi.string().uuid().required() } diff --git a/src/services/MemberTraitService.js b/src/services/MemberTraitService.js index 4bad096..730016e 100644 --- a/src/services/MemberTraitService.js +++ b/src/services/MemberTraitService.js @@ -278,16 +278,9 @@ async function getTraits (currentUser, handle, query) { return helper.convertBigIntDeep(result) } -const handleSchema = Joi.any().custom((value, helpers) => { - if (typeof value !== 'string' || value.trim().length === 0) { - return helpers.error('any.invalid') - } - return value -}, 'handle validation').required() - getTraits.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ traitIds: Joi.string(), fields: Joi.string() @@ -511,7 +504,7 @@ async function createTraits (currentUser, handle, data) { createTraits.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), data: Joi.array().items(Joi.object().keys({ traitId: Joi.string().valid(...TRAIT_IDS).required(), categoryName: Joi.string(), @@ -663,7 +656,7 @@ async function removeTraits (currentUser, handle, query) { removeTraits.schema = { currentUser: Joi.any(), - handle: handleSchema, + handle: Joi.string().unsafe().required(), query: Joi.object().keys({ traitIds: Joi.string() // if not provided, then all member traits are removed }).unknown(true) From 701fb48a0a563eacad83a8d1846d8173a166ca38 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Tue, 12 May 2026 13:51:49 +0530 Subject: [PATCH 08/15] Revert "updates to User Exist handles" This reverts commit 849989d608ca849d4862da7a1a247aeeaec550cf. --- src/common/helper.js | 7 +------ src/services/MemberService.js | 22 +++++++++++----------- src/services/MemberTraitService.js | 6 +++--- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 7f216cf..90cf857 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -201,12 +201,7 @@ function checkIfExists (source, term) { * @param {String} handle the member handle * @returns {Promise} the member of given handle */ -async function getMemberByHandle (handle) { - const normalizedHandle = handle.trim().toLowerCase() - if (!normalizedHandle) { - throw new errors.BadRequestError('Member handle must not be blank.') - } - +async function getMemberByHandle (handle) { const ret = await prisma.member.findUnique({ where: { handleLower: handle.trim().toLowerCase() diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 7e29d6e..5ad1634 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -342,7 +342,7 @@ async function getMember (currentUser, handle, query) { getMember.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true) @@ -416,7 +416,7 @@ async function getMemberSendgridEmails (currentUser, handle) { getMemberSendgridEmails.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required() + handle: Joi.string().required() } /** @@ -582,7 +582,7 @@ async function getProfileCompleteness (currentUser, handle, query) { getProfileCompleteness.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string(), toast: Joi.string() @@ -775,7 +775,7 @@ async function updateMember (currentUser, handle, query, data) { updateMember.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -922,7 +922,7 @@ async function updateHandle (currentUser, handle, query, data) { updateHandle.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -985,7 +985,7 @@ async function verifyEmail (currentUser, handle, query) { verifyEmail.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), query: Joi.object().keys({ token: Joi.string().required() }).unknown(true).required() @@ -1262,7 +1262,7 @@ async function updateIdentityRecords (userId, handle, email, timestamp) { uploadPhoto.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), files: Joi.object().keys({ photo: Joi.object().required() }).required() @@ -1270,7 +1270,7 @@ uploadPhoto.schema = { deleteMember.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), data: Joi.object().keys({ ticketUrl: Joi.string().uri().required() }).required() @@ -1309,7 +1309,7 @@ async function confirmProfileData (currentUser, handle) { confirmProfileData.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required() + handle: Joi.string().required() } /** @@ -1958,7 +1958,7 @@ async function downloadProfile (currentUser, handle) { downloadProfile.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required() + handle: Joi.string().required() } /** @@ -2206,7 +2206,7 @@ async function getMemberSkill (currentUser, handle, skillId) { getMemberSkill.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), skillId: Joi.string().uuid().required() } diff --git a/src/services/MemberTraitService.js b/src/services/MemberTraitService.js index 730016e..1b99f96 100644 --- a/src/services/MemberTraitService.js +++ b/src/services/MemberTraitService.js @@ -280,7 +280,7 @@ async function getTraits (currentUser, handle, query) { getTraits.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), query: Joi.object().keys({ traitIds: Joi.string(), fields: Joi.string() @@ -504,7 +504,7 @@ async function createTraits (currentUser, handle, data) { createTraits.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), data: Joi.array().items(Joi.object().keys({ traitId: Joi.string().valid(...TRAIT_IDS).required(), categoryName: Joi.string(), @@ -656,7 +656,7 @@ async function removeTraits (currentUser, handle, query) { removeTraits.schema = { currentUser: Joi.any(), - handle: Joi.string().unsafe().required(), + handle: Joi.string().required(), query: Joi.object().keys({ traitIds: Joi.string() // if not provided, then all member traits are removed }).unknown(true) From 1579f2cdfeb3595cb2b39154c402428f358a8038 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Tue, 12 May 2026 15:44:15 +0530 Subject: [PATCH 09/15] updates to User Exist Handles --- src/common/helper.js | 16 +++++++++--- src/services/MemberService.js | 46 ++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 90cf857..4b2e023 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -202,12 +202,20 @@ function checkIfExists (source, term) { * @returns {Promise} the member of given handle */ async function getMemberByHandle (handle) { - const ret = await prisma.member.findUnique({ - where: { - handleLower: handle.trim().toLowerCase() - }, + const trimmedHandle = handle.trim().toLowerCase() + + let ret = await prisma.member.findUnique({ + where: { handleLower: trimmedHandle }, include: { maxRating: true, phones: true } }) + + if ((!ret || !ret.userId) && trimmedHandle !== handle.toLowerCase()) { + ret = await prisma.member.findUnique({ + where: { handleLower: handle.toLowerCase() }, + include: { maxRating: true, phones: true } + }) + } + if (!ret || !ret.userId) { throw new errors.NotFoundError(`Member with handle: "${handle}" doesn't exist`) } diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 5ad1634..d5cc897 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -248,6 +248,7 @@ async function getMemberData (handle, query, allowedFields = MEMBER_FIELDS) { async function getMember (currentUser, handle, query) { // Check if user has permission to see phones // Phones are visible to: self, users with sensitive data roles (Talent Manager, admin) and M2M + const normalizedHandle = decodeURIComponent(handle).trim() const hasSensitiveDataRole = helper.hasSensitiveDataRole(currentUser) const isM2M = currentUser && currentUser.isMachine const isSelf = currentUser && currentUser.handle && @@ -342,12 +343,30 @@ async function getMember (currentUser, handle, query) { getMember.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true) } +// In the controller that calls getMemberService.getMember: +async function getMemberController(req, res, next) { + try { + // Safely decode and normalize the handle + let handle = req.params.handle + try { + handle = decodeURIComponent(handle).trim() + } catch (e) { + // If decoding fails (malformed URI), use as-is trimmed + handle = handle.trim() + } + + const result = await memberService.getMember(req.authUser, handle, req.query) + res.json(result) + } catch (err) { + next(err) + } +} /** * Build query string for SendGrid email activity API. * @param {String} email recipient email @@ -416,7 +435,7 @@ async function getMemberSendgridEmails (currentUser, handle) { getMemberSendgridEmails.schema = { currentUser: Joi.any(), - handle: Joi.string().required() +handle: Joi.string().trim().required() } /** @@ -582,7 +601,7 @@ async function getProfileCompleteness (currentUser, handle, query) { getProfileCompleteness.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), query: Joi.object().keys({ fields: Joi.string(), toast: Joi.string() @@ -775,7 +794,7 @@ async function updateMember (currentUser, handle, query, data) { updateMember.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -922,12 +941,15 @@ async function updateHandle (currentUser, handle, query, data) { updateHandle.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), data: Joi.object().keys({ - newHandle: Joi.string().required() + newHandle: Joi.string() + .trim() + .pattern(/^[^<>]+$/, 'no angle brackets') + .required() }).required() } @@ -985,7 +1007,7 @@ async function verifyEmail (currentUser, handle, query) { verifyEmail.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), query: Joi.object().keys({ token: Joi.string().required() }).unknown(true).required() @@ -1262,7 +1284,7 @@ async function updateIdentityRecords (userId, handle, email, timestamp) { uploadPhoto.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), files: Joi.object().keys({ photo: Joi.object().required() }).required() @@ -1270,7 +1292,7 @@ uploadPhoto.schema = { deleteMember.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), data: Joi.object().keys({ ticketUrl: Joi.string().uri().required() }).required() @@ -1309,7 +1331,7 @@ async function confirmProfileData (currentUser, handle) { confirmProfileData.schema = { currentUser: Joi.any(), - handle: Joi.string().required() + handle: Joi.string().trim().required() } /** @@ -1958,7 +1980,7 @@ async function downloadProfile (currentUser, handle) { downloadProfile.schema = { currentUser: Joi.any(), - handle: Joi.string().required() + handle: Joi.string().trim().required() } /** @@ -2206,7 +2228,7 @@ async function getMemberSkill (currentUser, handle, skillId) { getMemberSkill.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), skillId: Joi.string().uuid().required() } From b01acb0174b0d93b12f95b72f34e7c253d0b158b Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Tue, 12 May 2026 16:10:10 +0530 Subject: [PATCH 10/15] updates to User Exist Handles --- src/services/MemberService.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/services/MemberService.js b/src/services/MemberService.js index d5cc897..38cf901 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -248,6 +248,8 @@ async function getMemberData (handle, query, allowedFields = MEMBER_FIELDS) { async function getMember (currentUser, handle, query) { // Check if user has permission to see phones // Phones are visible to: self, users with sensitive data roles (Talent Manager, admin) and M2M + handle = handle.trim() + const operatorId = currentUser.userId || currentUser.sub const normalizedHandle = decodeURIComponent(handle).trim() const hasSensitiveDataRole = helper.hasSensitiveDataRole(currentUser) const isM2M = currentUser && currentUser.isMachine @@ -343,7 +345,7 @@ async function getMember (currentUser, handle, query) { getMember.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true) @@ -388,6 +390,7 @@ function buildSendgridEmailActivityQuery (email, startTime, endTime) { * @returns {Array} up to the most recent SendGrid message activity records */ async function getMemberSendgridEmails (currentUser, handle) { + handle = handle.trim() if (!currentUser || (!currentUser.isMachine && !helper.hasAdminRole(currentUser))) { throw new errors.ForbiddenError('You are not allowed to view SendGrid email activity.') } @@ -435,7 +438,7 @@ async function getMemberSendgridEmails (currentUser, handle) { getMemberSendgridEmails.schema = { currentUser: Joi.any(), -handle: Joi.string().trim().required() +handle: Joi.string().required() } /** @@ -446,6 +449,7 @@ handle: Joi.string().trim().required() * @returns {Object} the member profile data */ async function getProfileCompleteness (currentUser, handle, query) { + handle = handle.trim() // Don't pass the query parameter to the trait service - we want *all* traits and member data // to come back for calculation of the completeness const memberTraits = await memberTraitService.getTraits(currentUser, handle, {}) @@ -601,7 +605,7 @@ async function getProfileCompleteness (currentUser, handle, query) { getProfileCompleteness.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string(), toast: Joi.string() @@ -794,7 +798,7 @@ async function updateMember (currentUser, handle, query, data) { updateMember.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -839,6 +843,7 @@ updateMember.schema = { * @returns {Object} the updated member data */ async function updateHandle (currentUser, handle, query, data) { + handle = handle.trim() const operatorId = currentUser.userId || currentUser.sub const member = await helper.getMemberByHandle(handle) @@ -941,7 +946,7 @@ async function updateHandle (currentUser, handle, query, data) { updateHandle.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -961,6 +966,7 @@ updateHandle.schema = { * @returns {Object} the verification result */ async function verifyEmail (currentUser, handle, query) { + handle = handle.trim() const member = await helper.getMemberByHandle(handle) if (!helper.canManageMember(currentUser, member)) { throw new errors.ForbiddenError('You are not allowed to update the member.') @@ -1007,7 +1013,7 @@ async function verifyEmail (currentUser, handle, query) { verifyEmail.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), query: Joi.object().keys({ token: Joi.string().required() }).unknown(true).required() @@ -1021,6 +1027,7 @@ verifyEmail.schema = { * @returns {Object} the upload result */ async function uploadPhoto (currentUser, handle, files) { + handle = handle.trim() const member = await helper.getMemberByHandle(handle) // check authorization if (!helper.canManageMember(currentUser, member)) { @@ -1092,6 +1099,7 @@ async function uploadPhoto (currentUser, handle, files) { * @returns {Object} the deletion result */ async function deleteMember (currentUser, handle, data) { + handle = handle.trim() if (!currentUser || (!currentUser.isMachine && !helper.hasAdminRole(currentUser))) { throw new errors.ForbiddenError('You are not allowed to delete the member.') } @@ -1284,7 +1292,7 @@ async function updateIdentityRecords (userId, handle, email, timestamp) { uploadPhoto.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), files: Joi.object().keys({ photo: Joi.object().required() }).required() @@ -1292,7 +1300,7 @@ uploadPhoto.schema = { deleteMember.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), data: Joi.object().keys({ ticketUrl: Joi.string().uri().required() }).required() @@ -1305,6 +1313,7 @@ deleteMember.schema = { * @returns {Object} the updated member profile data */ async function confirmProfileData (currentUser, handle) { + handle = handle.trim() const member = await helper.getMemberByHandle(handle) // check authorization - only the profile owner or admin can confirm if (!helper.canManageMember(currentUser, member)) { @@ -1331,7 +1340,7 @@ async function confirmProfileData (currentUser, handle) { confirmProfileData.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required() + handle: Joi.string().required() } /** @@ -1961,6 +1970,7 @@ async function aggregatePDFData (currentUser, handle) { * @returns {Stream} PDF stream */ async function downloadProfile (currentUser, handle) { + handle = handle.trim() // Validate handle exists const member = await helper.getMemberByHandle(handle) @@ -1980,7 +1990,7 @@ async function downloadProfile (currentUser, handle) { downloadProfile.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required() + handle: Joi.string().required() } /** @@ -1991,6 +2001,7 @@ downloadProfile.schema = { * @returns {Object} the member skill data */ async function getMemberSkill (currentUser, handle, skillId) { + handle = handle.trim() // Get member data first to get userId const member = await getMemberData(handle, {}) @@ -2228,7 +2239,7 @@ async function getMemberSkill (currentUser, handle, skillId) { getMemberSkill.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), skillId: Joi.string().uuid().required() } From d56261272e73babad62b4757878fed3dc4fbc3f9 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Tue, 12 May 2026 16:23:17 +0530 Subject: [PATCH 11/15] updates to User Exist Handles --- src/common/helper.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 4b2e023..20e91d5 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -202,16 +202,17 @@ function checkIfExists (source, term) { * @returns {Promise} the member of given handle */ async function getMemberByHandle (handle) { - const trimmedHandle = handle.trim().toLowerCase() + const handleStr = (handle == null) ? '' : String(handle) + const trimmed = handleStr.trim().toLowerCase() let ret = await prisma.member.findUnique({ - where: { handleLower: trimmedHandle }, + where: { handleLower: trimmed }, include: { maxRating: true, phones: true } }) - if ((!ret || !ret.userId) && trimmedHandle !== handle.toLowerCase()) { + if ((!ret || !ret.userId) && trimmed !== handleStr.toLowerCase()) { ret = await prisma.member.findUnique({ - where: { handleLower: handle.toLowerCase() }, + where: { handleLower: handleStr.toLowerCase() }, include: { maxRating: true, phones: true } }) } From 78b6e1063ee81a7c905b5286df5c56c43c35fcbf Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Tue, 12 May 2026 16:45:37 +0530 Subject: [PATCH 12/15] Revert "updates to User Exist Handles" This reverts commit d56261272e73babad62b4757878fed3dc4fbc3f9. --- src/common/helper.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 20e91d5..4b2e023 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -202,17 +202,16 @@ function checkIfExists (source, term) { * @returns {Promise} the member of given handle */ async function getMemberByHandle (handle) { - const handleStr = (handle == null) ? '' : String(handle) - const trimmed = handleStr.trim().toLowerCase() + const trimmedHandle = handle.trim().toLowerCase() let ret = await prisma.member.findUnique({ - where: { handleLower: trimmed }, + where: { handleLower: trimmedHandle }, include: { maxRating: true, phones: true } }) - if ((!ret || !ret.userId) && trimmed !== handleStr.toLowerCase()) { + if ((!ret || !ret.userId) && trimmedHandle !== handle.toLowerCase()) { ret = await prisma.member.findUnique({ - where: { handleLower: handleStr.toLowerCase() }, + where: { handleLower: handle.toLowerCase() }, include: { maxRating: true, phones: true } }) } From bdddd840fa0490230b615380e0996e58568c54d1 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Tue, 12 May 2026 16:46:00 +0530 Subject: [PATCH 13/15] Revert "updates to User Exist Handles" This reverts commit b01acb0174b0d93b12f95b72f34e7c253d0b158b. --- src/services/MemberService.js | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 38cf901..d5cc897 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -248,8 +248,6 @@ async function getMemberData (handle, query, allowedFields = MEMBER_FIELDS) { async function getMember (currentUser, handle, query) { // Check if user has permission to see phones // Phones are visible to: self, users with sensitive data roles (Talent Manager, admin) and M2M - handle = handle.trim() - const operatorId = currentUser.userId || currentUser.sub const normalizedHandle = decodeURIComponent(handle).trim() const hasSensitiveDataRole = helper.hasSensitiveDataRole(currentUser) const isM2M = currentUser && currentUser.isMachine @@ -345,7 +343,7 @@ async function getMember (currentUser, handle, query) { getMember.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true) @@ -390,7 +388,6 @@ function buildSendgridEmailActivityQuery (email, startTime, endTime) { * @returns {Array} up to the most recent SendGrid message activity records */ async function getMemberSendgridEmails (currentUser, handle) { - handle = handle.trim() if (!currentUser || (!currentUser.isMachine && !helper.hasAdminRole(currentUser))) { throw new errors.ForbiddenError('You are not allowed to view SendGrid email activity.') } @@ -438,7 +435,7 @@ async function getMemberSendgridEmails (currentUser, handle) { getMemberSendgridEmails.schema = { currentUser: Joi.any(), -handle: Joi.string().required() +handle: Joi.string().trim().required() } /** @@ -449,7 +446,6 @@ handle: Joi.string().required() * @returns {Object} the member profile data */ async function getProfileCompleteness (currentUser, handle, query) { - handle = handle.trim() // Don't pass the query parameter to the trait service - we want *all* traits and member data // to come back for calculation of the completeness const memberTraits = await memberTraitService.getTraits(currentUser, handle, {}) @@ -605,7 +601,7 @@ async function getProfileCompleteness (currentUser, handle, query) { getProfileCompleteness.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), query: Joi.object().keys({ fields: Joi.string(), toast: Joi.string() @@ -798,7 +794,7 @@ async function updateMember (currentUser, handle, query, data) { updateMember.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -843,7 +839,6 @@ updateMember.schema = { * @returns {Object} the updated member data */ async function updateHandle (currentUser, handle, query, data) { - handle = handle.trim() const operatorId = currentUser.userId || currentUser.sub const member = await helper.getMemberByHandle(handle) @@ -946,7 +941,7 @@ async function updateHandle (currentUser, handle, query, data) { updateHandle.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -966,7 +961,6 @@ updateHandle.schema = { * @returns {Object} the verification result */ async function verifyEmail (currentUser, handle, query) { - handle = handle.trim() const member = await helper.getMemberByHandle(handle) if (!helper.canManageMember(currentUser, member)) { throw new errors.ForbiddenError('You are not allowed to update the member.') @@ -1013,7 +1007,7 @@ async function verifyEmail (currentUser, handle, query) { verifyEmail.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), query: Joi.object().keys({ token: Joi.string().required() }).unknown(true).required() @@ -1027,7 +1021,6 @@ verifyEmail.schema = { * @returns {Object} the upload result */ async function uploadPhoto (currentUser, handle, files) { - handle = handle.trim() const member = await helper.getMemberByHandle(handle) // check authorization if (!helper.canManageMember(currentUser, member)) { @@ -1099,7 +1092,6 @@ async function uploadPhoto (currentUser, handle, files) { * @returns {Object} the deletion result */ async function deleteMember (currentUser, handle, data) { - handle = handle.trim() if (!currentUser || (!currentUser.isMachine && !helper.hasAdminRole(currentUser))) { throw new errors.ForbiddenError('You are not allowed to delete the member.') } @@ -1292,7 +1284,7 @@ async function updateIdentityRecords (userId, handle, email, timestamp) { uploadPhoto.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), files: Joi.object().keys({ photo: Joi.object().required() }).required() @@ -1300,7 +1292,7 @@ uploadPhoto.schema = { deleteMember.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), data: Joi.object().keys({ ticketUrl: Joi.string().uri().required() }).required() @@ -1313,7 +1305,6 @@ deleteMember.schema = { * @returns {Object} the updated member profile data */ async function confirmProfileData (currentUser, handle) { - handle = handle.trim() const member = await helper.getMemberByHandle(handle) // check authorization - only the profile owner or admin can confirm if (!helper.canManageMember(currentUser, member)) { @@ -1340,7 +1331,7 @@ async function confirmProfileData (currentUser, handle) { confirmProfileData.schema = { currentUser: Joi.any(), - handle: Joi.string().required() + handle: Joi.string().trim().required() } /** @@ -1970,7 +1961,6 @@ async function aggregatePDFData (currentUser, handle) { * @returns {Stream} PDF stream */ async function downloadProfile (currentUser, handle) { - handle = handle.trim() // Validate handle exists const member = await helper.getMemberByHandle(handle) @@ -1990,7 +1980,7 @@ async function downloadProfile (currentUser, handle) { downloadProfile.schema = { currentUser: Joi.any(), - handle: Joi.string().required() + handle: Joi.string().trim().required() } /** @@ -2001,7 +1991,6 @@ downloadProfile.schema = { * @returns {Object} the member skill data */ async function getMemberSkill (currentUser, handle, skillId) { - handle = handle.trim() // Get member data first to get userId const member = await getMemberData(handle, {}) @@ -2239,7 +2228,7 @@ async function getMemberSkill (currentUser, handle, skillId) { getMemberSkill.schema = { currentUser: Joi.any(), - handle: Joi.string().required(), + handle: Joi.string().trim().required(), skillId: Joi.string().uuid().required() } From fb6b62419fcb936e0a2bf851d4ad7a950537ade1 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Tue, 12 May 2026 16:46:23 +0530 Subject: [PATCH 14/15] Revert "updates to User Exist Handles" This reverts commit 1579f2cdfeb3595cb2b39154c402428f358a8038. --- src/common/helper.js | 16 +++--------- src/services/MemberService.js | 46 +++++++++-------------------------- 2 files changed, 16 insertions(+), 46 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 4b2e023..90cf857 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -202,20 +202,12 @@ function checkIfExists (source, term) { * @returns {Promise} the member of given handle */ async function getMemberByHandle (handle) { - const trimmedHandle = handle.trim().toLowerCase() - - let ret = await prisma.member.findUnique({ - where: { handleLower: trimmedHandle }, + const ret = await prisma.member.findUnique({ + where: { + handleLower: handle.trim().toLowerCase() + }, include: { maxRating: true, phones: true } }) - - if ((!ret || !ret.userId) && trimmedHandle !== handle.toLowerCase()) { - ret = await prisma.member.findUnique({ - where: { handleLower: handle.toLowerCase() }, - include: { maxRating: true, phones: true } - }) - } - if (!ret || !ret.userId) { throw new errors.NotFoundError(`Member with handle: "${handle}" doesn't exist`) } diff --git a/src/services/MemberService.js b/src/services/MemberService.js index d5cc897..5ad1634 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -248,7 +248,6 @@ async function getMemberData (handle, query, allowedFields = MEMBER_FIELDS) { async function getMember (currentUser, handle, query) { // Check if user has permission to see phones // Phones are visible to: self, users with sensitive data roles (Talent Manager, admin) and M2M - const normalizedHandle = decodeURIComponent(handle).trim() const hasSensitiveDataRole = helper.hasSensitiveDataRole(currentUser) const isM2M = currentUser && currentUser.isMachine const isSelf = currentUser && currentUser.handle && @@ -343,30 +342,12 @@ async function getMember (currentUser, handle, query) { getMember.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true) } -// In the controller that calls getMemberService.getMember: -async function getMemberController(req, res, next) { - try { - // Safely decode and normalize the handle - let handle = req.params.handle - try { - handle = decodeURIComponent(handle).trim() - } catch (e) { - // If decoding fails (malformed URI), use as-is trimmed - handle = handle.trim() - } - - const result = await memberService.getMember(req.authUser, handle, req.query) - res.json(result) - } catch (err) { - next(err) - } -} /** * Build query string for SendGrid email activity API. * @param {String} email recipient email @@ -435,7 +416,7 @@ async function getMemberSendgridEmails (currentUser, handle) { getMemberSendgridEmails.schema = { currentUser: Joi.any(), -handle: Joi.string().trim().required() + handle: Joi.string().required() } /** @@ -601,7 +582,7 @@ async function getProfileCompleteness (currentUser, handle, query) { getProfileCompleteness.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string(), toast: Joi.string() @@ -794,7 +775,7 @@ async function updateMember (currentUser, handle, query, data) { updateMember.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), @@ -941,15 +922,12 @@ async function updateHandle (currentUser, handle, query, data) { updateHandle.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), query: Joi.object().keys({ fields: Joi.string() }).unknown(true), data: Joi.object().keys({ - newHandle: Joi.string() - .trim() - .pattern(/^[^<>]+$/, 'no angle brackets') - .required() + newHandle: Joi.string().required() }).required() } @@ -1007,7 +985,7 @@ async function verifyEmail (currentUser, handle, query) { verifyEmail.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), query: Joi.object().keys({ token: Joi.string().required() }).unknown(true).required() @@ -1284,7 +1262,7 @@ async function updateIdentityRecords (userId, handle, email, timestamp) { uploadPhoto.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), files: Joi.object().keys({ photo: Joi.object().required() }).required() @@ -1292,7 +1270,7 @@ uploadPhoto.schema = { deleteMember.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), data: Joi.object().keys({ ticketUrl: Joi.string().uri().required() }).required() @@ -1331,7 +1309,7 @@ async function confirmProfileData (currentUser, handle) { confirmProfileData.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required() + handle: Joi.string().required() } /** @@ -1980,7 +1958,7 @@ async function downloadProfile (currentUser, handle) { downloadProfile.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required() + handle: Joi.string().required() } /** @@ -2228,7 +2206,7 @@ async function getMemberSkill (currentUser, handle, skillId) { getMemberSkill.schema = { currentUser: Joi.any(), - handle: Joi.string().trim().required(), + handle: Joi.string().required(), skillId: Joi.string().uuid().required() } From 4cd974cb23035e088c489eee53ca4d712f98b5e2 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Thu, 14 May 2026 13:30:59 +0530 Subject: [PATCH 15/15] User Exist with Handles that are not supported --- src/common/helper.js | 18 ++++++++++++++++-- src/controllers/MemberController.js | 11 +++++++++++ src/services/MemberService.js | 6 ++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 90cf857..f343a80 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -630,7 +630,20 @@ function convertBigIntDeep (value) { } return value } - +/** + * Validate that a member handle contains only permitted characters. + * Handles with special chars like <, >, ?, spaces, or raw Unicode + * that were created via legacy systems should return a clean 404. + * @param {String} handle the member handle + * @throws {NotFoundError} if handle contains invalid characters + */ +function validateHandle (handle) { + // Topcoder handles: 2–50 chars, letters, digits, hyphens, underscores, periods only + const VALID_HANDLE_RE = /^[a-zA-Z0-9\-_.]{2,50}$/ + if (!VALID_HANDLE_RE.test(handle)) { + throw new errors.NotFoundError(`Member with handle: "${handle}" doesn't exist`) + } +} module.exports = { wrapExpress, autoWrapExpress, @@ -661,5 +674,6 @@ module.exports = { secureMemberAddressData, truncateLastName, bigIntToNumber, - convertBigIntDeep + convertBigIntDeep, + validateHandle } diff --git a/src/controllers/MemberController.js b/src/controllers/MemberController.js index a772468..a18e67c 100644 --- a/src/controllers/MemberController.js +++ b/src/controllers/MemberController.js @@ -9,6 +9,7 @@ const service = require('../services/MemberService') * @param {Object} res the response */ async function getMember (req, res) { + const handle = decodeURIComponent(req.params.handle) const result = await service.getMember(req.authUser, req.params.handle, req.query) res.send(result) } @@ -18,6 +19,7 @@ async function getMember (req, res) { * @param {Object} res the response */ async function getProfileCompleteness (req, res) { + const handle = decodeURIComponent(req.params.handle) const result = await service.getProfileCompleteness(req.authUser, req.params.handle, req.query) res.send(result) } @@ -38,6 +40,7 @@ async function getMemberUserIdSignature (req, res) { * @param {Object} res the response */ async function getMemberSkill (req, res) { + const handle = decodeURIComponent(req.params.handle) const result = await service.getMemberSkill(req.authUser, req.params.handle, req.params.skillid) res.send(result) } @@ -48,6 +51,7 @@ async function getMemberSkill (req, res) { * @param {Object} res the response */ async function updateMember (req, res) { + const handle = decodeURIComponent(req.params.handle) const result = await service.updateMember(req.authUser, req.params.handle, req.query, req.body) res.send(result) } @@ -58,6 +62,7 @@ async function updateMember (req, res) { * @param {Object} res the response */ async function updateHandle (req, res) { + const handle = decodeURIComponent(req.params.handle) const result = await service.updateHandle(req.authUser, req.params.handle, req.query, req.body) res.send(result) } @@ -68,6 +73,7 @@ async function updateHandle (req, res) { * @param {Object} res the response */ async function verifyEmail (req, res) { + const handle = decodeURIComponent(req.params.handle) const result = await service.verifyEmail(req.authUser, req.params.handle, req.query) res.send(result) } @@ -78,6 +84,7 @@ async function verifyEmail (req, res) { * @param {Object} res the response */ async function uploadPhoto (req, res) { + const handle = decodeURIComponent(req.params.handle) const result = await service.uploadPhoto(req.authUser, req.params.handle, req.files) res.send(result) } @@ -88,6 +95,7 @@ async function uploadPhoto (req, res) { * @param {Object} res the response */ async function deleteMember (req, res) { + const handle = decodeURIComponent(req.params.handle) const result = await service.deleteMember(req.authUser, req.params.handle, req.body) res.send(result) } @@ -98,6 +106,7 @@ async function deleteMember (req, res) { * @param {Object} res the response */ async function confirmProfileData (req, res) { + const handle = decodeURIComponent(req.params.handle) const result = await service.confirmProfileData(req.authUser, req.params.handle) res.send(result) } @@ -108,6 +117,7 @@ async function confirmProfileData (req, res) { * @param {Object} res the response */ async function downloadProfile (req, res) { + const handle = decodeURIComponent(req.params.handle) const pdfStream = await service.downloadProfile(req.authUser, req.params.handle) res.setHeader('Content-Type', 'application/pdf') res.setHeader('Content-Disposition', `attachment; filename="topcoder-profile-${req.params.handle}.pdf"`) @@ -120,6 +130,7 @@ async function downloadProfile (req, res) { * @param {Object} res the response */ async function getMemberSendgridEmails (req, res) { + const handle = decodeURIComponent(req.params.handle) const result = await service.getMemberSendgridEmails(req.authUser, req.params.handle) res.send(result) } diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 5ad1634..3aa9621 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -202,6 +202,7 @@ function getCountryNameFromCode (isoCode3) { * @returns {Object} the member profile data */ async function getMemberData (handle, query, allowedFields = MEMBER_FIELDS) { + helper.validateHandle(handle) // validate and parse query parameter const selectFields = helper.parseCommaSeparatedString(query.fields, allowedFields) || allowedFields @@ -369,6 +370,7 @@ function buildSendgridEmailActivityQuery (email, startTime, endTime) { * @returns {Array} up to the most recent SendGrid message activity records */ async function getMemberSendgridEmails (currentUser, handle) { + helper.validateHandle(handle) if (!currentUser || (!currentUser.isMachine && !helper.hasAdminRole(currentUser))) { throw new errors.ForbiddenError('You are not allowed to view SendGrid email activity.') } @@ -622,6 +624,7 @@ getMemberUserIdSignature.schema = { * @returns {Object} the updated member data */ async function updateMember (currentUser, handle, query, data) { + helper.validateHandle(handle) const operatorId = currentUser.userId || currentUser.sub const member = await helper.getMemberByHandle(handle) // check authorization @@ -820,6 +823,7 @@ updateMember.schema = { * @returns {Object} the updated member data */ async function updateHandle (currentUser, handle, query, data) { + helper.validateHandle(handle) const operatorId = currentUser.userId || currentUser.sub const member = await helper.getMemberByHandle(handle) @@ -1070,6 +1074,7 @@ async function uploadPhoto (currentUser, handle, files) { * @returns {Object} the deletion result */ async function deleteMember (currentUser, handle, data) { + helper.validateHandle(handle) if (!currentUser || (!currentUser.isMachine && !helper.hasAdminRole(currentUser))) { throw new errors.ForbiddenError('You are not allowed to delete the member.') } @@ -1939,6 +1944,7 @@ async function aggregatePDFData (currentUser, handle) { * @returns {Stream} PDF stream */ async function downloadProfile (currentUser, handle) { + helper.validateHandle(handle) // Validate handle exists const member = await helper.getMemberByHandle(handle)