fix(history): use OR instead of AND for JID validation in extractLidP…

fix(history): use OR instead of AND for JID validation in extractLidP…
This commit is contained in:
Renato Alcara
2026-01-20 20:27:35 -03:00
committed by GitHub
2 changed files with 127 additions and 12 deletions
+8 -2
View File
@@ -145,6 +145,9 @@ export function extractLidPnFromConversation(
* - `key.remoteJidAlt` - Alternative remote JID format * - `key.remoteJidAlt` - Alternative remote JID format
* - `key.participantAlt` - Alternative participant JID format (for groups) * - `key.participantAlt` - Alternative participant JID format (for groups)
* *
* IMPORTANT: Uses || (OR) to ensure BOTH JIDs are person JIDs before extracting.
* This prevents "poisoned" mappings where one side is a group/newsletter/broadcast.
*
* @param remoteJid - The primary remote JID * @param remoteJid - The primary remote JID
* @param remoteJidAlt - The alternative remote JID (may be LID or PN) * @param remoteJidAlt - The alternative remote JID (may be LID or PN)
* @param participant - The primary participant JID (for group messages) * @param participant - The primary participant JID (for group messages)
@@ -165,8 +168,11 @@ export function extractLidPnFromMessage(
return undefined return undefined
} }
// Skip non-person JIDs // FIXED: Use || (OR) instead of && (AND)
if (!isPersonJid(primaryJid) && !isPersonJid(altJid)) { // Both JIDs MUST be person JIDs to create a valid mapping.
// Using && would allow mixed scenarios (e.g., person + group) to proceed,
// resulting in invalid "poisoned" mappings.
if (!isPersonJid(primaryJid) || !isPersonJid(altJid)) {
return undefined return undefined
} }
+119 -10
View File
@@ -149,6 +149,86 @@ describe('extractLidPnFromMessage', () => {
expect(result).toBeUndefined() expect(result).toBeUndefined()
}) })
// CRITICAL: Edge cases for the && vs || fix (bug fix validation)
it('should return undefined when primary is person but alt is GROUP', () => {
const result = extractLidPnFromMessage(
'123456789012345@lid',
'123456789@g.us', // group JID
undefined,
undefined
)
// With the fix (using ||), this should return undefined
// because group JIDs cannot be part of a valid LID-PN mapping
expect(result).toBeUndefined()
})
it('should return undefined when primary is person but alt is BROADCAST', () => {
const result = extractLidPnFromMessage(
'123456789012345@lid',
'status@broadcast', // broadcast JID
undefined,
undefined
)
expect(result).toBeUndefined()
})
it('should return undefined when primary is person but alt is NEWSLETTER', () => {
const result = extractLidPnFromMessage(
'123456789012345@lid',
'123456789@newsletter', // newsletter JID
undefined,
undefined
)
expect(result).toBeUndefined()
})
it('should return undefined when alt is person but primary is GROUP', () => {
const result = extractLidPnFromMessage(
'123456789@g.us', // group JID
'5511999999999@s.whatsapp.net',
undefined,
undefined
)
expect(result).toBeUndefined()
})
it('should return undefined when alt is person but primary is BROADCAST', () => {
const result = extractLidPnFromMessage(
'status@broadcast', // broadcast JID
'5511999999999@s.whatsapp.net',
undefined,
undefined
)
expect(result).toBeUndefined()
})
it('should return undefined when alt is person but primary is NEWSLETTER', () => {
const result = extractLidPnFromMessage(
'123456789@newsletter', // newsletter JID
'5511999999999@s.whatsapp.net',
undefined,
undefined
)
expect(result).toBeUndefined()
})
it('should return undefined when BOTH are non-person JIDs', () => {
const result = extractLidPnFromMessage(
'123456789@g.us', // group JID
'status@broadcast', // broadcast JID
undefined,
undefined
)
expect(result).toBeUndefined()
})
}) })
}) })
@@ -174,7 +254,6 @@ describe('extractLidPnFromConversation', () => {
'5511999999999@hosted' '5511999999999@hosted'
) )
// jidNormalizedUser preserves hosted formats (they are distinct servers)
expect(result).toEqual({ expect(result).toEqual({
lid: '123456789012345@hosted.lid', lid: '123456789012345@hosted.lid',
pn: '5511999999999@hosted' pn: '5511999999999@hosted'
@@ -203,7 +282,6 @@ describe('extractLidPnFromConversation', () => {
undefined undefined
) )
// jidNormalizedUser preserves hosted formats (they are distinct servers)
expect(result).toEqual({ expect(result).toEqual({
lid: '123456789012345@hosted.lid', lid: '123456789012345@hosted.lid',
pn: '5511999999999@hosted' pn: '5511999999999@hosted'
@@ -263,7 +341,6 @@ describe('extractLidPnFromConversation', () => {
}) })
it('should return undefined for LID chat with lidJid (no pnJid)', () => { it('should return undefined for LID chat with lidJid (no pnJid)', () => {
// Edge case: LID chat has lidJid but no pnJid
const result = extractLidPnFromConversation( const result = extractLidPnFromConversation(
'123456789012345@lid', '123456789012345@lid',
'987654321098765@lid', '987654321098765@lid',
@@ -274,7 +351,6 @@ describe('extractLidPnFromConversation', () => {
}) })
it('should return undefined for PN chat with pnJid (no lidJid)', () => { it('should return undefined for PN chat with pnJid (no lidJid)', () => {
// Edge case: PN chat has pnJid but no lidJid
const result = extractLidPnFromConversation( const result = extractLidPnFromConversation(
'5511999999999@s.whatsapp.net', '5511999999999@s.whatsapp.net',
undefined, undefined,
@@ -375,6 +451,45 @@ describe('processHistoryMessage', () => {
phoneNumber: '1234567890123@s.whatsapp.net' phoneNumber: '1234567890123@s.whatsapp.net'
}) })
}) })
it('should extract LID-PN mappings from conversation lidJid/pnJid properties', () => {
const historySync: proto.IHistorySync = {
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
conversations: [
{
id: '1234567890123@s.whatsapp.net',
name: 'Test User',
lidJid: '11111111111111@lid',
pnJid: '1234567890123@s.whatsapp.net'
}
]
}
const result = processHistoryMessage(historySync)
expect(result.lidPnMappings).toContainEqual({
lid: '11111111111111@lid',
pn: '1234567890123@s.whatsapp.net'
})
})
it('should NOT extract LID-PN mappings from group conversations', () => {
const historySync: proto.IHistorySync = {
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
conversations: [
{
id: '123456789@g.us',
name: 'Test Group',
lidJid: '11111111111111@lid',
pnJid: '1234567890123@s.whatsapp.net'
}
]
}
const result = processHistoryMessage(historySync)
expect(result.lidPnMappings).toEqual([])
})
}) })
describe('LID-PN extraction from conversations', () => { describe('LID-PN extraction from conversations', () => {
@@ -433,7 +548,6 @@ describe('processHistoryMessage', () => {
const result = processHistoryMessage(historySync) const result = processHistoryMessage(historySync)
// Should have only 1 mapping (deduplicated)
expect(result.lidPnMappings).toHaveLength(1) expect(result.lidPnMappings).toHaveLength(1)
expect(result.lidPnMappings[0]).toEqual({ expect(result.lidPnMappings[0]).toEqual({
lid: '11111111111111@lid', lid: '11111111111111@lid',
@@ -499,7 +613,6 @@ describe('processHistoryMessage', () => {
const result = processHistoryMessage(historySync) const result = processHistoryMessage(historySync)
// jidNormalizedUser preserves hosted formats (they are distinct servers)
expect(result.lidPnMappings).toEqual([ expect(result.lidPnMappings).toEqual([
{ lid: '11111111111111@hosted.lid', pn: '5511999999999@hosted' } { lid: '11111111111111@hosted.lid', pn: '5511999999999@hosted' }
]) ])
@@ -545,7 +658,6 @@ describe('processHistoryMessage', () => {
const result = processHistoryMessage(historySync) const result = processHistoryMessage(historySync)
// Should still extract from phoneNumberToLidMappings
expect(result.lidPnMappings).toEqual([ expect(result.lidPnMappings).toEqual([
{ lid: '11111111111111@lid', pn: '5511999999999@s.whatsapp.net' } { lid: '11111111111111@lid', pn: '5511999999999@s.whatsapp.net' }
]) ])
@@ -585,8 +697,6 @@ describe('processHistoryMessage', () => {
}) })
describe('LID-PN extraction from messages (remoteJidAlt)', () => { describe('LID-PN extraction from messages (remoteJidAlt)', () => {
// Note: remoteJidAlt and participantAlt are runtime extensions to IMessageKey
// defined in WAMessageKey type. We cast via unknown for test data.
it('should extract mapping from message with remoteJidAlt', () => { it('should extract mapping from message with remoteJidAlt', () => {
const historySync = { const historySync = {
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
@@ -682,7 +792,6 @@ describe('processHistoryMessage', () => {
const result = processHistoryMessage(historySync) const result = processHistoryMessage(historySync)
// Should have only 1 mapping (deduplicated across all 3 sources)
expect(result.lidPnMappings).toHaveLength(1) expect(result.lidPnMappings).toHaveLength(1)
expect(result.lidPnMappings[0]).toEqual({ expect(result.lidPnMappings[0]).toEqual({
lid: '11111111111111@lid', lid: '11111111111111@lid',