Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

People not removed in an exclude should be able to post on the new epoch #78

Merged
merged 20 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@

name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]
on: push

jobs:
licenses:
Expand Down
67 changes: 67 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,73 @@ module.exports = {
if (err) return cb(clarify(err, 'Error finding or creating additions feed when starting ssb-tribes2'))
return cb()
})

// look for new epochs that we're added to
ssb.metafeeds.findOrCreate((err, myRoot) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Error getting own root in start()'))

pull(
ssb.db.query(
// TODO: does this output new stuff if we accept an invite to an old epoch and then find additions to newer epochs?
where(and(isDecrypted('box2'), type('group/add-member'))),
live({ old: true }),
toPullStream()
),
pull.filter(isAddMember),
// groups/epochs we're added to
pull.filter((msg) => {
return msg.value.content.recps.includes(myRoot.id)
}),
// to find new epochs we only check groups we've accepted the invite to
paraMap((msg, cb) => {
pull(
ssb.box2.listGroupIds(),
pull.collect((err, groupIds) => {
// prettier-ignore
if (err) return cb(clarify(err, "Error getting groups we're already in when looking for new epochs"))

if (groupIds.includes(msg.value.content.recps[0])) {
return cb(null, msg)
} else {
return cb()
}
})
)
}, 4),
pull.filter(Boolean),
pull.drain(
(msg) => {
const groupId = msg.value.content.recps[0]

const newKey = Buffer.from(msg.value.content.groupKey, 'base64')
ssb.box2.addGroupInfo(groupId, { key: newKey }, (err) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Error adding new epoch key that we found'))

const newKeyPick = {
key: newKey,
scheme: keySchemes.private_group,
}
// TODO: naively guessing that this is the latest key for now
ssb.box2.pickGroupWriteKey(groupId, newKeyPick, (err) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Error switching to new epoch key that we found'))

ssb.db.reindexEncrypted((err) => {
// prettier-ignore
if (err) cb(clarify(err, 'Error reindexing after finding new epoch'))
})
})
})
},
(err) => {
// prettier-ignore
if (err) return cb(clarify(err, "Error finding new epochs we've been added to"))
}
)
)
})
}

return {
Expand Down
129 changes: 129 additions & 0 deletions test/exclude-members.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { fromMessageSigil } = require('ssb-uri2')
const Testbot = require('./helpers/testbot')
const replicate = require('./helpers/replicate')
const countGroupFeeds = require('./helpers/count-group-feeds')
const pull = require('pull-stream')

test('add and remove a person, post on the new feed', async (t) => {
// Alice's feeds should look like
Expand Down Expand Up @@ -174,3 +175,131 @@ test('add and remove a person, post on the new feed', async (t) => {
await p(alice.close)(true)
await p(bob.close)(true)
})

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) => {
// alice creates the group. adds bob and carol. removes bob.
// bob gets added and is removed
// carol stays in the group
const alice = Testbot({
keys: ssbKeys.generate(null, 'alice'),
mfSeed: Buffer.from(
'000000000000000000000000000000000000000000000000000000000000a1ce',
'hex'
),
})
const bob = Testbot({
keys: ssbKeys.generate(null, 'bob'),
mfSeed: Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000b0b',
'hex'
),
})
const carol = Testbot({
keys: ssbKeys.generate(null, 'carol'),
mfSeed: Buffer.from(
'00000000000000000000000000000000000000000000000000000000000ca201',
'hex'
),
})

await alice.tribes2.start()
await bob.tribes2.start()
await carol.tribes2.start()
t.pass('tribes2 started for everyone')

await p(alice.metafeeds.findOrCreate)()
const bobRoot = await p(bob.metafeeds.findOrCreate)()
const carolRoot = await p(carol.metafeeds.findOrCreate)()

await replicate(alice, bob)
await replicate(alice, carol)
await replicate(bob, carol)
t.pass('everyone replicates their trees')

const { id: groupId, writeKey: writeKey1 } = await alice.tribes2
.create()
.catch((err) => t.error(err, 'alice failed to create group'))

await replicate(alice, carol)

await alice.tribes2
.addMembers(groupId, [bobRoot.id, carolRoot.id])
.catch((err) => t.error(err, 'add bob fail'))

await replicate(alice, carol)

await carol.tribes2.acceptInvite(groupId)

await replicate(alice, carol)

const {
value: { author: firstFeedId },
} = await carol.tribes2
.publish({
type: 'test',
text: 'first post',
recps: [groupId],
})
.catch((err) => t.error(err, 'carol failed to publish on first feed'))
if (firstFeedId) t.pass('carol posted first post')

await alice.tribes2
.excludeMembers(groupId, [bobRoot.id])
.then((res) => {
t.pass('alice excluded bob')
return res
})
.catch((err) => t.error(err, 'remove member fail'))

await replicate(alice, carol).catch(t.error)

// let carol find the new epoch and switch to the new key
await p(setTimeout)(500)

const {
value: { author: secondFeedId },
} = await carol.tribes2
.publish({
type: 'test',
text: 'second post',
recps: [groupId],
})
.catch(t.fail)
if (secondFeedId) t.pass('carol posted second post')

t.notEquals(
secondFeedId,
firstFeedId,
'feed for second publish is different to first publish'
)

const { writeKey: writeKey2 } = await carol.tribes2.get(groupId)

const branches = await pull(
carol.metafeeds.branchStream({
root: carolRoot.id,
old: true,
live: false,
}),
pull.collectAsPromise()
)

const groupFeedPurposes = branches
.filter((branch) => branch.length === 4)
.map((branch) => branch[3])
.filter((feed) => feed.recps && feed.purpose.length === 44)
.map((feed) => feed.purpose)

t.true(
groupFeedPurposes.includes(writeKey1.key.toString('base64')),
'Carol has a feed for the old key'
)
t.true(
groupFeedPurposes.includes(writeKey2.key.toString('base64')),
'Carol has a feed for the new key'
)

await p(alice.close)(true)
await p(bob.close)(true)
await p(carol.close)(true)
})
4 changes: 2 additions & 2 deletions test/helpers/replicate.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = async function replicate(person1, person2) {
await retryUntil(async () => {
const clocks = await Promise.all([
p(person1.getVectorClock)(),
p(person2.getVectorClock)()
p(person2.getVectorClock)(),
])
return deepEqual(...clocks)
})
Expand All @@ -31,7 +31,7 @@ module.exports = async function replicate(person1, person2) {
await p(conn.close)(true)
}

function setupFeedRequests (person1, person2) {
function setupFeedRequests(person1, person2) {
let drain
pull(
pullMany([
Expand Down