@wireapp/core 46.40.7 → 46.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/conversation/ConversationService/ConversationService.d.ts +5 -0
- package/lib/conversation/ConversationService/ConversationService.d.ts.map +1 -1
- package/lib/conversation/ConversationService/ConversationService.js +125 -19
- package/lib/conversation/ConversationService/ConversationService.test.js +149 -0
- package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.d.ts +33 -0
- package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.d.ts.map +1 -1
- package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.js +41 -1
- package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.test.d.ts +2 -0
- package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.test.d.ts.map +1 -0
- package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.test.js +124 -0
- package/lib/messagingProtocols/mls/MLSService/MLSService.d.ts +6 -8
- package/lib/messagingProtocols/mls/MLSService/MLSService.d.ts.map +1 -1
- package/lib/messagingProtocols/mls/MLSService/MLSService.js +52 -19
- package/package.json +3 -3
|
@@ -31,6 +31,8 @@ export declare class ConversationService extends TypedEventEmitter<Events> {
|
|
|
31
31
|
private readonly _mlsService?;
|
|
32
32
|
readonly messageTimer: MessageTimer;
|
|
33
33
|
private readonly logger;
|
|
34
|
+
private readonly recoveringKeyMaterialGroups;
|
|
35
|
+
private groupIdConversationMap;
|
|
34
36
|
constructor(apiClient: APIClient, proteusService: ProteusService, coreDatabase: CoreDatabase, groupIdFromConversationId: (conversationId: QualifiedId, subconversationId?: SUBCONVERSATION_ID) => Promise<string | undefined>, subconversationService: SubconversationService, isMLSConversationRecoveryEnabled: () => Promise<boolean>, _mlsService?: MLSService | undefined);
|
|
35
37
|
get mlsService(): MLSService;
|
|
36
38
|
/**
|
|
@@ -123,6 +125,9 @@ export declare class ConversationService extends TypedEventEmitter<Events> {
|
|
|
123
125
|
shouldRetry?: boolean;
|
|
124
126
|
}): Promise<Conversation>;
|
|
125
127
|
joinByExternalCommit(conversationId: QualifiedId, shouldRetry?: boolean): Promise<void>;
|
|
128
|
+
private refreshGroupIdConversationMap;
|
|
129
|
+
private getConversationByGroupId;
|
|
130
|
+
private reactToKeyMaterialUpdateFailure;
|
|
126
131
|
private resetMLSConversation;
|
|
127
132
|
/**
|
|
128
133
|
* Will check if mls group exists locally.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationService.d.ts","sourceRoot":"","sources":["../../../src/conversation/ConversationService/ConversationService.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,YAAY,EACZ,2BAA2B,EAC3B,WAAW,EACX,eAAe,EACf,oBAAoB,EAEpB,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAKnB,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EACL,YAAY,EAIZ,4BAA4B,EAE7B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAC,WAAW,EAAC,MAAM,8BAA8B,CAAC;AACzD,OAAO,EAAC,GAAG,EAAC,MAAM,oCAAoC,CAAC;AAGvD,OAAO,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAa,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAY/D,OAAO,EACL,cAAc,EACd,8BAA8B,EAE9B,oBAAoB,EACpB,UAAU,EACX,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAC,YAAY,EAAuB,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACzF,OAAO,EAAC,UAAU,EAAE,gBAAgB,EAAC,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"ConversationService.d.ts","sourceRoot":"","sources":["../../../src/conversation/ConversationService/ConversationService.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,6 +44,9 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
44
44
|
_mlsService;
|
|
45
45
|
messageTimer;
|
|
46
46
|
logger = commons_1.LogFactory.getLogger('@wireapp/core/ConversationService');
|
|
47
|
+
// Track groups currently undergoing recovery due to key material update failure to prevent duplicate work
|
|
48
|
+
recoveringKeyMaterialGroups = new Set();
|
|
49
|
+
groupIdConversationMap = new Map();
|
|
47
50
|
constructor(apiClient, proteusService, coreDatabase, groupIdFromConversationId, subconversationService, isMLSConversationRecoveryEnabled, _mlsService) {
|
|
48
51
|
super();
|
|
49
52
|
this.apiClient = apiClient;
|
|
@@ -58,6 +61,10 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
58
61
|
this.mlsService.on(mls_1.MLSServiceEvents.MLS_EVENT_DISTRIBUTED, data => {
|
|
59
62
|
this.emit(mls_1.MLSServiceEvents.MLS_EVENT_DISTRIBUTED, data);
|
|
60
63
|
});
|
|
64
|
+
this.mlsService.on(mls_1.MLSServiceEvents.KEY_MATERIAL_UPDATE_FAILURE, ({ error, groupId }) => {
|
|
65
|
+
this.logger.warn(`Key material update failure for group ${groupId}`, { error });
|
|
66
|
+
return this.reactToKeyMaterialUpdateFailure({ error, groupId });
|
|
67
|
+
});
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
get mlsService() {
|
|
@@ -274,7 +281,7 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
274
281
|
/**
|
|
275
282
|
* Only thrown by core-crypto when we call commitPendingProposals
|
|
276
283
|
*/
|
|
277
|
-
if (
|
|
284
|
+
if ((0, CoreCryptoMLSError_1.isBrokenMLSConversationError)(error)) {
|
|
278
285
|
this.logger.info('Failed to send MLS message because broken MLS conversation, triggering a reset', {
|
|
279
286
|
error,
|
|
280
287
|
groupId,
|
|
@@ -286,22 +293,37 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
286
293
|
* core-crypto throws its own error class when we call commitPendingProposals
|
|
287
294
|
* backend error mapper throws its own error class when we call postMlsMessage
|
|
288
295
|
*/
|
|
289
|
-
if (
|
|
296
|
+
if ((0, CoreCryptoMLSError_1.isMLSStaleMessageError)(error) || error instanceof conversation_1.MLSStaleMessageError) {
|
|
290
297
|
this.logger.info('Failed to send MLS message because of stale message, recovering by joining with external commit', {
|
|
291
298
|
error,
|
|
292
299
|
groupId,
|
|
293
300
|
});
|
|
294
301
|
await this.recoverMLSGroupFromEpochMismatch(conversationId);
|
|
295
302
|
}
|
|
296
|
-
|
|
303
|
+
/**
|
|
304
|
+
* We may have the same error from core-crypto or from the backend error mapper
|
|
305
|
+
* core-crypto throws its own error class when we call commitPendingProposals
|
|
306
|
+
* backend error mapper throws its own error class when we call postMlsMessage
|
|
307
|
+
*/
|
|
308
|
+
if ((0, CoreCryptoMLSError_1.isMLSGroupOutOfSyncError)(error) || error instanceof conversation_1.MLSGroupOutOfSyncError) {
|
|
297
309
|
this.logger.info('Failed to send MLS message because of group out of sync, recovering by adding missing users', {
|
|
298
310
|
error,
|
|
299
311
|
groupId,
|
|
300
312
|
});
|
|
313
|
+
/**
|
|
314
|
+
* We may get the missing users either from core-crypto error or from the backend error mapper
|
|
315
|
+
*/
|
|
316
|
+
let missingUsers = [];
|
|
317
|
+
if ((0, CoreCryptoMLSError_1.isMLSGroupOutOfSyncError)(error)) {
|
|
318
|
+
missingUsers = (0, CoreCryptoMLSError_1.getMLSGroupOutOfSyncErrorMissingUsers)(error);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
missingUsers = error.missing_users;
|
|
322
|
+
}
|
|
301
323
|
await this.addUsersToMLSConversation({
|
|
302
324
|
groupId,
|
|
303
325
|
conversationId,
|
|
304
|
-
qualifiedUsers:
|
|
326
|
+
qualifiedUsers: missingUsers,
|
|
305
327
|
});
|
|
306
328
|
}
|
|
307
329
|
return this.sendMLSMessage(params, false);
|
|
@@ -333,14 +355,27 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
333
355
|
}
|
|
334
356
|
catch (error) {
|
|
335
357
|
this.logger.warn('Failed to add users to MLS conversation', { error, groupId, conversationId });
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
358
|
+
if (!shouldRetry) {
|
|
359
|
+
this.logger.warn("Tried to add users to MLS conversation but it's still broken after reset", error);
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
if ((0, CoreCryptoMLSError_1.isBrokenMLSConversationError)(error)) {
|
|
341
363
|
this.logger.warn("Tried to add users to MLS conversation but it's broken, resetting the conversation", error);
|
|
342
364
|
return this.handleBrokenMLSConversation(conversationId, newGroupId => this.addUsersToMLSConversation({ qualifiedUsers, groupId: newGroupId, conversationId, shouldRetry: false }));
|
|
343
365
|
}
|
|
366
|
+
if ((0, CoreCryptoMLSError_1.isMLSGroupOutOfSyncError)(error)) {
|
|
367
|
+
this.logger.info('Failed to send MLS message because of group out of sync, recovering by adding missing users', {
|
|
368
|
+
error,
|
|
369
|
+
groupId,
|
|
370
|
+
});
|
|
371
|
+
const missingUsers = (0, CoreCryptoMLSError_1.getMLSGroupOutOfSyncErrorMissingUsers)(error);
|
|
372
|
+
return this.addUsersToMLSConversation({
|
|
373
|
+
groupId,
|
|
374
|
+
conversationId,
|
|
375
|
+
qualifiedUsers: missingUsers,
|
|
376
|
+
shouldRetry: false,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
344
379
|
throw error;
|
|
345
380
|
}
|
|
346
381
|
}
|
|
@@ -354,11 +389,11 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
354
389
|
return await this.getConversation(conversationId);
|
|
355
390
|
}
|
|
356
391
|
catch (error) {
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
392
|
+
if (!shouldRetry) {
|
|
393
|
+
this.logger.warn("Tried to remove users from MLS conversation but it's still broken", error);
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
if ((0, CoreCryptoMLSError_1.isBrokenMLSConversationError)(error)) {
|
|
362
397
|
this.logger.info("Tried to remove users from MLS conversation but it's broken, resetting the conversation", error);
|
|
363
398
|
return this.handleBrokenMLSConversation(conversationId, newGroupId => this.removeUsersFromMLSConversation({
|
|
364
399
|
groupId: newGroupId,
|
|
@@ -367,6 +402,24 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
367
402
|
shouldRetry: false,
|
|
368
403
|
}));
|
|
369
404
|
}
|
|
405
|
+
if ((0, CoreCryptoMLSError_1.isMLSGroupOutOfSyncError)(error)) {
|
|
406
|
+
this.logger.info('Failed to send MLS message because of group out of sync, recovering by adding missing users', {
|
|
407
|
+
error,
|
|
408
|
+
groupId,
|
|
409
|
+
});
|
|
410
|
+
const missingUsers = (0, CoreCryptoMLSError_1.getMLSGroupOutOfSyncErrorMissingUsers)(error);
|
|
411
|
+
await this.addUsersToMLSConversation({
|
|
412
|
+
groupId,
|
|
413
|
+
conversationId,
|
|
414
|
+
qualifiedUsers: missingUsers,
|
|
415
|
+
});
|
|
416
|
+
return this.removeUsersFromMLSConversation({
|
|
417
|
+
groupId,
|
|
418
|
+
conversationId,
|
|
419
|
+
qualifiedUserIds,
|
|
420
|
+
shouldRetry: false,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
370
423
|
throw error;
|
|
371
424
|
}
|
|
372
425
|
}
|
|
@@ -375,18 +428,71 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
375
428
|
await this.mlsService.joinByExternalCommit(() => this.apiClient.api.conversation.getGroupInfo(conversationId));
|
|
376
429
|
}
|
|
377
430
|
catch (error) {
|
|
431
|
+
if (!shouldRetry) {
|
|
432
|
+
this.logger.warn("Tried to join MLS conversation but it's still broken after reset", error);
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
378
435
|
this.logger.warn(`Failed to join MLS conversation ${conversationId.id} via external commit`, error);
|
|
379
|
-
if (
|
|
436
|
+
if ((0, CoreCryptoMLSError_1.isBrokenMLSConversationError)(error)) {
|
|
380
437
|
this.logger.info('Resetting MLS conversation due to broken mls conversation error', error);
|
|
381
|
-
if (!shouldRetry) {
|
|
382
|
-
this.logger.warn("Tried to join MLS conversation but it's still broken after reset", error);
|
|
383
|
-
throw error;
|
|
384
|
-
}
|
|
385
438
|
return this.handleBrokenMLSConversation(conversationId);
|
|
386
439
|
}
|
|
387
440
|
throw error;
|
|
388
441
|
}
|
|
389
442
|
}
|
|
443
|
+
async refreshGroupIdConversationMap() {
|
|
444
|
+
const conversations = await this.apiClient.api.conversation.getConversationList();
|
|
445
|
+
this.groupIdConversationMap.clear();
|
|
446
|
+
for (const conversation of conversations.found || []) {
|
|
447
|
+
if (conversation.group_id) {
|
|
448
|
+
this.groupIdConversationMap.set(conversation.group_id, conversation);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
async getConversationByGroupId(groupId) {
|
|
453
|
+
if (!this.groupIdConversationMap.has(groupId)) {
|
|
454
|
+
await this.refreshGroupIdConversationMap();
|
|
455
|
+
}
|
|
456
|
+
return this.groupIdConversationMap.get(groupId);
|
|
457
|
+
}
|
|
458
|
+
reactToKeyMaterialUpdateFailure = async ({ error, groupId }) => {
|
|
459
|
+
// Deduplicate concurrent recoveries for the same group
|
|
460
|
+
if (this.recoveringKeyMaterialGroups.has(groupId)) {
|
|
461
|
+
this.logger.info(`Recovery already in progress for group ${groupId}, skipping duplicate trigger`);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
this.recoveringKeyMaterialGroups.add(groupId);
|
|
465
|
+
try {
|
|
466
|
+
this.logger.info(`Reacting to key material update failure for group ${groupId}`);
|
|
467
|
+
const conversation = await this.getConversationByGroupId(groupId);
|
|
468
|
+
if (!conversation) {
|
|
469
|
+
this.logger.warn(`No conversation found for group ${groupId}`);
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
if ((0, CoreCryptoMLSError_1.isBrokenMLSConversationError)(error)) {
|
|
473
|
+
this.logger.info('Tried to update key material for a broken MLS conversation, initiating reset', error);
|
|
474
|
+
return this.handleBrokenMLSConversation(conversation.qualified_id);
|
|
475
|
+
}
|
|
476
|
+
if ((0, CoreCryptoMLSError_1.isMLSGroupOutOfSyncError)(error)) {
|
|
477
|
+
this.logger.info('Tried to update key material for an out of sync conversation, recovering by adding missing users', {
|
|
478
|
+
error,
|
|
479
|
+
groupId,
|
|
480
|
+
});
|
|
481
|
+
const missingUsers = (0, CoreCryptoMLSError_1.getMLSGroupOutOfSyncErrorMissingUsers)(error);
|
|
482
|
+
await this.addUsersToMLSConversation({
|
|
483
|
+
groupId,
|
|
484
|
+
conversationId: conversation.qualified_id,
|
|
485
|
+
qualifiedUsers: missingUsers,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
this.logger.error('Failed to react to key material update failure', { error, groupId });
|
|
491
|
+
}
|
|
492
|
+
finally {
|
|
493
|
+
this.recoveringKeyMaterialGroups.delete(groupId);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
390
496
|
async resetMLSConversation(conversationId) {
|
|
391
497
|
this.logger.info(`Resetting MLS conversation with id ${conversationId.id}`);
|
|
392
498
|
// STEP 1: Fetch the conversation to retrieve the group ID & epoch
|
|
@@ -50,6 +50,7 @@ const api_client_1 = require("@wireapp/api-client");
|
|
|
50
50
|
const core_crypto_1 = require("@wireapp/core-crypto");
|
|
51
51
|
const __1 = require("..");
|
|
52
52
|
const CoreCryptoMLSError_1 = require("../../messagingProtocols/mls/MLSService/CoreCryptoMLSError");
|
|
53
|
+
const MLSService_1 = require("../../messagingProtocols/mls/MLSService/MLSService");
|
|
53
54
|
const MessagingProtocols = __importStar(require("../../messagingProtocols/proteus"));
|
|
54
55
|
const CoreDB_1 = require("../../storage/CoreDB");
|
|
55
56
|
const PayloadHelper = __importStar(require("../../test/PayloadHelper"));
|
|
@@ -643,6 +644,154 @@ describe('ConversationService', () => {
|
|
|
643
644
|
expect(conversationService.addUsersToMLSConversation).not.toHaveBeenCalled();
|
|
644
645
|
});
|
|
645
646
|
});
|
|
647
|
+
describe('reactToKeyMaterialUpdateFailure', () => {
|
|
648
|
+
function getKeyMaterialFailureHandler(mlsService) {
|
|
649
|
+
const onMock = mlsService.on;
|
|
650
|
+
const call = onMock.mock.calls.find(([event]) => event === MLSService_1.MLSServiceEvents.KEY_MATERIAL_UPDATE_FAILURE);
|
|
651
|
+
expect(call).toBeTruthy();
|
|
652
|
+
return call[1];
|
|
653
|
+
}
|
|
654
|
+
it('resets a broken MLS conversation', async () => {
|
|
655
|
+
const [conversationService, { apiClient, mlsService }] = await buildConversationService();
|
|
656
|
+
const groupId = 'group-1';
|
|
657
|
+
const qualified_id = { id: 'conv-1', domain: 'staging.zinfra.io' };
|
|
658
|
+
jest.spyOn(apiClient.api.conversation, 'getConversationList').mockResolvedValueOnce({
|
|
659
|
+
found: [{ group_id: groupId, qualified_id, protocol: conversation_1.ConversationProtocol.MLS, epoch: 1 }],
|
|
660
|
+
});
|
|
661
|
+
const resetSpy = jest
|
|
662
|
+
.spyOn(conversationService, 'handleBrokenMLSConversation')
|
|
663
|
+
.mockResolvedValue(undefined);
|
|
664
|
+
const addUsersSpy = jest.spyOn(conversationService, 'addUsersToMLSConversation');
|
|
665
|
+
const handler = getKeyMaterialFailureHandler(mlsService);
|
|
666
|
+
const brokenErr = {
|
|
667
|
+
type: core_crypto_1.ErrorType.Mls,
|
|
668
|
+
context: {
|
|
669
|
+
type: core_crypto_1.MlsErrorType.MessageRejected,
|
|
670
|
+
context: {
|
|
671
|
+
reason: (0, CoreCryptoMLSError_1.serializeAbortReason)({ message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.BROKEN_MLS_CONVERSATION }),
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
};
|
|
675
|
+
await handler({ error: brokenErr, groupId });
|
|
676
|
+
expect(resetSpy).toHaveBeenCalledWith(qualified_id);
|
|
677
|
+
expect(addUsersSpy).not.toHaveBeenCalled();
|
|
678
|
+
});
|
|
679
|
+
it('adds missing users when group is out of sync', async () => {
|
|
680
|
+
const [conversationService, { apiClient, mlsService }] = await buildConversationService();
|
|
681
|
+
const groupId = 'group-2';
|
|
682
|
+
const qualified_id = { id: 'conv-2', domain: 'staging.zinfra.io' };
|
|
683
|
+
const missingUsers = [
|
|
684
|
+
{ id: 'u1', domain: 'staging.zinfra.io' },
|
|
685
|
+
{ id: 'u2', domain: 'staging.zinfra.io' },
|
|
686
|
+
];
|
|
687
|
+
jest.spyOn(apiClient.api.conversation, 'getConversationList').mockResolvedValueOnce({
|
|
688
|
+
found: [{ group_id: groupId, qualified_id, protocol: conversation_1.ConversationProtocol.MLS, epoch: 1 }],
|
|
689
|
+
});
|
|
690
|
+
const addUsersSpy = jest
|
|
691
|
+
.spyOn(conversationService, 'addUsersToMLSConversation')
|
|
692
|
+
.mockResolvedValue({ conversation: {} });
|
|
693
|
+
const resetSpy = jest
|
|
694
|
+
.spyOn(conversationService, 'handleBrokenMLSConversation')
|
|
695
|
+
.mockResolvedValue(undefined);
|
|
696
|
+
const handler = getKeyMaterialFailureHandler(mlsService);
|
|
697
|
+
const outOfSyncErr = {
|
|
698
|
+
type: core_crypto_1.ErrorType.Mls,
|
|
699
|
+
context: {
|
|
700
|
+
type: core_crypto_1.MlsErrorType.MessageRejected,
|
|
701
|
+
context: {
|
|
702
|
+
reason: (0, CoreCryptoMLSError_1.serializeAbortReason)({
|
|
703
|
+
message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_GROUP_OUT_OF_SYNC,
|
|
704
|
+
missing_users: missingUsers,
|
|
705
|
+
}),
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
await handler({ error: outOfSyncErr, groupId });
|
|
710
|
+
expect(addUsersSpy).toHaveBeenCalledWith({
|
|
711
|
+
groupId,
|
|
712
|
+
conversationId: qualified_id,
|
|
713
|
+
qualifiedUsers: missingUsers,
|
|
714
|
+
});
|
|
715
|
+
expect(resetSpy).not.toHaveBeenCalled();
|
|
716
|
+
});
|
|
717
|
+
it('deduplicates concurrent recoveries for the same group id', async () => {
|
|
718
|
+
const [conversationService, { apiClient, mlsService }] = await buildConversationService();
|
|
719
|
+
const groupId = 'group-dup';
|
|
720
|
+
const qualified_id = { id: 'conv-dup', domain: 'staging.zinfra.io' };
|
|
721
|
+
jest.spyOn(apiClient.api.conversation, 'getConversationList').mockResolvedValue({
|
|
722
|
+
found: [{ group_id: groupId, qualified_id, protocol: conversation_1.ConversationProtocol.MLS, epoch: 1 }],
|
|
723
|
+
});
|
|
724
|
+
// Make the recovery hang until we resolve it, to simulate overlapping calls
|
|
725
|
+
let resolveDeferred;
|
|
726
|
+
const deferred = new Promise(res => (resolveDeferred = res));
|
|
727
|
+
const resetSpy = jest
|
|
728
|
+
.spyOn(conversationService, 'handleBrokenMLSConversation')
|
|
729
|
+
.mockReturnValue(deferred);
|
|
730
|
+
const handler = getKeyMaterialFailureHandler(mlsService);
|
|
731
|
+
const brokenErr = {
|
|
732
|
+
type: core_crypto_1.ErrorType.Mls,
|
|
733
|
+
context: {
|
|
734
|
+
type: core_crypto_1.MlsErrorType.MessageRejected,
|
|
735
|
+
context: {
|
|
736
|
+
reason: (0, CoreCryptoMLSError_1.serializeAbortReason)({ message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.BROKEN_MLS_CONVERSATION }),
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
};
|
|
740
|
+
const p1 = handler({ error: brokenErr, groupId });
|
|
741
|
+
const p2 = handler({ error: brokenErr, groupId });
|
|
742
|
+
// Complete the first recovery
|
|
743
|
+
if (resolveDeferred) {
|
|
744
|
+
resolveDeferred();
|
|
745
|
+
}
|
|
746
|
+
await Promise.allSettled([p1, p2]);
|
|
747
|
+
expect(resetSpy).toHaveBeenCalledTimes(1);
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
describe('groupIdConversationMap cache', () => {
|
|
751
|
+
function makeConversation(group_id, id) {
|
|
752
|
+
return {
|
|
753
|
+
group_id,
|
|
754
|
+
qualified_id: { id, domain: 'staging.zinfra.io' },
|
|
755
|
+
protocol: conversation_1.ConversationProtocol.MLS,
|
|
756
|
+
epoch: 1,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
it('refreshGroupIdConversationMap builds the cache from backend list', async () => {
|
|
760
|
+
const [conversationService, { apiClient }] = await buildConversationService();
|
|
761
|
+
const conv1 = makeConversation('g-1', 'c-1');
|
|
762
|
+
const conv2 = makeConversation('g-2', 'c-2');
|
|
763
|
+
const getListSpy = jest
|
|
764
|
+
.spyOn(apiClient.api.conversation, 'getConversationList')
|
|
765
|
+
.mockResolvedValueOnce({ found: [conv1, conv2] });
|
|
766
|
+
await conversationService.refreshGroupIdConversationMap();
|
|
767
|
+
expect(getListSpy).toHaveBeenCalledTimes(1);
|
|
768
|
+
// Validate through the private cache map
|
|
769
|
+
const cache = conversationService.groupIdConversationMap;
|
|
770
|
+
expect(cache.get('g-1')?.qualified_id.id).toBe('c-1');
|
|
771
|
+
expect(cache.get('g-2')?.qualified_id.id).toBe('c-2');
|
|
772
|
+
});
|
|
773
|
+
it('getConversationByGroupId uses cache when available without backend call', async () => {
|
|
774
|
+
const [conversationService, { apiClient }] = await buildConversationService();
|
|
775
|
+
const conv = makeConversation('g-hit', 'c-hit');
|
|
776
|
+
// Pre-populate cache
|
|
777
|
+
conversationService.groupIdConversationMap.set('g-hit', conv);
|
|
778
|
+
const getListSpy = jest
|
|
779
|
+
.spyOn(apiClient.api.conversation, 'getConversationList')
|
|
780
|
+
.mockImplementation(() => Promise.reject(new Error('should not be called')));
|
|
781
|
+
const result = await conversationService.getConversationByGroupId('g-hit');
|
|
782
|
+
expect(result?.qualified_id.id).toBe('c-hit');
|
|
783
|
+
expect(getListSpy).not.toHaveBeenCalled();
|
|
784
|
+
});
|
|
785
|
+
it('getConversationByGroupId refreshes on cache miss and returns undefined if not found', async () => {
|
|
786
|
+
const [conversationService, { apiClient }] = await buildConversationService();
|
|
787
|
+
const getListSpy = jest
|
|
788
|
+
.spyOn(apiClient.api.conversation, 'getConversationList')
|
|
789
|
+
.mockResolvedValueOnce({ found: [makeConversation('g-else', 'c-else')] });
|
|
790
|
+
const result = await conversationService.getConversationByGroupId('g-missing');
|
|
791
|
+
expect(result).toBeUndefined();
|
|
792
|
+
expect(getListSpy).toHaveBeenCalledTimes(1);
|
|
793
|
+
});
|
|
794
|
+
});
|
|
646
795
|
});
|
|
647
796
|
function generateImage() {
|
|
648
797
|
const image = {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { QualifiedId } from '@wireapp/api-client/lib/user';
|
|
2
|
+
import { CoreCryptoError, ErrorContext, ErrorType, MlsErrorType } from '@wireapp/core-crypto';
|
|
1
3
|
export declare const CORE_CRYPTO_ERROR_NAMES: {
|
|
2
4
|
MlsErrorConversationAlreadyExists: string;
|
|
3
5
|
MlsErrorDuplicateMessage: string;
|
|
@@ -15,4 +17,35 @@ export declare const CORE_CRYPTO_ERROR_NAMES: {
|
|
|
15
17
|
export declare const isCoreCryptoMLSWrongEpochError: (error: unknown) => boolean;
|
|
16
18
|
export declare const isCoreCryptoMLSConversationAlreadyExistsError: (error: unknown) => boolean;
|
|
17
19
|
export declare const shouldMLSDecryptionErrorBeIgnored: (error: unknown) => error is Error;
|
|
20
|
+
export declare const UPLOAD_COMMIT_BUNDLE_ABORT_REASONS: {
|
|
21
|
+
BROKEN_MLS_CONVERSATION: string;
|
|
22
|
+
MLS_STALE_MESSAGE: string;
|
|
23
|
+
MLS_GROUP_OUT_OF_SYNC: string;
|
|
24
|
+
OTHER: string;
|
|
25
|
+
};
|
|
26
|
+
type MessageRejectedError = CoreCryptoError<ErrorType.Mls> & {
|
|
27
|
+
context: Extract<ErrorContext[ErrorType.Mls], {
|
|
28
|
+
type: MlsErrorType.MessageRejected;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
export declare function isBrokenMLSConversationError(error: unknown): error is MessageRejectedError;
|
|
32
|
+
export declare function isMLSStaleMessageError(error: unknown): error is MessageRejectedError;
|
|
33
|
+
export declare function isMLSGroupOutOfSyncError(error: unknown): error is MessageRejectedError;
|
|
34
|
+
export declare function getMLSGroupOutOfSyncErrorMissingUsers(error: unknown): QualifiedId[];
|
|
35
|
+
type AbortReasonBrokenMLSConversation = {
|
|
36
|
+
message: typeof UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.BROKEN_MLS_CONVERSATION;
|
|
37
|
+
};
|
|
38
|
+
type AbortReasonMLSStaleMessage = {
|
|
39
|
+
message: typeof UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_STALE_MESSAGE;
|
|
40
|
+
};
|
|
41
|
+
type AbortReasonMLSGroupOutOfSync = {
|
|
42
|
+
message: typeof UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_GROUP_OUT_OF_SYNC;
|
|
43
|
+
missing_users: QualifiedId[];
|
|
44
|
+
};
|
|
45
|
+
type AbortReasonOther = {
|
|
46
|
+
message: typeof UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.OTHER;
|
|
47
|
+
};
|
|
48
|
+
type AbortReasons = AbortReasonBrokenMLSConversation | AbortReasonMLSStaleMessage | AbortReasonMLSGroupOutOfSync | AbortReasonOther;
|
|
49
|
+
export declare function serializeAbortReason(reason: AbortReasons): string;
|
|
50
|
+
export {};
|
|
18
51
|
//# sourceMappingURL=CoreCryptoMLSError.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoreCryptoMLSError.d.ts","sourceRoot":"","sources":["../../../../src/messagingProtocols/mls/MLSService/CoreCryptoMLSError.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;CAanC,CAAC;AAEF,eAAO,MAAM,8BAA8B,UAAW,OAAO,KAAG,OAE/D,CAAC;AAEF,eAAO,MAAM,6CAA6C,UAAW,OAAO,KAAG,OAE9E,CAAC;AAoBF,eAAO,MAAM,iCAAiC,UAAW,OAAO,KAAG,KAAK,IAAI,KAI3E,CAAC"}
|
|
1
|
+
{"version":3,"file":"CoreCryptoMLSError.d.ts","sourceRoot":"","sources":["../../../../src/messagingProtocols/mls/MLSService/CoreCryptoMLSError.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAC,WAAW,EAAC,MAAM,8BAA8B,CAAC;AAEzD,OAAO,EAAC,eAAe,EAAE,YAAY,EAAE,SAAS,EAA6B,YAAY,EAAC,MAAM,sBAAsB,CAAC;AAEvH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;CAanC,CAAC;AAEF,eAAO,MAAM,8BAA8B,UAAW,OAAO,KAAG,OAE/D,CAAC;AAEF,eAAO,MAAM,6CAA6C,UAAW,OAAO,KAAG,OAE9E,CAAC;AAoBF,eAAO,MAAM,iCAAiC,UAAW,OAAO,KAAG,KAAK,IAAI,KAI3E,CAAC;AAEF,eAAO,MAAM,kCAAkC;;;;;CAK9C,CAAC;AAEF,KAAK,oBAAoB,GAAG,eAAe,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG;IAC3D,OAAO,EAAE,OAAO,CACd,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,EAC3B;QACE,IAAI,EAAE,YAAY,CAAC,eAAe,CAAC;KACpC,CACF,CAAC;CACH,CAAC;AAEF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB,CAM1F;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB,CAMpF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB,CAMtF;AAED,wBAAgB,qCAAqC,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,EAAE,CAOnF;AAED,KAAK,gCAAgC,GAAG;IACtC,OAAO,EAAE,OAAO,kCAAkC,CAAC,uBAAuB,CAAC;CAC5E,CAAC;AAEF,KAAK,0BAA0B,GAAG;IAChC,OAAO,EAAE,OAAO,kCAAkC,CAAC,iBAAiB,CAAC;CACtE,CAAC;AAEF,KAAK,4BAA4B,GAAG;IAClC,OAAO,EAAE,OAAO,kCAAkC,CAAC,qBAAqB,CAAC;IACzE,aAAa,EAAE,WAAW,EAAE,CAAC;CAC9B,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,OAAO,EAAE,OAAO,kCAAkC,CAAC,KAAK,CAAC;CAC1D,CAAC;AAEF,KAAK,YAAY,GACb,gCAAgC,GAChC,0BAA0B,GAC1B,4BAA4B,GAC5B,gBAAgB,CAAC;AAErB,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAEjE"}
|
|
@@ -18,7 +18,13 @@
|
|
|
18
18
|
*
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.shouldMLSDecryptionErrorBeIgnored = exports.isCoreCryptoMLSConversationAlreadyExistsError = exports.isCoreCryptoMLSWrongEpochError = exports.CORE_CRYPTO_ERROR_NAMES = void 0;
|
|
21
|
+
exports.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS = exports.shouldMLSDecryptionErrorBeIgnored = exports.isCoreCryptoMLSConversationAlreadyExistsError = exports.isCoreCryptoMLSWrongEpochError = exports.CORE_CRYPTO_ERROR_NAMES = void 0;
|
|
22
|
+
exports.isBrokenMLSConversationError = isBrokenMLSConversationError;
|
|
23
|
+
exports.isMLSStaleMessageError = isMLSStaleMessageError;
|
|
24
|
+
exports.isMLSGroupOutOfSyncError = isMLSGroupOutOfSyncError;
|
|
25
|
+
exports.getMLSGroupOutOfSyncErrorMissingUsers = getMLSGroupOutOfSyncErrorMissingUsers;
|
|
26
|
+
exports.serializeAbortReason = serializeAbortReason;
|
|
27
|
+
const core_crypto_1 = require("@wireapp/core-crypto");
|
|
22
28
|
exports.CORE_CRYPTO_ERROR_NAMES = {
|
|
23
29
|
MlsErrorConversationAlreadyExists: 'MlsErrorConversationAlreadyExists',
|
|
24
30
|
MlsErrorDuplicateMessage: 'MlsErrorDuplicateMessage',
|
|
@@ -59,3 +65,37 @@ const shouldMLSDecryptionErrorBeIgnored = (error) => {
|
|
|
59
65
|
return (error instanceof Error && (mlsDecryptionErrorNamesToIgnore.includes(error.name) || isOtherErrorToIgnore(error)));
|
|
60
66
|
};
|
|
61
67
|
exports.shouldMLSDecryptionErrorBeIgnored = shouldMLSDecryptionErrorBeIgnored;
|
|
68
|
+
exports.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS = {
|
|
69
|
+
BROKEN_MLS_CONVERSATION: 'BROKEN_MLS_CONVERSATION',
|
|
70
|
+
MLS_STALE_MESSAGE: 'MLS_STALE_MESSAGE',
|
|
71
|
+
MLS_GROUP_OUT_OF_SYNC: 'MLS_GROUP_OUT_OF_SYNC',
|
|
72
|
+
OTHER: 'OTHER',
|
|
73
|
+
};
|
|
74
|
+
function isBrokenMLSConversationError(error) {
|
|
75
|
+
return ((0, core_crypto_1.isMlsMessageRejectedError)(error) &&
|
|
76
|
+
deserializeAbortReason(error.context.context.reason).message ===
|
|
77
|
+
exports.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.BROKEN_MLS_CONVERSATION);
|
|
78
|
+
}
|
|
79
|
+
function isMLSStaleMessageError(error) {
|
|
80
|
+
return ((0, core_crypto_1.isMlsMessageRejectedError)(error) &&
|
|
81
|
+
deserializeAbortReason(error.context.context.reason).message ===
|
|
82
|
+
exports.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_STALE_MESSAGE);
|
|
83
|
+
}
|
|
84
|
+
function isMLSGroupOutOfSyncError(error) {
|
|
85
|
+
return ((0, core_crypto_1.isMlsMessageRejectedError)(error) &&
|
|
86
|
+
deserializeAbortReason(error.context.context.reason).message ===
|
|
87
|
+
exports.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_GROUP_OUT_OF_SYNC);
|
|
88
|
+
}
|
|
89
|
+
function getMLSGroupOutOfSyncErrorMissingUsers(error) {
|
|
90
|
+
if (isMLSGroupOutOfSyncError(error)) {
|
|
91
|
+
const reason = deserializeAbortReason(error.context.context.reason);
|
|
92
|
+
return reason.missing_users;
|
|
93
|
+
}
|
|
94
|
+
throw new Error('Error is not MLSGroupOutOfSyncError');
|
|
95
|
+
}
|
|
96
|
+
function serializeAbortReason(reason) {
|
|
97
|
+
return JSON.stringify(reason);
|
|
98
|
+
}
|
|
99
|
+
function deserializeAbortReason(reasonString) {
|
|
100
|
+
return JSON.parse(reasonString);
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CoreCryptoMLSError.test.d.ts","sourceRoot":"","sources":["../../../../src/messagingProtocols/mls/MLSService/CoreCryptoMLSError.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Wire
|
|
4
|
+
* Copyright (C) 2025 Wire Swiss GmbH
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU General Public License
|
|
17
|
+
* along with this program. If not, see http://www.gnu.org/licenses/.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
const core_crypto_1 = require("@wireapp/core-crypto");
|
|
22
|
+
const CoreCryptoMLSError_1 = require("./CoreCryptoMLSError");
|
|
23
|
+
describe('CoreCryptoMLSError helpers', () => {
|
|
24
|
+
describe('epoch and conversation existence guards', () => {
|
|
25
|
+
it('detects MlsErrorWrongEpoch by name', () => {
|
|
26
|
+
const err = new Error('wrong epoch');
|
|
27
|
+
err.name = CoreCryptoMLSError_1.CORE_CRYPTO_ERROR_NAMES.MlsErrorWrongEpoch;
|
|
28
|
+
expect((0, CoreCryptoMLSError_1.isCoreCryptoMLSWrongEpochError)(err)).toBe(true);
|
|
29
|
+
const other = new Error('nope');
|
|
30
|
+
expect((0, CoreCryptoMLSError_1.isCoreCryptoMLSWrongEpochError)(other)).toBe(false);
|
|
31
|
+
expect((0, CoreCryptoMLSError_1.isCoreCryptoMLSWrongEpochError)('not-an-error')).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it('detects MlsErrorConversationAlreadyExists by name', () => {
|
|
34
|
+
const err = new Error('already exists');
|
|
35
|
+
err.name = CoreCryptoMLSError_1.CORE_CRYPTO_ERROR_NAMES.MlsErrorConversationAlreadyExists;
|
|
36
|
+
expect((0, CoreCryptoMLSError_1.isCoreCryptoMLSConversationAlreadyExistsError)(err)).toBe(true);
|
|
37
|
+
const other = new Error('nope');
|
|
38
|
+
expect((0, CoreCryptoMLSError_1.isCoreCryptoMLSConversationAlreadyExistsError)(other)).toBe(false);
|
|
39
|
+
expect((0, CoreCryptoMLSError_1.isCoreCryptoMLSConversationAlreadyExistsError)(42)).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('shouldMLSDecryptionErrorBeIgnored', () => {
|
|
43
|
+
const namesToIgnore = [
|
|
44
|
+
CoreCryptoMLSError_1.CORE_CRYPTO_ERROR_NAMES.MlsErrorStaleCommit,
|
|
45
|
+
CoreCryptoMLSError_1.CORE_CRYPTO_ERROR_NAMES.MlsErrorStaleProposal,
|
|
46
|
+
CoreCryptoMLSError_1.CORE_CRYPTO_ERROR_NAMES.MlsErrorDuplicateMessage,
|
|
47
|
+
CoreCryptoMLSError_1.CORE_CRYPTO_ERROR_NAMES.MlsErrorBufferedFutureMessage,
|
|
48
|
+
CoreCryptoMLSError_1.CORE_CRYPTO_ERROR_NAMES.MlsErrorUnmergedPendingGroup,
|
|
49
|
+
];
|
|
50
|
+
it('returns true for known ignorable error names', () => {
|
|
51
|
+
for (const name of namesToIgnore) {
|
|
52
|
+
const err = new Error('ignore me');
|
|
53
|
+
err.name = name;
|
|
54
|
+
expect((0, CoreCryptoMLSError_1.shouldMLSDecryptionErrorBeIgnored)(err)).toBe(true);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
it('returns true for generic MlsErrorOther with expected commit/proposals message', () => {
|
|
58
|
+
const err = new Error('Incoming message is a commit for which we have not yet received all the proposals: details...');
|
|
59
|
+
err.name = CoreCryptoMLSError_1.CORE_CRYPTO_ERROR_NAMES.MlsErrorOther;
|
|
60
|
+
expect((0, CoreCryptoMLSError_1.shouldMLSDecryptionErrorBeIgnored)(err)).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
it('returns false for other errors or non-Error values', () => {
|
|
63
|
+
const err = new Error('some real failure');
|
|
64
|
+
err.name = 'RandomErrorName';
|
|
65
|
+
expect((0, CoreCryptoMLSError_1.shouldMLSDecryptionErrorBeIgnored)(err)).toBe(false);
|
|
66
|
+
expect((0, CoreCryptoMLSError_1.shouldMLSDecryptionErrorBeIgnored)('not-an-error')).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('abort reason based guards', () => {
|
|
70
|
+
function makeRejectedError(reason) {
|
|
71
|
+
return {
|
|
72
|
+
type: core_crypto_1.ErrorType.Mls,
|
|
73
|
+
context: {
|
|
74
|
+
type: core_crypto_1.MlsErrorType.MessageRejected,
|
|
75
|
+
context: {
|
|
76
|
+
reason,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
it('detects broken MLS conversation errors', () => {
|
|
82
|
+
const err = makeRejectedError((0, CoreCryptoMLSError_1.serializeAbortReason)({ message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.BROKEN_MLS_CONVERSATION }));
|
|
83
|
+
expect((0, CoreCryptoMLSError_1.isBrokenMLSConversationError)(err)).toBe(true);
|
|
84
|
+
expect((0, CoreCryptoMLSError_1.isMLSStaleMessageError)(err)).toBe(false);
|
|
85
|
+
expect((0, CoreCryptoMLSError_1.isMLSGroupOutOfSyncError)(err)).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
it('detects MLS stale message errors', () => {
|
|
88
|
+
const err = makeRejectedError((0, CoreCryptoMLSError_1.serializeAbortReason)({ message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_STALE_MESSAGE }));
|
|
89
|
+
expect((0, CoreCryptoMLSError_1.isMLSStaleMessageError)(err)).toBe(true);
|
|
90
|
+
expect((0, CoreCryptoMLSError_1.isBrokenMLSConversationError)(err)).toBe(false);
|
|
91
|
+
expect((0, CoreCryptoMLSError_1.isMLSGroupOutOfSyncError)(err)).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
it('detects MLS group out-of-sync errors and exposes missing users', () => {
|
|
94
|
+
const missing = [
|
|
95
|
+
{ id: 'u1', domain: 'example.com' },
|
|
96
|
+
{ id: 'u2', domain: 'example.com' },
|
|
97
|
+
];
|
|
98
|
+
const err = makeRejectedError((0, CoreCryptoMLSError_1.serializeAbortReason)({
|
|
99
|
+
message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_GROUP_OUT_OF_SYNC,
|
|
100
|
+
missing_users: missing,
|
|
101
|
+
}));
|
|
102
|
+
expect((0, CoreCryptoMLSError_1.isMLSGroupOutOfSyncError)(err)).toBe(true);
|
|
103
|
+
expect((0, CoreCryptoMLSError_1.getMLSGroupOutOfSyncErrorMissingUsers)(err)).toEqual(missing);
|
|
104
|
+
});
|
|
105
|
+
it('throws when getting missing users from non-MLSGroupOutOfSync error', () => {
|
|
106
|
+
const err = makeRejectedError((0, CoreCryptoMLSError_1.serializeAbortReason)({ message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_STALE_MESSAGE }));
|
|
107
|
+
expect(() => (0, CoreCryptoMLSError_1.getMLSGroupOutOfSyncErrorMissingUsers)(err)).toThrow('Error is not MLSGroupOutOfSyncError');
|
|
108
|
+
});
|
|
109
|
+
it('returns false for non-mls message rejected errors', () => {
|
|
110
|
+
const err = {
|
|
111
|
+
type: core_crypto_1.ErrorType.Mls,
|
|
112
|
+
context: {
|
|
113
|
+
type: core_crypto_1.MlsErrorType.Other,
|
|
114
|
+
context: {
|
|
115
|
+
reason: (0, CoreCryptoMLSError_1.serializeAbortReason)({ message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_STALE_MESSAGE }),
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
expect((0, CoreCryptoMLSError_1.isBrokenMLSConversationError)(err)).toBe(false);
|
|
120
|
+
expect((0, CoreCryptoMLSError_1.isMLSStaleMessageError)(err)).toBe(false);
|
|
121
|
+
expect((0, CoreCryptoMLSError_1.isMLSGroupOutOfSyncError)(err)).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -34,9 +34,14 @@ export declare enum MLSServiceEvents {
|
|
|
34
34
|
NEW_EPOCH = "newEpoch",
|
|
35
35
|
MLS_CLIENT_MISMATCH = "mlsClientMismatch",
|
|
36
36
|
NEW_CRL_DISTRIBUTION_POINTS = "newCrlDistributionPoints",
|
|
37
|
-
MLS_EVENT_DISTRIBUTED = "mlsEventDistributed"
|
|
37
|
+
MLS_EVENT_DISTRIBUTED = "mlsEventDistributed",
|
|
38
|
+
KEY_MATERIAL_UPDATE_FAILURE = "keyMaterialUpdateFailure"
|
|
38
39
|
}
|
|
39
40
|
type Events = {
|
|
41
|
+
[MLSServiceEvents.KEY_MATERIAL_UPDATE_FAILURE]: {
|
|
42
|
+
error: unknown;
|
|
43
|
+
groupId: string;
|
|
44
|
+
};
|
|
40
45
|
[MLSServiceEvents.NEW_EPOCH]: {
|
|
41
46
|
epoch: number;
|
|
42
47
|
groupId: string;
|
|
@@ -57,13 +62,6 @@ export declare class MLSService extends TypedEventEmitter<Events> {
|
|
|
57
62
|
private _config?;
|
|
58
63
|
private readonly textEncoder;
|
|
59
64
|
private readonly textDecoder;
|
|
60
|
-
static UPLOAD_COMMIT_BUNDLE_ABORT_REASONS: {
|
|
61
|
-
BROKEN_MLS_CONVERSATION: string;
|
|
62
|
-
MLS_STALE_MESSAGE: string;
|
|
63
|
-
OTHER: string;
|
|
64
|
-
};
|
|
65
|
-
static isBrokenMLSConversationError(error: unknown): boolean;
|
|
66
|
-
static isMLSStaleMessageError(error: unknown): boolean;
|
|
67
65
|
constructor(apiClient: APIClient, coreCryptoClient: CoreCrypto, coreDatabase: CoreDatabase, recurringTaskScheduler: RecurringTaskScheduler);
|
|
68
66
|
/**
|
|
69
67
|
* return true if the MLS service if configured and ready to be used
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MLSService.d.ts","sourceRoot":"","sources":["../../../../src/messagingProtocols/mls/MLSService/MLSService.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAqB,kBAAkB,EAAE,gBAAgB,EAAC,MAAM,gCAAgC,CAAC;AAC7G,OAAO,
|
|
1
|
+
{"version":3,"file":"MLSService.d.ts","sourceRoot":"","sources":["../../../../src/messagingProtocols/mls/MLSService/MLSService.ts"],"names":[],"mappings":"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"}
|
|
@@ -49,6 +49,7 @@ var MLSServiceEvents;
|
|
|
49
49
|
MLSServiceEvents["MLS_CLIENT_MISMATCH"] = "mlsClientMismatch";
|
|
50
50
|
MLSServiceEvents["NEW_CRL_DISTRIBUTION_POINTS"] = "newCrlDistributionPoints";
|
|
51
51
|
MLSServiceEvents["MLS_EVENT_DISTRIBUTED"] = "mlsEventDistributed";
|
|
52
|
+
MLSServiceEvents["KEY_MATERIAL_UPDATE_FAILURE"] = "keyMaterialUpdateFailure";
|
|
52
53
|
})(MLSServiceEvents || (exports.MLSServiceEvents = MLSServiceEvents = {}));
|
|
53
54
|
class MLSService extends commons_1.TypedEventEmitter {
|
|
54
55
|
apiClient;
|
|
@@ -59,19 +60,6 @@ class MLSService extends commons_1.TypedEventEmitter {
|
|
|
59
60
|
_config;
|
|
60
61
|
textEncoder = new TextEncoder();
|
|
61
62
|
textDecoder = new TextDecoder();
|
|
62
|
-
static UPLOAD_COMMIT_BUNDLE_ABORT_REASONS = {
|
|
63
|
-
BROKEN_MLS_CONVERSATION: 'BROKEN_MLS_CONVERSATION',
|
|
64
|
-
MLS_STALE_MESSAGE: 'MLS_STALE_MESSAGE',
|
|
65
|
-
OTHER: 'OTHER',
|
|
66
|
-
};
|
|
67
|
-
static isBrokenMLSConversationError(error) {
|
|
68
|
-
return ((0, core_crypto_1.isMlsMessageRejectedError)(error) &&
|
|
69
|
-
error.context.context.reason === MLSService.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.BROKEN_MLS_CONVERSATION);
|
|
70
|
-
}
|
|
71
|
-
static isMLSStaleMessageError(error) {
|
|
72
|
-
return ((0, core_crypto_1.isMlsMessageRejectedError)(error) &&
|
|
73
|
-
error.context.context.reason === MLSService.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_STALE_MESSAGE);
|
|
74
|
-
}
|
|
75
63
|
constructor(apiClient, coreCryptoClient, coreDatabase, recurringTaskScheduler) {
|
|
76
64
|
super();
|
|
77
65
|
this.apiClient = apiClient;
|
|
@@ -192,18 +180,39 @@ class MLSService extends commons_1.TypedEventEmitter {
|
|
|
192
180
|
if (error instanceof conversation_1.MLSInvalidLeafNodeSignatureError || error instanceof conversation_1.MLSInvalidLeafNodeIndexError) {
|
|
193
181
|
this.logger.info('Aborting commit bundle upload due to broken MLS conversation');
|
|
194
182
|
return {
|
|
195
|
-
abort: {
|
|
183
|
+
abort: {
|
|
184
|
+
reason: (0, CoreCryptoMLSError_1.serializeAbortReason)({
|
|
185
|
+
message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.BROKEN_MLS_CONVERSATION,
|
|
186
|
+
}),
|
|
187
|
+
},
|
|
196
188
|
};
|
|
197
189
|
}
|
|
198
190
|
if (error instanceof conversation_1.MLSStaleMessageError) {
|
|
199
191
|
this.logger.info('Aborting commit bundle upload due to stale MLS message');
|
|
200
192
|
return {
|
|
201
|
-
abort: {
|
|
193
|
+
abort: {
|
|
194
|
+
reason: (0, CoreCryptoMLSError_1.serializeAbortReason)({ message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_STALE_MESSAGE }),
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (error instanceof conversation_1.MLSGroupOutOfSyncError) {
|
|
199
|
+
this.logger.info('Aborting commit bundle upload due to group out of sync');
|
|
200
|
+
return {
|
|
201
|
+
abort: {
|
|
202
|
+
reason: (0, CoreCryptoMLSError_1.serializeAbortReason)({
|
|
203
|
+
message: CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.MLS_GROUP_OUT_OF_SYNC,
|
|
204
|
+
missing_users: error.missing_users,
|
|
205
|
+
}),
|
|
206
|
+
},
|
|
202
207
|
};
|
|
203
208
|
}
|
|
204
209
|
this.logger.info('Aborting commit bundle upload due to unknown error');
|
|
205
210
|
return {
|
|
206
|
-
abort: {
|
|
211
|
+
abort: {
|
|
212
|
+
reason: (0, CoreCryptoMLSError_1.serializeAbortReason)({
|
|
213
|
+
message: error instanceof Error ? error.message : CoreCryptoMLSError_1.UPLOAD_COMMIT_BUNDLE_ABORT_REASONS.OTHER,
|
|
214
|
+
}),
|
|
215
|
+
},
|
|
207
216
|
};
|
|
208
217
|
}
|
|
209
218
|
};
|
|
@@ -365,9 +374,33 @@ class MLSService extends commons_1.TypedEventEmitter {
|
|
|
365
374
|
async encryptMessage(conversationId, message) {
|
|
366
375
|
return this.coreCryptoClient.transaction(cx => cx.encryptMessage(conversationId, message));
|
|
367
376
|
}
|
|
368
|
-
async updateKeyingMaterial(groupId) {
|
|
369
|
-
|
|
370
|
-
|
|
377
|
+
async updateKeyingMaterial(groupId, retry = true) {
|
|
378
|
+
try {
|
|
379
|
+
const groupIdBytes = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
380
|
+
await this.coreCryptoClient.transaction(cx => cx.updateKeyingMaterial(new core_crypto_1.ConversationId(groupIdBytes)));
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
if (!retry) {
|
|
384
|
+
this.logger.error(`Failed to update keying material for group retrying did not fix the issue`, {
|
|
385
|
+
error,
|
|
386
|
+
groupId,
|
|
387
|
+
});
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
this.logger.warn(`Failed to update keying material for group`, { error, groupId });
|
|
391
|
+
this.emit(MLSServiceEvents.KEY_MATERIAL_UPDATE_FAILURE, { error, groupId });
|
|
392
|
+
setTimeout(async () => {
|
|
393
|
+
try {
|
|
394
|
+
await this.updateKeyingMaterial(groupId, false);
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
this.logger.error(`Failed to update keying material for group on retry`, {
|
|
398
|
+
error,
|
|
399
|
+
groupId,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}, commons_1.TimeUtil.TimeInMillis.SECOND * 10); // retry after 10 seconds
|
|
403
|
+
}
|
|
371
404
|
}
|
|
372
405
|
/**
|
|
373
406
|
* Will create an empty conversation inside of coreCrypto.
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"./lib/cryptography/AssetCryptography/crypto.node": "./lib/cryptography/AssetCryptography/crypto.browser.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@wireapp/api-client": "^27.
|
|
14
|
+
"@wireapp/api-client": "^27.84.0",
|
|
15
15
|
"@wireapp/commons": "^5.4.9",
|
|
16
16
|
"@wireapp/core-crypto": "9.1.0",
|
|
17
17
|
"@wireapp/cryptobox": "12.8.0",
|
|
@@ -61,6 +61,6 @@
|
|
|
61
61
|
"test:coverage": "jest --coverage",
|
|
62
62
|
"watch": "tsc --watch"
|
|
63
63
|
},
|
|
64
|
-
"version": "46.
|
|
65
|
-
"gitHead": "
|
|
64
|
+
"version": "46.41.0",
|
|
65
|
+
"gitHead": "ccf0832c9aba3fdafe377ef5c21d515b24f54bb1"
|
|
66
66
|
}
|