profileKey: Check length of incoming values, clear on failed send/fetch

This commit is contained in:
Scott Nonnenberg 2022-02-22 12:34:57 -08:00 committed by GitHub
parent b96c7e90fe
commit b33b5d2a30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 284 additions and 56 deletions

View file

@ -2590,7 +2590,7 @@ export async function startApp(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const conversation = window.ConversationController.get(detailsId)!; const conversation = window.ConversationController.get(detailsId)!;
if (details.profileKey) { if (details.profileKey && details.profileKey.length > 0) {
const profileKey = Bytes.toBase64(details.profileKey); const profileKey = Bytes.toBase64(details.profileKey);
conversation.setProfileKey(profileKey); conversation.setProfileKey(profileKey);
} }

View file

@ -2730,7 +2730,11 @@ async function updateGroup(
'private' 'private'
); );
if (member.profileKey && !contact.get('profileKey')) { if (
member.profileKey &&
member.profileKey.length > 0 &&
!contact.get('profileKey')
) {
contactsWithoutProfileKey.push(contact); contactsWithoutProfileKey.push(contact);
contact.setProfileKey(member.profileKey); contact.setProfileKey(member.profileKey);
} }

View file

@ -7,6 +7,7 @@ import { getSendOptions } from '../../util/getSendOptions';
import { import {
isDirectConversation, isDirectConversation,
isGroupV2, isGroupV2,
isMe,
} from '../../util/whatTypeOfConversation'; } from '../../util/whatTypeOfConversation';
import { SignalService as Proto } from '../../protobuf'; import { SignalService as Proto } from '../../protobuf';
import { import {
@ -22,6 +23,9 @@ import type {
DeleteForEveryoneJobData, DeleteForEveryoneJobData,
} from '../conversationJobQueue'; } from '../conversationJobQueue';
import { getUntrustedConversationIds } from './getUntrustedConversationIds'; import { getUntrustedConversationIds } from './getUntrustedConversationIds';
import { handleMessageSend } from '../../util/handleMessageSend';
import { isConversationAccepted } from '../../util/isConversationAccepted';
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
// Note: because we don't have a recipient map, if some sends fail, we will resend this // Note: because we don't have a recipient map, if some sends fail, we will resend this
// message to folks that got it on the first go-round. This is okay, because a delete // message to folks that got it on the first go-round. This is okay, because a delete
@ -78,7 +82,48 @@ export async function sendDeleteForEveryone(
const sendOptions = await getSendOptions(conversation.attributes); const sendOptions = await getSendOptions(conversation.attributes);
try { try {
if (isDirectConversation(conversation.attributes)) { if (isMe(conversation.attributes)) {
const proto = await window.textsecure.messaging.getContentMessage({
deletedForEveryoneTimestamp: targetTimestamp,
profileKey,
recipients: conversation.getRecipients(),
timestamp,
});
if (!proto.dataMessage) {
log.error(
"ContentMessage proto didn't have a data message; cancelling job."
);
return;
}
await handleMessageSend(
window.textsecure.messaging.sendSyncMessage({
encodedDataMessage: Proto.DataMessage.encode(
proto.dataMessage
).finish(),
destination: conversation.get('e164'),
destinationUuid: conversation.get('uuid'),
expirationStartTimestamp: null,
options: sendOptions,
timestamp,
}),
{ messageIds, sendType }
);
} else if (isDirectConversation(conversation.attributes)) {
if (!isConversationAccepted(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is not accepted; refusing to send`
);
return;
}
if (isConversationUnregistered(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is unregistered; refusing to send`
);
return;
}
await wrapWithSyncMessageSend({ await wrapWithSyncMessageSend({
conversation, conversation,
logId, logId,

View file

@ -16,6 +16,9 @@ import type {
ExpirationTimerUpdateJobData, ExpirationTimerUpdateJobData,
ConversationQueueJobBundle, ConversationQueueJobBundle,
} from '../conversationJobQueue'; } from '../conversationJobQueue';
import { handleMessageSend } from '../../util/handleMessageSend';
import { isConversationAccepted } from '../../util/isConversationAccepted';
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
export async function sendDirectExpirationTimerUpdate( export async function sendDirectExpirationTimerUpdate(
conversation: ConversationModel, conversation: ConversationModel,
@ -86,17 +89,33 @@ export async function sendDirectExpirationTimerUpdate(
try { try {
if (isMe(conversation.attributes)) { if (isMe(conversation.attributes)) {
await window.textsecure.messaging.sendSyncMessage({ await handleMessageSend(
encodedDataMessage: Proto.DataMessage.encode( window.textsecure.messaging.sendSyncMessage({
proto.dataMessage encodedDataMessage: Proto.DataMessage.encode(
).finish(), proto.dataMessage
destination: conversation.get('e164'), ).finish(),
destinationUuid: conversation.get('uuid'), destination: conversation.get('e164'),
expirationStartTimestamp: null, destinationUuid: conversation.get('uuid'),
options: sendOptions, expirationStartTimestamp: null,
timestamp, options: sendOptions,
}); timestamp,
}),
{ messageIds: [], sendType }
);
} else if (isDirectConversation(conversation.attributes)) { } else if (isDirectConversation(conversation.attributes)) {
if (!isConversationAccepted(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is not accepted; refusing to send`
);
return;
}
if (isConversationUnregistered(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is unregistered; refusing to send`
);
return;
}
await wrapWithSyncMessageSend({ await wrapWithSyncMessageSend({
conversation, conversation,
logId, logId,

View file

@ -29,6 +29,8 @@ import type {
import { handleMultipleSendErrors } from './handleMultipleSendErrors'; import { handleMultipleSendErrors } from './handleMultipleSendErrors';
import { ourProfileKeyService } from '../../services/ourProfileKey'; import { ourProfileKeyService } from '../../services/ourProfileKey';
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
import { isConversationAccepted } from '../../util/isConversationAccepted';
export async function sendNormalMessage( export async function sendNormalMessage(
conversation: ConversationModel, conversation: ConversationModel,
@ -209,6 +211,25 @@ export async function sendNormalMessage(
}) })
); );
} else { } else {
if (!isConversationAccepted(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is not accepted; refusing to send`
);
markMessageFailed(message, [
new Error('Message request was not accepted'),
]);
return;
}
if (isConversationUnregistered(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is unregistered; refusing to send`
);
markMessageFailed(message, [
new Error('Contact no longer has a Signal account'),
]);
return;
}
log.info('sending direct message'); log.info('sending direct message');
innerPromise = window.textsecure.messaging.sendMessageToIdentifier({ innerPromise = window.textsecure.messaging.sendMessageToIdentifier({
identifier: recipientIdentifiersWithoutMe[0], identifier: recipientIdentifiersWithoutMe[0],

View file

@ -24,6 +24,8 @@ import type {
import type { CallbackResultType } from '../../textsecure/Types.d'; import type { CallbackResultType } from '../../textsecure/Types.d';
import { getUntrustedConversationIds } from './getUntrustedConversationIds'; import { getUntrustedConversationIds } from './getUntrustedConversationIds';
import { areAllErrorsUnregistered } from './areAllErrorsUnregistered'; import { areAllErrorsUnregistered } from './areAllErrorsUnregistered';
import { isConversationAccepted } from '../../util/isConversationAccepted';
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
// Note: because we don't have a recipient map, we will resend this message to folks that // Note: because we don't have a recipient map, we will resend this message to folks that
// got it on the first go-round, if some sends fail. This is okay, because a recipient // got it on the first go-round, if some sends fail. This is okay, because a recipient
@ -83,6 +85,19 @@ export async function sendProfileKey(
} }
if (isDirectConversation(conversation.attributes)) { if (isDirectConversation(conversation.attributes)) {
if (!isConversationAccepted(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is not accepted; refusing to send`
);
return;
}
if (isConversationUnregistered(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is unregistered; refusing to send`
);
return;
}
const proto = await window.textsecure.messaging.getContentMessage({ const proto = await window.textsecure.messaging.getContentMessage({
flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE, flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE,
profileKey, profileKey,

View file

@ -31,6 +31,8 @@ import type {
ConversationQueueJobBundle, ConversationQueueJobBundle,
ReactionJobData, ReactionJobData,
} from '../conversationJobQueue'; } from '../conversationJobQueue';
import { isConversationAccepted } from '../../util/isConversationAccepted';
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
export async function sendReaction( export async function sendReaction(
conversation: ConversationModel, conversation: ConversationModel,
@ -180,6 +182,21 @@ export async function sendReaction(
let promise: Promise<CallbackResultType>; let promise: Promise<CallbackResultType>;
if (isDirectConversation(conversation.attributes)) { if (isDirectConversation(conversation.attributes)) {
if (!isConversationAccepted(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is not accepted; refusing to send`
);
markReactionFailed(message, pendingReaction);
return;
}
if (isConversationUnregistered(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is unregistered; refusing to send`
);
markReactionFailed(message, pendingReaction);
return;
}
log.info('sending direct reaction message'); log.info('sending direct reaction message');
promise = window.textsecure.messaging.sendMessageToIdentifier({ promise = window.textsecure.messaging.sendMessageToIdentifier({
identifier: recipientIdentifiersWithoutMe[0], identifier: recipientIdentifiersWithoutMe[0],

View file

@ -34,11 +34,11 @@ export function initializeAllJobQueues({
deliveryReceiptsJobQueue.streamJobs(); deliveryReceiptsJobQueue.streamJobs();
readReceiptsJobQueue.streamJobs(); readReceiptsJobQueue.streamJobs();
viewedReceiptsJobQueue.streamJobs(); viewedReceiptsJobQueue.streamJobs();
viewOnceOpenJobQueue.streamJobs();
// Syncs to ourselves // Syncs to ourselves
readSyncJobQueue.streamJobs(); readSyncJobQueue.streamJobs();
viewSyncJobQueue.streamJobs(); viewSyncJobQueue.streamJobs();
viewOnceOpenJobQueue.streamJobs();
// Other queues // Other queues
removeStorageKeyJobQueue.streamJobs(); removeStorageKeyJobQueue.streamJobs();

View file

@ -20,6 +20,8 @@ import {
handleMultipleSendErrors, handleMultipleSendErrors,
maybeExpandErrors, maybeExpandErrors,
} from './helpers/handleMultipleSendErrors'; } from './helpers/handleMultipleSendErrors';
import { isConversationUnregistered } from '../util/isConversationUnregistered';
import { isConversationAccepted } from '../util/isConversationAccepted';
const MAX_RETRY_TIME = DAY; const MAX_RETRY_TIME = DAY;
const MAX_PARALLEL_JOBS = 5; const MAX_PARALLEL_JOBS = 5;
@ -76,6 +78,19 @@ export class SingleProtoJobQueue extends JobQueue<SingleProtoJobData> {
); );
} }
if (!isConversationAccepted(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is not accepted; refusing to send`
);
return;
}
if (isConversationUnregistered(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is unregistered; refusing to send`
);
return;
}
const proto = Proto.Content.decode(Bytes.fromBase64(protoBase64)); const proto = Proto.Content.decode(Bytes.fromBase64(protoBase64));
const options = await getSendOptions(conversation.attributes, { const options = await getSendOptions(conversation.attributes, {
syncMessage: isSyncMessage, syncMessage: isSyncMessage,

View file

@ -4624,7 +4624,7 @@ export class ConversationModel extends window.Backbone
} }
async setProfileKey( async setProfileKey(
profileKey: string, profileKey: string | undefined,
{ viaStorageServiceSync = false } = {} { viaStorageServiceSync = false } = {}
): Promise<void> { ): Promise<void> {
// profileKey is a string so we can compare it directly // profileKey is a string so we can compare it directly
@ -4643,7 +4643,10 @@ export class ConversationModel extends window.Backbone
sealedSender: SEALED_SENDER.UNKNOWN, sealedSender: SEALED_SENDER.UNKNOWN,
}); });
if (!viaStorageServiceSync) { // If our profile key was cleared above, we don't tell our linked devices about it.
// We want linked devices to tell us what it should be, instead of telling them to
// erase their local value.
if (!viaStorageServiceSync && profileKey) {
this.captureChange('profileKey'); this.captureChange('profileKey');
} }
@ -4686,6 +4689,12 @@ export class ConversationModel extends window.Backbone
profileKey, profileKey,
uuid uuid
); );
if (!profileKeyVersion) {
log.warn(
'deriveProfileKeyVersionIfNeeded: Failed to derive profile key version, clearing profile key.'
);
this.setProfileKey(undefined);
}
this.set({ profileKeyVersion }); this.set({ profileKeyVersion });
} }

View file

@ -19,6 +19,7 @@ import {
repeat, repeat,
zipObject, zipObject,
} from '../util/iterables'; } from '../util/iterables';
import type { SentEventData } from '../textsecure/messageReceiverEvents';
import { isNotNil } from '../util/isNotNil'; import { isNotNil } from '../util/isNotNil';
import { isNormalNumber } from '../util/isNormalNumber'; import { isNormalNumber } from '../util/isNormalNumber';
import { strictAssert } from '../util/assert'; import { strictAssert } from '../util/assert';
@ -72,6 +73,7 @@ import { markRead, markViewed } from '../services/MessageUpdater';
import { isMessageUnread } from '../util/isMessageUnread'; import { isMessageUnread } from '../util/isMessageUnread';
import { import {
isDirectConversation, isDirectConversation,
isGroup,
isGroupV1, isGroupV1,
isGroupV2, isGroupV2,
isMe, isMe,
@ -1376,7 +1378,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
break; break;
} }
case 'UnregisteredUserError': case 'UnregisteredUserError':
shouldSaveError = false; if (conversation && isGroup(conversation.attributes)) {
shouldSaveError = false;
}
// If we just found out that we couldn't send to a user because they are no // If we just found out that we couldn't send to a user because they are no
// longer registered, we will update our unregistered flag. In groups we // longer registered, we will update our unregistered flag. In groups we
// will not event try to send to them for 6 hours. And we will never try // will not event try to send to them for 6 hours. And we will never try
@ -2171,11 +2175,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
} }
} }
handleDataMessage( async handleDataMessage(
initialMessage: ProcessedDataMessage, initialMessage: ProcessedDataMessage,
confirm: () => void, confirm: () => void,
options: { data?: typeof window.WhatIsThis } = {} options: { data?: SentEventData } = {}
): WhatIsThis { ): Promise<void> {
const { data } = options; const { data } = options;
// This function is called from the background script in a few scenarios: // This function is called from the background script in a few scenarios:
@ -2198,7 +2202,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const conversation = window.ConversationController.get(conversationId)!; const conversation = window.ConversationController.get(conversationId)!;
return conversation.queueJob('handleDataMessage', async () => { await conversation.queueJob('handleDataMessage', async () => {
log.info( log.info(
`Starting handleDataMessage for message ${message.idForLogging()} in conversation ${conversation.idForLogging()}` `Starting handleDataMessage for message ${message.idForLogging()} in conversation ${conversation.idForLogging()}`
); );
@ -2243,7 +2247,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}; };
const unidentifiedStatus: Array<ProcessedUnidentifiedDeliveryStatus> = const unidentifiedStatus: Array<ProcessedUnidentifiedDeliveryStatus> =
Array.isArray(data.unidentifiedStatus) data && Array.isArray(data.unidentifiedStatus)
? data.unidentifiedStatus ? data.unidentifiedStatus
: []; : [];
@ -2265,9 +2269,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
return; return;
} }
const updatedAt: number = isNormalNumber(data.timestamp) const updatedAt: number =
? data.timestamp data && isNormalNumber(data.timestamp)
: Date.now(); ? data.timestamp
: Date.now();
const previousSendState = getOwn( const previousSendState = getOwn(
sendStateByConversationId, sendStateByConversationId,
@ -2747,7 +2752,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
} }
if (dataMessage.profileKey) { if (dataMessage.profileKey) {
const profileKey = dataMessage.profileKey.toString('base64'); const { profileKey } = dataMessage;
if ( if (
source === window.textsecure.storage.user.getNumber() || source === window.textsecure.storage.user.getNumber() ||
sourceUuid === sourceUuid ===

View file

@ -1,4 +1,4 @@
// Copyright 2020-2021 Signal Messenger, LLC // Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { isEqual, isNumber } from 'lodash'; import { isEqual, isNumber } from 'lodash';
@ -796,7 +796,7 @@ export async function mergeContactRecord(
'private' 'private'
); );
if (contactRecord.profileKey) { if (contactRecord.profileKey && contactRecord.profileKey.length > 0) {
await conversation.setProfileKey(Bytes.toBase64(contactRecord.profileKey), { await conversation.setProfileKey(Bytes.toBase64(contactRecord.profileKey), {
viaStorageServiceSync: true, viaStorageServiceSync: true,
}); });
@ -1102,7 +1102,7 @@ export async function mergeAccountRecord(
storageVersion, storageVersion,
}); });
if (accountRecord.profileKey) { if (accountRecord.profileKey && accountRecord.profileKey.length > 0) {
await conversation.setProfileKey(Bytes.toBase64(accountRecord.profileKey)); await conversation.setProfileKey(Bytes.toBase64(accountRecord.profileKey));
} }

View file

@ -1,4 +1,4 @@
// Copyright 2020-2021 Signal Messenger, LLC // Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-bitwise */ /* eslint-disable no-bitwise */
@ -1824,7 +1824,10 @@ export default class MessageReceiver
} }
if (msg.flags && msg.flags & Proto.DataMessage.Flags.PROFILE_KEY_UPDATE) { if (msg.flags && msg.flags & Proto.DataMessage.Flags.PROFILE_KEY_UPDATE) {
strictAssert(msg.profileKey, 'PROFILE_KEY_UPDATE without profileKey'); strictAssert(
msg.profileKey && msg.profileKey.length > 0,
'PROFILE_KEY_UPDATE without profileKey'
);
const ev = new ProfileKeyUpdateEvent( const ev = new ProfileKeyUpdateEvent(
{ {

View file

@ -1,4 +1,4 @@
// Copyright 2020-2021 Signal Messenger, LLC // Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import Long from 'long'; import Long from 'long';
@ -263,9 +263,10 @@ export async function processDataMessage(
groupV2: processGroupV2Context(message.groupV2), groupV2: processGroupV2Context(message.groupV2),
flags: message.flags ?? 0, flags: message.flags ?? 0,
expireTimer: message.expireTimer ?? 0, expireTimer: message.expireTimer ?? 0,
profileKey: message.profileKey profileKey:
? Bytes.toBase64(message.profileKey) message.profileKey && message.profileKey.length > 0
: undefined, ? Bytes.toBase64(message.profileKey)
: undefined,
timestamp, timestamp,
quote: processQuote(message.quote), quote: processQuote(message.quote),
contact: processContact(message.contact), contact: processContact(message.contact),

View file

@ -1,4 +1,4 @@
// Copyright 2020-2021 Signal Messenger, LLC // Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { ProfileKeyCredentialRequestContext } from '@signalapp/signal-client/zkgroup'; import type { ProfileKeyCredentialRequestContext } from '@signalapp/signal-client/zkgroup';
@ -244,12 +244,27 @@ export async function getProfile(
} }
} catch (error) { } catch (error) {
switch (error?.code) { switch (error?.code) {
case 401:
case 403: case 403:
throw error; if (
c.get('sealedSender') === SEALED_SENDER.ENABLED ||
c.get('sealedSender') === SEALED_SENDER.UNRESTRICTED
) {
log.warn(
`getProfile: Got 401/403 when using accessKey for ${c.idForLogging()}, removing profileKey`
);
c.setProfileKey(undefined);
}
if (c.get('sealedSender') === SEALED_SENDER.UNKNOWN) {
log.warn(
`getProfile: Got 401/403 when using accessKey for ${c.idForLogging()}, setting sealedSender = DISABLED`
);
c.set('sealedSender', SEALED_SENDER.DISABLED);
}
return;
case 404: case 404:
log.warn( log.info(
`getProfile failure: failed to find a profile for ${c.idForLogging()}`, `getProfile: failed to find a profile for ${c.idForLogging()}`
error && error.stack ? error.stack : error
); );
c.setUnregistered(); c.setUnregistered();
return; return;

View file

@ -6,15 +6,15 @@ import { isNumber } from 'lodash';
import type { CallbackResultType } from '../textsecure/Types.d'; import type { CallbackResultType } from '../textsecure/Types.d';
import dataInterface from '../sql/Client'; import dataInterface from '../sql/Client';
import * as log from '../logging/log'; import * as log from '../logging/log';
import {
OutgoingMessageError,
SendMessageNetworkError,
SendMessageProtoError,
UnregisteredUserError,
} from '../textsecure/Errors';
import { SEALED_SENDER } from '../types/SealedSender';
const { insertSentProto } = dataInterface; const { insertSentProto, updateConversation } = dataInterface;
export const SEALED_SENDER = {
UNKNOWN: 0,
ENABLED: 1,
DISABLED: 2,
UNRESTRICTED: 3,
};
export const sendTypesEnum = z.enum([ export const sendTypesEnum = z.enum([
'blockSyncRequest', 'blockSyncRequest',
@ -72,6 +72,53 @@ export function shouldSaveProto(sendType: SendTypesType): boolean {
return true; return true;
} }
function processError(error: unknown): void {
if (
error instanceof OutgoingMessageError ||
error instanceof SendMessageNetworkError
) {
const conversation = window.ConversationController.getOrCreate(
error.identifier,
'private'
);
if (error.code === 401 || error.code === 403) {
if (
conversation.get('sealedSender') === SEALED_SENDER.ENABLED ||
conversation.get('sealedSender') === SEALED_SENDER.UNRESTRICTED
) {
log.warn(
`handleMessageSend: Got 401/403 for ${conversation.idForLogging()}, removing profile key`
);
conversation.setProfileKey(undefined);
}
if (conversation.get('sealedSender') === SEALED_SENDER.UNKNOWN) {
log.warn(
`handleMessageSend: Got 401/403 for ${conversation.idForLogging()}, setting sealedSender = DISABLED`
);
conversation.set('sealedSender', SEALED_SENDER.DISABLED);
updateConversation(conversation.attributes);
}
}
if (error.code === 404) {
log.warn(
`handleMessageSend: Got 404 for ${conversation.idForLogging()}, marking unregistered.`
);
conversation.setUnregistered();
}
}
if (error instanceof UnregisteredUserError) {
const conversation = window.ConversationController.getOrCreate(
error.identifier,
'private'
);
log.warn(
`handleMessageSend: Got 404 for ${conversation.idForLogging()}, marking unregistered.`
);
conversation.setUnregistered();
}
}
export async function handleMessageSend( export async function handleMessageSend(
promise: Promise<CallbackResultType>, promise: Promise<CallbackResultType>,
options: { options: {
@ -91,12 +138,17 @@ export async function handleMessageSend(
return result; return result;
} catch (err) { } catch (err) {
if (err) { processError(err);
if (err instanceof SendMessageProtoError) {
await handleMessageSendResult( await handleMessageSendResult(
err.failoverIdentifiers, err.failoverIdentifiers,
err.unidentifiedDeliveries err.unidentifiedDeliveries
); );
err.errors?.forEach(processError);
} }
throw err; throw err;
} }
} }

View file

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { chunk } from 'lodash'; import { chunk } from 'lodash';
@ -8,6 +8,7 @@ import { ReceiptType } from '../types/Receipt';
import { getSendOptions } from './getSendOptions'; import { getSendOptions } from './getSendOptions';
import { handleMessageSend } from './handleMessageSend'; import { handleMessageSend } from './handleMessageSend';
import { isConversationAccepted } from './isConversationAccepted'; import { isConversationAccepted } from './isConversationAccepted';
import { isConversationUnregistered } from './isConversationUnregistered';
import { map } from './iterables'; import { map } from './iterables';
import { missingCaseError } from './missingCaseError'; import { missingCaseError } from './missingCaseError';
@ -91,6 +92,15 @@ export async function sendReceipts({
} }
if (!isConversationAccepted(sender.attributes)) { if (!isConversationAccepted(sender.attributes)) {
log.info(
`conversation ${sender.idForLogging()} is not accepted; refusing to send`
);
return;
}
if (isConversationUnregistered(sender.attributes)) {
log.info(
`conversation ${sender.idForLogging()} is unregistered; refusing to send`
);
return; return;
} }

View file

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { differenceWith, omit, partition } from 'lodash'; import { differenceWith, omit, partition } from 'lodash';
@ -46,11 +46,8 @@ import type {
SenderKeyInfoType, SenderKeyInfoType,
} from '../model-types.d'; } from '../model-types.d';
import type { SendTypesType } from './handleMessageSend'; import type { SendTypesType } from './handleMessageSend';
import { import { handleMessageSend, shouldSaveProto } from './handleMessageSend';
handleMessageSend, import { SEALED_SENDER } from '../types/SealedSender';
SEALED_SENDER,
shouldSaveProto,
} from './handleMessageSend';
import { parseIntOrThrow } from './parseIntOrThrow'; import { parseIntOrThrow } from './parseIntOrThrow';
import { import {
multiRecipient200ResponseSchema, multiRecipient200ResponseSchema,