Support reporting token on envelope
This commit is contained in:
parent
dc8d8e529d
commit
486cbe0471
12 changed files with 98 additions and 26 deletions
|
@ -36,7 +36,8 @@ message Envelope {
|
||||||
optional bool urgent = 14 [default=true]; // indicates that the content is considered timely by the sender; defaults to true so senders have to opt-out to say something isn't time critical
|
optional bool urgent = 14 [default=true]; // indicates that the content is considered timely by the sender; defaults to true so senders have to opt-out to say something isn't time critical
|
||||||
optional string updated_pni = 15;
|
optional string updated_pni = 15;
|
||||||
optional bool story = 16; // indicates that the content is a story.
|
optional bool story = 16; // indicates that the content is a story.
|
||||||
// next: 17
|
optional bytes reporting_token = 17;
|
||||||
|
// next: 18
|
||||||
}
|
}
|
||||||
|
|
||||||
message Content {
|
message Content {
|
||||||
|
|
|
@ -2853,7 +2853,7 @@ export async function startApp(): Promise<void> {
|
||||||
}: EnvelopeEvent): Promise<void> {
|
}: EnvelopeEvent): Promise<void> {
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
|
const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||||
if (envelope.sourceUuid && envelope.sourceUuid !== ourUuid) {
|
if (envelope.sourceUuid && envelope.sourceUuid !== ourUuid) {
|
||||||
const { mergePromises } =
|
const { mergePromises, conversation } =
|
||||||
window.ConversationController.maybeMergeContacts({
|
window.ConversationController.maybeMergeContacts({
|
||||||
e164: envelope.source,
|
e164: envelope.source,
|
||||||
aci: envelope.sourceUuid,
|
aci: envelope.sourceUuid,
|
||||||
|
@ -2863,6 +2863,10 @@ export async function startApp(): Promise<void> {
|
||||||
if (mergePromises.length > 0) {
|
if (mergePromises.length > 0) {
|
||||||
await Promise.all(mergePromises);
|
await Promise.all(mergePromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (envelope.reportingToken) {
|
||||||
|
await conversation.updateReportingToken(envelope.reportingToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { assertDev } from '../../util/assert';
|
import { assertDev } from '../../util/assert';
|
||||||
|
import { isDirectConversation } from '../../util/whatTypeOfConversation';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import type { ConversationType } from '../../state/ducks/conversations';
|
import type { ConversationAttributesType } from '../../model-types.d';
|
||||||
import type { reportSpamJobQueue } from '../reportSpamJobQueue';
|
import type { reportSpamJobQueue } from '../reportSpamJobQueue';
|
||||||
|
|
||||||
export async function addReportSpamJob({
|
export async function addReportSpamJob({
|
||||||
|
@ -11,14 +12,16 @@ export async function addReportSpamJob({
|
||||||
getMessageServerGuidsForSpam,
|
getMessageServerGuidsForSpam,
|
||||||
jobQueue,
|
jobQueue,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
conversation: Readonly<ConversationType>;
|
conversation: Readonly<
|
||||||
|
Pick<ConversationAttributesType, 'id' | 'type' | 'uuid' | 'reportingToken'>
|
||||||
|
>;
|
||||||
getMessageServerGuidsForSpam: (
|
getMessageServerGuidsForSpam: (
|
||||||
conversationId: string
|
conversationId: string
|
||||||
) => Promise<Array<string>>;
|
) => Promise<Array<string>>;
|
||||||
jobQueue: Pick<typeof reportSpamJobQueue, 'add'>;
|
jobQueue: Pick<typeof reportSpamJobQueue, 'add'>;
|
||||||
}>): Promise<void> {
|
}>): Promise<void> {
|
||||||
assertDev(
|
assertDev(
|
||||||
conversation.type === 'direct',
|
isDirectConversation(conversation),
|
||||||
'addReportSpamJob: cannot report spam for non-direct conversations'
|
'addReportSpamJob: cannot report spam for non-direct conversations'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -41,5 +44,5 @@ export async function addReportSpamJob({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await jobQueue.add({ uuid, serverGuids });
|
await jobQueue.add({ uuid, serverGuids, token: conversation.reportingToken });
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ const isRetriable4xxStatus = (code: number): boolean =>
|
||||||
|
|
||||||
const reportSpamJobDataSchema = z.object({
|
const reportSpamJobDataSchema = z.object({
|
||||||
uuid: z.string().min(1),
|
uuid: z.string().min(1),
|
||||||
|
token: z.string().optional(),
|
||||||
serverGuids: z.string().array().min(1).max(1000),
|
serverGuids: z.string().array().min(1).max(1000),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ export class ReportSpamJobQueue extends JobQueue<ReportSpamJobData> {
|
||||||
{ data }: Readonly<{ data: ReportSpamJobData }>,
|
{ data }: Readonly<{ data: ReportSpamJobData }>,
|
||||||
{ log }: Readonly<{ log: LoggerType }>
|
{ log }: Readonly<{ log: LoggerType }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { uuid, serverGuids } = data;
|
const { uuid: senderUuid, token, serverGuids } = data;
|
||||||
|
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>(resolve => {
|
||||||
window.storage.onready(resolve);
|
window.storage.onready(resolve);
|
||||||
|
@ -66,7 +67,9 @@ export class ReportSpamJobQueue extends JobQueue<ReportSpamJobData> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
map(serverGuids, serverGuid => server.reportMessage(uuid, serverGuid))
|
map(serverGuids, serverGuid =>
|
||||||
|
server.reportMessage({ senderUuid, serverGuid, token })
|
||||||
|
)
|
||||||
);
|
);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
if (!(err instanceof HTTPError)) {
|
if (!(err instanceof HTTPError)) {
|
||||||
|
|
1
ts/model-types.d.ts
vendored
1
ts/model-types.d.ts
vendored
|
@ -353,6 +353,7 @@ export type ConversationAttributesType = {
|
||||||
username?: string;
|
username?: string;
|
||||||
shareMyPhoneNumber?: boolean;
|
shareMyPhoneNumber?: boolean;
|
||||||
previousIdentityKey?: string;
|
previousIdentityKey?: string;
|
||||||
|
reportingToken?: string;
|
||||||
|
|
||||||
// Group-only
|
// Group-only
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
|
|
|
@ -2090,6 +2090,18 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateReportingToken(token?: Uint8Array): Promise<void> {
|
||||||
|
const oldValue = this.get('reportingToken');
|
||||||
|
const newValue = token ? Bytes.toBase64(token) : undefined;
|
||||||
|
|
||||||
|
if (oldValue === newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set('reportingToken', newValue);
|
||||||
|
await window.Signal.Data.updateConversation(this.attributes);
|
||||||
|
}
|
||||||
|
|
||||||
incrementMessageCount(): void {
|
incrementMessageCount(): void {
|
||||||
this.set({
|
this.set({
|
||||||
messageCount: (this.get('messageCount') || 0) + 1,
|
messageCount: (this.get('messageCount') || 0) + 1,
|
||||||
|
|
|
@ -264,6 +264,7 @@ export type UnprocessedType = {
|
||||||
decrypted?: string;
|
decrypted?: string;
|
||||||
urgent?: boolean;
|
urgent?: boolean;
|
||||||
story?: boolean;
|
story?: boolean;
|
||||||
|
reportingToken?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UnprocessedUpdateType = {
|
export type UnprocessedUpdateType = {
|
||||||
|
|
|
@ -2870,7 +2870,7 @@ function blockAndReportSpam(
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
conversation.syncMessageRequestResponse(messageRequestEnum.BLOCK),
|
conversation.syncMessageRequestResponse(messageRequestEnum.BLOCK),
|
||||||
addReportSpamJob({
|
addReportSpamJob({
|
||||||
conversation: conversation.format(),
|
conversation: conversation.attributes,
|
||||||
getMessageServerGuidsForSpam:
|
getMessageServerGuidsForSpam:
|
||||||
window.Signal.Data.getMessageServerGuidsForSpam,
|
window.Signal.Data.getMessageServerGuidsForSpam,
|
||||||
jobQueue: reportSpamJobQueue,
|
jobQueue: reportSpamJobQueue,
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
|
||||||
import { Job } from '../../../jobs/Job';
|
import { Job } from '../../../jobs/Job';
|
||||||
|
import { UUID } from '../../../types/UUID';
|
||||||
|
|
||||||
import { addReportSpamJob } from '../../../jobs/helpers/addReportSpamJob';
|
import { addReportSpamJob } from '../../../jobs/helpers/addReportSpamJob';
|
||||||
|
|
||||||
|
@ -11,6 +11,12 @@ describe('addReportSpamJob', () => {
|
||||||
let getMessageServerGuidsForSpam: sinon.SinonStub;
|
let getMessageServerGuidsForSpam: sinon.SinonStub;
|
||||||
let jobQueue: { add: sinon.SinonStub };
|
let jobQueue: { add: sinon.SinonStub };
|
||||||
|
|
||||||
|
const conversation = {
|
||||||
|
id: 'convo',
|
||||||
|
type: 'private' as const,
|
||||||
|
uuid: UUID.generate().toString(),
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getMessageServerGuidsForSpam = sinon.stub().resolves(['abc', 'xyz']);
|
getMessageServerGuidsForSpam = sinon.stub().resolves(['abc', 'xyz']);
|
||||||
jobQueue = {
|
jobQueue = {
|
||||||
|
@ -31,7 +37,10 @@ describe('addReportSpamJob', () => {
|
||||||
|
|
||||||
it('does nothing if the conversation lacks a UUID', async () => {
|
it('does nothing if the conversation lacks a UUID', async () => {
|
||||||
await addReportSpamJob({
|
await addReportSpamJob({
|
||||||
conversation: getDefaultConversation({ uuid: undefined }),
|
conversation: {
|
||||||
|
...conversation,
|
||||||
|
uuid: undefined,
|
||||||
|
},
|
||||||
getMessageServerGuidsForSpam,
|
getMessageServerGuidsForSpam,
|
||||||
jobQueue,
|
jobQueue,
|
||||||
});
|
});
|
||||||
|
@ -44,7 +53,7 @@ describe('addReportSpamJob', () => {
|
||||||
getMessageServerGuidsForSpam.resolves([]);
|
getMessageServerGuidsForSpam.resolves([]);
|
||||||
|
|
||||||
await addReportSpamJob({
|
await addReportSpamJob({
|
||||||
conversation: getDefaultConversation(),
|
conversation,
|
||||||
getMessageServerGuidsForSpam,
|
getMessageServerGuidsForSpam,
|
||||||
jobQueue,
|
jobQueue,
|
||||||
});
|
});
|
||||||
|
@ -52,9 +61,7 @@ describe('addReportSpamJob', () => {
|
||||||
sinon.assert.notCalled(jobQueue.add);
|
sinon.assert.notCalled(jobQueue.add);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('enqueues a job', async () => {
|
it('enqueues a job without a token', async () => {
|
||||||
const conversation = getDefaultConversation();
|
|
||||||
|
|
||||||
await addReportSpamJob({
|
await addReportSpamJob({
|
||||||
conversation,
|
conversation,
|
||||||
getMessageServerGuidsForSpam,
|
getMessageServerGuidsForSpam,
|
||||||
|
@ -68,6 +75,28 @@ describe('addReportSpamJob', () => {
|
||||||
sinon.assert.calledWith(jobQueue.add, {
|
sinon.assert.calledWith(jobQueue.add, {
|
||||||
uuid: conversation.uuid,
|
uuid: conversation.uuid,
|
||||||
serverGuids: ['abc', 'xyz'],
|
serverGuids: ['abc', 'xyz'],
|
||||||
|
token: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enqueues a job with a token', async () => {
|
||||||
|
await addReportSpamJob({
|
||||||
|
conversation: {
|
||||||
|
...conversation,
|
||||||
|
reportingToken: 'uvw',
|
||||||
|
},
|
||||||
|
getMessageServerGuidsForSpam,
|
||||||
|
jobQueue,
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(getMessageServerGuidsForSpam);
|
||||||
|
sinon.assert.calledWith(getMessageServerGuidsForSpam, conversation.id);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(jobQueue.add);
|
||||||
|
sinon.assert.calledWith(jobQueue.add, {
|
||||||
|
uuid: conversation.uuid,
|
||||||
|
serverGuids: ['abc', 'xyz'],
|
||||||
|
token: 'uvw',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -412,6 +412,9 @@ export default class MessageReceiver
|
||||||
serverTimestamp,
|
serverTimestamp,
|
||||||
urgent: isBoolean(decoded.urgent) ? decoded.urgent : true,
|
urgent: isBoolean(decoded.urgent) ? decoded.urgent : true,
|
||||||
story: decoded.story,
|
story: decoded.story,
|
||||||
|
reportingToken: decoded.reportingToken?.length
|
||||||
|
? decoded.reportingToken
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
// After this point, decoding errors are not the server's
|
// After this point, decoding errors are not the server's
|
||||||
|
@ -848,6 +851,9 @@ export default class MessageReceiver
|
||||||
item.serverTimestamp || decoded.serverTimestamp?.toNumber(),
|
item.serverTimestamp || decoded.serverTimestamp?.toNumber(),
|
||||||
urgent: isBoolean(item.urgent) ? item.urgent : true,
|
urgent: isBoolean(item.urgent) ? item.urgent : true,
|
||||||
story: Boolean(item.story),
|
story: Boolean(item.story),
|
||||||
|
reportingToken: item.reportingToken
|
||||||
|
? Bytes.fromBase64(item.reportingToken)
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { decrypted } = item;
|
const { decrypted } = item;
|
||||||
|
@ -1123,6 +1129,9 @@ export default class MessageReceiver
|
||||||
receivedAtCounter: envelope.receivedAtCounter,
|
receivedAtCounter: envelope.receivedAtCounter,
|
||||||
urgent: envelope.urgent,
|
urgent: envelope.urgent,
|
||||||
story: envelope.story,
|
story: envelope.story,
|
||||||
|
reportingToken: envelope.reportingToken
|
||||||
|
? Bytes.toBase64(envelope.reportingToken)
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
this.decryptAndCacheBatcher.add({
|
this.decryptAndCacheBatcher.add({
|
||||||
request,
|
request,
|
||||||
|
@ -1262,14 +1271,12 @@ export default class MessageReceiver
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (envelope.content) {
|
if (!envelope.content) {
|
||||||
await this.innerHandleContentMessage(envelope, plaintext);
|
this.removeFromCache(envelope);
|
||||||
|
throw new Error('Received message with no content');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.removeFromCache(envelope);
|
await this.innerHandleContentMessage(envelope, plaintext);
|
||||||
throw new Error('Received message with no content');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async unsealEnvelope(
|
private async unsealEnvelope(
|
||||||
|
|
1
ts/textsecure/Types.d.ts
vendored
1
ts/textsecure/Types.d.ts
vendored
|
@ -97,6 +97,7 @@ export type ProcessedEnvelope = Readonly<{
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
urgent?: boolean;
|
urgent?: boolean;
|
||||||
story?: boolean;
|
story?: boolean;
|
||||||
|
reportingToken?: Uint8Array;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type ProcessedAttachment = {
|
export type ProcessedAttachment = {
|
||||||
|
|
|
@ -812,6 +812,12 @@ export type ConfirmCodeOptionsType = Readonly<{
|
||||||
accessKey?: Uint8Array;
|
accessKey?: Uint8Array;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type ReportMessageOptionsType = Readonly<{
|
||||||
|
senderUuid: string;
|
||||||
|
serverGuid: string;
|
||||||
|
token?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type WebAPIType = {
|
export type WebAPIType = {
|
||||||
startRegistration(): unknown;
|
startRegistration(): unknown;
|
||||||
finishRegistration(baton: unknown): void;
|
finishRegistration(baton: unknown): void;
|
||||||
|
@ -931,7 +937,7 @@ export type WebAPIType = {
|
||||||
registerCapabilities: (capabilities: CapabilitiesUploadType) => Promise<void>;
|
registerCapabilities: (capabilities: CapabilitiesUploadType) => Promise<void>;
|
||||||
registerKeys: (genKeys: KeysType, uuidKind: UUIDKind) => Promise<void>;
|
registerKeys: (genKeys: KeysType, uuidKind: UUIDKind) => Promise<void>;
|
||||||
registerSupportForUnauthenticatedDelivery: () => Promise<void>;
|
registerSupportForUnauthenticatedDelivery: () => Promise<void>;
|
||||||
reportMessage: (senderUuid: string, serverGuid: string) => Promise<void>;
|
reportMessage: (options: ReportMessageOptionsType) => Promise<void>;
|
||||||
requestVerificationSMS: (number: string, token: string) => Promise<void>;
|
requestVerificationSMS: (number: string, token: string) => Promise<void>;
|
||||||
requestVerificationVoice: (number: string, token: string) => Promise<void>;
|
requestVerificationVoice: (number: string, token: string) => Promise<void>;
|
||||||
checkAccountExistence: (uuid: UUID) => Promise<boolean>;
|
checkAccountExistence: (uuid: UUID) => Promise<boolean>;
|
||||||
|
@ -1800,15 +1806,19 @@ export function initialize({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reportMessage(
|
async function reportMessage({
|
||||||
senderUuid: string,
|
senderUuid,
|
||||||
serverGuid: string
|
serverGuid,
|
||||||
): Promise<void> {
|
token,
|
||||||
|
}: ReportMessageOptionsType): Promise<void> {
|
||||||
|
const jsonData = { token };
|
||||||
|
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'reportMessage',
|
call: 'reportMessage',
|
||||||
httpType: 'POST',
|
httpType: 'POST',
|
||||||
urlParameters: urlPathFromComponents([senderUuid, serverGuid]),
|
urlParameters: urlPathFromComponents([senderUuid, serverGuid]),
|
||||||
responseType: 'bytes',
|
responseType: 'bytes',
|
||||||
|
jsonData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue