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

Basic member exclusion flow #65

Merged
merged 40 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
eb2f4eb
Add some basic exclusion code
Powersource Feb 22, 2023
7ab7f26
Add comment
Powersource Feb 23, 2023
a20ccba
Merge branch 'master' of github.com:ssbc/ssb-tribes2 into exclude-member
Powersource Feb 24, 2023
7ddc5ef
Try to create new feed
Powersource Mar 7, 2023
c122e2d
Merge branch 'box2-pickWrite' of github.com:ssbc/ssb-tribes2 into exc…
Powersource Mar 12, 2023
5bcba48
Remove old todo comments
Powersource Mar 12, 2023
2c8005f
Merge branch 'master' of github.com:ssbc/ssb-tribes2 into exclude-member
Powersource Mar 12, 2023
c607e2f
Start to add test
Powersource Mar 13, 2023
e854e1a
Add opts to publish
Powersource Mar 13, 2023
75f054c
Let publish handle creating the new feed
Powersource Mar 13, 2023
5c2cb46
Remove spec todos
Powersource Mar 13, 2023
2c93839
Add license to count-group-feeds
Powersource Mar 13, 2023
6fbe838
Remove console log
Powersource Mar 13, 2023
1ddb4e9
Post init message on new epoch
Powersource Mar 15, 2023
8ac8f62
Fix publish usage in test
Powersource Mar 15, 2023
ade87ff
Start checking that messages look correct
Powersource Mar 15, 2023
0d2d713
Figure out that additions are on the additions feed
Powersource Mar 16, 2023
e8a81fe
Post re-add messages on old feed
Powersource Mar 16, 2023
5be659c
Use addMembers to re-add members
Powersource Mar 16, 2023
cd8910a
Use publish in addMembers
Powersource Mar 16, 2023
d8baa13
Always have publish add the group tangle at least
Powersource Mar 16, 2023
91f7182
Test exclude message
Powersource Mar 16, 2023
6a47f71
Test member tangle for exclude
Powersource Mar 17, 2023
3dfe59f
Clarify exclusion errors
Powersource Mar 17, 2023
641ee1e
Document excludeMembers
Powersource Mar 17, 2023
591ebbf
remove un-necessary lookups of feeds for create + exclude members
mixmix Mar 20, 2023
74baa4a
fix crash resistence for create
mixmix Mar 20, 2023
b8ee799
Fix message sigils in tests
Powersource Mar 20, 2023
b725ee5
Deduplicate publish feedKeys code
Powersource Mar 20, 2023
34daa39
Remove incorrect 'pass' assertions
Powersource Mar 21, 2023
af04b2e
Remove brackets from arrow fn
Powersource Mar 21, 2023
1e7026e
Test epoch tangle
Powersource Mar 21, 2023
215ae03
spec->isValid
Powersource Mar 21, 2023
b138979
Add default error on failed validation
Powersource Mar 21, 2023
96a195b
Clean up feedKeys opts logic
Powersource Mar 21, 2023
138da88
Default value on destructure
Powersource Mar 21, 2023
ef40b28
Remove cipherlink term
Powersource Mar 22, 2023
d69ec72
Apply suggestions from code review
Powersource Mar 22, 2023
7fb846f
Update comment
Powersource Mar 22, 2023
c21c8e2
Make addMembers feedKeys opt private
Powersource Mar 22, 2023
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
73 changes: 67 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const {
},
keySchemes,
} = require('private-group-spec')
const { SecretKey } = require('ssb-private-group-keys')
const { fromMessageSigil, isBendyButtV1FeedSSBURI } = require('ssb-uri2')
const buildGroupId = require('./lib/build-group-id')
const AddTangles = require('./lib/add-tangles')
Expand All @@ -43,14 +44,14 @@ module.exports = {
secretKeyFromString,
findOrCreateAdditionsFeed,
findOrCreateGroupFeed,
findOrCreateGroupWithoutMembers,
findOrCreateEpochWithoutMembers,
getRootFeedIdFromMsgId,
} = MetaFeedHelpers(ssb)

function create(opts = {}, cb) {
if (cb === undefined) return promisify(create)(opts)

findOrCreateGroupWithoutMembers((err, group) => {
Powersource marked this conversation as resolved.
Show resolved Hide resolved
findOrCreateEpochWithoutMembers((err, group) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to create group init message when creating a group'))

Expand Down Expand Up @@ -159,22 +160,81 @@ module.exports = {
})
}

function publish(content, cb) {
if (cb === undefined) return promisify(publish)(content)
function excludeMembers(groupId, feedIds, opts = {}, cb) {
if (cb === undefined)
return promisify(excludeMembers)(groupId, feedIds, opts)

const excludeContent = {
type: 'group/exclude',
excludes: feedIds,
recps: [groupId],
}
const excludeOpts = { tangles: ['group', 'members'], spec: () => true }
publish(excludeContent, excludeOpts, (err, exclusionMsg) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to publish exclude msg'))

pull(
listMembers(groupId),
pull.collect((err, beforeMembers) => {
if (err) return cb(err)

const remainingMembers = beforeMembers.filter(
(member) => !feedIds.includes(member)
)
const newGroupKey = new SecretKey()
const addInfo = { key: newGroupKey.toBuffer() }

ssb.box2.addGroupInfo(groupId, addInfo, (err) => {
if (err) return cb(err)

const newKey = {
key: newGroupKey.toBuffer(),
scheme: keySchemes.private_group,
}
ssb.box2.pickGroupWriteKey(groupId, newKey, (err) => {
if (err) return cb(err)

const newKeyContent = {
type: 'group/move-epoch',
secret: newGroupKey.toString('base64'),
exclusion: fromMessageSigil(exclusionMsg.key),
recps: [groupId, ...remainingMembers],
}
// TODO: loop if many members
// TODO: post this on old feed
publish(newKeyContent, { spec: () => true }, (err) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to tell people about new epoch'))

return cb()
})
})
})
})
)
})
}

function publish(content, opts, cb) {
if (cb === undefined) return promisify(publish)(content, opts)

if (!content) return cb(new Error('Missing content'))

const isSpec = opts?.spec ?? isContent
Powersource marked this conversation as resolved.
Show resolved Hide resolved
const tangles = opts?.tangles ?? ['group']
Powersource marked this conversation as resolved.
Show resolved Hide resolved

const recps = content.recps
if (!recps || !Array.isArray(recps) || recps.length < 1) {
return cb(new Error('Missing recps'))
}
const groupId = recps[0]

addTangles(content, ['group'], (err, content) => {
addTangles(content, tangles, (err, content) => {
Powersource marked this conversation as resolved.
Show resolved Hide resolved
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to add group tangle when publishing to a group'))

if (!isContent(content)) return cb(new Error(isContent.errorsString))
if (!isSpec(content)) return cb(new Error(isSpec.errorsString))
Powersource marked this conversation as resolved.
Show resolved Hide resolved

get(groupId, (err, { writeKey }) => {
// prettier-ignore
Expand Down Expand Up @@ -314,6 +374,7 @@ module.exports = {
get,
list,
addMembers,
excludeMembers,
publish,
listMembers,
listInvites,
Expand Down
4 changes: 2 additions & 2 deletions lib/meta-feed-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ module.exports = (ssb) => {
/** more specifically: a group that has never had any members. i.e. either
* 1. newly created but tribes2 crashed before we had time to add ourselves to it. in that case find and return it. if we can't find such a group then
* 2. freshly create a new group, and return */
function findOrCreateGroupWithoutMembers(cb) {
function findOrCreateEpochWithoutMembers(cb) {
ssb.metafeeds.findOrCreate(function gotRoot(err, myRoot) {
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to find or create root feed when creating a group'))
Expand Down Expand Up @@ -260,7 +260,7 @@ module.exports = (ssb) => {
findOrCreateFromSecret,
findOrCreateGroupFeed,
createGroupWithoutMembers,
findOrCreateGroupWithoutMembers,
findOrCreateEpochWithoutMembers,
getRootFeedIdFromMsgId,
}
}
15 changes: 1 addition & 14 deletions test/create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const { keySchemes } = require('private-group-spec')
const Ref = require('ssb-ref')
const { promisify: p } = require('util')
const { where, type, toPromise } = require('ssb-db2/operators')
const pull = require('pull-stream')
const Testbot = require('./helpers/testbot')
const countGroupFeeds = require('./helpers/count-group-feeds')

test('create', async (t) => {
const ssb = Testbot()
Expand Down Expand Up @@ -104,19 +104,6 @@ test('root message is encrypted', async (t) => {
await p(alice.close)(true)
})

function countGroupFeeds(server, cb) {
pull(
server.metafeeds.branchStream({ old: true, live: false }),
pull.filter((branch) => branch.length === 4),
pull.map((branch) => branch[3]),
pull.filter((feed) => feed.recps),
pull.collect((err, feeds) => {
if (err) return cb(err)
return cb(null, feeds.length)
})
)
}

function createEmptyGroupFeed({ server, root }, cb) {
const secret = new SecretKey()
server.metafeeds.findOrCreate(
Expand Down
86 changes: 86 additions & 0 deletions test/exclude-members.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2022 Andre 'Staltz' Medeiros <[email protected]>
//
// SPDX-License-Identifier: CC0-1.0

const test = require('tape')
const { promisify: p } = require('util')
const ssbKeys = require('ssb-keys')
const Testbot = require('./helpers/testbot')
const replicate = require('./helpers/replicate')
const countGroupFeeds = require('./helpers/count-group-feeds')

test('add and remove a person, post on the new feed', async (t) => {
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'
),
})

await alice.tribes2.start()
await bob.tribes2.start()
t.pass('tribes2 started for both alice and bob')

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

await replicate(alice, bob)
t.pass('alice and bob replicate their trees')

const { id: groupId } = await alice.tribes2.create().catch((err) => {
console.error('alice failed to create group', err)
t.fail(err)
})
t.pass('alice created a group')

await alice.tribes2.addMembers(groupId, [bobRoot.id]).catch((err) => {
console.error('add member fail', err)
t.fail(err)
})
t.pass('alice added bob to the group')

t.equals(
await p(countGroupFeeds)(alice),
1,
'before exclude alice has 1 group feed'
)

await alice.tribes2.excludeMembers(groupId, [bobRoot.id]).catch((err) => {
console.error('remove member fail', err)
t.fail(err)
})
Powersource marked this conversation as resolved.
Show resolved Hide resolved

// TODO: will be 2 if we post an init message on the new group feed
t.equals(
await p(countGroupFeeds)(alice),
1,
'after exclude alice has some number of group feeds?'
)

await alice.tribes2
.publish({
type: 'test',
text: 'post',
recps: [groupId],
})
.catch(t.fail)

t.equals(
await p(countGroupFeeds)(alice),
2,
'alice has 2 group feeds after publishing on the new feed'
)

// TODO: verify that message was published on the new feed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 we need to test more of Bob's experience, specifically:

  1. confirm he sees the exclude dumping him
  2. confirm he can no longer publish to the group using ssb.tribes2.publish after exclude

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was gonna save that for this #71

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I support doing this in a separate PR to avoid "PR constipation".

await p(alice.close)(true)
await p(bob.close)(true)
})
12 changes: 8 additions & 4 deletions test/get-tangle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ test('get-tangle unit test', (t) => {
recps: [group.id],
}

server.tribes2.publish(content, (err, msg) => {
server.tribes2.publish(content, null, (err, msg) => {
t.error(err, 'no error')

getTangle(group.id, (err, { root, previous }) => {
Expand All @@ -74,7 +74,7 @@ test('get-tangle unit test', (t) => {
'adding message to root'
)

server.tribes2.publish(content, (err, msg) => {
server.tribes2.publish(content, null, (err, msg) => {
t.error(err, 'no error')

getTangle(group.id, (err, { root, previous }) => {
Expand Down Expand Up @@ -110,7 +110,11 @@ test(`get-tangle-${n}-publishes`, (t) => {
pull.values(publishArray),
paraMap(
(value, cb) =>
server.tribes2.publish({ type: 'memo', value, recps: [groupId] }, cb),
server.tribes2.publish(
{ type: 'memo', value, recps: [groupId] },
null,
cb
),
4
),
paraMap((msg, cb) => server.db.getMsg(msg.key, cb), 10),
Expand Down Expand Up @@ -149,7 +153,7 @@ test('get-tangle', (t) => {
}

ssb.db.onMsgAdded((lastMsgAfterCreate) => {
ssb.tribes2.publish(content, (err, msg) => {
ssb.tribes2.publish(content, null, (err, msg) => {
t.error(err, 'publish a message')

ssb.db.get(msg.key, (err, A) => {
Expand Down
18 changes: 18 additions & 0 deletions test/helpers/count-group-feeds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2022 Andre 'Staltz' Medeiros <[email protected]>
//
// SPDX-License-Identifier: CC0-1.0

const pull = require('pull-stream')

module.exports = function countGroupFeeds(server, cb) {
pull(
server.metafeeds.branchStream({ old: true, live: false }),
pull.filter((branch) => branch.length === 4),
pull.map((branch) => branch[3]),
pull.filter((feed) => feed.recps),
pull.collect((err, feeds) => {
if (err) return cb(err)
return cb(null, feeds.length)
})
)
}
1 change: 1 addition & 0 deletions test/list-and-get.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ test('get', async (t) => {
t.true(isIdentityGroupSSBURI(group.id))
t.true(Buffer.isBuffer(group.writeKey.key), 'writeKey has key buffer')
t.equal(writeKey.key, group.writeKey.key)
t.true(Buffer.isBuffer(group.readKeys[0].key))
t.equal(readKeys[0].key, group.readKeys[0].key)
t.true(isClassicMessageSSBURI(group.root), 'has root')
t.equal(root, group.root)
Expand Down
1 change: 1 addition & 0 deletions test/prune-publish.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ test('publish many messages that might need pruning', (t) => {
new Promise((res, rej) => {
ssb.tribes2.publish(
{ type: 'potato', content: value, recps: [group.id] },
null,
Powersource marked this conversation as resolved.
Show resolved Hide resolved
(err, msg) => {
if (err) return rej(err)
return res(msg)
Expand Down