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",
"glob": "10.3.10",
"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",
"linkify-it": "^4.0.1",
"lodash": "^4.17.21",

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

@ -1053,6 +1053,6 @@ export async function promoteUsersInGroup({
for (let index = 0; index < membersHex.length; index++) {
const member = membersHex[index];
// 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> {
public readonly destination: GroupPubkeyType;
public readonly timestamp: number;
public readonly revokeTokenHex: Array<string>;
public readonly tokensHex: Array<string>;
protected readonly adminSecretKey: Uint8Array;
constructor({
groupPk,
timestamp,
revokeTokenHex,
tokensHex,
secretKey,
method,
}: WithGroupPubkey &
WithTimestamp &
WithSecretKey & { revokeTokenHex: Array<string>; method: T }) {
}: WithGroupPubkey & WithTimestamp & WithSecretKey & { tokensHex: Array<string>; method: T }) {
super({ method });
this.destination = groupPk;
this.timestamp = timestamp;
this.revokeTokenHex = revokeTokenHex;
this.tokensHex = tokensHex;
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');
}
}
@ -429,7 +427,7 @@ abstract class AbstractRevokeSubRequest<
if (!this.adminSecretKey) {
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 sigResult = await SnodeGroupSignature.signDataWithAdminSecret(
@ -461,7 +459,7 @@ export class SubaccountRevokeSubRequest extends AbstractRevokeSubRequest<'revoke
params: {
pubkey: this.destination,
signature,
revoke: this.revokeTokenHex,
revoke: this.tokensHex,
timestamp: this.timestamp,
},
};
@ -483,7 +481,7 @@ export class SubaccountUnrevokeSubRequest extends AbstractRevokeSubRequest<'unre
params: {
pubkey: this.destination,
signature,
unrevoke: this.revokeTokenHex,
unrevoke: this.tokensHex,
timestamp: this.timestamp,
},
};

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

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

@ -23,10 +23,14 @@ import { showUpdateGroupMembersByConvoId } from '../../../../interactions/conver
import { ConvoHub } from '../../../conversations';
import { MessageSender } from '../../../sending';
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 { DURATION } from '../../../constants';
import { timeoutWithAbort } from '../../Promise';
import { StoreGroupRequestFactory } from '../../../apis/snode_api/factories/StoreGroupRequestFactory';
const defaultMsBetweenRetries = 10000;
const defaultMaxAttempts = 1;
@ -35,12 +39,6 @@ type JobExtraArgs = {
groupPk: GroupPubkeyType;
member: PubkeyType;
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) {
@ -59,13 +57,12 @@ const invitesFailed = new Map<
}
>();
async function addJob({ groupPk, member, inviteAsAdmin, forceUnrevoke }: JobExtraArgs) {
if (shouldAddJob({ groupPk, member, inviteAsAdmin, forceUnrevoke })) {
async function addJob({ groupPk, member, inviteAsAdmin }: JobExtraArgs) {
if (shouldAddJob({ groupPk, member, inviteAsAdmin })) {
const groupInviteJob = new GroupInviteJob({
groupPk,
member,
inviteAsAdmin,
forceUnrevoke,
nextAttemptTimestamp: Date.now(),
});
window.log.debug(
@ -146,9 +143,8 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
nextAttemptTimestamp,
maxAttempts,
currentRetry,
forceUnrevoke,
identifier,
}: Pick<GroupInvitePersistedData, 'groupPk' | 'member' | 'inviteAsAdmin' | 'forceUnrevoke'> &
}: Pick<GroupInvitePersistedData, 'groupPk' | 'member' | 'inviteAsAdmin'> &
Partial<
Pick<
GroupInvitePersistedData,
@ -161,7 +157,6 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
member,
groupPk,
inviteAsAdmin,
forceUnrevoke,
delayBetweenRetries: defaultMsBetweenRetries,
maxAttempts: isNumber(maxAttempts) ? maxAttempts : defaultMaxAttempts,
nextAttemptTimestamp: nextAttemptTimestamp || Date.now() + defaultMsBetweenRetries,
@ -187,36 +182,51 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
let failed = true;
try {
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 (this.persistedData.forceUnrevoke) {
const token = await MetaGroupWrapperActions.swarmSubAccountToken(groupPk, member);
const unrevokeSubRequest = new SubaccountUnrevokeSubRequest({
groupPk,
revokeTokenHex: [token],
timestamp: NetworkTime.now(),
secretKey: group.secretKey,
});
const sequenceResult = await GroupSync.pushChangesToGroupSwarmIfNeeded({
if (memberObj.supplement) {
const encryptedSupplementKeys = await MetaGroupWrapperActions.generateSupplementKeys(
groupPk,
unrevokeSubRequest,
extraStoreRequests: [],
allow401s: false,
timeoutMs: 10 * DURATION.SECONDS,
[member]
);
supplementalKeysSubRequest = StoreGroupRequestFactory.makeStoreGroupKeysSubRequest({
group,
encryptedSupplementKeys,
});
window?.inboxStore?.dispatch(
groupInfoActions.refreshGroupDetailsFromWrapper({ groupPk }) as any
}
const token = await MetaGroupWrapperActions.swarmSubAccountToken(groupPk, member);
const unrevokeSubRequest = new SubaccountUnrevokeSubRequest({
groupPk,
tokensHex: [token],
timestamp: NetworkTime.now(),
secretKey: group.secretKey,
});
const sequenceResult = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk,
unrevokeSubRequest,
supplementalKeysSubRequest,
extraStoreRequests: [],
allow401s: false,
timeoutMs: 10 * DURATION.SECONDS,
});
window?.inboxStore?.dispatch(
groupInfoActions.refreshGroupDetailsFromWrapper({ groupPk }) as any
);
if (sequenceResult !== RunJobResult.Success) {
window.log.warn(
`GroupInvite: GroupSync.pushChangesToGroupSwarmIfNeeded failed after ${Date.now() - start}ms`
);
if (sequenceResult !== RunJobResult.Success) {
window.log.warn(
`GroupInvite: GroupSync.pushChangesToGroupSwarmIfNeeded failed after ${Date.now() - start}ms`
);
await LibSessionUtil.saveDumpsToDb(groupPk);
await LibSessionUtil.saveDumpsToDb(groupPk);
throw new Error(
'GroupInviteJob: SubaccountUnrevokeSubRequest push() did not return success'
);
}
throw new Error(
'GroupInviteJob: SubaccountUnrevokeSubRequest push() did not return success'
);
}
const inviteDetails = inviteAsAdmin
@ -328,7 +338,11 @@ export const GroupInvite = {
debounceFailedStateForMember,
};
function debounceFailedStateForMember(groupPk: GroupPubkeyType, member: PubkeyType, failed: boolean) {
function debounceFailedStateForMember(
groupPk: GroupPubkeyType,
member: PubkeyType,
failed: boolean
) {
let thisGroupFailure = invitesFailed.get(groupPk);
if (!failed) {

@ -546,6 +546,7 @@ async function handleWithHistoryMembers({
});
// a group invite job will be added to the queue
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
GroupInvite.debounceFailedStateForMember(groupPk, member, false);
}
@ -1505,8 +1506,6 @@ async function scheduleGroupInviteJobs(
const merged = uniq(concat(withHistory, withoutHistory));
for (let index = 0; index < merged.length; index++) {
const member = merged[index];
// Note: forceUnrevoke is false, because `scheduleGroupInviteJobs` is always called after we've done
// a batch unrevoke of all the members' pk
await GroupInvite.addJob({ groupPk, member, inviteAsAdmin, forceUnrevoke: false });
await GroupInvite.addJob({ groupPk, member, inviteAsAdmin });
}
}

@ -26,6 +26,7 @@ function emptyMember(pubkeyHex: PubkeyType): GroupMemberGet {
},
nominatedAdmin: false,
pubkeyHex,
supplement: false,
};
}
@ -155,7 +156,14 @@ describe('libsession_metagroup', () => {
const memberCreated = metaGroupWrapper.memberGetOrConstruct(member);
console.info('Object.keys(memberCreated) ', JSON.stringify(Object.keys(memberCreated)));
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'
);
});
@ -278,6 +286,19 @@ describe('libsession_metagroup', () => {
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', () => {
metaGroupWrapper.memberConstructAndSet(member);
metaGroupWrapper.memberSetPromotionAccepted(member);

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

@ -4944,9 +4944,9 @@ levn@~0.3.0:
prelude-ls "~1.1.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":
version "0.4.12"
resolved "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.12/libsession_util_nodejs-v0.4.12.tar.gz#6f0eae5c81f9a3e5101e038dbb7c82a9d50bfb7a"
"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.15"
resolved "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.15/libsession_util_nodejs-v0.4.15.tar.gz#de0e90e14327e60d81d2a6941bcd0af33fcfed82"
dependencies:
cmake-js "7.2.1"
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==
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"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
version "7.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
serialize-error@^7.0.1:
version "7.0.1"

Loading…
Cancel
Save