diff --git a/ts/data/settings-key.ts b/ts/data/settings-key.ts index 67f2291d1..6fd295133 100644 --- a/ts/data/settings-key.ts +++ b/ts/data/settings-key.ts @@ -14,6 +14,11 @@ const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem'; const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp'; const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed'; +// user config tracking timestamps (to discard incoming messages which would make a change we reverted in the last config message we merged) +const latestUserProfileEnvelopeTimestamp = 'latestUserProfileEnvelopeTimestamp'; +const latestUserGroupEnvelopeTimestamp = 'latestUserGroupEnvelopeTimestamp'; +const latestUserContactsEnvelopeTimestamp = 'latestUserContactsEnvelopeTimestamp'; + export const SettingsKey = { settingsReadReceipt, settingsTypingIndicator, @@ -29,6 +34,9 @@ export const SettingsKey = { hasSyncedInitialConfigurationItem, lastAvatarUploadTimestamp, hasLinkPreviewPopupBeenDisplayed, + latestUserProfileEnvelopeTimestamp, + latestUserGroupEnvelopeTimestamp, + latestUserContactsEnvelopeTimestamp, } as const; export const KNOWN_BLINDED_KEYS_ITEM = 'KNOWN_BLINDED_KEYS_ITEM'; diff --git a/ts/receiver/closedGroups.ts b/ts/receiver/closedGroups.ts index 9b99a76b3..1e7f0ed73 100644 --- a/ts/receiver/closedGroups.ts +++ b/ts/receiver/closedGroups.ts @@ -1,25 +1,29 @@ +import { Data } from '../../ts/data/data'; import { SignalService } from '../protobuf'; -import { removeFromCache } from './cache'; -import { EnvelopePlus } from './types'; -import { PubKey } from '../session/types'; -import { toHex } from '../session/utils/String'; +import { getMessageQueue } from '../session'; import { getConversationController } from '../session/conversations'; import * as ClosedGroup from '../session/group/closed-group'; +import { PubKey } from '../session/types'; +import { toHex } from '../session/utils/String'; import { BlockedNumberController } from '../util'; -import { getMessageQueue } from '../session'; +import { removeFromCache } from './cache'; import { decryptWithSessionProtocol } from './contentMessage'; -import { Data } from '../../ts/data/data'; +import { EnvelopePlus } from './types'; -import { ECKeyPair, HexKeyPair } from './keypairs'; -import { UserUtils } from '../session/utils'; +import _, { isNumber, toNumber } from 'lodash'; import { ConversationModel } from '../models/conversation'; -import _ from 'lodash'; -import { ClosedGroupEncryptionPairReplyMessage } from '../session/messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairReplyMessage'; -import { queueAllCachedFromSource } from './receiver'; -import { getSwarmPollingInstance } from '../session/apis/snode_api'; -import { perfEnd, perfStart } from '../session/utils/Performance'; import { ConversationTypeEnum } from '../models/conversationAttributes'; +import { getSwarmPollingInstance } from '../session/apis/snode_api'; import { SnodeNamespaces } from '../session/apis/snode_api/namespaces'; +import { ClosedGroupEncryptionPairReplyMessage } from '../session/messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairReplyMessage'; +import { UserUtils } from '../session/utils'; +import { perfEnd, perfStart } from '../session/utils/Performance'; +import { ReleasedFeatures } from '../util/releaseFeature'; +import { Storage } from '../util/storage'; +import { ConfigWrapperObjectTypes } from '../webworker/workers/browser/libsession_worker_functions'; +import { getSettingsKeyFromLibsessionWrapper } from './configMessage'; +import { ECKeyPair, HexKeyPair } from './keypairs'; +import { queueAllCachedFromSource } from './receiver'; export const distributingClosedGroupEncryptionKeyPairs = new Map(); @@ -113,7 +117,7 @@ export async function handleClosedGroupControlMessage( ); return; } - await handleNewClosedGroup(envelope, groupUpdate); + await handleNewClosedGroup(envelope, groupUpdate, false); return; } @@ -199,9 +203,48 @@ function sanityCheckNewGroup( return true; } +/** + * If we merged a more recent wrapper, we must not apply the changes from some incoming messages as it would override a change already set in the wrapper. + * + * This is mostly to take care of the link a device logic, where we apply the changes from a wrapper, and then start polling from our swarm namespace 0. + * Some messages on our swarm might unhide a contact which was marked hidden after that message was already received on another device. Same for groups left/joined etc. + * + * @returns true if the user config release is live AND the latest processed corresponding wrapper is supposed to have already included the changes this message did. + * So if that message should not make any changes to the ata tracked in the wrappers (just add messages if needed, but don't set members, unhide contact etc). + */ +export async function sentAtMoreRecentThanWrapper( + envelopeSentAtMs: number, + variant: ConfigWrapperObjectTypes +): Promise<'unknown' | 'wrapper_more_recent' | 'envelope_more_recent'> { + const userConfigReleased = await ReleasedFeatures.checkIsUserConfigFeatureReleased(); + if (!userConfigReleased) { + return 'unknown'; + } + + const settingsKey = getSettingsKeyFromLibsessionWrapper(variant); + if (!settingsKey) { + return 'unknown'; + } + const latestProcessedEnvelope = Storage.get(settingsKey); + if (!isNumber(latestProcessedEnvelope) || !latestProcessedEnvelope) { + // We want to process the message if we do not have valid data in the db. + // Also, we DO want to process a message if we DO NOT have a latest processed timestamp for that wrapper yet + return 'envelope_more_recent'; + } + + // this must return true if the message we are considering should have already been handled based on our `latestProcessedEnvelope`. + // so if that message was sent before `latestProcessedEnvelope - 2 mins`, we must return true; + const latestProcessedEnvelopeLess2Mins = latestProcessedEnvelope - 2 * 60 * 1000; + + return envelopeSentAtMs > latestProcessedEnvelopeLess2Mins + ? 'envelope_more_recent' + : 'wrapper_more_recent'; +} + export async function handleNewClosedGroup( envelope: EnvelopePlus, - groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage + groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage, + fromLegacyConfig: boolean ) { if (groupUpdate.type !== SignalService.DataMessage.ClosedGroupControlMessage.Type.NEW) { return; @@ -229,10 +272,21 @@ export async function handleNewClosedGroup( const groupId = toHex(publicKey); const members = membersAsData.map(toHex); const admins = adminsAsData.map(toHex); - const envelopeTimestamp = _.toNumber(envelope.timestamp); + const envelopeTimestamp = toNumber(envelope.timestamp); // a type new is sent and received on one to one so do not use envelope.senderIdentity here const sender = envelope.source; + if ( + !fromLegacyConfig && + (await sentAtMoreRecentThanWrapper(envelopeTimestamp, 'UserGroupsConfig')) === + 'wrapper_more_recent' + ) { + // not from legacy config, so this is a new closed group deposited on our swarm by a user. + // we do not want to process it if our wrapper is more recent that that invite to group envelope. + window.log.info('dropping invite to legacy group because our wrapper is more recent'); + return removeFromCache(envelope); + } + if (!members.includes(ourNumber.key)) { window?.log?.info( 'Got a new group message but apparently we are not a member of it. Dropping it.' @@ -276,7 +330,7 @@ export async function handleNewClosedGroup( groupConvo.set({ left: false, isKickedFromGroup: false, - lastJoinedTimestamp: _.toNumber(envelope.timestamp), + lastJoinedTimestamp: toNumber(envelope.timestamp), // we just got readded. Consider the zombie list to have been cleared zombies: [], @@ -493,7 +547,7 @@ async function performIfValid( lastJoinedTimestamp = aYearAgo; } - const envelopeTimestamp = _.toNumber(envelope.timestamp); + const envelopeTimestamp = toNumber(envelope.timestamp); if (envelopeTimestamp <= lastJoinedTimestamp) { window?.log?.warn( 'Got a group update with an older timestamp than when we joined this group last time. Dropping it.' @@ -513,26 +567,28 @@ async function performIfValid( // make sure the conversation with this user exist (even if it's just hidden) await getConversationController().getOrCreateAndWait(sender, ConversationTypeEnum.PRIVATE); + const moreRecentOrNah = await sentAtMoreRecentThanWrapper(envelopeTimestamp, 'UserGroupsConfig'); + const shouldNotApplyGroupChange = moreRecentOrNah === 'wrapper_more_recent'; + if (groupUpdate.type === Type.NAME_CHANGE) { - await handleClosedGroupNameChanged(envelope, groupUpdate, convo); + await handleClosedGroupNameChanged(envelope, groupUpdate, convo, shouldNotApplyGroupChange); } else if (groupUpdate.type === Type.MEMBERS_ADDED) { - await handleClosedGroupMembersAdded(envelope, groupUpdate, convo); + await handleClosedGroupMembersAdded(envelope, groupUpdate, convo, shouldNotApplyGroupChange); } else if (groupUpdate.type === Type.MEMBERS_REMOVED) { - await handleClosedGroupMembersRemoved(envelope, groupUpdate, convo); + await handleClosedGroupMembersRemoved(envelope, groupUpdate, convo, shouldNotApplyGroupChange); } else if (groupUpdate.type === Type.MEMBER_LEFT) { - await handleClosedGroupMemberLeft(envelope, convo); + await handleClosedGroupMemberLeft(envelope, convo, shouldNotApplyGroupChange); } else if (groupUpdate.type === Type.ENCRYPTION_KEY_PAIR_REQUEST) { await removeFromCache(envelope); } // if you add a case here, remember to add it where performIfValid is called too. - - return true; } async function handleClosedGroupNameChanged( envelope: EnvelopePlus, groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage, - convo: ConversationModel + convo: ConversationModel, + shouldOnlyAddUpdateMessage: boolean // set this to true to not apply the change to the convo itself, just add the update in the conversation ) { // Only add update message if we have something to show const newName = groupUpdate.name; @@ -546,9 +602,11 @@ async function handleClosedGroupNameChanged( convo, groupDiff, envelope.senderIdentity, - _.toNumber(envelope.timestamp) + toNumber(envelope.timestamp) ); - convo.set({ displayNameInProfile: newName }); + if (!shouldOnlyAddUpdateMessage) { + convo.set({ displayNameInProfile: newName }); + } convo.updateLastMessage(); await convo.commit(); } @@ -559,7 +617,8 @@ async function handleClosedGroupNameChanged( async function handleClosedGroupMembersAdded( envelope: EnvelopePlus, groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage, - convo: ConversationModel + convo: ConversationModel, + shouldOnlyAddUpdateMessage: boolean // set this to true to not apply the change to the convo itself, just add the update in the conversation ) { const { members: addedMembersBinary } = groupUpdate; const addedMembers = (addedMembersBinary || []).map(toHex); @@ -602,10 +661,12 @@ async function handleClosedGroupMembersAdded( convo, groupDiff, envelope.senderIdentity, - _.toNumber(envelope.timestamp) + toNumber(envelope.timestamp) ); - convo.set({ members }); + if (!shouldOnlyAddUpdateMessage) { + convo.set({ members }); + } convo.updateLastMessage(); await convo.commit(); @@ -625,7 +686,8 @@ async function areWeAdmin(groupConvo: ConversationModel) { async function handleClosedGroupMembersRemoved( envelope: EnvelopePlus, groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage, - convo: ConversationModel + convo: ConversationModel, + shouldOnlyAddUpdateMessage: boolean // set this to true to not apply the change to the convo itself, just add the update in the conversation ) { // Check that the admin wasn't removed const currentMembers = convo.get('members'); @@ -681,7 +743,7 @@ async function handleClosedGroupMembersRemoved( convo, groupDiff, envelope.senderIdentity, - _.toNumber(envelope.timestamp) + toNumber(envelope.timestamp) ); convo.updateLastMessage(); } @@ -689,9 +751,10 @@ async function handleClosedGroupMembersRemoved( // Update the group const zombies = convo.get('zombies').filter(z => membersAfterUpdate.includes(z)); - convo.set({ members: membersAfterUpdate }); - convo.set({ zombies }); - + if (!shouldOnlyAddUpdateMessage) { + convo.set({ members: membersAfterUpdate }); + convo.set({ zombies }); + } await convo.commit(); } await removeFromCache(envelope); @@ -761,7 +824,11 @@ async function handleClosedGroupLeftOurself(groupId: string, envelope: EnvelopeP await removeFromCache(envelope); } -async function handleClosedGroupMemberLeft(envelope: EnvelopePlus, convo: ConversationModel) { +async function handleClosedGroupMemberLeft( + envelope: EnvelopePlus, + convo: ConversationModel, + shouldOnlyAddUpdateMessage: boolean // set this to true to not apply the change to the convo itself, just add the update in the conversation +) { const sender = envelope.senderIdentity; const groupPublicKey = envelope.source; const didAdminLeave = convo.get('groupAdmins')?.includes(sender) || false; @@ -778,7 +845,6 @@ async function handleClosedGroupMemberLeft(envelope: EnvelopePlus, convo: Conver const ourPubkey = UserUtils.getOurPubKeyStrFromCache(); // if the admin leaves, the group is disabled for everyone - if (didAdminLeave) { await handleClosedGroupAdminMemberLeft(groupPublicKey, envelope); return; @@ -801,14 +867,16 @@ async function handleClosedGroupMemberLeft(envelope: EnvelopePlus, convo: Conver convo, groupDiff, envelope.senderIdentity, - _.toNumber(envelope.timestamp) + toNumber(envelope.timestamp) ); convo.updateLastMessage(); // if a user just left and we are the admin, we remove him right away for everyone by sending a MEMBERS_REMOVED message so no need to add him as a zombie if (oldMembers.includes(sender)) { addMemberToZombies(envelope, PubKey.cast(sender), convo); } - convo.set('members', newMembers); + if (!shouldOnlyAddUpdateMessage) { + convo.set('members', newMembers); + } await convo.commit(); diff --git a/ts/receiver/configMessage.ts b/ts/receiver/configMessage.ts index b4f2ee228..0d814306a 100644 --- a/ts/receiver/configMessage.ts +++ b/ts/receiver/configMessage.ts @@ -146,6 +146,50 @@ async function mergeConfigsWithIncomingUpdates( } } +export function getSettingsKeyFromLibsessionWrapper( + wrapperType: ConfigWrapperObjectTypes +): string | null { + switch (wrapperType) { + case 'UserConfig': + return SettingsKey.latestUserProfileEnvelopeTimestamp; + case 'ContactsConfig': + return SettingsKey.latestUserContactsEnvelopeTimestamp; + case 'UserGroupsConfig': + return SettingsKey.latestUserGroupEnvelopeTimestamp; + case 'ConvoInfoVolatileConfig': + return null; // we don't really care about the convo info volatile one + default: + try { + assertUnreachable( + wrapperType, + `getSettingsKeyFromLibsessionWrapper unknown type: ${wrapperType}` + ); + } catch (e) { + window.log.warn('assertUnreachable:', e.message); + } + return null; + } +} + +async function updateLibsessionLatestProcessedUserTimestamp( + wrapperType: ConfigWrapperObjectTypes, + latestEnvelopeTimestamp: number +) { + const settingsKey = getSettingsKeyFromLibsessionWrapper(wrapperType); + if (!settingsKey) { + return; + } + const currentLatestEnvelopeProcessed = Storage.get(settingsKey) || 0; + + const newLatestProcessed = Math.max( + latestEnvelopeTimestamp, + isNumber(currentLatestEnvelopeProcessed) ? currentLatestEnvelopeProcessed : 0 + ); + if (newLatestProcessed !== currentLatestEnvelopeProcessed || currentLatestEnvelopeProcessed) { + await Storage.put(settingsKey, newLatestProcessed); + } +} + async function handleUserProfileUpdate(result: IncomingConfResult): Promise { const updateUserInfo = await UserConfigWrapperActions.getUserInfo(); if (!updateUserInfo) { @@ -161,6 +205,17 @@ async function handleUserProfileUpdate(result: IncomingConfResult): Promise { } } +async function shouldDropIncomingPrivateMessage(sentAtTimestamp: number, envelope: EnvelopePlus) { + // sentAtMoreRecentThanWrapper is going to be true, if the latest contact wrapper we processed was roughly more recent that this message timestamp + const moreRecentOrNah = await sentAtMoreRecentThanWrapper(sentAtTimestamp, 'ContactsConfig'); + + if (moreRecentOrNah === 'wrapper_more_recent') { + // we need to check if that conversation is already in the wrapper, and if yes + try { + const privateConvoInWrapper = await ContactsWrapperActions.get(envelope.source); + if ( + !privateConvoInWrapper || + privateConvoInWrapper.priority <= CONVERSATION_PRIORITIES.hidden + ) { + // the wrapper is more recent that this message and there is no such private conversation. Just drop this incoming message. + window.log.info( + `received message from contact ${envelope.source} which appears to be hidden/removed in our most recent libsession contactconfig, sentAt: ${sentAtTimestamp}. Dropping it` + ); + return true; + } else { + window.log.info( + `received message from contact ${envelope.source} which appears to NOT be hidden/removed in our most recent libsession contactconfig, sentAt: ${sentAtTimestamp}. ` + ); + } + } catch (e) { + window.log.warn( + 'ContactsWrapperActions.get in handleSwarmDataMessage failed with', + e.message + ); + } + } + return false; +} + function shouldDropBlockedUserMessage( content: SignalService.Content, groupPubkey: string @@ -373,6 +410,12 @@ export async function innerHandleSwarmContentMessage( // if this is a closed group message, envelope.senderIdentity is the sender's pubkey and envelope.source is the closed group's pubkey const isPrivateConversationMessage = !envelope.senderIdentity; + if (isPrivateConversationMessage) { + if (await shouldDropIncomingPrivateMessage(sentAtTimestamp, envelope)) { + return removeFromCache(envelope); + } + } + /** * For a closed group message, this holds the conversation with that specific user outside of the closed group. * For a private conversation message, this is just the conversation with that user @@ -396,7 +439,7 @@ export async function innerHandleSwarmContentMessage( if (content.dataMessage) { // because typescript is funky with incoming protobufs - if (content.dataMessage.profileKey && content.dataMessage.profileKey.length === 0) { + if (isEmpty(content.dataMessage.profileKey)) { content.dataMessage.profileKey = null; } perfStart(`handleSwarmDataMessage-${envelope.id}`); diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts index ad28ae03d..9f7d1e7ea 100644 --- a/ts/receiver/dataMessage.ts +++ b/ts/receiver/dataMessage.ts @@ -1,28 +1,28 @@ import { SignalService } from './../protobuf'; import { removeFromCache } from './cache'; -import { EnvelopePlus } from './types'; import { getEnvelopeId } from './common'; +import { EnvelopePlus } from './types'; -import { PubKey } from '../session/types'; -import { handleMessageJob, toRegularMessage } from './queuedJob'; import { isEmpty, isFinite, noop, omit, toNumber } from 'lodash'; -import { StringUtils, UserUtils } from '../session/utils'; -import { getConversationController } from '../session/conversations'; -import { handleClosedGroupControlMessage } from './closedGroups'; import { Data } from '../../ts/data/data'; import { ConversationModel } from '../models/conversation'; +import { getConversationController } from '../session/conversations'; +import { PubKey } from '../session/types'; +import { StringUtils, UserUtils } from '../session/utils'; +import { handleClosedGroupControlMessage } from './closedGroups'; +import { handleMessageJob, toRegularMessage } from './queuedJob'; +import { ConversationTypeEnum } from '../models/conversationAttributes'; +import { MessageModel } from '../models/message'; import { createSwarmMessageSentFromNotUs, createSwarmMessageSentFromUs, } from '../models/messageFactory'; -import { MessageModel } from '../models/message'; -import { isUsFromCache } from '../session/utils/User'; -import { toLogFormat } from '../types/attachments/Errors'; -import { ConversationTypeEnum } from '../models/conversationAttributes'; -import { Reactions } from '../util/reactions'; -import { Action, Reaction } from '../types/Reaction'; import { ProfileManager } from '../session/profile_manager/ProfileManager'; +import { isUsFromCache } from '../session/utils/User'; +import { Action, Reaction } from '../types/Reaction'; +import { toLogFormat } from '../types/attachments/Errors'; +import { Reactions } from '../util/reactions'; function cleanAttachment(attachment: any) { return { @@ -185,8 +185,6 @@ export async function handleSwarmDataMessage( if (isSyncedMessage && !isMe) { window?.log?.warn('Got a sync message from someone else than me. Dropping it.'); return removeFromCache(envelope); - } else if (isSyncedMessage) { - // we should create the synTarget convo but I have no idea how to know if this is a private or closed group convo? } const convoIdToAddTheMessageTo = PubKey.removeTextSecurePrefixIfNeeded( isSyncedMessage ? cleanDataMessage.syncTarget : envelope.source @@ -201,16 +199,16 @@ export async function handleSwarmDataMessage( typeOfConvo = ConversationTypeEnum.GROUP; } + window?.log?.info( + `Handle dataMessage about convo ${convoIdToAddTheMessageTo} from user: ${convoIdOfSender}` + ); + + // remove the prefix from the source object so this is correct for all other const convoToAddMessageTo = await getConversationController().getOrCreateAndWait( convoIdToAddTheMessageTo, typeOfConvo ); - window?.log?.info( - `Handle dataMessage about convo ${convoIdToAddTheMessageTo} from user: ${convoIdOfSender}` - ); - // remove the prefix from the source object so this is correct for all other - // Check if we need to update any profile names if ( !isMe && diff --git a/ts/session/apis/snode_api/SNodeAPI.ts b/ts/session/apis/snode_api/SNodeAPI.ts index aed7acc4a..19b2773c9 100644 --- a/ts/session/apis/snode_api/SNodeAPI.ts +++ b/ts/session/apis/snode_api/SNodeAPI.ts @@ -25,6 +25,7 @@ const forceNetworkDeletion = async (): Promise | null> => { return null; } const method = 'delete_all' as const; + const namespace = 'all' as const; try { const maliciousSnodes = await pRetry( @@ -41,12 +42,12 @@ const forceNetworkDeletion = async (): Promise | null> => { async () => { const signOpts = await SnodeSignature.getSnodeSignatureParams({ method, - namespace: null, + namespace, pubkey: userX25519PublicKey, }); const ret = await doSnodeBatchRequest( - [{ method, params: signOpts }], + [{ method, params: { ...signOpts, namespace } }], snodeToMakeRequestTo, 10000, userX25519PublicKey @@ -112,12 +113,18 @@ const forceNetworkDeletion = async (): Promise | null> => { return snodePubkey; } - const hashes = snodeJson.deleted as Array; + const deletedObj = snodeJson.deleted as Record>; + const hashes: Array = []; + for (const key in deletedObj) { + hashes.push(...deletedObj[key]); + } + const sortedHashes = hashes.sort(); const signatureSnode = snodeJson.signature as string; - // The signature format is ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] ) - const dataToVerify = `${userX25519PublicKey}${signOpts.timestamp}${hashes.join( - '' - )}`; + // The signature format is (with sortedHashes) ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] ) + const dataToVerify = `${userX25519PublicKey}${ + signOpts.timestamp + }${sortedHashes.join('')}`; + const dataToVerifyUtf8 = StringUtils.encode(dataToVerify, 'utf8'); const isValid = sodium.crypto_sign_verify_detached( fromBase64ToArray(signatureSnode), diff --git a/ts/session/apis/snode_api/SnodeRequestTypes.ts b/ts/session/apis/snode_api/SnodeRequestTypes.ts index 69a539a84..693fded70 100644 --- a/ts/session/apis/snode_api/SnodeRequestTypes.ts +++ b/ts/session/apis/snode_api/SnodeRequestTypes.ts @@ -101,6 +101,7 @@ export type StoreOnNodeParamsNoSig = Pick< export type DeleteFromNodeWithTimestampParams = { timestamp: string | number; + namespace: number | null | 'all'; } & DeleteSigParameters; export type DeleteByHashesFromNodeParams = { messages: Array } & DeleteSigParameters; diff --git a/ts/session/apis/snode_api/namespaces.ts b/ts/session/apis/snode_api/namespaces.ts index 23d60450c..1adb6d5e2 100644 --- a/ts/session/apis/snode_api/namespaces.ts +++ b/ts/session/apis/snode_api/namespaces.ts @@ -1,5 +1,6 @@ import { last, orderBy } from 'lodash'; import { assertUnreachable } from '../../../types/sqlSharedTypes'; +import { PickEnum } from '../../../types/Enums'; export enum SnodeNamespaces { /** @@ -36,10 +37,6 @@ export enum SnodeNamespaces { // ClosedGroupInfo = 1, } -type PickEnum = { - [P in keyof K]: P extends K ? P : never; -}; - export type SnodeNamespacesGroup = PickEnum< SnodeNamespaces, SnodeNamespaces.ClosedGroupMessage // | SnodeNamespaces.ClosedGroupInfo diff --git a/ts/session/apis/snode_api/snodeSignatures.ts b/ts/session/apis/snode_api/snodeSignatures.ts index dfee91598..0789180d7 100644 --- a/ts/session/apis/snode_api/snodeSignatures.ts +++ b/ts/session/apis/snode_api/snodeSignatures.ts @@ -54,7 +54,7 @@ async function getSnodeSignatureByHashesParams({ async function getSnodeSignatureParams(params: { pubkey: string; - namespace: number | null; + namespace: number | null | 'all'; // 'all' can be used to clear all namespaces (during account deletion) method: 'retrieve' | 'store' | 'delete_all'; }): Promise { const ourEd25519Key = await UserUtils.getUserED25519KeyPair(); @@ -69,10 +69,12 @@ async function getSnodeSignatureParams(params: { const signatureTimestamp = GetNetworkTime.getNowWithNetworkOffset(); + const withoutNamespace = `${params.method}${signatureTimestamp}`; + const withNamespace = `${params.method}${namespace}${signatureTimestamp}`; const verificationData = namespace === 0 - ? StringUtils.encode(`${params.method}${signatureTimestamp}`, 'utf8') - : StringUtils.encode(`${params.method}${namespace}${signatureTimestamp}`, 'utf8'); + ? StringUtils.encode(withoutNamespace, 'utf8') + : StringUtils.encode(withNamespace, 'utf8'); const message = new Uint8Array(verificationData); diff --git a/ts/session/group/closed-group.ts b/ts/session/group/closed-group.ts index 5c1c07722..230fc2b68 100644 --- a/ts/session/group/closed-group.ts +++ b/ts/session/group/closed-group.ts @@ -393,8 +393,8 @@ async function generateAndSendNewEncryptionKeyPair( return; } - const ourNumber = UserUtils.getOurPubKeyFromCache(); - if (!groupConvo.get('groupAdmins')?.includes(ourNumber.key)) { + const ourNumber = UserUtils.getOurPubKeyStrFromCache(); + if (!groupConvo.get('groupAdmins')?.includes(ourNumber)) { window?.log?.warn('generateAndSendNewEncryptionKeyPair: cannot send it as a non admin'); return; } diff --git a/ts/session/utils/User.ts b/ts/session/utils/User.ts index de2d41b6f..2d6540083 100644 --- a/ts/session/utils/User.ts +++ b/ts/session/utils/User.ts @@ -20,6 +20,7 @@ export type ByteKeyPair = { /** * Check if this pubkey is us, using the cache. + * This does not check for us blinded. To check for us or us blinded, use isUsAnySogsFromCache() * Throws an error if our pubkey is not set */ export function isUsFromCache(pubKey: string | PubKey | undefined): boolean { diff --git a/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts b/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts index 587169481..f50135a14 100644 --- a/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts +++ b/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts @@ -18,6 +18,7 @@ import { } from '../PersistedJob'; import { ReleasedFeatures } from '../../../../util/releaseFeature'; import { allowOnlyOneAtATime } from '../../Promise'; +import { isSignInByLinking } from '../../../../util/storage'; const defaultMsBetweenRetries = 30000; // a long time between retries, to avoid running multiple jobs at the same time, when one was postponed at the same time as one already planned (5s) const defaultMaxAttempts = 2; @@ -284,6 +285,11 @@ class ConfigurationSyncJob extends PersistedJob * A ConfigurationSyncJob can only be added if there is none of the same type queued already. */ async function queueNewJobIfNeeded() { + if (isSignInByLinking()) { + window.log.info('NOT Scheduling ConfSyncJob: as we are linking a device'); + + return; + } if ( !lastRunConfigSyncJobTimestamp || lastRunConfigSyncJobTimestamp < Date.now() - defaultMsBetweenRetries diff --git a/ts/session/utils/libsession/libsession_utils_contacts.ts b/ts/session/utils/libsession/libsession_utils_contacts.ts index a7323414b..5dd749564 100644 --- a/ts/session/utils/libsession/libsession_utils_contacts.ts +++ b/ts/session/utils/libsession/libsession_utils_contacts.ts @@ -71,7 +71,6 @@ async function insertContactFromDBIntoWrapperAndRefresh(id: string): Promise { - // const ourDevice = TestUtils.generateFakePubKey(); - // const ourNumber = ourDevice.key; - // const groupId = TestUtils.generateFakePubKey().key; - // const members = TestUtils.generateFakePubKeys(10); - // const sender = members[3].key; - // const getConvo = sandbox.stub(ConversationController, 'get'); - // beforeEach(async () => { - // // Utils Stubs - // sandbox.stub(UserUtils, 'getCurrentDevicePubKey').resolves(ourNumber); - // }); - // afterEach(() => { - // sandbox.restore(); - // }); - // describe('handleClosedGroupControlMessage', () => { - // describe('performIfValid', () => { - // it('does not perform if convo does not exist', async () => { - // const envelope = generateEnvelopePlusClosedGroup(groupId, sender); - // const groupUpdate = generateGroupUpdateNameChange(groupId); - // getConvo.returns(undefined as any); - // await handleClosedGroupControlMessage(envelope, groupUpdate); - // }); - // }); - // // describe('handleClosedGroupNameChanged', () => { - // // it('does not trigger an update of the group if the name is the same', async () => { - // // const envelope = generateEnvelopePlusClosedGroup(groupId, sender); - // // const groupUpdate = generateGroupUpdateNameChange(groupId); - // // await handleClosedGroupControlMessage(envelope, groupUpdate); - // // }); - // // }); - // }); -}); diff --git a/ts/types/Enums.ts b/ts/types/Enums.ts new file mode 100644 index 000000000..233af200c --- /dev/null +++ b/ts/types/Enums.ts @@ -0,0 +1,3 @@ +export type PickEnum = { + [P in keyof K]: P extends K ? P : never; +};