diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index c02ec9b3a4..cccdbde5a0 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -14,7 +14,7 @@ jobs: uses: ./.github/workflows/e2e-win.yml build-linux: - needs: run-e2e-tests-linux + # needs: run-e2e-tests-linux runs-on: ubuntu-22.04 if: | startsWith(github.ref, 'refs/tags/quiet') @@ -86,7 +86,7 @@ jobs: asset_content_type: application/.AppImage build-macos: - needs: run-e2e-tests-mac + # needs: run-e2e-tests-mac runs-on: macos-latest if: | startsWith(github.ref, 'refs/tags/quiet') diff --git a/CHANGELOG.md b/CHANGELOG.md index e50c9f9912..d79ed2725b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ [unreleased] +* Changed registration process - user connects to the libp2p network directly instead of using registrar. Invitation link format changed. User csr is now saved to database. + * Fixed android stucking on username registration screen introduced in previous alpha. * Added creator username to initial channel message. @@ -8,4 +10,8 @@ * Fixed bug with displaying incorrect default settings tab. -* Replaced source of publicKey in sendMessage saga to CSR \ No newline at end of file +* Replaced source of publicKey in sendMessage saga to CSR + +* Labels for unregistered and duplicate usernames with modals + +* Fixed LoadingPanel useEffect bug. diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 897c806d93..ab4e936edc 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.0.0-alpha.2](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.0-alpha.1...@quiet/backend@2.0.0-alpha.2) (2023-09-06) + +**Note:** Version bump only for package @quiet/backend + + + + + # [2.0.0-alpha.1](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.0-alpha.0...@quiet/backend@2.0.0-alpha.1) (2023-09-05) **Note:** Version bump only for package @quiet/backend diff --git a/packages/backend/package-lock.json b/packages/backend/package-lock.json index ffdf4325a7..6960df47a0 100644 --- a/packages/backend/package-lock.json +++ b/packages/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "@quiet/backend", - "version": "2.0.0-alpha.1", + "version": "2.0.0-alpha.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@quiet/backend", - "version": "2.0.0-alpha.1", + "version": "2.0.0-alpha.2", "license": "MIT", "dependencies": { "@chainsafe/libp2p-gossipsub": "6.1.0", diff --git a/packages/backend/package.json b/packages/backend/package.json index c218fad5dc..c5e6fcfc49 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "@quiet/backend", - "version": "2.0.0-alpha.1", + "version": "2.0.0-alpha.2", "description": "tlg-manager", "types": "lib/index.d.ts", "type": "module", diff --git a/packages/backend/src/nest/common/utils.ts b/packages/backend/src/nest/common/utils.ts index 22e843a62c..0e271922b1 100644 --- a/packages/backend/src/nest/common/utils.ts +++ b/packages/backend/src/nest/common/utils.ts @@ -12,6 +12,7 @@ import { TestConfig } from '../const' import logger from './logger' import { createCertificatesTestHelper } from './client-server' import { Libp2pNodeParams } from '../libp2p/libp2p.types' +import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common' const log = logger('test') export interface Ports { @@ -143,14 +144,6 @@ export const torDirForPlatform = (basePath?: string): string => { return torPath } -export const createLibp2pAddress = (address: string, peerId: string) => { - return `/dns4/${address}/tcp/80/ws/p2p/${peerId}` -} - -export const createLibp2pListenAddress = (address: string) => { - return `/dns4/${address}/tcp/80/ws` -} - export const getUsersAddresses = async (users: User[]): Promise => { const peers = users.map(async (userData: User) => { return createLibp2pAddress(userData.onionAddress, userData.peerId) @@ -208,9 +201,6 @@ export const libp2pInstanceParams = async (): Promise => { listenAddresses: [createLibp2pListenAddress('localhost')], agent: createHttpsProxyAgent({ port: 1234, host: 'localhost' }), localAddress: createLibp2pAddress('localhost', peerId.toString()), - cert: pems.userCert, - key: pems.userKey, - ca: [pems.ca], targetPort: port, peers: [remoteAddress], } diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts index 2bd278647b..abc3ad923e 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts @@ -1,34 +1,23 @@ +import { jest } from '@jest/globals' +import { LazyModuleLoader } from '@nestjs/core' import { Test, TestingModule } from '@nestjs/testing' +import { getFactory, prepareStore, type Store, type communities, type identity } from '@quiet/state-manager' +import { type Community, type Identity, type InitCommunityPayload, type LaunchRegistrarPayload } from '@quiet/types' +import { type FactoryGirl } from 'factory-girl' +import PeerId from 'peer-id' import { TestModule } from '../common/test.module' +import { libp2pInstanceParams, removeFilesFromDir } from '../common/utils' import { QUIET_DIR, TOR_PASSWORD_PROVIDER } from '../const' -import { ConnectionsManagerModule } from './connections-manager.module' -import { ConnectionsManagerService } from './connections-manager.service' -import PeerId from 'peer-id' -import { libp2pInstanceParams } from '../common/utils' -import { jest } from '@jest/globals' -import { CustomEvent } from '@libp2p/interfaces/events' -import { type communities, getFactory, prepareStore, type identity, type Store } from '@quiet/state-manager' -import { type FactoryGirl } from 'factory-girl' -import { DateTime } from 'luxon' -import waitForExpect from 'wait-for-expect' -import { - type Community, - type Identity, - type InitCommunityPayload, - type LaunchRegistrarPayload, - type NetworkStats, -} from '@quiet/types' -import { LocalDBKeys } from '../local-db/local-db.types' +import { Libp2pModule } from '../libp2p/libp2p.module' +import { Libp2pService } from '../libp2p/libp2p.service' import { LocalDbModule } from '../local-db/local-db.module' import { LocalDbService } from '../local-db/local-db.service' -import { RegistrationService } from '../registration/registration.service' +import { LocalDBKeys } from '../local-db/local-db.types' import { RegistrationModule } from '../registration/registration.module' -import { LazyModuleLoader } from '@nestjs/core' -import { Libp2pService } from '../libp2p/libp2p.service' -import { Libp2pModule } from '../libp2p/libp2p.module' +import { RegistrationService } from '../registration/registration.service' import { SocketModule } from '../socket/socket.module' -import { removeFilesFromDir } from '../common/utils' -import { Libp2pEvents } from '../libp2p/libp2p.types' +import { ConnectionsManagerModule } from './connections-manager.module' +import { ConnectionsManagerService } from './connections-manager.service' describe('ConnectionsManagerService', () => { let module: TestingModule @@ -126,7 +115,7 @@ describe('ConnectionsManagerService', () => { expect(launchCommunitySpy).toHaveBeenCalledWith(launchCommunityPayload) }) - it('launches community and registrar on init if their data exists in local db', async () => { + it('launches community on init if their data exists in local db', async () => { const launchCommunityPayload: InitCommunityPayload = { id: community.id, peerId: userIdentity.peerId, @@ -141,20 +130,9 @@ describe('ConnectionsManagerService', () => { peers: community.peerList, } - const launchRegistrarPayload: LaunchRegistrarPayload = { - id: community.id, - peerId: userIdentity.peerId.id, - // @ts-expect-error - rootCertString: community.CA?.rootCertString, - // @ts-expect-error - rootKeyString: community.CA?.rootKeyString, - privateKey: 'privateKey', - } - await localDbService.put(LocalDBKeys.COMMUNITY, launchCommunityPayload) - await localDbService.put(LocalDBKeys.REGISTRAR, launchRegistrarPayload) - const peerAddress = '/dns4/test.onion/tcp/443/wss/p2p/peerid' + const peerAddress = '/dns4/test.onion/tcp/80/ws/p2p/peerid' await localDbService.put(LocalDBKeys.PEERS, { [peerAddress]: { peerId: 'QmaEvCkpUG7GxhgvMkk8wxurfi1ehjHhSUNRksWTmXN2ix', @@ -166,21 +144,17 @@ describe('ConnectionsManagerService', () => { await connectionsManagerService.closeAllServices() const launchCommunitySpy = jest.spyOn(connectionsManagerService, 'launchCommunity').mockResolvedValue() - const launchRegistrarSpy = jest.spyOn(registrationService, 'launchRegistrar').mockResolvedValue() await connectionsManagerService.init() expect(launchCommunitySpy).toHaveBeenCalledWith(Object.assign(launchCommunityPayload, { peers: [peerAddress] })) - expect(launchRegistrarSpy).toHaveBeenCalledWith(launchRegistrarPayload) }) it('does not launch community on init if its data does not exist in local db', async () => { await connectionsManagerService.closeAllServices() await connectionsManagerService.init() const launchCommunitySpy = jest.spyOn(connectionsManagerService, 'launchCommunity') - const launchRegistrarSpy = jest.spyOn(registrationService, 'launchRegistrar') expect(launchCommunitySpy).not.toHaveBeenCalled() - expect(launchRegistrarSpy).not.toHaveBeenCalled() }) // At this moment, that test have to be skipped, because checking statues is called before launchCommunity method @@ -210,7 +184,7 @@ describe('ConnectionsManagerService', () => { expect(launchSpy).toBeCalledTimes(1) }) - it('Bug reproduction - Error on startup - Error: TOR: Connection already established - Trigger launchCommunity and launchRegistrar from backend and state manager', async () => { + it('Bug reproduction - Error on startup - Error: TOR: Connection already established - Trigger launchCommunity from backend and state manager', async () => { const launchCommunityPayload: InitCommunityPayload = { id: community.id, peerId: userIdentity.peerId, @@ -225,21 +199,10 @@ describe('ConnectionsManagerService', () => { peers: community.peerList, } - const launchRegistrarPayload: LaunchRegistrarPayload = { - id: community.id, - peerId: userIdentity.peerId.id, - // @ts-expect-error - rootCertString: community.CA?.rootCertString, - // @ts-expect-error - rootKeyString: community.CA?.rootKeyString, - privateKey: '', - } - // await connectionsManager.init() await localDbService.put(LocalDBKeys.COMMUNITY, launchCommunityPayload) - await localDbService.put(LocalDBKeys.REGISTRAR, launchRegistrarPayload) - const peerAddress = '/dns4/test.onion/tcp/443/wss/p2p/peerid' + const peerAddress = '/dns4/test.onion/tcp/80/ws/p2p/peerid' await localDbService.put(LocalDBKeys.PEERS, { [peerAddress]: { peerId: 'QmaEvCkpUG7GxhgvMkk8wxurfi1ehjHhSUNRksWTmXN2ix', @@ -251,11 +214,9 @@ describe('ConnectionsManagerService', () => { await connectionsManagerService.closeAllServices() const launchCommunitySpy = jest.spyOn(connectionsManagerService, 'launchCommunity').mockResolvedValue() - const launchRegistrarSpy = jest.spyOn(registrationService, 'launchRegistrar').mockResolvedValue() await connectionsManagerService.init() expect(launchCommunitySpy).toBeCalledTimes(1) - expect(launchRegistrarSpy).toBeCalledTimes(1) }) }) diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts index e88e7151b2..8c759f8571 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts @@ -4,14 +4,7 @@ import crypto from 'crypto' import { CustomEvent } from '@libp2p/interfaces/events' import { jest, beforeEach, describe, it, expect, afterEach } from '@jest/globals' import { communities, getFactory, identity, prepareStore, Store } from '@quiet/state-manager' -import { - createLibp2pAddress, - createPeerId, - createTmpDir, - libp2pInstanceParams, - removeFilesFromDir, - tmpQuietDirPath, -} from '../common/utils' +import { createPeerId, createTmpDir, libp2pInstanceParams, removeFilesFromDir, tmpQuietDirPath } from '../common/utils' import { NetworkStats, type Community, type Identity, type InitCommunityPayload } from '@quiet/types' import { LazyModuleLoader } from '@nestjs/core' @@ -37,6 +30,7 @@ import { DateTime } from 'luxon' import waitForExpect from 'wait-for-expect' import { Libp2pEvents } from '../libp2p/libp2p.types' import { sleep } from '../common/sleep' +import { createLibp2pAddress } from '@quiet/common' jest.setTimeout(100_000) diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.ts index c62fcc8911..4baec55254 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.ts @@ -42,6 +42,10 @@ import { StorePeerListPayload, UploadFilePayload, PeerId as PeerIdType, + SaveCSRPayload, + SendUserCertificatePayload, + CommunityMetadata, + CommunityMetadataPayload, } from '@quiet/types' import { CONFIG_OPTIONS, QUIET_DIR, SERVER_IO_PROVIDER, SOCKS_PROXY_AGENT } from '../const' import { ConfigOptions, GetPorts, ServerIoProviderTypes } from '../types' @@ -165,17 +169,9 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI if ([ServiceState.LAUNCHING, ServiceState.LAUNCHED].includes(this.communityState)) return this.communityState = ServiceState.LAUNCHING } - const registrarData: LaunchRegistrarPayload = await this.localDbService.get(LocalDBKeys.REGISTRAR) - if (registrarData) { - if ([ServiceState.LAUNCHING, ServiceState.LAUNCHED].includes(this.registrarState)) return - this.registrarState = ServiceState.LAUNCHING - } if (community) { await this.launchCommunity(community) } - if (registrarData) { - await this.registrationService.launchRegistrar(registrarData) - } } public async closeAllServices(options: { saveTor: boolean } = { saveTor: false }) { @@ -273,7 +269,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI community: { ...community, privateKey: network2.hiddenService.privateKey, - registrarUrl: community.registrarUrl || network2.hiddenService.onionAddress.split('.')[0], + registrarUrl: community.registrarUrl || network2.hiddenService.onionAddress.split('.')[0], // TODO: remove }, network, } @@ -281,12 +277,14 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI } public async createCommunity(payload: InitCommunityPayload) { + console.log('ConnectionsManager.createCommunity peers:', payload.peers) await this.launchCommunity(payload) this.logger(`Created and launched community ${payload.id}`) this.serverIoProvider.io.emit(SocketActionTypes.NEW_COMMUNITY, { id: payload.id }) } public async launchCommunity(payload: InitCommunityPayload) { + console.log('ConnectionsManager.launchCommunity peers:', payload.peers) this.communityState = ServiceState.LAUNCHING const communityData: InitCommunityPayload = await this.localDbService.get(LocalDBKeys.COMMUNITY) if (!communityData) { @@ -327,7 +325,6 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI const { Libp2pModule } = await import('../libp2p/libp2p.module') const moduleRef = await this.lazyModuleLoader.load(() => Libp2pModule) - this.logger('launchCommunityFromStorage') const { Libp2pService } = await import('../libp2p/libp2p.service') const lazyService = moduleRef.get(Libp2pService) this.libp2pService = lazyService @@ -336,6 +333,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI const _peerId = await peerIdFromKeys(restoredRsa.marshalPubKey(), restoredRsa.marshalPrivKey()) let peers = payload.peers + console.log(`Launching community ${payload.id}, payload peers: ${peers}`) if (!peers || peers.length === 0) { peers = [this.libp2pService.createLibp2pAddress(onionAddress, _peerId.toString())] } @@ -344,9 +342,6 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI peerId: _peerId, listenAddresses: [this.libp2pService.createLibp2pListenAddress(onionAddress)], agent: this.socksProxyAgent, - cert: payload.certs.certificate, - key: payload.certs.key, - ca: payload.certs.CA, localAddress: this.libp2pService.createLibp2pAddress(onionAddress, _peerId.toString()), targetPort: this.ports.libp2pHiddenService, peers, @@ -397,19 +392,9 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.registrationService.on(SocketActionTypes.SAVED_OWNER_CERTIFICATE, payload => { this.serverIoProvider.io.emit(SocketActionTypes.SAVED_OWNER_CERTIFICATE, payload) }) - this.registrationService.on(RegistrationEvents.SPAWN_HS_FOR_REGISTRAR, async payload => { - await this.tor.spawnHiddenService({ - targetPort: payload.port, - privKey: payload.privateKey, - virtPort: payload.targetPort, - }) - }) this.registrationService.on(RegistrationEvents.ERROR, payload => { emitError(this.serverIoProvider.io, payload) }) - this.registrationService.on(SocketActionTypes.SEND_USER_CERTIFICATE, payload => { - this.serverIoProvider.io.emit(SocketActionTypes.SEND_USER_CERTIFICATE, payload) - }) this.registrationService.on(RegistrationEvents.NEW_USER, async payload => { await this.storageService?.saveCertificate(payload) }) @@ -446,18 +431,16 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.communityState = ServiceState.LAUNCHING await this.launchCommunity(args) }) - // Registration this.socketService.on(SocketActionTypes.LAUNCH_REGISTRAR, async (args: LaunchRegistrarPayload) => { + // Event left for setting permsData purposes this.logger(`socketService - ${SocketActionTypes.LAUNCH_REGISTRAR}`) - - const communityData = await this.localDbService.get(LocalDBKeys.REGISTRAR) - if (!communityData) { - await this.localDbService.put(LocalDBKeys.REGISTRAR, args) + this.registrationService.permsData = { + certificate: args.rootCertString, + privKey: args.rootKeyString, } - console.log('this.registrarState', this.registrarState) - if ([ServiceState.LAUNCHING, ServiceState.LAUNCHED].includes(this.registrarState)) return - this.registrarState = ServiceState.LAUNCHING - await this.registrationService.launchRegistrar(args) + }) + this.socketService.on(SocketActionTypes.SEND_COMMUNITY_METADATA, async (payload: CommunityMetadata) => { + await this.storageService?.updateCommunityMetadata(payload) }) this.socketService.on(SocketActionTypes.SAVED_OWNER_CERTIFICATE, async (args: SaveOwnerCertificatePayload) => { const saveCertificatePayload: SaveCertificatePayload = { @@ -466,18 +449,10 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI } await this.storageService?.saveCertificate(saveCertificatePayload) }) - this.socketService.on(SocketActionTypes.REGISTER_USER_CERTIFICATE, async (args: RegisterUserCertificatePayload) => { - // if (!this.socksProxyAgent) { - // this.createAgent() - // } - - await this.registrationService.sendCertificateRegistrationRequest( - args.serviceAddress, - args.userCsr, - args.communityId, - 120_000, - this.socksProxyAgent - ) + this.socketService.on(SocketActionTypes.SAVE_USER_CSR, async (payload: SaveCSRPayload) => { + console.log(`On ${SocketActionTypes.SAVE_USER_CSR}: ${payload.csr}`) + await this.storageService?.saveCSR(payload) + this.serverIoProvider.io.emit(SocketActionTypes.SAVED_USER_CSR, payload) }) this.socketService.on( SocketActionTypes.REGISTER_OWNER_CERTIFICATE, @@ -592,5 +567,17 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI console.log('emitting deleted channel event back to state manager') this.serverIoProvider.io.emit(SocketActionTypes.CHANNEL_DELETION_RESPONSE, payload) }) + this.storageService.on(StorageEvents.REPLICATED_CSR, async (payload: { csr: string }) => { + console.log(`On ${StorageEvents.REPLICATED_CSR}`) + this.registrationService.emit(RegistrationEvents.REGISTER_USER_CERTIFICATE, payload.csr) + }) + this.storageService.on(StorageEvents.REPLICATED_COMMUNITY_METADATA, (payload: CommunityMetadata) => { + console.log(`On ${StorageEvents.REPLICATED_COMMUNITY_METADATA}: ${payload}`) + const communityMetadataPayload: CommunityMetadataPayload = { + rootCa: payload.rootCa, + ownerCertificate: payload.ownerCertificate, + } + this.serverIoProvider.io.emit(SocketActionTypes.SAVE_COMMUNITY_METADATA, communityMetadataPayload) + }) } } diff --git a/packages/backend/src/nest/libp2p/libp2p.service.spec.ts b/packages/backend/src/nest/libp2p/libp2p.service.spec.ts index 9c7724eee4..6775f9a2e2 100644 --- a/packages/backend/src/nest/libp2p/libp2p.service.spec.ts +++ b/packages/backend/src/nest/libp2p/libp2p.service.spec.ts @@ -38,11 +38,11 @@ describe('Libp2pService', () => { it('creates libp2p address with proper ws type (%s)', async () => { const libp2pAddress = libp2pService.createLibp2pAddress(params.localAddress, params.peerId.toString()) - expect(libp2pAddress).toStrictEqual(`/dns4/${params.localAddress}.onion/tcp/80/wss/p2p/${params.peerId.toString()}`) + expect(libp2pAddress).toStrictEqual(`/dns4/${params.localAddress}.onion/tcp/80/ws/p2p/${params.peerId.toString()}`) }) it('creates libp2p listen address', async () => { const libp2pListenAddress = libp2pService.createLibp2pListenAddress('onionAddress') - expect(libp2pListenAddress).toStrictEqual(`/dns4/onionAddress.onion/tcp/80/wss`) + expect(libp2pListenAddress).toStrictEqual(`/dns4/onionAddress.onion/tcp/80/ws`) }) }) diff --git a/packages/backend/src/nest/libp2p/libp2p.service.ts b/packages/backend/src/nest/libp2p/libp2p.service.ts index fa98820843..311b8f2b47 100644 --- a/packages/backend/src/nest/libp2p/libp2p.service.ts +++ b/packages/backend/src/nest/libp2p/libp2p.service.ts @@ -15,10 +15,10 @@ import { multiaddr } from '@multiformats/multiaddr' import { ConnectionProcessInfo, PeerId, SocketActionTypes } from '@quiet/types' import { SERVER_IO_PROVIDER, SOCKS_PROXY_AGENT } from '../const' import { ServerIoProviderTypes } from '../types' -import { createLibp2pListenAddress, createLibp2pAddress } from './libp2p.utils' import Logger from '../common/logger' import { webSockets } from '../websocketOverTor' import { all } from '../websocketOverTor/filters' +import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common' @Injectable() export class Libp2pService extends EventEmitter { @@ -44,7 +44,7 @@ export class Libp2pService extends EventEmitter { return createLibp2pListenAddress(address) } - public async createInstance(params: Libp2pNodeParams): Promise { + public async createInstance(params: Libp2pNodeParams): Promise { if (this.libp2pInstance) { return this.libp2pInstance } @@ -118,6 +118,7 @@ export class Libp2pService extends EventEmitter { dialInChunks.stop() this.connectedPeers.set(remotePeerId, DateTime.utc().valueOf()) + this.logger(`${this.connectedPeers.size} connected peers`) this.emit(Libp2pEvents.PEER_CONNECTED, { peers: [remotePeerId], @@ -144,6 +145,7 @@ export class Libp2pService extends EventEmitter { const connectionDuration: number = connectionEndTime - connectionStartTime this.connectedPeers.delete(remotePeerId) + this.logger(`${this.connectedPeers.size} connected peers`) this.emit(Libp2pEvents.PEER_DISCONNECTED, { peer: remotePeerId, diff --git a/packages/backend/src/nest/libp2p/libp2p.types.ts b/packages/backend/src/nest/libp2p/libp2p.types.ts index a912015c23..d32cc0230f 100644 --- a/packages/backend/src/nest/libp2p/libp2p.types.ts +++ b/packages/backend/src/nest/libp2p/libp2p.types.ts @@ -11,9 +11,6 @@ export interface Libp2pNodeParams { peerId: any listenAddresses: string[] agent: Agent - cert: string - key: string - ca: string[] localAddress: string targetPort: number peers: string[] diff --git a/packages/backend/src/nest/local-db/local-db.service.spec.ts b/packages/backend/src/nest/local-db/local-db.service.spec.ts index 0ac06cba05..0cf4af79cf 100644 --- a/packages/backend/src/nest/local-db/local-db.service.spec.ts +++ b/packages/backend/src/nest/local-db/local-db.service.spec.ts @@ -73,7 +73,7 @@ describe('LocalDbService', () => { it('get sorted peers', async () => { const extraPeers = [ - '/dns4/zl37gnntp64dhnisddftypxbt5cqx6cum65vdv6oeaffrbqmemwc52ad.onion/tcp/443/wss/p2p/QmPGdGDUV1PXaJky4V53KSvFszdqEcM7KCoDpF2uFPf5w6', + '/dns4/zl37gnntp64dhnisddftypxbt5cqx6cum65vdv6oeaffrbqmemwc52ad.onion/tcp/443/ws/p2p/QmPGdGDUV1PXaJky4V53KSvFszdqEcM7KCoDpF2uFPf5w6', ] await localDbService.put(LocalDBKeys.PEERS, { ...peer1Stats, diff --git a/packages/backend/src/nest/registration/registration.service.ts b/packages/backend/src/nest/registration/registration.service.ts index c3d8a9b172..f3bb56d8e4 100644 --- a/packages/backend/src/nest/registration/registration.service.ts +++ b/packages/backend/src/nest/registration/registration.service.ts @@ -30,7 +30,7 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { private _server: Server private _port: number public registrationService: any - public certificates: string[] + public certificates: string[] = [] private _permsData: PermsData private _ownerCertificate: string @@ -40,8 +40,17 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { onModuleInit() { this.on(RegistrationEvents.SET_CERTIFICATES, certs => { + console.log('SET CERTIFICATES', certs) this.setCertificates(certs) }) + this.on(RegistrationEvents.REGISTER_USER_CERTIFICATE, async (csr: string) => { + if (!this._permsData) { + console.log('NO PERMS DATA') + return + } + console.log('CSR in registration service', csr) + const response = await this.registerUser(csr) + }) this.setRouting() } @@ -84,6 +93,14 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { }) } + public set permsData(perms: PermsData) { + console.log('Setting owner perms data') + this._permsData = { + certificate: perms.certificate, + privKey: perms.privKey, + } + } + public async registerOwnerCertificate(payload: RegisterOwnerCertificatePayload): Promise { let cert: string try { @@ -123,7 +140,7 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { this.emit(response.eventType, response.data) } - private async registerUser(csr: string): Promise<{ status: number; body: any }> { + public async registerUser(csr: string): Promise<{ status: number; body: any }> { const result = await registerUser(csr, this._permsData, this.certificates, this._ownerCertificate) if (result?.status === 200) { this.emit(RegistrationEvents.NEW_USER, { certificate: result.body.certificate, rootPermsData: this._permsData }) diff --git a/packages/backend/src/nest/registration/registration.types.ts b/packages/backend/src/nest/registration/registration.types.ts index 62e81b59c1..dcffe9d7f9 100644 --- a/packages/backend/src/nest/registration/registration.types.ts +++ b/packages/backend/src/nest/registration/registration.types.ts @@ -4,4 +4,5 @@ export enum RegistrationEvents { NEW_USER = 'newUser', SET_CERTIFICATES = 'setCertificates', REGISTRAR_STATE = 'registrarState', + REGISTER_USER_CERTIFICATE = 'registerUserCertificate', } diff --git a/packages/backend/src/nest/socket/socket.service.ts b/packages/backend/src/nest/socket/socket.service.ts index 4936e4a32a..bb554a0916 100644 --- a/packages/backend/src/nest/socket/socket.service.ts +++ b/packages/backend/src/nest/socket/socket.service.ts @@ -15,6 +15,8 @@ import { LaunchRegistrarPayload, Community, DeleteFilesFromChannelSocketPayload, + SaveCSRPayload, + CommunityMetadata, } from '@quiet/types' import cors, { CorsOptions } from 'cors' import EventEmitter from 'events' @@ -100,11 +102,11 @@ export class SocketService extends EventEmitter implements OnModuleInit { this.emit(SocketActionTypes.ASK_FOR_MESSAGES, payload) }) - socket.on(SocketActionTypes.REGISTER_USER_CERTIFICATE, async (payload: RegisterUserCertificatePayload) => { - this.logger(`Registering user certificate (${payload.communityId}) on ${payload.serviceAddress}`) - this.emit(SocketActionTypes.REGISTER_USER_CERTIFICATE, payload) + socket.on(SocketActionTypes.SAVE_USER_CSR, async (payload: SaveCSRPayload) => { + this.logger(`SAVING user CSR ${payload.csr}`) + this.emit(SocketActionTypes.SAVE_USER_CSR, payload) await new Promise(resolve => setTimeout(() => resolve(), 2000)) - this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.REGISTERING_USER_CERTIFICATE) + this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.SAVING_USER_CSR) }) socket.on(SocketActionTypes.REGISTER_OWNER_CERTIFICATE, async (payload: RegisterOwnerCertificatePayload) => { this.logger(`Registering owner certificate (${payload.communityId})`) @@ -114,6 +116,13 @@ export class SocketService extends EventEmitter implements OnModuleInit { socket.on(SocketActionTypes.SAVE_OWNER_CERTIFICATE, async (payload: SaveOwnerCertificatePayload) => { this.logger(`Saving owner certificate (${payload.peerId}), community: ${payload.id}`) this.emit(SocketActionTypes.SAVED_OWNER_CERTIFICATE, payload) + const communityMetadataPayload: CommunityMetadata = { + id: payload.id, + ownerCertificate: payload.certificate, + rootCa: payload.permsData.certificate, + } + console.log('meta from state-manager', communityMetadataPayload) + this.emit(SocketActionTypes.SEND_COMMUNITY_METADATA, communityMetadataPayload) }) socket.on(SocketActionTypes.CREATE_COMMUNITY, async (payload: InitCommunityPayload) => { this.logger(`Creating community ${payload.id}`) diff --git a/packages/backend/src/nest/storage/storage.service.spec.ts b/packages/backend/src/nest/storage/storage.service.spec.ts index 8e95303aad..9012cf0785 100644 --- a/packages/backend/src/nest/storage/storage.service.spec.ts +++ b/packages/backend/src/nest/storage/storage.service.spec.ts @@ -255,6 +255,10 @@ describe('StorageService', () => { const channelsDbAddress = storageService.channels?.address // @ts-expect-error 'certificates' is private const certificatesDbAddress = storageService.certificates.address + // @ts-expect-error 'certificatesRequests' is private + const certificatesRequestsDbAddress = storageService.certificatesRequests.address + // @ts-expect-error 'communityMetadata' is private + const communityMetadataDbAddress = storageService.communityMetadata.address expect(channelsDbAddress).not.toBeFalsy() expect(certificatesDbAddress).not.toBeFalsy() expect(subscribeToPubSubSpy).toBeCalledTimes(2) @@ -262,6 +266,8 @@ describe('StorageService', () => { expect(subscribeToPubSubSpy).toHaveBeenNthCalledWith(1, [ StorageService.dbAddress(channelsDbAddress), StorageService.dbAddress(certificatesDbAddress), + StorageService.dbAddress(certificatesRequestsDbAddress), + StorageService.dbAddress(communityMetadataDbAddress), ]) // Creating channel: expect(subscribeToPubSubSpy).toHaveBeenNthCalledWith(2, [StorageService.dbAddress(db.address)]) @@ -540,7 +546,7 @@ describe('StorageService', () => { }) describe('Users', () => { - it('gets all users from db', async () => { + it('gets all registered users from db', async () => { await storageService.init(peerId) const mockGetCertificates = jest.fn() // @ts-ignore - Property 'getAllEventLogEntries' is protected @@ -549,7 +555,7 @@ describe('StorageService', () => { 'MIICWzCCAgGgAwIBAgIGAYKIVrmoMAoGCCqGSM49BAMCMA8xDTALBgNVBAMTBG1haW4wHhcNMjIwODEwMTUxOTIxWhcNMzAwMTMxMjMwMDAwWjBJMUcwRQYDVQQDEz5wM29xZHI1M2RrZ2czbjVudWV6bHp5YXdoeHZpdDVlZnh6bHVudnpwN243bG12YTZmajNpNDNhZC5vbmlvbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCAjxbiV781WC8O5emEdavPaQfR0FD8CaqC+P3R3uRdL9xuzGeUu8f5NIplSJ6abBMnanGgcMs34u82buiFROHqjggENMIIBCTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIAgDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwLwYJKoZIhvcNAQkMBCIEICSr5xj+pjBSb+YOZ7TMPQJHYs4KASfnc9TugSpKJUG/MBUGCisGAQQBg4wbAgEEBxMFZGV2dnYwPQYJKwYBAgEPAwEBBDATLlFtVlRrVWFkMkdxM01rQ2E4Z2YxMlIxZ3NXRGZrMnlpVEVxYjZZR1hERzJpUTMwSQYDVR0RBEIwQII+cDNvcWRyNTNka2dnM241bnVlemx6eWF3aHh2aXQ1ZWZ4emx1bnZ6cDduN2xtdmE2ZmozaTQzYWQub25pb24wCgYIKoZIzj0EAwIDSAAwRQIhAIXhkkgs3H6GcZ1GYrSL2qJYDRQcpZlmcbq7YjpJHaORAiBMfkwP75v08R/ud6BPWvdS36corT+596+HzpqFt6bffw==', 'MIICYTCCAgegAwIBAgIGAYKIYnYuMAoGCCqGSM49BAMCMA8xDTALBgNVBAMTBG1haW4wHhcNMjIwODEwMTUzMjEwWhcNMzAwMTMxMjMwMDAwWjBJMUcwRQYDVQQDEz52bnl3dWl5bDdwN2lnMm11cmNzY2R5emtza281M2U0azNkcGRtMnlvb3B2dnUyNXA2d3dqcWJhZC5vbmlvbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABM0cOt7jMJ6YhRvL9nhbDCh42QJPKDet/Zc2PJ9rm6CzYz1IXc5uRUCUNZSnNykVMZknogAavp0FjV+cFXzV8gGjggETMIIBDzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIAgDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwLwYJKoZIhvcNAQkMBCIEIIsBwPwIhLSltj9dnkgkMq3sOe3RVha9Mhukop6XOoISMBsGCisGAQQBg4wbAgEEDRMLZHNrZmpia3NmaWcwPQYJKwYBAgEPAwEBBDATLlFtZDJVbjlBeW5va1pyY1pHc011YXFndXBUdGlkSEdRblVrTlZmRkZBZWY5N0MwSQYDVR0RBEIwQII+dm55d3VpeWw3cDdpZzJtdXJjc2NkeXprc2tvNTNlNGszZHBkbTJ5b29wdnZ1MjVwNnd3anFiYWQub25pb24wCgYIKoZIzj0EAwIDSAAwRQIgAiCmGfUuSG010CxLEzu9mAQOgDq//SHI9LkXbmCxaAUCIQC9xzmkRBxq5HmNomYJ9ZAJXaY3J6+VqBYthaVnv0bhMw==', ]) - const allUsers = storageService.getAllUsers() + const allUsers = storageService.getAllRegisteredUsers() expect(allUsers).toStrictEqual([ { onionAddress: 'p3oqdr53dkgg3n5nuezlzyawhxvit5efxzlunvzp7n7lmva6fj3i43ad.onion', @@ -565,6 +571,34 @@ describe('StorageService', () => { }, ]) }) + it('gets all users from db', async () => { + await storageService.init(peerId) + const mockGetCsrs = jest.fn() + // @ts-ignore - Property 'getAllEventLogEntries' is protected + storageService.getAllEventLogEntries = mockGetCsrs + mockGetCsrs.mockReturnValue([ + 'MIIDHjCCAsMCAQAwSTFHMEUGA1UEAxM+NnZ1MmJ4a2k3NzdpdDNjcGF5djZmcTZ2cGw0a2Uza3pqN2d4aWNmeWdtNTVkaGh0cGh5ZmR2eWQub25pb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATMpfp2hSfWFL26OZlZKZEWG9fyAM1ndlEzO0kLxT0pA/7/fs+a5X/s4TkzqCVVQSzhas/84q0WE99ScAcM1LQJoIICFjAuBgkqhkiG9w0BCQ4xITAfMB0GA1UdDgQWBBR6VRzktP1pzZxsGUaJivNUrtgSrzCCAUcGCSqGSIb3DQEJDDGCATgEggE0KZq9s6HEViRfplVgYkulg6XV411ZRe4U1UjfXTf1pRaygfcenGbT6RRagPtZzjuq5hHdYhqDjRzZhnbn8ZASYTgBM7qcseUq5UpS1pE08DI2jePKqatp3Pzm6a/MGSziESnREx784JlKfwKMjJl33UA8lQm9nhSeAIHyBx3c4Lf8IXdW2n3rnhbVfjpBMAxwh6lt+e5agtGXy+q/xAESUeLPfUgRYWctlLgt8Op+WTpLyBkZsVFoBvJrMt2XdM0RI32YzTRr56GXFa4VyQmY5xXwlQSPgidAP7jPkVygNcoeXvAz2ZCk3IR1Cn3mX8nMko53MlDNaMYldUQA0ug28/S7BlSlaq2CDD4Ol3swTq7C4KGTxKrI36ruYUZx7NEaQDF5V7VvqPCZ0fZoTIJuSYTQ67gwEQYKKwYBBAGDjBsCATEDEwFvMD0GCSsGAQIBDwMBATEwEy5RbVhSWTRyaEF4OE11cThkTUdrcjlxa25KZEU2VUhaRGRHYURSVFFFYndGTjViMEcGA1UdETFAEz42dnUyYnhraTc3N2l0M2NwYXl2NmZxNnZwbDRrZTNremo3Z3hpY2Z5Z201NWRoaHRwaHlmZHZ5ZC5vbmlvbjAKBggqhkjOPQQDAgNJADBGAiEAt+f1u/bchg5AZHv6NTGNoXeejTRWUhX3ioGwW6TGg84CIQCHqKNzDh2JjS/hUHx5PApAmfNnQTSf19X6LnNHQweU1g==', + 'MIIDHTCCAsMCAQAwSTFHMEUGA1UEAxM+eTd5Y3ptdWdsMnRla2FtaTdzYmR6NXBmYWVtdng3YmFod3RocmR2Y2J6dzV2ZXgyY3JzcjI2cWQub25pb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATMq0l4bCmjdb0grtzpwtDVLM9E1IQpL9vrB4+lD9OBZzlrx2365jV7shVu9utas8w8fxtKoBZSnT5+32ZMFTB4oIICFjAuBgkqhkiG9w0BCQ4xITAfMB0GA1UdDgQWBBSoDQpTZdEvi1/Rr/muVXT1clyKRDCCAUcGCSqGSIb3DQEJDDGCATgEggE0BQvyvkiiXEf/PLKnsR1Ba9AhYsVO8o56bnftUnoVzBlRZgUzLJvOSroPk/EmbVz+okhMrcYNgCWHvxrAqHVVq0JRP6bi98BtCUotx6OPFHp5K5QCL60hod1uAnhKocyJG9tsoM9aS+krn/k+g4RCBjiPZ25cC7QG/UNr6wyIQ8elBho4MKm8iOp7EShSsZOV1f6xrnXYCC/zyUc85GEuycLzVImgAQvPATbdMzY4zSGnNLHxkvSUNxaR9LnEWf+i1jeqcOiXOvmdyU5Be3ZqhGKvvBg/5vyLQiCIfeapjZemnLqFHQBitglDm2xnKL6HzMyfZoAHPV7YcWYR4spU9Ju8Q8aqSeAryx7sx55eSR4GO5UQTo5DrQn6xtkwOZ/ytsOknFthF8jcA9uTAMDKA2TylCUwEQYKKwYBBAGDjBsCATEDEwFvMD0GCSsGAQIBDwMBATEwEy5RbVQxOFV2blVCa3NlTWMzU3FuZlB4cEh3TjhuekxySmVOU0xadGM4ckFGWGh6MEcGA1UdETFAEz55N3ljem11Z2wydGVrYW1pN3NiZHo1cGZhZW12eDdiYWh3dGhyZHZjYnp3NXZleDJjcnNyMjZxZC5vbmlvbjAKBggqhkjOPQQDAgNIADBFAiEAoFrAglxmk7ciD6AHQOB1qEoLu0NARcxgwmIry8oeTHwCICyXp5NJQ9Z8vReIAQNng2H2+/XjHifZEWzhoN0VkcBx', + ]) + const allUsers = storageService.getAllUsers() + + expect(allUsers).toStrictEqual([ + { + onionAddress: '6vu2bxki777it3cpayv6fq6vpl4ke3kzj7gxicfygm55dhhtphyfdvyd.onion', + peerId: 'QmXRY4rhAx8Muq8dMGkr9qknJdE6UHZDdGaDRTQEbwFN5b', + dmPublicKey: + '299abdb3a1c456245fa65560624ba583a5d5e35d5945ee14d548df5d37f5a516b281f71e9c66d3e9145a80fb59ce3baae611dd621a838d1cd98676e7f1901261380133ba9cb1e52ae54a52d69134f032368de3caa9ab69dcfce6e9afcc192ce21129d1131efce0994a7f028c8c9977dd403c9509bd9e149e0081f2071ddce0b7fc217756da7deb9e16d57e3a41300c7087a96df9ee5a82d197cbeabfc4011251e2cf7d481161672d94b82df0ea7e593a4bc81919b1516806f26b32dd9774cd11237d98cd346be7a19715ae15c90998e715f095048f8227403fb8cf915ca035ca1e5ef033d990a4dc84750a7de65fc9cc928e773250cd68c625754400d2e836f3f4bb0654a56aad820c3e0e977b304eaec2e0a193c4aac8dfaaee614671ecd11a40317957b56fa8f099d1f6684c826e4984d0ebb8', + username: 'o', + }, + { + onionAddress: 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd.onion', + peerId: 'QmT18UvnUBkseMc3SqnfPxpHwN8nzLrJeNSLZtc8rAFXhz', + dmPublicKey: + '050bf2be48a25c47ff3cb2a7b11d416bd02162c54ef28e7a6e77ed527a15cc19516605332c9bce4aba0f93f1266d5cfea2484cadc60d802587bf1ac0a87555ab42513fa6e2f7c06d094a2dc7a38f147a792b94022fad21a1dd6e02784aa1cc891bdb6ca0cf5a4be92b9ff93e83844206388f676e5c0bb406fd436beb0c8843c7a5061a3830a9bc88ea7b112852b19395d5feb1ae75d8082ff3c9473ce4612ec9c2f35489a0010bcf0136dd333638cd21a734b1f192f494371691f4b9c459ffa2d637aa70e8973af99dc94e417b766a8462afbc183fe6fc8b4220887de6a98d97a69cba851d0062b609439b6c6728be87cccc9f6680073d5ed8716611e2ca54f49bbc43c6aa49e02bcb1eecc79e5e491e063b95104e8e43ad09fac6d930399ff2b6c3a49c5b6117c8dc03db9300c0ca0364f29425', + username: 'o', + }, + ]) + }) }) describe('Files deletion', () => { diff --git a/packages/backend/src/nest/storage/storage.service.ts b/packages/backend/src/nest/storage/storage.service.ts index 1b2300e9d3..0742523494 100644 --- a/packages/backend/src/nest/storage/storage.service.ts +++ b/packages/backend/src/nest/storage/storage.service.ts @@ -7,6 +7,8 @@ import { parseCertificate, verifySignature, verifyUserCert, + parseCertificationRequest, + getReqFieldValue, } from '@quiet/identity' import type { IPFS } from 'ipfs-core' import OrbitDB from 'orbit-db' @@ -15,18 +17,20 @@ import KeyValueStore from 'orbit-db-kvstore' import path from 'path' import { EventEmitter } from 'events' import PeerId from 'peer-id' -import { getCrypto } from 'pkijs' +import { CertificationRequest, getCrypto } from 'pkijs' import { stringToArrayBuffer } from 'pvutils' import validate from '../validation/validators' import { CID } from 'multiformats/cid' import { ChannelMessage, + CommunityMetadata, ConnectionProcessInfo, DeleteFilesFromChannelSocketPayload, FileMetadata, NoCryptoEngineError, PublicChannel, PushNotificationPayload, + SaveCSRPayload, SaveCertificatePayload, SocketActionTypes, User, @@ -43,7 +47,7 @@ import AccessControllers from 'orbit-db-access-controllers' import { MessagesAccessController } from './MessagesAccessController' import { createChannelAccessController } from './ChannelsAccessController' import Logger from '../common/logger' -import { DirectMessagesRepo, IMessageThread, PublicChannelsRepo } from '../common/types' +import { DirectMessagesRepo, PublicChannelsRepo } from '../common/types' import { removeFiles, removeDirs, createPaths, getUsersAddresses } from '../common/utils' import { StorageEvents } from './storage.types' @@ -55,10 +59,12 @@ interface DBOptions { export class StorageService extends EventEmitter { public channels: KeyValueStore private certificates: EventStore + private certificatesRequests: EventStore public publicChannelsRepos: Map = new Map() public directMessagesRepos: Map = new Map() private publicKeysMap: Map = new Map() private userNamesMap: Map = new Map() + private communityMetadata: KeyValueStore private ipfs: IPFS private orbitDb: OrbitDB private filesManager: IpfsFileManagerService @@ -150,6 +156,12 @@ export class StorageService extends EventEmitter { if (this.certificates?.address) { dbs.push(this.certificates.address) } + if (this.certificatesRequests?.address) { + dbs.push(this.certificatesRequests.address) + } + if (this.communityMetadata?.address) { + dbs.push(this.communityMetadata.address) + } const channels = this.publicChannelsRepos.values() @@ -172,7 +184,7 @@ export class StorageService extends EventEmitter { return } for (const a of addr) { - this.logger(`Pubsub - subscribe to ${addr}`) + this.logger(`Pubsub - subscribe to ${a}`) // @ts-ignore await this.orbitDb._pubsub.subscribe( a, @@ -203,16 +215,53 @@ export class StorageService extends EventEmitter { } public async initDatabases() { - this.logger('1/4') + this.logger('1/5') await this.createDbForChannels() - this.logger('2/4') + this.logger('2/5') await this.createDbForCertificates() - this.logger('3/4') + this.logger('3/5') + await this.createDbForCertificatesRequests() + this.logger('4/5') + await this.createDbForCommunityMetadata() + this.logger('5/5') await this.initAllChannels() - this.logger('4/4') this.logger('Initialized DBs') this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.INITIALIZED_DBS) } + private async createDbForCommunityMetadata() { + this.logger('createDbForCommunityMetadata init') + this.communityMetadata = await this.orbitDb.keyvalue('community-metadata', { + replicate: false, + accessController: { + write: ['*'], + }, + }) + + this.communityMetadata.events.on('write', async (_address, _entry) => { + this.logger('WRITE: communityMetadata') + }) + + 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]) + }) + // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' + await this.communityMetadata.load({ fetchEntryTimeout: 15000 }) + } + + public async updateCommunityMetadata(communityMetadata: CommunityMetadata) { + this.logger(`Updating community metadata`) + if (!communityMetadata.id) return + const meta = this.communityMetadata.get(communityMetadata.id) + console.log('meta from db', meta) + if (meta?.ownerCertificate && meta?.rootCa) return + await this.communityMetadata.put(communityMetadata.id, { + ...meta, + ...communityMetadata, + }) + } private async __stopOrbitDb() { if (this.orbitDb) { @@ -245,19 +294,25 @@ export class StorageService extends EventEmitter { public async stopOrbitDb() { try { - if (this.channels) { - await this.channels.close() - } + await this.channels?.close() } catch (e) { - this.logger.error('channels', e) + this.logger.error('Error closing channels db', e) } try { - if (this.certificates) { - await this.certificates.close() - } + await this.certificates?.close() } catch (e) { - this.logger.error('certificates', e) + this.logger.error('Error closing certificates db', e) + } + try { + await this.certificatesRequests?.close() + } catch (e) { + this.logger.error('Error closing certificates db', e) + } + try { + await this.communityMetadata?.close() + } catch (e) { + this.logger.error('Error closing community metadata db', e) } await this.__stopOrbitDb() await this.__stopIPFS() @@ -265,7 +320,9 @@ export class StorageService extends EventEmitter { public async updatePeersList() { const allUsers = this.getAllUsers() - const peers = await getUsersAddresses(allUsers) + const registeredUsers = this.getAllRegisteredUsers() + const peers = [...new Set(await getUsersAddresses(allUsers.concat(registeredUsers)))] + console.log('updatePeersList, peers count:', peers.length) const community = await this.localDbService.get(LocalDBKeys.COMMUNITY) this.emit(StorageEvents.UPDATE_PEERS_LIST, { communityId: community.id, peerList: peers }) } @@ -332,6 +389,51 @@ export class StorageService extends EventEmitter { this.logger('STORAGE: Finished createDbForCertificates') } + public async createDbForCertificatesRequests() { + this.logger('certificatesRequests db init') + this.certificatesRequests = await this.orbitDb.log('csrs', { + replicate: false, + accessController: { + write: ['*'], + }, + }) + this.certificatesRequests.events.on('replicate.progress', async (_address, _hash, entry, _progress, _total) => { + const csr = entry.payload.value + this.logger('REPLICATED CSR', csr) + let parsedCSR: CertificationRequest + try { + parsedCSR = parseCertificationRequest(csr) + } catch (e) { + this.logger.error(`csrs replicate.progress: could not parse certificate request`) + return + } + + const username = getReqFieldValue(parsedCSR, CertFieldsTypes.nickName) + if (!username) { + this.logger.error( + `csrs replicate.progress: could not parse certificate request for field type ${CertFieldsTypes.nickName}` + ) + return + } + + this.emit(StorageEvents.REPLICATED_CSR, { csr: csr }) + }) + this.certificatesRequests.events.on('replicated', async () => { + this.logger('REPLICATED: CSRs') + await this.updatePeersList() + }) + this.certificatesRequests.events.on('write', async (_address, entry) => { + this.logger('Saved CSR locally') + await this.updatePeersList() + }) + + // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' + await this.certificates.load({ fetchEntryTimeout: 15000 }) + const allcsrs = this.getAllEventLogEntries(this.certificatesRequests) + this.logger('ALL Certificates COUNT:', allcsrs.length) + this.logger('STORAGE: Finished creating certificatesRequests db') + } + public async loadAllChannels() { this.logger('Getting all channels') // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' @@ -755,7 +857,30 @@ export class StorageService extends EventEmitter { return true } - public getAllUsers(): User[] { + public async saveCSR(payload: SaveCSRPayload): Promise { + this.logger('About to save csr...') + if (!payload.csr) { + this.logger('CSR is either null or undefined, not saving to db') + return false + } + // TODO: Verify CSR + try { + parseCertificationRequest(payload.csr) + } catch (e) { + this.logger.error(`Cannot save csr ${payload.csr}. Reason: ${e.message}`) + return false + } + + const csrs = this.getAllEventLogEntries(this.certificatesRequests) + + if (csrs.includes(payload.csr)) return false + + this.logger('Saving csr...') + await this.certificatesRequests.add(payload.csr) + return true + } + + public getAllRegisteredUsers(): User[] { const certs = this.getAllEventLogEntries(this.certificates) const allUsers: User[] = [] for (const cert of certs) { @@ -770,6 +895,22 @@ export class StorageService extends EventEmitter { return allUsers } + public getAllUsers(): User[] { + const csrs = this.getAllEventLogEntries(this.certificatesRequests) + console.log('csrs', csrs.length) + const allUsers: User[] = [] + for (const csr of csrs) { + const parsedCert = parseCertificationRequest(csr) + const onionAddress = getReqFieldValue(parsedCert, CertFieldsTypes.commonName) + const peerId = getReqFieldValue(parsedCert, CertFieldsTypes.peerId) + const username = getReqFieldValue(parsedCert, CertFieldsTypes.nickName) + const dmPublicKey = getReqFieldValue(parsedCert, CertFieldsTypes.dmPublicKey) + if (!onionAddress || !peerId || !username || !dmPublicKey) continue + allUsers.push({ onionAddress, peerId, username, dmPublicKey }) + } + return allUsers + } + public usernameCert(username: string): string | null { /** * Check if given username is already in use diff --git a/packages/backend/src/nest/storage/storage.types.ts b/packages/backend/src/nest/storage/storage.types.ts index 617b944333..84b3286b5d 100644 --- a/packages/backend/src/nest/storage/storage.types.ts +++ b/packages/backend/src/nest/storage/storage.types.ts @@ -4,6 +4,7 @@ export enum StorageEvents { // Peers UPDATE_PEERS_LIST = 'updatePeersList', LOAD_CERTIFICATES = 'loadCertificates', + REPLICATED_CSR = 'replicatedCsr', // Public Channels LOAD_PUBLIC_CHANNELS = 'loadPublicChannels', LOAD_ALL_PRIVATE_CONVERSATIONS = 'loadAllPrivateConversations', @@ -22,6 +23,8 @@ export enum StorageEvents { LOAD_ALL_DIRECT_MESSAGES = 'loadAllDirectMessages', // Misc SEND_PUSH_NOTIFICATION = 'sendPushNotification', + // Community + REPLICATED_COMMUNITY_METADATA = 'replicatedCommunityMetadata', } export interface InitStorageParams { communityId: string diff --git a/packages/backend/src/nest/tor/tor-control.service.ts b/packages/backend/src/nest/tor/tor-control.service.ts index 1c2e6f509b..f833154e87 100644 --- a/packages/backend/src/nest/tor/tor-control.service.ts +++ b/packages/backend/src/nest/tor/tor-control.service.ts @@ -50,7 +50,7 @@ export class TorControl implements OnModuleInit { }) } - private async disconnect() { + private disconnect() { try { this.connection?.end() } catch (e) { @@ -62,11 +62,12 @@ export class TorControl implements OnModuleInit { // eslint-disable-next-line @typescript-eslint/ban-types private async _sendCommand(command: string, resolve: Function, reject: Function) { await this.connect() + const connectionTimeout = setTimeout(() => { reject('TOR: Send command timeout') }, 5000) this.connection?.on('data', async data => { - await this.disconnect() + this.disconnect() const dataArray = data.toString().split(/\r?\n/) if (dataArray[0].startsWith('250')) { resolve({ code: 250, messages: dataArray }) diff --git a/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts b/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts index 0ac6482471..1ffde5daa4 100644 --- a/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts +++ b/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts @@ -4,13 +4,7 @@ import { multiaddr } from '@multiformats/multiaddr' import getPort from 'get-port' import { type DirResult } from 'tmp' import { jest, describe, it, expect, afterEach, beforeAll, afterAll } from '@jest/globals' -import { - createLibp2pAddress, - torBinForPlatform, - torDirForPlatform, - createTmpDir, - tmpQuietDirPath, -} from '../common/utils' +import { torBinForPlatform, torDirForPlatform, createTmpDir, tmpQuietDirPath } from '../common/utils' import { type CreateListenerOptions } from '@libp2p/interface-transport' import { createServer } from 'it-ws/server' import { createCertificatesTestHelper } from '../common/client-server' @@ -22,6 +16,7 @@ import { Tor } from '../tor/tor.service' import crypto from 'crypto' import { TorControl } from '../tor/tor-control.service' import { TorControlAuthType } from '../tor/tor.types' +import { createLibp2pAddress } from '@quiet/common' jest.setTimeout(120000) describe('websocketOverTor', () => { @@ -283,73 +278,4 @@ describe('websocketOverTor', () => { }) ).rejects.toBeTruthy() }) - - it.skip('rejects connection if server cert is invalid', async () => { - const pems = await createCertificatesTestHelper(`${service1.onionAddress}`, `${service2.onionAddress}`) - const anotherPems = await createCertificatesTestHelper(`${service1.onionAddress}`, `${service2.onionAddress}`) - - const prepareListenerArg: CreateListenerOptions = { - handler: x => x, - upgrader: { - // @ts-expect-error - upgradeOutbound, - // @ts-expect-error - upgradeInbound, - }, - } - - const signal: AbortSignal = { - ...abortSignalOpts, - addEventListener, - removeEventListener, - } - - const peerId1 = 'Qme5NiSQ6V3cc3nyfYVtkkXDPGBSYEVUNCN5sM4DbyYc7s' - const peerId2 = 'QmeCWxba5Yk1ZAKogQJsaHXoAermE7PgFZqpqyKNg65cSN' - - const websocketsOverTorData1 = { - filter: all, - websocket: { - agent, - cert: anotherPems.servCert, - key: anotherPems.servKey, - ca: [pems.ca], - }, - localAddress: createLibp2pAddress(service1.onionAddress, peerId1), - targetPort: port1Target, - createServer, - } - - const websocketsOverTorData2 = { - filter: all, - websocket: { - agent, - cert: pems.servCert, - key: pems.servKey, - ca: [pems.ca], - }, - localAddress: createLibp2pAddress(service2.onionAddress, peerId2), - targetPort: port2Target, - createServer, - } - - const multiAddress = multiaddr(createLibp2pAddress(service1.onionAddress, peerId1)) - - const ws1 = webSockets(websocketsOverTorData1)() - const ws2 = webSockets(websocketsOverTorData2)() - - listener = await ws1.prepareListener(prepareListenerArg) - - await listener.listen(multiAddress) - - const onConnection = jest.fn() - listener.on('connection', onConnection) - - await expect( - ws2.dial(multiAddress, { - signal, - upgrader: prepareListenerArg.upgrader, - }) - ).rejects.toBeTruthy() - }) }) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 914f45c769..67ce8a7b5b 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -7,3 +7,4 @@ export * from './sortPeers' export * from './channelAddress' export * from './naming' export * from './fileData' +export * from './libp2p' diff --git a/packages/common/src/invitationCode.test.ts b/packages/common/src/invitationCode.test.ts index 78bd1420c1..28254563bc 100644 --- a/packages/common/src/invitationCode.test.ts +++ b/packages/common/src/invitationCode.test.ts @@ -1,24 +1,75 @@ -import { argvInvitationCode, invitationDeepUrl, invitationShareUrl } from './invitationCode' -import { Site } from './static' +import { + argvInvitationCode, + invitationDeepUrl, + invitationShareUrl, + pairsToInvitationShareUrl, + retrieveInvitationCode, +} from './invitationCode' +import { QUIET_JOIN_PAGE } from './static' describe('Invitation code helper', () => { + const peerId1 = 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSA' + const address1 = 'gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad' + const peerId2 = 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE' + const address2 = 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd' + it('retrieves invitation code from argv', () => { + const expectedCodes = [ + { peerId: peerId1, onionAddress: address1 }, + { peerId: peerId2, onionAddress: address2 }, + ] const result = argvInvitationCode([ 'something', 'quiet:/invalid', 'zbay://invalid', 'quiet://invalid', 'quiet://?param=invalid', - invitationDeepUrl('validCode'), + invitationDeepUrl(expectedCodes), ]) - expect(result).toBe('validCode') + expect(result).toEqual(expectedCodes) }) it('builds proper invitation deep url', () => { - expect(invitationDeepUrl('validCode')).toEqual('quiet://?code=validCode') + expect( + invitationDeepUrl([ + { peerId: 'peerID1', onionAddress: 'address1' }, + { peerId: 'peerID2', onionAddress: 'address2' }, + ]) + ).toEqual('quiet://?peerID1=address1&peerID2=address2') + }) + + it('creates invitation share url based on invitation pairs', () => { + const pairs = [ + { peerId: 'peerID1', onionAddress: 'address1' }, + { peerId: 'peerID2', onionAddress: 'address2' }, + ] + const expected = `${QUIET_JOIN_PAGE}#peerID1=address1&peerID2=address2` + expect(pairsToInvitationShareUrl(pairs)).toEqual(expected) }) it('builds proper invitation share url', () => { - expect(invitationShareUrl('validCode')).toEqual(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#validCode`) + const peerList = [ + '/dns4/gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad.onion/tcp/443/wss/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE', + 'invalidAddress', + '/dns4/somethingElse.onion/tcp/443/wss/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSA', + ] + expect(invitationShareUrl(peerList)).toEqual( + `${QUIET_JOIN_PAGE}#QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE=gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad&QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSA=somethingElse` + ) + }) + + it('retrieves invitation codes from deep url', () => { + const codes = retrieveInvitationCode(`quiet://?${peerId1}=${address1}&${peerId2}=${address2}`) + expect(codes).toEqual([ + { peerId: peerId1, onionAddress: address1 }, + { peerId: peerId2, onionAddress: address2 }, + ]) + }) + + it('retrieves invitation codes from deep url with partly invalid codes', () => { + const peerId2 = 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLs' + const address2 = 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd' + const codes = retrieveInvitationCode(`quiet://?${peerId1}=${address1}&${peerId2}=${address2}}`) + expect(codes).toEqual([{ peerId: peerId1, onionAddress: address1 }]) }) }) diff --git a/packages/common/src/invitationCode.ts b/packages/common/src/invitationCode.ts index 6d17e38251..e5e6af26f7 100644 --- a/packages/common/src/invitationCode.ts +++ b/packages/common/src/invitationCode.ts @@ -1,46 +1,132 @@ -import { InvitationParams, Site } from './static' - -export const retrieveInvitationCode = (url: string): string => { +import { InvitationPair } from '@quiet/types' +import { ONION_ADDRESS_REGEX, Site } from './static' +import { createLibp2pAddress } from './libp2p' +export const retrieveInvitationCode = (url: string): InvitationPair[] => { /** - * Extract invitation code from deep url. - * Valid format: quiet://?code= + * Extract invitation codes from deep url. + * Valid format: quiet://?=&= */ let data: URL try { data = new URL(url) } catch (e) { - return '' + return [] } - if (!data || data.protocol !== 'quiet:') return '' - const code = data.searchParams.get(InvitationParams.CODE) - if (code) { - console.log('Retrieved code:', code) - return code + if (!data || data.protocol !== 'quiet:') return [] + const params = data.searchParams + const codes: InvitationPair[] = [] + for (const [peerId, onionAddress] of params.entries()) { + if (!invitationCodeValid(peerId, onionAddress)) continue + codes.push({ + peerId, + onionAddress, + }) } - return '' + console.log('Retrieved codes:', codes) + return codes } -export const argvInvitationCode = (argv: string[]): string => { +export const invitationShareUrl = (peers: string[] = []): string => { /** - * Extract invitation code from deep url if url is present in argv + * @arg {string[]} peers - List of peer's p2p addresses + * @returns {string} - Complete shareable invitation link, e.g. https://tryquiet.org/join/#=&= */ - let invitationCode = '' - for (const arg of argv) { - invitationCode = retrieveInvitationCode(arg) - if (invitationCode) { - break + console.log('Invitation share url, peers:', peers) + const pairs = [] + for (const peerAddress of peers) { + let peerId: string + let onionAddress: string + try { + peerId = peerAddress.split('/p2p/')[1] + } catch (e) { + console.info(`Could not add peer address '${peerAddress}' to invitation url. Reason: ${e.message}`) + continue + } + try { + onionAddress = peerAddress.split('/tcp/')[0].split('/dns4/')[1] + } catch (e) { + console.info(`Could not add peer address '${peerAddress}' to invitation url. Reason: ${e.message}`) + continue } + + if (!peerId || !onionAddress) { + console.error(`No peerId or address in ${peerAddress}`) + continue + } + const rawAddress = onionAddress.endsWith('.onion') ? onionAddress.split('.')[0] : onionAddress + pairs.push(`${peerId}=${rawAddress}`) + } + + console.log('invitationShareUrl', pairs.join('&')) + const url = new URL(`${Site.MAIN_PAGE}${Site.JOIN_PAGE}#${pairs.join('&')}`) + return url.href +} + +export const pairsToP2pAddresses = (pairs: InvitationPair[]): string[] => { + const addresses: string[] = [] + for (const pair of pairs) { + addresses.push(createLibp2pAddress(pair.onionAddress, pair.peerId)) + } + return addresses +} + +export const pairsToInvitationShareUrl = (pairs: InvitationPair[]) => { + const url = new URL(`${Site.MAIN_PAGE}${Site.JOIN_PAGE}`) + for (const pair of pairs) { + url.searchParams.append(pair.peerId, pair.onionAddress) } - return invitationCode + return url.href.replace('?', '#') } -export const invitationDeepUrl = (code = ''): string => { +export const invitationDeepUrl = (pairs: InvitationPair[] = []): string => { const url = new URL('quiet://') - url.searchParams.append(InvitationParams.CODE, code) + for (const pair of pairs) { + url.searchParams.append(pair.peerId, pair.onionAddress) + } return url.href } -export const invitationShareUrl = (code = ''): string => { - const url = new URL(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#${code}`) - return url.href +export const argvInvitationCode = (argv: string[]): InvitationPair[] => { + /** + * Extract invitation codes from deep url if url is present in argv + */ + let invitationCodes: InvitationPair[] = [] + for (const arg of argv) { + invitationCodes = retrieveInvitationCode(arg) + if (invitationCodes.length > 0) { + break + } + } + return invitationCodes +} + +export const invitationCodeValid = (peerId: string, onionAddress: string): boolean => { + if (!peerId.match(/^[a-zA-Z0-9]{46}$/g)) { + // TODO: test it more properly e.g with PeerId.createFromB58String(peerId.trim()) + console.log(`PeerId ${peerId} is not valid`) + return false + } + if (!onionAddress.trim().match(ONION_ADDRESS_REGEX)) { + console.log(`Onion address ${onionAddress} is not valid`) + return false + } + return true +} + +export const getInvitationPairs = (code: string) => { + /** + * @param code =&= + */ + const pairs = code.split('&') + const codes: InvitationPair[] = [] + for (const pair of pairs) { + const [peerId, address] = pair.split('=') + if (!peerId || !address) continue + if (!invitationCodeValid(peerId, address)) continue + codes.push({ + peerId: peerId, + onionAddress: address, + }) + } + return codes } diff --git a/packages/backend/src/nest/libp2p/libp2p.utils.ts b/packages/common/src/libp2p.ts similarity index 74% rename from packages/backend/src/nest/libp2p/libp2p.utils.ts rename to packages/common/src/libp2p.ts index 447ffa9267..e86ee508e2 100644 --- a/packages/backend/src/nest/libp2p/libp2p.utils.ts +++ b/packages/common/src/libp2p.ts @@ -2,10 +2,10 @@ const ONION = '.onion' export const createLibp2pAddress = (address: string, peerId: string) => { if (!address.endsWith(ONION)) address += ONION - return `/dns4/${address}/tcp/80/wss/p2p/${peerId}` + return `/dns4/${address}/tcp/80/ws/p2p/${peerId}` } export const createLibp2pListenAddress = (address: string) => { if (!address.endsWith(ONION)) address += ONION - return `/dns4/${address}/tcp/80/wss` + return `/dns4/${address}/tcp/80/ws` } diff --git a/packages/common/src/static.ts b/packages/common/src/static.ts index 057018d0a7..05b1f35f0a 100644 --- a/packages/common/src/static.ts +++ b/packages/common/src/static.ts @@ -1,11 +1,9 @@ export const ONION_ADDRESS_REGEX = /^[a-z0-9]{56}$/g -export enum InvitationParams { - CODE = 'code', -} - export enum Site { DOMAIN = 'tryquiet.org', MAIN_PAGE = 'https://tryquiet.org/', JOIN_PAGE = 'join', } + +export const QUIET_JOIN_PAGE = `${Site.MAIN_PAGE}${Site.JOIN_PAGE}` diff --git a/packages/common/tsconfig.build.json b/packages/common/tsconfig.build.json index 4c2fe1bc29..b889234c8b 100644 --- a/packages/common/tsconfig.build.json +++ b/packages/common/tsconfig.build.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.build.json", "compilerOptions": { + "target": "ES2020", "rootDir": "./src", "outDir": "./lib", "typeRoots": [ diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index afd02831cc..6adb0ef8de 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "target": "es2017", + "target": "ES2020", "outDir": "./lib", "typeRoots": [ "../@types", diff --git a/packages/desktop/CHANGELOG.md b/packages/desktop/CHANGELOG.md index 95e5d1de64..d11551cead 100644 --- a/packages/desktop/CHANGELOG.md +++ b/packages/desktop/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.0.0-alpha.7](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.6...quiet@2.0.0-alpha.7) (2023-09-07) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.6](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.5...quiet@2.0.0-alpha.6) (2023-09-07) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.5](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.4...quiet@2.0.0-alpha.5) (2023-09-06) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.4](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.3...quiet@2.0.0-alpha.4) (2023-09-06) + +**Note:** Version bump only for package quiet + + + + + # [2.0.0-alpha.3](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.2...quiet@2.0.0-alpha.3) (2023-09-05) **Note:** Version bump only for package quiet diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 5dbf233bcf..0cad5cc8b4 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -22,7 +22,7 @@ npm run start ## Versioning packages -Before trying to release a new version, make sure you have GH_TOKEN env set. +Before trying to release a new version, make sure you have `GH_TOKEN` env set. The project uses independent versioning which means each package has its own version number. Only those packages in which something has changed since the last release will be bumped. @@ -60,7 +60,7 @@ npm run lerna add luxon packages/state-manager ---- -Lerna takes care of all the packages. You can execute scripts is every pakcage by simpy running: +Lerna takes care of all the packages. You can execute scripts is every package by simply running: ``` npm run lerna run