Skip to content

Commit

Permalink
feat: add basic server stored metadata validator
Browse files Browse the repository at this point in the history
  • Loading branch information
EmiM committed Apr 2, 2024
1 parent 7e6b4bc commit 0963a59
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Test, TestingModule } from '@nestjs/testing'
import { TestModule } from '../common/test.module'
import { Test } from '@nestjs/testing'
import { ServerProxyServiceModule } from './storageServerProxy.module'
import { ServerProxyService } from './storageServerProxy.service'
import { ServerStoredCommunityMetadata } from './storageServerProxy.types'
Expand All @@ -21,11 +20,10 @@ const mockFetch = async (responseData: Partial<Response>[]) => {
const module = await Test.createTestingModule({
imports: [ServerProxyServiceModule],
}).compile()
return { service: module.get<ServerProxyService>(ServerProxyService), module }
return module.get<ServerProxyService>(ServerProxyService)
}

describe('Server Proxy Service', () => {
let testingModule: TestingModule
let clientMetadata: ServerStoredCommunityMetadata
beforeEach(() => {
const data = getValidInvitationUrlTestData(validInvitationDatav1[0]).data
Expand All @@ -41,27 +39,38 @@ describe('Server Proxy Service', () => {

afterEach(async () => {
jest.clearAllMocks()
await testingModule.close()
})

it('downloads data for existing cid and proper server address', async () => {
const { module, service } = await mockFetch([
const service = await mockFetch([
{ status: 200, json: () => Promise.resolve({ access_token: 'secretToken' }) },
{ status: 200, json: () => Promise.resolve(clientMetadata) },
])
testingModule = module
service.setServerAddress('http://whatever')
const data = await service.downloadData('cid')
expect(data).toEqual(clientMetadata)
expect(global.fetch).toHaveBeenCalledTimes(2)
})

it('throws error if downloaded metadata does not have proper schema', async () => {
const metadataLackingField = {
id: clientMetadata.id,
ownerCertificate: clientMetadata.ownerCertificate,
rootCa: clientMetadata.rootCa,
ownerOrbitDbIdentity: clientMetadata.ownerOrbitDbIdentity,
peerList: clientMetadata.peerList,
}
const service = await mockFetch([
{ status: 200, json: () => Promise.resolve({ access_token: 'secretToken' }) },
{ status: 200, json: () => Promise.resolve(metadataLackingField) },
])
service.setServerAddress('http://whatever')
expect(service.downloadData('cid')).rejects.toThrow('Invalid metadata')
})

it('obtains token', async () => {
const expectedToken = 'verySecretToken'
const { module, service } = await mockFetch([
{ status: 200, json: () => Promise.resolve({ access_token: expectedToken }) },
])
testingModule = module
const service = await mockFetch([{ status: 200, json: () => Promise.resolve({ access_token: expectedToken }) }])
service.setServerAddress('http://whatever')
const token = await service.auth()
expect(token).toEqual(expectedToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import EventEmitter from 'events'
import { ServerStoredCommunityMetadata } from './storageServerProxy.types'
import fetchRetry from 'fetch-retry'
import Logger from '../common/logger'
import { isServerStoredMetadata } from '../validation/validators'

class HTTPResponseError extends Error {
response: Response
Expand Down Expand Up @@ -57,8 +58,12 @@ export class ServerProxyService extends EventEmitter {
return authResponseData['access_token']
}

validateMetadata = (data: ServerStoredCommunityMetadata) => {
if (!isServerStoredMetadata(data)) throw new Error('Invalid metadata')
}

public downloadData = async (cid: string): Promise<ServerStoredCommunityMetadata> => {
this.logger('Downloading data', cid)
this.logger(`Downloading data for cid: ${cid}`)
const accessToken = await this.auth()
const dataResponse: Response = await this.fetch(this.getInviteUrl(cid), {
method: 'GET',
Expand All @@ -70,12 +75,14 @@ export class ServerProxyService extends EventEmitter {
throw new HTTPResponseError('Failed to download data', dataResponse)
}
const data: ServerStoredCommunityMetadata = await dataResponse.json()
this.validateMetadata(data)
this.logger('Downloaded data', data)
return data
}

public uploadData = async (cid: string, data: ServerStoredCommunityMetadata) => {
this.logger('Uploading data', cid, data)
this.logger(`Uploading data for cid: ${cid}`, data)
this.validateMetadata(data)
const accessToken = await this.auth()
const dataResponse: Response = await this.fetch(this.getInviteUrl(cid), {
method: 'PUT',
Expand Down
17 changes: 17 additions & 0 deletions packages/backend/src/nest/validation/validators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ from 'validator'
import joi from 'joi'
import { ChannelMessage, PublicChannel } from '@quiet/types'
import { ServerStoredCommunityMetadata } from '../storageServerProxy/storageServerProxy.types'

const messageMediaSchema = joi.object({
path: joi.string().allow(null),
Expand Down Expand Up @@ -38,6 +39,16 @@ const channelSchema = joi.object({
address: joi.string(),
})

// TODO: make this validator more strict
const metadataSchema = joi.object({
id: joi.string().required(),
ownerCertificate: joi.string().required(),
rootCa: joi.string().required(),
ownerOrbitDbIdentity: joi.string().required(),
peerList: joi.array().items(joi.string()).required(),
psk: joi.string().required(),
})

export const isUser = (publicKey: string, halfKey: string): boolean => {
return publicKey.length === 66 && halfKey.length === 64 && _.isHexadecimal(publicKey) && _.isHexadecimal(halfKey)
}
Expand All @@ -61,10 +72,16 @@ export const isChannel = (channel: PublicChannel): boolean => {
return !value.error
}

export const isServerStoredMetadata = (metadata: ServerStoredCommunityMetadata): boolean => {
const value = metadataSchema.validate(metadata)
return !value.error
}

export default {
isUser,
isMessage,
isDirectMessage,
isChannel,
isConversation,
isServerStoredMetadata,
}

0 comments on commit 0963a59

Please sign in to comment.