Skip to content

Commit

Permalink
Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas Leblow committed Nov 21, 2023
1 parent dfaac9e commit e0c75a0
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
this.logger("Creating network: received owner's OrbitDB identity")
await this.localDbService.putOwnerOrbitDbIdentity(ownerOrbitDbIdentity)
} else {
this.logger.error('Creating network: received Libp2p PSK is not valid')
this.logger.error("Creating network: received owner's OrbitDB identity is not valid")
emitError(this.serverIoProvider.io, {
type: SocketActionTypes.NETWORK,
message: ErrorMessages.NETWORK_SETUP_FAILED,
Expand Down Expand Up @@ -495,7 +495,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
}
})
this.socketService.on(SocketActionTypes.SEND_COMMUNITY_METADATA, async (payload: CommunityMetadata) => {
const meta = await this.storageService?.updateCommunityMetadata(payload)
const meta = await this.storageService?.communityMetadataStore?.updateCommunityMetadata(payload)
this.serverIoProvider.io.emit(SocketActionTypes.COMMUNITY_METADATA_SAVED, meta)
})
this.socketService.on(SocketActionTypes.SAVE_USER_CSR, async (payload: SaveCSRPayload) => {
Expand Down Expand Up @@ -623,8 +623,8 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
this.registrationService.emit(RegistrationEvents.REGISTER_USER_CERTIFICATE, payload)
}
)
this.storageService.on(StorageEvents.REPLICATED_COMMUNITY_METADATA, (payload: CommunityMetadata) => {
this.logger(`Storage - ${StorageEvents.REPLICATED_COMMUNITY_METADATA}: ${payload}`)
this.storageService.on(StorageEvents.COMMUNITY_METADATA_LOADED, (payload: CommunityMetadata) => {
this.logger(`Storage - ${StorageEvents.COMMUNITY_METADATA_LOADED}: ${payload}`)
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,31 @@ import { sha256 } from 'multiformats/hashes/sha2'
import { stringToArrayBuffer } from 'pvutils'
import * as Block from 'multiformats/block'
import * as dagCbor from '@ipld/dag-cbor'
import { IdentityProvider } from 'orbit-db-identity-provider'

import { Logger } from '@quiet/logger'
import { NoCryptoEngineError, UserProfile } from '@quiet/types'
import { keyObjectFromString, verifyDataSignature } from '@quiet/identity'
import { CommunityMetadata, NoCryptoEngineError } from '@quiet/types'
import { keyFromCertificate, keyObjectFromString, loadCertificate } from '@quiet/identity'

import { StorageEvents } from './storage.types'
import createLogger from '../common/logger'
import { KeyValueIndex } from '../orbitdb/keyValueIndex.ts'
import { StorageEvents } from '../storage.types'
import createLogger from '../../common/logger'
import { KeyValueIndex } from '../orbitDb/keyValueIndex'
import { LocalDbService } from '../../local-db/local-db.service'

const logger = createLogger('CommunityMetadataStore')

/**
* Helper function that allows one to use partial-application with a
* constructor. Given a classname and constructor params, returns a
* new constructor which can be called like normal (`new x()`).
*
* This is useful because in OrbitDB they expect the store index to be
* constructable with zero arguments, so they call it with the `new`
* keyword.
*/
function constructPartial(constructor: Function, args: any[]) {
return constructor.bind.apply(constructor, [null, ...args])
}

@Injectable()
export class CommunityMetadataStore {
public orbitDb: OrbitDB
Expand All @@ -30,14 +44,23 @@ export class CommunityMetadataStore {
public static readonly codec = dagCbor
public static readonly hasher = sha256

constructor(
private readonly localDbService: LocalDbService,
) {}

public async init(orbitDb: OrbitDB, emitter: EventEmitter) {
logger('Initializing community metadata key/value store')

this.orbitDb = orbitDb

const ownerOrbitDbIdentity = this.localDbService.getOwnerOrbitDbIdentity()

this.store = await this.orbitDb.keyvalue<CommunityMetadata>('community-metadata', {
replicate: false,
Index: CommunityMetadataKeyValueIndex,
// Partially construct index so that we can include an
// IdentityProvider in the index validation logic.
// @ts-expect-error - OrbitDB's type declaration of OrbitDB lacks identity
Index: constructPartial(CommunityMetadataKeyValueIndex, orbitDb.identity.provider, ownerOrbitDbIdentity),
accessController: {
write: ['*'],
},
Expand Down Expand Up @@ -70,24 +93,26 @@ export class CommunityMetadataStore {
await this.store?.close()
}

public async updateCommunityMetadata(communityMetadata: CommunityMetadata): Promise<CommunityMetadata | undefined> {
public async updateCommunityMetadata(newMeta: CommunityMetadata): Promise<CommunityMetadata | undefined> {
try {
if (!CommunityMetadataStore.validateCommunityMetadata(communityMetadata)) {
if (!CommunityMetadataStore.validateCommunityMetadata(newMeta)) {
// TODO: Send validation errors to frontend or replicate
// validation on frontend?
logger.error('Failed to update community metadata')
return
}

this.logger(`About to update community metadata`)
logger(`About to update community metadata`)
if (!newMeta.id) return

const oldMeta = this.communityMetadata.get(newMeta.id)
// FIXME: update community metadata if it has changed (so that
// we can migrate community metadata easily)
const oldMeta = this.store.get(newMeta.id)
if (oldMeta?.ownerCertificate && oldMeta?.rootCa) {
return oldMeta
}

this.logger(`Updating community metadata`)
logger(`Updating community metadata`)
// @ts-expect-error - OrbitDB's type declaration of OrbitDB lacks identity
const orbitDbIdentity = this.orbitDb.identity.id
const meta = {
Expand All @@ -96,8 +121,8 @@ export class CommunityMetadataStore {
orbitDbIdentity,
}
await this.store.put(meta.id, meta)
// perhaps keep this in storage.service?
// this.localDbService.putOwnerOrbitDbIdentity(orbitDbIdentity)

this.localDbService.putOwnerOrbitDbIdentity(orbitDbIdentity)

return meta

Expand All @@ -106,70 +131,61 @@ export class CommunityMetadataStore {
}
}

public static async validateCommunityMetadata(communityMetadata: CommunityMetadata) {
public static async validateCommunityMetadata(communityMetadata: CommunityMetadata): Promise<boolean> {
// FIXME: Add additional validation to verify communityMetadata
// contains required fields

// FIXME!
// Verify that the community metadata is signed by the owner's public key
// contains required fields.

/**
try {
const crypto = getCrypto()
if (!crypto) {
throw new NoCryptoEngineError()
}

const profile = userProfile.profile
const pubKey = await keyObjectFromString(userProfile.pubKey, crypto)
const profileSig = stringToArrayBuffer(userProfile.profileSig)
const { bytes } = await Block.encode({
value: profile,
codec: CommunityMetadataStore.codec,
hasher: CommunityMetadataStore.hasher,
})
const verify = await verifyDataSignature(profileSig, bytes, pubKey)
if (!verify) {
logger.error('User profile contains invalid signature', userProfile.pubKey)
return false
}
// TODO: Should we sign community metadata with root private key
// and verify? I'm not sure it matters that much.

const rootCert = loadCertificate(communityMetadata.rootCa)
const ownerCert = loadCertificate(communityMetadata.ownerCertificate)

// Verify that owner certificate is signed by root certificate
return await ownerCert.verify(rootCert)

} catch (err) {
logger.error('Failed to validate user profile:', userProfile.pubKey, err?.message)
logger.error('Failed to validate community metadata:', communityMetadata.id, err?.message)
return false
}
return true
**/
}

public static async validateCommunityMetadataEntry(entry: LogEntry<CommunityMetadata>) {
let verify = false

public static async validateCommunityMetadataEntry(ownerOrbitDbIdentity: string, identityProvider: typeof IdentityProvider, entry: LogEntry<CommunityMetadata>): Promise<boolean> {
try {
if (entry.payload.key !== entry.payload.value.id) {
logger.error('Failed to verify community metadata entry:', entry.hash, 'entry key != payload id')
return false
}

if (entry.identity.id !== ownerOrbitDbIdentity) {
logger.error('Failed to verify community metadata entry:', entry.hash, 'entry identity != owner identity')
return false
}

if (!identities.verifyIdentity(writerIdentity)) {
const identityVerified = await identityProvider.verifyIdentity(entry.identity)
if (!identityVerified) {
logger.error('Failed to verify community metadata entry:', entry.hash, 'entry verification failed')
return false
}

verify = await CommunityMetadataStore.validateCommunityMetadata(entry.payload.value)
return await CommunityMetadataStore.validateCommunityMetadata(entry.payload.value)

} catch (err) {
logger.error('Failed to verify user profile entry:', entry.hash, err?.message)
return false
}

return verify
}

public async getCommunityMetadata(): Promise<CommunityMetadata | undefined> {
// @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options'
await this.store.load({ fetchEntryTimeout: 15000 })

const metadata = Object.values(this.store.all)

if (metadata.length > 0) {
Expand All @@ -179,7 +195,7 @@ export class CommunityMetadataStore {
}

export class CommunityMetadataKeyValueIndex extends KeyValueIndex<CommunityMetadata> {
constructor() {
super(CommunityMetadataStore.validateCommunityMetadataEntry)
constructor(identityProvider: typeof IdentityProvider, ownerOrbitDbIdentity: string) {
super(identityProvider, CommunityMetadataStore.validateCommunityMetadataEntry.bind(null, ownerOrbitDbIdentity))
}
}
16 changes: 13 additions & 3 deletions packages/backend/src/nest/storage/orbitDb/keyValueIndex.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { IdentityProvider } from 'orbit-db-identity-provider'

import createLogger from '../../common/logger'

const logger = createLogger('KeyValueIndex')

type ValidateFn<T> = (identityProvider: typeof IdentityProvider, entry: LogEntry<T>) => Promise<boolean>

/**
* Modified from:
* https://github.com/orbitdb/orbit-db-kvstore/blob/main/src/KeyValueIndex.js
Expand All @@ -9,11 +17,13 @@
*/
export class KeyValueIndex<T> {
private _index: Record<string, any>
private validateFn: (entry: LogEntry<T>) => Promise<boolean>
private validateFn: ValidateFn<T>
private identityProvider: typeof IdentityProvider

constructor(validateFn: (entry: LogEntry<T>) => Promise<boolean>) {
constructor(identityProvider: typeof IdentityProvider, validateFn: ValidateFn<T>) {
this._index = {}
this.validateFn = validateFn
this.identityProvider = identityProvider
}

get(key: string) {
Expand All @@ -25,7 +35,7 @@ export class KeyValueIndex<T> {
const handled: Record<string, boolean> = {}

for (const v of oplog.values) {
if (await this.validateFn(v)) {
if (await this.validateFn(this.identityProvider, v)) {
values.push(v)
}
}
Expand Down
5 changes: 3 additions & 2 deletions packages/backend/src/nest/storage/storage.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { Module } from '@nestjs/common'
import { StorageService } from './storage.service'
import { LocalDbModule } from '../local-db/local-db.module'
import { IpfsFileManagerModule } from '../ipfs-file-manager/ipfs-file-manager.module'
import { CommunityMetadataStore } from './communityMetadata/communityMetadata.store'

@Module({
imports: [LocalDbModule, IpfsFileManagerModule],
providers: [StorageService],
exports: [StorageService],
providers: [StorageService, CommunityMetadataStore],
exports: [StorageService, CommunityMetadataStore],
})
export class StorageModule {}
Loading

0 comments on commit e0c75a0

Please sign in to comment.