-
Notifications
You must be signed in to change notification settings - Fork 13.8k
feat: User phone identity lookup for external voice calls #40680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/voice-identity-lookup
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,12 +2,12 @@ import type { IUser, MediaCallActor, MediaCallActorType, MediaCallContact, Media | |||||||||||||||||||||||||||||||||||||||||
| import type { CallRole } from '@rocket.chat/media-signaling'; | ||||||||||||||||||||||||||||||||||||||||||
| import { Users } from '@rocket.chat/models'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| import { BroadcastActorAgent } from './BroadcastAgent'; | ||||||||||||||||||||||||||||||||||||||||||
| import type { IMediaCallAgent } from '../definition/IMediaCallAgent'; | ||||||||||||||||||||||||||||||||||||||||||
| import type { IMediaCallCastDirector } from '../definition/IMediaCallCastDirector'; | ||||||||||||||||||||||||||||||||||||||||||
| import type { GetActorContactOptions, MinimalUserData, MediaCallHeader } from '../definition/common'; | ||||||||||||||||||||||||||||||||||||||||||
| import { UserActorAgent } from '../internal/agents/UserActorAgent'; | ||||||||||||||||||||||||||||||||||||||||||
| import { logger } from '../logger'; | ||||||||||||||||||||||||||||||||||||||||||
| import { BroadcastActorAgent } from './BroadcastAgent'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| type ContactList = Record<MediaCallActorType, MediaCallContact | null>; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -89,11 +89,28 @@ export class MediaCallCastDirector implements IMediaCallCastDirector { | |||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const list = user | ||||||||||||||||||||||||||||||||||||||||||
| ? this.buildContactListForUser(user, defaultContactInfo) | ||||||||||||||||||||||||||||||||||||||||||
| : this.buildContactListForExtension(sipExtension, defaultContactInfo); | ||||||||||||||||||||||||||||||||||||||||||
| : await this.buildContactListForExtension(sipExtension, defaultContactInfo); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return this.getContactFromList(list, options); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| private async findUserByPhone(phoneNumber: string): Promise<Pick<IUser, '_id' | 'name' | 'username' | 'freeSwitchExtension'> | null> { | ||||||||||||||||||||||||||||||||||||||||||
| const users = await Users.findByPhone<Pick<IUser, '_id' | 'name' | 'username' | 'freeSwitchExtension'>>(phoneNumber, { | ||||||||||||||||||||||||||||||||||||||||||
| projection: { name: 1, username: 1, freeSwitchExtension: 1 }, | ||||||||||||||||||||||||||||||||||||||||||
| }).toArray(); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!users.length) { | ||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (users.length > 1) { | ||||||||||||||||||||||||||||||||||||||||||
| logger.warn({ msg: 'Multiple users found for phone number, identity cannot be resolved', phoneNumber }); | ||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+106
to
+108
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Locate CastDirector.ts and logger/redaction helpers"
fd -a 'CastDirector\.ts$' . || true
echo "--- logger/redaction search ---"
rg -n "redact|hash\\(|phoneNumber|personal |PII|logger\\.(warn|error|info)" --glob '!node_modules' --glob '!dist' --glob '!build' | head -200
echo "--- target file outline ---"
target="$(fd 'CastDirector\.ts$' . | head -1 || true)"
if [ -n "${target:-}" ]; then
wc -l "$target"
ast-grep outline "$target" --view expanded || true
echo "--- target relevant lines ---"
sed -n '1,180p' "$target" | cat -n
fiRepository: RocketChat/Rocket.Chat Length of output: 398 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Behavioral check: parse JS/TS logger calls and print whether a logger method call includes a spread/string/variable,
# and whether any nearby identifier could represent redaction/hashing.
python3 - <<'PY'
from pathlib import Path
import re
target = next(Path('.').rglob('ee/packages/media-calls/src/server/CastDirector.ts'), None)
if not target:
print("TARGET_NOT_FOUND")
raise SystemExit
text = target.read_text()
for i, line in enumerate(text.splitlines(), 1):
if re.search(r'\blogger\.\w+\s*\(', line):
print(f"LINE{i}: {line.strip()}")
PYRepository: RocketChat/Rocket.Chat Length of output: 334 🏁 Script executed: #!/bin/bash
set -euo pipefail
target="$(fd 'CastDirector\.ts$' . | grep '/ee/packages/media-calls/src/server/CastDirector.ts$' | head -1 || true)"
if [ -z "${target:-}" ]; then
echo "CastDirector.ts not found"
exit 1
fi
echo "--- target file outline ---"
ast-grep outline "$target" --view expanded || true
echo "--- relevant lines 1-180 ---"
sed -n '1,180p' "$target" | cat -n
echo "--- repository-wide logger/redaction calls/phrases (compact) ---"
rg -n "(redact|hash\\(|phoneNumber|personal data|PII|logger\\.(warn|error|info|debug))" --glob '!node_modules' --glob '!dist' --glob '!build' | head -300 || trueRepository: RocketChat/Rocket.Chat Length of output: 8776 Do not log the raw phone number.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return users[0]; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| public async getAgentForActorAndRole(actor: MediaCallContact, role: CallRole): Promise<IMediaCallAgent | null> { | ||||||||||||||||||||||||||||||||||||||||||
| if (actor.type === 'user') { | ||||||||||||||||||||||||||||||||||||||||||
| return this.getAgentForUserActorAndRole(actor, role); | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -141,10 +158,17 @@ export class MediaCallCastDirector implements IMediaCallCastDirector { | |||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| protected buildContactListForExtension(sipExtension: string, defaultContactInfo?: MediaCallContactInformation): ContactList { | ||||||||||||||||||||||||||||||||||||||||||
| protected async buildContactListForExtension( | ||||||||||||||||||||||||||||||||||||||||||
| sipExtension: string, | ||||||||||||||||||||||||||||||||||||||||||
| defaultContactInfo?: MediaCallContactInformation, | ||||||||||||||||||||||||||||||||||||||||||
| ): Promise<ContactList> { | ||||||||||||||||||||||||||||||||||||||||||
| const user = await this.findUserByPhone(sipExtension); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const data: Partial<MediaCallContact> = { | ||||||||||||||||||||||||||||||||||||||||||
| ...defaultContactInfo, | ||||||||||||||||||||||||||||||||||||||||||
| ...(sipExtension && { sipExtension }), | ||||||||||||||||||||||||||||||||||||||||||
| ...(user?.username && { username: user.username }), | ||||||||||||||||||||||||||||||||||||||||||
| ...(user?.name && { displayName: user.name }), | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+165
to
+171
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Return the matched user contact for Line 165 obtains a unique user, but the returned Populate the user contact from the matched user while retaining the original SIP contact. Proposed change const data: Partial<MediaCallContact> = {
...defaultContactInfo,
...(sipExtension && { sipExtension }),
...(user?.username && { username: user.username }),
...(user?.name && { displayName: user.name }),
};
+ const userContact = user ? this.buildContactListForUser(user, defaultContactInfo).user : null;
return {
- user: null,
+ user: userContact,
sip: {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,6 +80,7 @@ export class UsersRaw extends BaseRaw<IUser, DefaultFields<IUser>> implements IU | |
| { key: { openBusinessHours: 1 }, sparse: true }, | ||
| { key: { statusLivechat: 1 }, sparse: true }, | ||
| { key: { freeSwitchExtension: 1 }, sparse: true, unique: true }, | ||
| { key: { 'phones.number': 1 }, sparse: true }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 4 --glob '*.ts' --glob '*.tsx' --glob '*.js' '\bphones\b|\bphone\b' .Repository: RocketChat/Rocket.Chat Length of output: 50379 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'Files of interest:\n'
git ls-files 'packages/models/src/models/Users.ts' 'packages/core-typings/src/IUser.ts' | sed -n '1,80p'
printf '\nUsers.ts index and findByPhone context:\n'
sed -n '60,85p' packages/models/src/models/Users.ts
sed -n '2520,2548p' packages/models/src/models/Users.ts
printf '\nIUser.ts:\n'
sed -n '1,140p' packages/core-typings/src/IUser.ts
printf '\nExact Users.ts/phone schemas and writes:\n'
rg -n -C 3 --glob '*.ts' --glob '*.tsx' '\bphones\.number\b|schema:\s*\{[^{}]*phone\b|phone\b.*\?(\{|\[[^]*\]?|}\)|phone:?\b|schemas/Users|Users' packages/server packages/applications packages/core services packages/models | sed -n '1,220p'Repository: RocketChat/Rocket.Chat Length of output: 5375 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'Git diff summary:\n'
git diff --stat || true
printf '\nRelevant tracked files for Users/phone schema:\n'
git ls-files | rg '(^|/)Users\.ts$|IUser\.ts$|Phone|phone|Users\.schema|users' | sed -n '1,240p'
printf '\nFocused schema/phone searches:\n'
rg -n --glob '*.ts' --glob '*.tsx' 'phones\.number|phones\s*:|phone\s*(\?|[:=])|Schema.*Users|users.*Schema|phone.*number|number.*phone' packages/server packages/models packages/settings packages/applications packages/core-typings packages/rest-typings 2>/dev/null | sed -n '1,260p'
printf '\nFindAll/phone writer candidates:\n'
rg -n --glob '*.ts' --glob '*.tsx' 'set\s*:\s*\{[^{}]*phone|\$set[^}]*phone|phone:|phoneNumber|createUser|register|set password|password' packages/services packages/applications packages/server | sed -n '1,260p'Repository: RocketChat/Rocket.Chat Length of output: 16281 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'Read relevant Users schema excerpt:\n'
sed -n '50,95p' packages/models/src/models/Users.ts
printf '\nRead core typings phone section:\n'
sed -n '200,225p' packages/core-typings/src/IUser.ts
printf '\nRead Users phone projection excerpt:\n'
sed -n '1958,1972p' packages/models/src/models/Users.ts
printf '\nRead REST user phone types:\n'
rg -n --glob '*.ts' 'phone.*string|phones.*string\[\]|phone?:|phone:' packages/rest-typings/src/v1/users packages/apps-engine/src/definition/users packages/core-typings/src/IUser.ts | sed -n '1,160p'
printf '\nCheck current index/query exact strings:\n'
python3 - <<'PY'
from pathlib import Path
users = Path('packages/models/src/models/Users.ts')
text = users.read_text()
for needle in ["'phones.number'", "\"phones.number\"", "findByPhone"]:
print(needle, text.find(needle))
PYRepository: RocketChat/Rocket.Chat Length of output: 3341 Align
🤖 Prompt for AI Agents |
||
| { key: { language: 1 }, sparse: true }, | ||
| { key: { 'active': 1, 'services.email2fa.enabled': 1 }, sparse: true }, // used by statistics | ||
| { key: { 'active': 1, 'services.totp.enabled': 1 }, sparse: true }, // used by statistics | ||
|
|
@@ -2713,6 +2714,15 @@ export class UsersRaw extends BaseRaw<IUser, DefaultFields<IUser>> implements IU | |
| ); | ||
| } | ||
|
|
||
| findByPhone<T extends Document = IUser>(phoneNumber: string, options: FindOptions<IUser> = {}): FindCursor<T> { | ||
| return this.find<T>( | ||
| { | ||
| 'phones.number': phoneNumber, | ||
| } as Filter<IUser>, | ||
| options, | ||
| ); | ||
| } | ||
|
|
||
| // UPDATE | ||
| addImportIds(_id: IUser['_id'], importIds: string[]) { | ||
| importIds = ([] as string[]).concat(importIds); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: RocketChat/Rocket.Chat
Length of output: 7434
🏁 Script executed:
Repository: RocketChat/Rocket.Chat
Length of output: 40603
🏁 Script executed:
Repository: RocketChat/Rocket.Chat
Length of output: 42065
🏁 Script executed:
Repository: RocketChat/Rocket.Chat
Length of output: 50378
Limit the phone lookup to two results.
Users.findByPhone()is non-unique because thephones.numberindex is not unique. This lookup only needs zero, one, or multiple results, but.toArray()fetches every matching user before deciding. Add.limit(2)before.toArray().Proposed change
📝 Committable suggestion
🤖 Prompt for AI Agents