fix: resend invite checks for supplement key

pull/3281/head
Audric Ackermann 3 months ago
parent 99529847e0
commit b2c34cb805
No known key found for this signature in database

@ -92,7 +92,7 @@
"fs-extra": "9.0.0", "fs-extra": "9.0.0",
"glob": "10.3.10", "glob": "10.3.10",
"image-type": "^4.1.0", "image-type": "^4.1.0",
"libsession_util_nodejs": "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.12/libsession_util_nodejs-v0.4.12.tar.gz", "libsession_util_nodejs": "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.15/libsession_util_nodejs-v0.4.15.tar.gz",
"libsodium-wrappers-sumo": "^0.7.9", "libsodium-wrappers-sumo": "^0.7.9",
"linkify-it": "^4.0.1", "linkify-it": "^4.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",

@ -277,7 +277,6 @@ const ResendButton = ({ groupPk, pubkey }: { pubkey: PubkeyType; groupPk: GroupP
groupPk, groupPk,
member: pubkey, member: pubkey,
inviteAsAdmin: member.nominatedAdmin, inviteAsAdmin: member.nominatedAdmin,
forceUnrevoke: true,
}); });
}} }}
/> />

@ -1053,6 +1053,6 @@ export async function promoteUsersInGroup({
for (let index = 0; index < membersHex.length; index++) { for (let index = 0; index < membersHex.length; index++) {
const member = membersHex[index]; const member = membersHex[index];
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await GroupInvite.addJob({ groupPk, member, inviteAsAdmin: true, forceUnrevoke: true }); await GroupInvite.addJob({ groupPk, member, inviteAsAdmin: true });
} }
} }

@ -403,24 +403,22 @@ abstract class AbstractRevokeSubRequest<
> extends SnodeAPISubRequest<T> { > extends SnodeAPISubRequest<T> {
public readonly destination: GroupPubkeyType; public readonly destination: GroupPubkeyType;
public readonly timestamp: number; public readonly timestamp: number;
public readonly revokeTokenHex: Array<string>; public readonly tokensHex: Array<string>;
protected readonly adminSecretKey: Uint8Array; protected readonly adminSecretKey: Uint8Array;
constructor({ constructor({
groupPk, groupPk,
timestamp, timestamp,
revokeTokenHex, tokensHex,
secretKey, secretKey,
method, method,
}: WithGroupPubkey & }: WithGroupPubkey & WithTimestamp & WithSecretKey & { tokensHex: Array<string>; method: T }) {
WithTimestamp &
WithSecretKey & { revokeTokenHex: Array<string>; method: T }) {
super({ method }); super({ method });
this.destination = groupPk; this.destination = groupPk;
this.timestamp = timestamp; this.timestamp = timestamp;
this.revokeTokenHex = revokeTokenHex; this.tokensHex = tokensHex;
this.adminSecretKey = secretKey; this.adminSecretKey = secretKey;
if (this.revokeTokenHex.length === 0) { if (this.tokensHex.length === 0) {
throw new Error('AbstractRevokeSubRequest needs at least one token to do a change'); throw new Error('AbstractRevokeSubRequest needs at least one token to do a change');
} }
} }
@ -429,7 +427,7 @@ abstract class AbstractRevokeSubRequest<
if (!this.adminSecretKey) { if (!this.adminSecretKey) {
throw new Error('we need an admin secretKey'); throw new Error('we need an admin secretKey');
} }
const tokensBytes = from_hex(this.revokeTokenHex.join('')); const tokensBytes = from_hex(this.tokensHex.join(''));
const prefix = new Uint8Array(StringUtils.encode(`${this.method}${this.timestamp}`, 'utf8')); const prefix = new Uint8Array(StringUtils.encode(`${this.method}${this.timestamp}`, 'utf8'));
const sigResult = await SnodeGroupSignature.signDataWithAdminSecret( const sigResult = await SnodeGroupSignature.signDataWithAdminSecret(
@ -461,7 +459,7 @@ export class SubaccountRevokeSubRequest extends AbstractRevokeSubRequest<'revoke
params: { params: {
pubkey: this.destination, pubkey: this.destination,
signature, signature,
revoke: this.revokeTokenHex, revoke: this.tokensHex,
timestamp: this.timestamp, timestamp: this.timestamp,
}, },
}; };
@ -483,7 +481,7 @@ export class SubaccountUnrevokeSubRequest extends AbstractRevokeSubRequest<'unre
params: { params: {
pubkey: this.destination, pubkey: this.destination,
signature, signature,
unrevoke: this.revokeTokenHex, unrevoke: this.tokensHex,
timestamp: this.timestamp, timestamp: this.timestamp,
}, },
}; };

@ -24,7 +24,7 @@ async function getRevokeSubaccountParams(
const revokeSubRequest = revokeChanges.length const revokeSubRequest = revokeChanges.length
? new SubaccountRevokeSubRequest({ ? new SubaccountRevokeSubRequest({
groupPk, groupPk,
revokeTokenHex: revokeChanges.map(m => m.tokenToRevokeHex), tokensHex: revokeChanges.map(m => m.tokenToRevokeHex),
timestamp: NetworkTime.now(), timestamp: NetworkTime.now(),
secretKey, secretKey,
}) })
@ -32,7 +32,7 @@ async function getRevokeSubaccountParams(
const unrevokeSubRequest = unrevokeChanges.length const unrevokeSubRequest = unrevokeChanges.length
? new SubaccountUnrevokeSubRequest({ ? new SubaccountUnrevokeSubRequest({
groupPk, groupPk,
revokeTokenHex: unrevokeChanges.map(m => m.tokenToRevokeHex), tokensHex: unrevokeChanges.map(m => m.tokenToRevokeHex),
timestamp: NetworkTime.now(), timestamp: NetworkTime.now(),
secretKey, secretKey,
}) })

@ -43,7 +43,6 @@ export interface GroupInvitePersistedData extends PersistedJobData {
groupPk: GroupPubkeyType; groupPk: GroupPubkeyType;
member: PubkeyType; member: PubkeyType;
inviteAsAdmin: boolean; inviteAsAdmin: boolean;
forceUnrevoke: boolean;
} }
export interface GroupPromotePersistedData extends PersistedJobData { export interface GroupPromotePersistedData extends PersistedJobData {

@ -23,10 +23,14 @@ import { showUpdateGroupMembersByConvoId } from '../../../../interactions/conver
import { ConvoHub } from '../../../conversations'; import { ConvoHub } from '../../../conversations';
import { MessageSender } from '../../../sending'; import { MessageSender } from '../../../sending';
import { NetworkTime } from '../../../../util/NetworkTime'; import { NetworkTime } from '../../../../util/NetworkTime';
import { SubaccountUnrevokeSubRequest } from '../../../apis/snode_api/SnodeRequestTypes'; import {
SubaccountUnrevokeSubRequest,
type StoreGroupKeysSubRequest,
} from '../../../apis/snode_api/SnodeRequestTypes';
import { GroupSync } from './GroupSyncJob'; import { GroupSync } from './GroupSyncJob';
import { DURATION } from '../../../constants'; import { DURATION } from '../../../constants';
import { timeoutWithAbort } from '../../Promise'; import { timeoutWithAbort } from '../../Promise';
import { StoreGroupRequestFactory } from '../../../apis/snode_api/factories/StoreGroupRequestFactory';
const defaultMsBetweenRetries = 10000; const defaultMsBetweenRetries = 10000;
const defaultMaxAttempts = 1; const defaultMaxAttempts = 1;
@ -35,12 +39,6 @@ type JobExtraArgs = {
groupPk: GroupPubkeyType; groupPk: GroupPubkeyType;
member: PubkeyType; member: PubkeyType;
inviteAsAdmin: boolean; inviteAsAdmin: boolean;
/**
* When inviting a member, we usually only want to sent a message to his swarm.
* In the case of an invitation resend process though, we also want to make sure his token is unrevoked from the group's swarm.
*
*/
forceUnrevoke: boolean;
}; };
export function shouldAddJob(args: JobExtraArgs) { export function shouldAddJob(args: JobExtraArgs) {
@ -59,13 +57,12 @@ const invitesFailed = new Map<
} }
>(); >();
async function addJob({ groupPk, member, inviteAsAdmin, forceUnrevoke }: JobExtraArgs) { async function addJob({ groupPk, member, inviteAsAdmin }: JobExtraArgs) {
if (shouldAddJob({ groupPk, member, inviteAsAdmin, forceUnrevoke })) { if (shouldAddJob({ groupPk, member, inviteAsAdmin })) {
const groupInviteJob = new GroupInviteJob({ const groupInviteJob = new GroupInviteJob({
groupPk, groupPk,
member, member,
inviteAsAdmin, inviteAsAdmin,
forceUnrevoke,
nextAttemptTimestamp: Date.now(), nextAttemptTimestamp: Date.now(),
}); });
window.log.debug( window.log.debug(
@ -146,9 +143,8 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
nextAttemptTimestamp, nextAttemptTimestamp,
maxAttempts, maxAttempts,
currentRetry, currentRetry,
forceUnrevoke,
identifier, identifier,
}: Pick<GroupInvitePersistedData, 'groupPk' | 'member' | 'inviteAsAdmin' | 'forceUnrevoke'> & }: Pick<GroupInvitePersistedData, 'groupPk' | 'member' | 'inviteAsAdmin'> &
Partial< Partial<
Pick< Pick<
GroupInvitePersistedData, GroupInvitePersistedData,
@ -161,7 +157,6 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
member, member,
groupPk, groupPk,
inviteAsAdmin, inviteAsAdmin,
forceUnrevoke,
delayBetweenRetries: defaultMsBetweenRetries, delayBetweenRetries: defaultMsBetweenRetries,
maxAttempts: isNumber(maxAttempts) ? maxAttempts : defaultMaxAttempts, maxAttempts: isNumber(maxAttempts) ? maxAttempts : defaultMaxAttempts,
nextAttemptTimestamp: nextAttemptTimestamp || Date.now() + defaultMsBetweenRetries, nextAttemptTimestamp: nextAttemptTimestamp || Date.now() + defaultMsBetweenRetries,
@ -187,18 +182,34 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
let failed = true; let failed = true;
try { try {
let start = Date.now(); let start = Date.now();
const memberObj = await MetaGroupWrapperActions.memberGet(groupPk, member);
if (!memberObj) {
throw new Error('Member should have been added before GroupInviteJob was run()');
}
let supplementalKeysSubRequest: StoreGroupKeysSubRequest | undefined;
if (memberObj.supplement) {
const encryptedSupplementKeys = await MetaGroupWrapperActions.generateSupplementKeys(
groupPk,
[member]
);
supplementalKeysSubRequest = StoreGroupRequestFactory.makeStoreGroupKeysSubRequest({
group,
encryptedSupplementKeys,
});
}
if (this.persistedData.forceUnrevoke) {
const token = await MetaGroupWrapperActions.swarmSubAccountToken(groupPk, member); const token = await MetaGroupWrapperActions.swarmSubAccountToken(groupPk, member);
const unrevokeSubRequest = new SubaccountUnrevokeSubRequest({ const unrevokeSubRequest = new SubaccountUnrevokeSubRequest({
groupPk, groupPk,
revokeTokenHex: [token], tokensHex: [token],
timestamp: NetworkTime.now(), timestamp: NetworkTime.now(),
secretKey: group.secretKey, secretKey: group.secretKey,
}); });
const sequenceResult = await GroupSync.pushChangesToGroupSwarmIfNeeded({ const sequenceResult = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk, groupPk,
unrevokeSubRequest, unrevokeSubRequest,
supplementalKeysSubRequest,
extraStoreRequests: [], extraStoreRequests: [],
allow401s: false, allow401s: false,
timeoutMs: 10 * DURATION.SECONDS, timeoutMs: 10 * DURATION.SECONDS,
@ -217,7 +228,6 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
'GroupInviteJob: SubaccountUnrevokeSubRequest push() did not return success' 'GroupInviteJob: SubaccountUnrevokeSubRequest push() did not return success'
); );
} }
}
const inviteDetails = inviteAsAdmin const inviteDetails = inviteAsAdmin
? await SnodeGroupSignature.getGroupPromoteMessage({ ? await SnodeGroupSignature.getGroupPromoteMessage({
@ -328,7 +338,11 @@ export const GroupInvite = {
debounceFailedStateForMember, debounceFailedStateForMember,
}; };
function debounceFailedStateForMember(groupPk: GroupPubkeyType, member: PubkeyType, failed: boolean) { function debounceFailedStateForMember(
groupPk: GroupPubkeyType,
member: PubkeyType,
failed: boolean
) {
let thisGroupFailure = invitesFailed.get(groupPk); let thisGroupFailure = invitesFailed.get(groupPk);
if (!failed) { if (!failed) {

@ -546,6 +546,7 @@ async function handleWithHistoryMembers({
}); });
// a group invite job will be added to the queue // a group invite job will be added to the queue
await MetaGroupWrapperActions.memberSetInviteNotSent(groupPk, member); await MetaGroupWrapperActions.memberSetInviteNotSent(groupPk, member);
await MetaGroupWrapperActions.memberSetSupplement(groupPk, member);
// update the in-memory failed state, so that if we fail again to send that invite, the toast is shown again // update the in-memory failed state, so that if we fail again to send that invite, the toast is shown again
GroupInvite.debounceFailedStateForMember(groupPk, member, false); GroupInvite.debounceFailedStateForMember(groupPk, member, false);
} }
@ -1505,8 +1506,6 @@ async function scheduleGroupInviteJobs(
const merged = uniq(concat(withHistory, withoutHistory)); const merged = uniq(concat(withHistory, withoutHistory));
for (let index = 0; index < merged.length; index++) { for (let index = 0; index < merged.length; index++) {
const member = merged[index]; const member = merged[index];
// Note: forceUnrevoke is false, because `scheduleGroupInviteJobs` is always called after we've done await GroupInvite.addJob({ groupPk, member, inviteAsAdmin });
// a batch unrevoke of all the members' pk
await GroupInvite.addJob({ groupPk, member, inviteAsAdmin, forceUnrevoke: false });
} }
} }

@ -26,6 +26,7 @@ function emptyMember(pubkeyHex: PubkeyType): GroupMemberGet {
}, },
nominatedAdmin: false, nominatedAdmin: false,
pubkeyHex, pubkeyHex,
supplement: false,
}; };
} }
@ -155,7 +156,14 @@ describe('libsession_metagroup', () => {
const memberCreated = metaGroupWrapper.memberGetOrConstruct(member); const memberCreated = metaGroupWrapper.memberGetOrConstruct(member);
console.info('Object.keys(memberCreated) ', JSON.stringify(Object.keys(memberCreated))); console.info('Object.keys(memberCreated) ', JSON.stringify(Object.keys(memberCreated)));
expect(Object.keys(memberCreated).sort()).to.be.deep.eq( expect(Object.keys(memberCreated).sort()).to.be.deep.eq(
['pubkeyHex', 'name', 'profilePicture', 'memberStatus', 'nominatedAdmin'].sort(), // if you change this value, also make sure you add a test, testing that new field, below [
'pubkeyHex',
'name',
'profilePicture',
'memberStatus',
'nominatedAdmin',
'supplement',
].sort(), // if you change this value, also make sure you add a test, testing that new field, below
'this test is designed to fail if you need to add tests to test a new field of libsession' 'this test is designed to fail if you need to add tests to test a new field of libsession'
); );
}); });
@ -278,6 +286,19 @@ describe('libsession_metagroup', () => {
expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected); expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected);
}); });
it('can add via supplement set', () => {
metaGroupWrapper.memberConstructAndSet(member);
metaGroupWrapper.memberSetSupplement(member);
expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1);
const expected = {
...emptyMember(member),
memberStatus: 'INVITE_SENDING', // invite_sending is the default state
supplement: true,
};
expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected);
});
it('can add via admin set', () => { it('can add via admin set', () => {
metaGroupWrapper.memberConstructAndSet(member); metaGroupWrapper.memberConstructAndSet(member);
metaGroupWrapper.memberSetPromotionAccepted(member); metaGroupWrapper.memberSetPromotionAccepted(member);

@ -657,6 +657,12 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = {
pubkeyHex, pubkeyHex,
name, name,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetNameTruncated']>>, ]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetNameTruncated']>>,
memberSetSupplement: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'memberSetSupplement',
pubkeyHex,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetSupplement']>>,
memberSetProfilePicture: async ( memberSetProfilePicture: async (
groupPk: GroupPubkeyType, groupPk: GroupPubkeyType,
pubkeyHex: PubkeyType, pubkeyHex: PubkeyType,

@ -4944,9 +4944,9 @@ levn@~0.3.0:
prelude-ls "~1.1.2" prelude-ls "~1.1.2"
type-check "~0.3.2" type-check "~0.3.2"
"libsession_util_nodejs@https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.12/libsession_util_nodejs-v0.4.12.tar.gz": "libsession_util_nodejs@https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.15/libsession_util_nodejs-v0.4.15.tar.gz":
version "0.4.12" version "0.4.15"
resolved "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.12/libsession_util_nodejs-v0.4.12.tar.gz#6f0eae5c81f9a3e5101e038dbb7c82a9d50bfb7a" resolved "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.15/libsession_util_nodejs-v0.4.15.tar.gz#de0e90e14327e60d81d2a6941bcd0af33fcfed82"
dependencies: dependencies:
cmake-js "7.2.1" cmake-js "7.2.1"
node-addon-api "^6.1.0" node-addon-api "^6.1.0"
@ -6859,9 +6859,9 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1:
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.1.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.4: semver@^7.1.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.4:
version "7.6.3" version "7.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
serialize-error@^7.0.1: serialize-error@^7.0.1:
version "7.0.1" version "7.0.1"

Loading…
Cancel
Save