2022-01-13 10:55:25 -05:00
/*
2024-09-09 14:57:16 +01:00
Copyright 2024 New Vector Ltd.
2022-01-13 10:55:25 -05:00
Copyright 2022 The Matrix.org Foundation C.I.C.
2024-09-09 14:57:16 +01:00
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
2022-01-13 10:55:25 -05:00
*/
2024-03-04 16:24:08 +00:00
import {
ClientEvent ,
MatrixEvent ,
MatrixEventEvent ,
SyncStateData ,
SyncState ,
ToDeviceMessageId ,
} from "matrix-js-sdk/src/matrix" ;
2022-01-31 10:49:27 -05:00
import { sleep } from "matrix-js-sdk/src/utils" ;
2024-03-04 16:24:08 +00:00
import { v4 as uuidv4 } from "uuid" ;
import { logger } from "matrix-js-sdk/src/logger" ;
2022-01-13 10:55:25 -05:00
import SdkConfig from "../SdkConfig" ;
import sendBugReport from "../rageshake/submit-rageshake" ;
import defaultDispatcher from "../dispatcher/dispatcher" ;
import { AsyncStoreWithClient } from "./AsyncStoreWithClient" ;
import { ActionPayload } from "../dispatcher/payloads" ;
import SettingsStore from "../settings/SettingsStore" ;
2022-02-11 14:00:37 +01:00
import { Action } from "../dispatcher/actions" ;
2022-01-13 10:55:25 -05:00
2022-01-31 10:49:27 -05:00
// Minimum interval of 1 minute between reports
const RAGESHAKE_INTERVAL = 60000 ;
// Before rageshaking, wait 5 seconds and see if the message has successfully decrypted
const GRACE_PERIOD = 5000 ;
2022-01-13 10:55:25 -05:00
// Event type for to-device messages requesting sender auto-rageshakes
const AUTO_RS_REQUEST = "im.vector.auto_rs_request" ;
interface IState {
reportedSessionIds : Set < string >;
lastRageshakeTime : number ;
2022-01-31 10:49:27 -05:00
initialSyncCompleted : boolean ;
2022-01-13 10:55:25 -05:00
}
/**
* Watches for decryption errors to auto-report if the relevant lab is
* enabled, and keeps track of session IDs that have already been
* reported.
*/
export default class AutoRageshakeStore extends AsyncStoreWithClient < IState > {
2022-08-30 15:13:39 -04:00
private static readonly internalInstance = (() => {
const instance = new AutoRageshakeStore ();
instance . start ();
return instance ;
})();
2022-01-13 10:55:25 -05:00
private constructor () {
super ( defaultDispatcher , {
reportedSessionIds : new Set < string >(),
lastRageshakeTime : 0 ,
2022-01-31 10:49:27 -05:00
initialSyncCompleted : false ,
2022-01-13 10:55:25 -05:00
});
this . onDecryptionAttempt = this . onDecryptionAttempt . bind ( this );
this . onDeviceMessage = this . onDeviceMessage . bind ( this );
2022-01-31 10:49:27 -05:00
this . onSyncStateChange = this . onSyncStateChange . bind ( this );
2022-01-13 10:55:25 -05:00
}
public static get instance () : AutoRageshakeStore {
return AutoRageshakeStore . internalInstance ;
}
2023-01-12 13:25:14 +00:00
protected async onAction ( payload : ActionPayload ) : Promise < void > {
2022-02-11 14:00:37 +01:00
switch ( payload . action ) {
case Action.ReportKeyBackupNotEnabled :
this.onReportKeyBackupNotEnabled ();
}
2022-01-13 10:55:25 -05:00
}
2023-01-12 13:25:14 +00:00
protected async onReady () : Promise < void > {
2022-01-13 10:55:25 -05:00
if ( ! SettingsStore . getValue ( "automaticDecryptionErrorReporting" )) return ;
if ( this . matrixClient ) {
2022-02-22 12:18:08 +00:00
this . matrixClient . on ( MatrixEventEvent . Decrypted , this . onDecryptionAttempt );
this . matrixClient . on ( ClientEvent . ToDeviceEvent , this . onDeviceMessage );
this . matrixClient . on ( ClientEvent . Sync , this . onSyncStateChange );
2022-01-13 10:55:25 -05:00
}
}
2023-01-12 13:25:14 +00:00
protected async onNotReady () : Promise < void > {
2022-01-13 10:55:25 -05:00
if ( this . matrixClient ) {
2022-02-22 12:18:08 +00:00
this . matrixClient . removeListener ( ClientEvent . ToDeviceEvent , this . onDeviceMessage );
this . matrixClient . removeListener ( MatrixEventEvent . Decrypted , this . onDecryptionAttempt );
this . matrixClient . removeListener ( ClientEvent . Sync , this . onSyncStateChange );
2022-01-13 10:55:25 -05:00
}
}
private async onDecryptionAttempt ( ev : MatrixEvent ) : Promise < void > {
2022-01-31 10:49:27 -05:00
if ( ! this . state . initialSyncCompleted ) {
return ;
}
2022-01-13 10:55:25 -05:00
const wireContent = ev . getWireContent ();
const sessionId = wireContent . session_id ;
if ( ev . isDecryptionFailure () && ! this . state . reportedSessionIds . has ( sessionId )) {
2022-01-31 10:49:27 -05:00
await sleep ( GRACE_PERIOD );
if ( ! ev . isDecryptionFailure ()) {
return ;
}
2022-01-13 10:55:25 -05:00
const newReportedSessionIds = new Set ( this . state . reportedSessionIds );
await this . updateState ({ reportedSessionIds : newReportedSessionIds.add ( sessionId ) });
const now = new Date (). getTime ();
if ( now - this . state . lastRageshakeTime < RAGESHAKE_INTERVAL ) {
2024-03-04 16:24:08 +00:00
logger . info (
`Not sending recipient-side autorageshake for event ${ ev . getId () } /session ${ sessionId } : last rageshake was too recent` ,
);
2022-01-13 10:55:25 -05:00
return ;
}
await this . updateState ({ lastRageshakeTime : now });
2024-03-04 16:24:08 +00:00
const senderUserId = ev . getSender () ! ;
2022-01-13 10:55:25 -05:00
const eventInfo = {
event_id : ev.getId (),
room_id : ev.getRoomId (),
session_id : sessionId ,
device_id : wireContent.device_id ,
2024-03-04 16:24:08 +00:00
user_id : senderUserId ,
2022-01-13 10:55:25 -05:00
sender_key : wireContent.sender_key ,
};
2024-03-04 16:24:08 +00:00
logger . info ( `Sending recipient-side autorageshake for event ${ ev . getId () } /session ${ sessionId } ` );
// XXX: the rageshake server returns the URL for the github issue... which is typically absent for
// auto-uisis, because we've disabled creation of GH issues for them. So the `recipient_rageshake`
// field is broken.
2022-01-13 10:55:25 -05:00
const rageshakeURL = await sendBugReport ( SdkConfig . get (). bug_report_endpoint_url , {
userText : "Auto-reporting decryption error (recipient)" ,
sendLogs : true ,
2022-01-26 14:30:45 -05:00
labels : [ "Z-UISI" , "web" , "uisi-recipient" ],
customApp : SdkConfig.get (). uisi_autorageshake_app ,
2022-01-13 10:55:25 -05:00
customFields : { auto_uisi : JSON.stringify ( eventInfo ) },
});
const messageContent = {
... eventInfo ,
recipient_rageshake : rageshakeURL ,
2024-03-04 16:24:08 +00:00
[ ToDeviceMessageId ] : uuidv4 (),
2022-01-13 10:55:25 -05:00
};
2023-04-06 11:10:14 +01:00
this . matrixClient ? . sendToDevice (
2023-03-28 11:23:16 +01:00
AUTO_RS_REQUEST ,
2024-03-04 16:24:08 +00:00
new Map ([[ senderUserId , new Map ([[ messageContent . device_id , messageContent ]])]]),
2023-03-28 11:23:16 +01:00
);
2022-01-13 10:55:25 -05:00
}
}
2023-05-12 09:49:37 +01:00
private async onSyncStateChange (
_state : SyncState ,
_prevState : SyncState | null ,
2023-08-10 09:01:14 +01:00
data? : SyncStateData ,
2023-05-12 09:49:37 +01:00
) : Promise < void > {
2022-01-31 10:49:27 -05:00
if ( ! this . state . initialSyncCompleted ) {
2023-05-09 08:50:46 +01:00
await this . updateState ({ initialSyncCompleted : !! data ? . nextSyncToken });
2022-01-31 10:49:27 -05:00
}
}
2022-01-13 10:55:25 -05:00
private async onDeviceMessage ( ev : MatrixEvent ) : Promise < void > {
if ( ev . getType () !== AUTO_RS_REQUEST ) return ;
const messageContent = ev . getContent ();
const recipientRageshake = messageContent [ "recipient_rageshake" ] || "" ;
const now = new Date (). getTime ();
if ( now - this . state . lastRageshakeTime > RAGESHAKE_INTERVAL ) {
await this . updateState ({ lastRageshakeTime : now });
2024-03-04 16:24:08 +00:00
logger . info (
`Sending sender-side autorageshake for event ${ messageContent [ "event_id" ] } /session ${ messageContent [ "session_id" ] } ` ,
);
2022-01-13 10:55:25 -05:00
await sendBugReport ( SdkConfig . get (). bug_report_endpoint_url , {
userText : `Auto-reporting decryption error (sender) \ nRecipient rageshake: ${ recipientRageshake } ` ,
sendLogs : true ,
2022-01-26 14:30:45 -05:00
labels : [ "Z-UISI" , "web" , "uisi-sender" ],
customApp : SdkConfig.get (). uisi_autorageshake_app ,
2022-01-13 10:55:25 -05:00
customFields : {
recipient_rageshake : recipientRageshake ,
auto_uisi : JSON.stringify ( messageContent ),
},
});
2024-03-04 16:24:08 +00:00
} else {
logger . info (
`Not sending sender-side autorageshake for event ${ messageContent [ "event_id" ] } /session ${ messageContent [ "session_id" ] } : last rageshake was too recent` ,
);
2022-01-13 10:55:25 -05:00
}
}
2022-02-11 14:00:37 +01:00
private async onReportKeyBackupNotEnabled () : Promise < void > {
if ( ! SettingsStore . getValue ( "automaticKeyBackNotEnabledReporting" )) return ;
await sendBugReport ( SdkConfig . get (). bug_report_endpoint_url , {
userText : `Auto-reporting key backup not enabled` ,
sendLogs : true ,
labels : [ "web" , Action . ReportKeyBackupNotEnabled ],
});
}
2022-01-13 10:55:25 -05:00
}
window . mxAutoRageshakeStore = AutoRageshakeStore . instance ;