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

Add communityMetadata store validation #1891

Closed
Tracked by #1902
leblowl opened this issue Oct 3, 2023 · 4 comments
Closed
Tracked by #1902

Add communityMetadata store validation #1891

leblowl opened this issue Oct 3, 2023 · 4 comments
Assignees
Labels
bug Something isn't working security

Comments

@leblowl
Copy link
Collaborator

leblowl commented Oct 3, 2023

Only the owner should be able to update the community metadata. Currently anyone can modify the community metadata. This allows anyone to change the owner certificate (and nickname) and community name.

As an example, we replicate community metadata here:

this.communityMetadata.events.on('replicated', async () => {
this.logger('Replicated community metadata')
// @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options'
await this.communityMetadata.load({ fetchEntryTimeout: 15000 })
this.emit(StorageEvents.REPLICATED_COMMUNITY_METADATA, Object.values(this.communityMetadata.all)[0])

We save the metadata to the Redux store:

socket.on(SocketActionTypes.SAVE_COMMUNITY_METADATA, (payload: CommunityMetadata) => {
console.log('SAVE COMMUNITY METADATA', payload)
emit(
communitiesActions.saveCommunityMetadata({
rootCa: payload.rootCa,
ownerCertificate: payload.ownerCertificate,
})
)
})

We get the owner nickname from ownerCertficate:

export const ownerNickname = createSelector(

Which is used to send a channel message:

const ownerNickname = yield* select(communitiesSelectors.ownerNickname)

Another example, we set the community name from communityMetadata.rootCa:

which is displayed to the user in the left-side panel:

const communityName = currentCommunity?.name || '...'

@leblowl leblowl changed the title Community metadata should only be signed and written to by the owner Community metadata should be signed and only written to by the owner Oct 3, 2023
@holmesworcester holmesworcester added the bug Something isn't working label Oct 3, 2023
@holmesworcester holmesworcester moved this to Sprint in Quiet Oct 3, 2023
@leblowl leblowl added the needs migration Needs data migration code for backward compatibility label Oct 3, 2023
@holmesworcester
Copy link
Contributor

holmesworcester commented Oct 4, 2023

Whoever tackles this should dig into it a bit and write up a document proposing how to solve the problem and get feedback before they start writing code.

Part of the complexity is that you need to know the owner's OrbitDB ID or something about the owner to have some sense of truth here.

@leblowl
Copy link
Collaborator Author

leblowl commented Oct 5, 2023

In order to only allow the community owner to edit community metadata, we need to know if an entry was written by the community owner. To do this we can use the OrbitDB identity ID as OrbitDB uses to verify write permissions in OrbitDB's IPFSAccessController: https://github.com/orbitdb/orbit-db-access-controllers/blob/3741eb318e9c7efea5af15a54110cf6de8ae1fe3/src/access-controllers/ipfs.js#L20. Since each member of the community will need to know this ID in order to validate community metadata entries, we can simply include the community owner's OrbitDB identity ID in the invite link.

After the owner initializes their OrbitDB databases for the first time, we can save their identity ID (orbitdb.identity.id) in LevelDB under a key like ownerOrbitDbIdentityId. We can create a new file like src/nest/storage/identity.ts with functions for storing and retrieving the owner identity ID (putOwnerOrbitDbIdentityId, getOwnerOrbitDbIdentityId).

The owner's identity ID should be sent to the frontend for incorporating it into the invite link. In order to include the owner's OrbitDB identity ID and a PSK (#1897) in the link, we will need to reduce the number of peers in the invite link by 1. So PSK + owner OrbitDB identity ID + 3 peers.

When a new user clicks that link and joins, the owner's identity ID should be parsed from the invite link and sent to the backend where it's stored in LevelDB (via the same interface defined in src/nest/storage/identity.ts). This should happen before initializing any OrbitDB databases. Once the owner ID is stored, proceed to first initialize the community metadata DB.

The community metadata DB stores the owner's public key, which we use for authentication in some instances (e.g. when checking cert validity). So we should wait until community metadata has been replicated before initializing other OrbitDB databases (or at least those that depend on the owner's cert).

As for community metadata validation, it looks like access controllers only validate heads and not each entry. So instead of using an access controller, we can simply validate each entry ourselves. We can create a new validation function in the backend validateCommunityMetadataEntry that, given an entry, validates the entry by checking that the entry is valid, its identity signature is valid and the entry identity matches the owner and then also checking that the public key included in the community metadata payload is used to sign the community metadata payload.

In order to validate entries in an EventStore, it's fairly straightforward as discussed in #1893. However, communityMetadata is currently a key/value store. Since we don't support multiple communities yet, I'm not sure we benefit from using a key/value store in this case, but we can continue with that approach to reduce refactoring.

For KeyValueStore databases, the interface does not include an iterator method, so we can create our own KeyValueIndex (https://github.com/orbitdb/orbit-db-kvstore/blob/main/src/KeyValueIndex.js) class with a custom updateIndex method to filter log values with validateCommunityMetadataEntry before they are indexed. We can provide this modified Index class to the KeyValue store during instantiation. For an example, see: #1923

Then we can create a new function, getCommunityMetadata that simply retrieves the community metadata for a given community ID.

I think it would be nice to encapsulate this logic in a CommunityMetadataStore class.

@leblowl leblowl removed their assignment Oct 5, 2023
@leblowl leblowl changed the title Community metadata should be signed and only written to by the owner Add communityMetadata store validation Oct 7, 2023
@leblowl leblowl removed the needs migration Needs data migration code for backward compatibility label Nov 13, 2023
@leblowl leblowl moved this from Sprint to In progress in Quiet Nov 13, 2023
@leblowl leblowl self-assigned this Nov 13, 2023
@holmesworcester holmesworcester moved this from In progress to Waiting for review in Quiet Dec 4, 2023
@EmiM
Copy link
Contributor

EmiM commented Dec 8, 2023

#2073

@EmiM EmiM moved this from Waiting for review to Merged (develop) in Quiet Dec 8, 2023
@Kacper-RF Kacper-RF moved this from Merged (develop) to Ready for QA in Quiet Dec 8, 2023
@kingalg
Copy link
Collaborator

kingalg commented Jan 11, 2024

2.0.3-alpha.15

Done

@kingalg kingalg closed this as completed Jan 11, 2024
@kingalg kingalg moved this from Ready for QA to Done in Quiet Jan 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working security
Projects
Archived in project
Development

No branches or pull requests

4 participants