Skip to content

Commit ba70493

Browse files
authored
Merge pull request #78 from ssbc/stay-on-exclude
People not removed in an exclude should be able to post on the new epoch
2 parents a72374f + 803b0dd commit ba70493

File tree

4 files changed

+199
-7
lines changed

4 files changed

+199
-7
lines changed

.github/workflows/node.js.yml

+1-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44

55
name: CI
66

7-
on:
8-
push:
9-
branches: [master]
10-
pull_request:
11-
branches: [master]
7+
on: push
128

139
jobs:
1410
licenses:

index.js

+67
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,73 @@ module.exports = {
415415
if (err) return cb(clarify(err, 'Error finding or creating additions feed when starting ssb-tribes2'))
416416
return cb()
417417
})
418+
419+
// look for new epochs that we're added to
420+
ssb.metafeeds.findOrCreate((err, myRoot) => {
421+
// prettier-ignore
422+
if (err) return cb(clarify(err, 'Error getting own root in start()'))
423+
424+
pull(
425+
ssb.db.query(
426+
// TODO: does this output new stuff if we accept an invite to an old epoch and then find additions to newer epochs?
427+
where(and(isDecrypted('box2'), type('group/add-member'))),
428+
live({ old: true }),
429+
toPullStream()
430+
),
431+
pull.filter(isAddMember),
432+
// groups/epochs we're added to
433+
pull.filter((msg) => {
434+
return msg.value.content.recps.includes(myRoot.id)
435+
}),
436+
// to find new epochs we only check groups we've accepted the invite to
437+
paraMap((msg, cb) => {
438+
pull(
439+
ssb.box2.listGroupIds(),
440+
pull.collect((err, groupIds) => {
441+
// prettier-ignore
442+
if (err) return cb(clarify(err, "Error getting groups we're already in when looking for new epochs"))
443+
444+
if (groupIds.includes(msg.value.content.recps[0])) {
445+
return cb(null, msg)
446+
} else {
447+
return cb()
448+
}
449+
})
450+
)
451+
}, 4),
452+
pull.filter(Boolean),
453+
pull.drain(
454+
(msg) => {
455+
const groupId = msg.value.content.recps[0]
456+
457+
const newKey = Buffer.from(msg.value.content.groupKey, 'base64')
458+
ssb.box2.addGroupInfo(groupId, { key: newKey }, (err) => {
459+
// prettier-ignore
460+
if (err) return cb(clarify(err, 'Error adding new epoch key that we found'))
461+
462+
const newKeyPick = {
463+
key: newKey,
464+
scheme: keySchemes.private_group,
465+
}
466+
// TODO: naively guessing that this is the latest key for now
467+
ssb.box2.pickGroupWriteKey(groupId, newKeyPick, (err) => {
468+
// prettier-ignore
469+
if (err) return cb(clarify(err, 'Error switching to new epoch key that we found'))
470+
471+
ssb.db.reindexEncrypted((err) => {
472+
// prettier-ignore
473+
if (err) cb(clarify(err, 'Error reindexing after finding new epoch'))
474+
})
475+
})
476+
})
477+
},
478+
(err) => {
479+
// prettier-ignore
480+
if (err) return cb(clarify(err, "Error finding new epochs we've been added to"))
481+
}
482+
)
483+
)
484+
})
418485
}
419486

420487
return {

test/exclude-members.test.js

+129
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const { fromMessageSigil } = require('ssb-uri2')
1010
const Testbot = require('./helpers/testbot')
1111
const replicate = require('./helpers/replicate')
1212
const countGroupFeeds = require('./helpers/count-group-feeds')
13+
const pull = require('pull-stream')
1314

1415
test('add and remove a person, post on the new feed', async (t) => {
1516
// Alice's feeds should look like
@@ -174,3 +175,131 @@ test('add and remove a person, post on the new feed', async (t) => {
174175
await p(alice.close)(true)
175176
await p(bob.close)(true)
176177
})
178+
179+
test("If you're not the excluder nor the excludee then you should still be in the group and have access to the new epoch", async (t) => {
180+
// alice creates the group. adds bob and carol. removes bob.
181+
// bob gets added and is removed
182+
// carol stays in the group
183+
const alice = Testbot({
184+
keys: ssbKeys.generate(null, 'alice'),
185+
mfSeed: Buffer.from(
186+
'000000000000000000000000000000000000000000000000000000000000a1ce',
187+
'hex'
188+
),
189+
})
190+
const bob = Testbot({
191+
keys: ssbKeys.generate(null, 'bob'),
192+
mfSeed: Buffer.from(
193+
'0000000000000000000000000000000000000000000000000000000000000b0b',
194+
'hex'
195+
),
196+
})
197+
const carol = Testbot({
198+
keys: ssbKeys.generate(null, 'carol'),
199+
mfSeed: Buffer.from(
200+
'00000000000000000000000000000000000000000000000000000000000ca201',
201+
'hex'
202+
),
203+
})
204+
205+
await alice.tribes2.start()
206+
await bob.tribes2.start()
207+
await carol.tribes2.start()
208+
t.pass('tribes2 started for everyone')
209+
210+
await p(alice.metafeeds.findOrCreate)()
211+
const bobRoot = await p(bob.metafeeds.findOrCreate)()
212+
const carolRoot = await p(carol.metafeeds.findOrCreate)()
213+
214+
await replicate(alice, bob)
215+
await replicate(alice, carol)
216+
await replicate(bob, carol)
217+
t.pass('everyone replicates their trees')
218+
219+
const { id: groupId, writeKey: writeKey1 } = await alice.tribes2
220+
.create()
221+
.catch((err) => t.error(err, 'alice failed to create group'))
222+
223+
await replicate(alice, carol)
224+
225+
await alice.tribes2
226+
.addMembers(groupId, [bobRoot.id, carolRoot.id])
227+
.catch((err) => t.error(err, 'add bob fail'))
228+
229+
await replicate(alice, carol)
230+
231+
await carol.tribes2.acceptInvite(groupId)
232+
233+
await replicate(alice, carol)
234+
235+
const {
236+
value: { author: firstFeedId },
237+
} = await carol.tribes2
238+
.publish({
239+
type: 'test',
240+
text: 'first post',
241+
recps: [groupId],
242+
})
243+
.catch((err) => t.error(err, 'carol failed to publish on first feed'))
244+
if (firstFeedId) t.pass('carol posted first post')
245+
246+
await alice.tribes2
247+
.excludeMembers(groupId, [bobRoot.id])
248+
.then((res) => {
249+
t.pass('alice excluded bob')
250+
return res
251+
})
252+
.catch((err) => t.error(err, 'remove member fail'))
253+
254+
await replicate(alice, carol).catch(t.error)
255+
256+
// let carol find the new epoch and switch to the new key
257+
await p(setTimeout)(500)
258+
259+
const {
260+
value: { author: secondFeedId },
261+
} = await carol.tribes2
262+
.publish({
263+
type: 'test',
264+
text: 'second post',
265+
recps: [groupId],
266+
})
267+
.catch(t.fail)
268+
if (secondFeedId) t.pass('carol posted second post')
269+
270+
t.notEquals(
271+
secondFeedId,
272+
firstFeedId,
273+
'feed for second publish is different to first publish'
274+
)
275+
276+
const { writeKey: writeKey2 } = await carol.tribes2.get(groupId)
277+
278+
const branches = await pull(
279+
carol.metafeeds.branchStream({
280+
root: carolRoot.id,
281+
old: true,
282+
live: false,
283+
}),
284+
pull.collectAsPromise()
285+
)
286+
287+
const groupFeedPurposes = branches
288+
.filter((branch) => branch.length === 4)
289+
.map((branch) => branch[3])
290+
.filter((feed) => feed.recps && feed.purpose.length === 44)
291+
.map((feed) => feed.purpose)
292+
293+
t.true(
294+
groupFeedPurposes.includes(writeKey1.key.toString('base64')),
295+
'Carol has a feed for the old key'
296+
)
297+
t.true(
298+
groupFeedPurposes.includes(writeKey2.key.toString('base64')),
299+
'Carol has a feed for the new key'
300+
)
301+
302+
await p(alice.close)(true)
303+
await p(bob.close)(true)
304+
await p(carol.close)(true)
305+
})

test/helpers/replicate.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ module.exports = async function replicate(person1, person2) {
2222
await retryUntil(async () => {
2323
const clocks = await Promise.all([
2424
p(person1.getVectorClock)(),
25-
p(person2.getVectorClock)()
25+
p(person2.getVectorClock)(),
2626
])
2727
return deepEqual(...clocks)
2828
})
@@ -31,7 +31,7 @@ module.exports = async function replicate(person1, person2) {
3131
await p(conn.close)(true)
3232
}
3333

34-
function setupFeedRequests (person1, person2) {
34+
function setupFeedRequests(person1, person2) {
3535
let drain
3636
pull(
3737
pullMany([

0 commit comments

Comments
 (0)