diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f8f4279fd..edc0685f61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+[unreleased]
+
+* Backward incompatible change: use pre shared key as connection protector in libp2p. Add libp2p psk to invitation link
+
[2.0.3-alpha.1]
* Temporarily hiding leave community button from Possible impersonation attack
diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md
index 0665f2f495..850cd66787 100644
--- a/packages/backend/CHANGELOG.md
+++ b/packages/backend/CHANGELOG.md
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [2.0.3-alpha.5](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.3-alpha.4...@quiet/backend@2.0.3-alpha.5) (2023-11-14)
+
+**Note:** Version bump only for package @quiet/backend
+
+
+
+
+
+## [2.0.3-alpha.4](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.3-alpha.3...@quiet/backend@2.0.3-alpha.4) (2023-11-14)
+
+**Note:** Version bump only for package @quiet/backend
+
+
+
+
+
## [2.0.3-alpha.3](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.3-alpha.2...@quiet/backend@2.0.3-alpha.3) (2023-11-13)
**Note:** Version bump only for package @quiet/backend
diff --git a/packages/backend/package-lock.json b/packages/backend/package-lock.json
index 15500283f1..4323d96834 100644
--- a/packages/backend/package-lock.json
+++ b/packages/backend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@quiet/backend",
- "version": "2.0.3-alpha.3",
+ "version": "2.0.3-alpha.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@quiet/backend",
- "version": "2.0.3-alpha.3",
+ "version": "2.0.3-alpha.5",
"license": "MIT",
"dependencies": {
"@chainsafe/libp2p-gossipsub": "6.1.0",
@@ -56,7 +56,7 @@
"socks-proxy-agent": "^5.0.0",
"string-replace-loader": "3.1.0",
"ts-jest-resolver": "^2.0.0",
- "validator": "^13.6.0"
+ "validator": "^13.11.0"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
@@ -72,7 +72,7 @@
"@types/orbit-db": "git+https://github.com/orbitdb/orbit-db-types.git",
"@types/supertest": "^2.0.11",
"@types/tmp": "^0.2.3",
- "@types/validator": "^13.1.4",
+ "@types/validator": "^13.11.5",
"@types/ws": "8.5.3",
"babel-jest": "^29.3.1",
"cross-env": "^5.2.0",
@@ -6580,8 +6580,9 @@
"license": "MIT"
},
"node_modules/@types/validator": {
- "version": "13.1.4",
- "license": "MIT"
+ "version": "13.11.6",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.6.tgz",
+ "integrity": "sha512-HUgHujPhKuNzgNXBRZKYexwoG+gHKU+tnfPqjWXFghZAnn73JElicMkuSKJyLGr9JgyA8IgK7fj88IyA9rwYeQ=="
},
"node_modules/@types/ws": {
"version": "8.5.3",
@@ -21995,8 +21996,9 @@
}
},
"node_modules/validator": {
- "version": "13.7.0",
- "license": "MIT",
+ "version": "13.11.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
+ "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==",
"engines": {
"node": ">= 0.10"
}
@@ -26934,7 +26936,9 @@
"dev": true
},
"@types/validator": {
- "version": "13.1.4"
+ "version": "13.11.6",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.6.tgz",
+ "integrity": "sha512-HUgHujPhKuNzgNXBRZKYexwoG+gHKU+tnfPqjWXFghZAnn73JElicMkuSKJyLGr9JgyA8IgK7fj88IyA9rwYeQ=="
},
"@types/ws": {
"version": "8.5.3",
@@ -36673,7 +36677,9 @@
}
},
"validator": {
- "version": "13.7.0"
+ "version": "13.11.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
+ "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ=="
},
"varint": {
"version": "6.0.0"
diff --git a/packages/backend/package.json b/packages/backend/package.json
index d167a3615d..0f5b46f61e 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@quiet/backend",
- "version": "2.0.3-alpha.3",
+ "version": "2.0.3-alpha.5",
"description": "tlg-manager",
"types": "lib/index.d.ts",
"type": "module",
@@ -54,7 +54,7 @@
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@quiet/eslint-config": "^2.0.2-alpha.0",
- "@quiet/state-manager": "^2.0.2-alpha.1",
+ "@quiet/state-manager": "^2.0.2-alpha.3",
"@types/crypto-js": "^4.0.2",
"@types/express": "^4.17.9",
"@types/jest": "28.1.8",
@@ -65,7 +65,7 @@
"@types/orbit-db": "git+https://github.com/orbitdb/orbit-db-types.git",
"@types/supertest": "^2.0.11",
"@types/tmp": "^0.2.3",
- "@types/validator": "^13.1.4",
+ "@types/validator": "^13.11.5",
"@types/ws": "8.5.3",
"babel-jest": "^29.3.1",
"cross-env": "^5.2.0",
@@ -89,10 +89,10 @@
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@peculiar/webcrypto": "1.4.3",
- "@quiet/common": "^2.0.2-alpha.0",
- "@quiet/identity": "^2.0.2-alpha.0",
+ "@quiet/common": "^2.0.2-alpha.1",
+ "@quiet/identity": "^2.0.2-alpha.2",
"@quiet/logger": "^2.0.2-alpha.0",
- "@quiet/types": "^2.0.2-alpha.0",
+ "@quiet/types": "^2.0.2-alpha.1",
"abortable-iterator": "^3.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.1",
@@ -134,7 +134,7 @@
"socks-proxy-agent": "^5.0.0",
"string-replace-loader": "3.1.0",
"ts-jest-resolver": "^2.0.0",
- "validator": "^13.6.0"
+ "validator": "^13.11.0"
},
"overrides": {
"level": "$level",
diff --git a/packages/backend/src/nest/common/utils.ts b/packages/backend/src/nest/common/utils.ts
index 23d0e875ed..eeff2ad29c 100644
--- a/packages/backend/src/nest/common/utils.ts
+++ b/packages/backend/src/nest/common/utils.ts
@@ -10,9 +10,10 @@ import crypto from 'crypto'
import { type PermsData } from '@quiet/types'
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'
+import { Libp2pService } from '../libp2p/libp2p.service'
+
const log = logger('test')
export interface Ports {
@@ -189,13 +190,12 @@ export const testBootstrapMultiaddrs = [
]
export const libp2pInstanceParams = async (): Promise => {
- const pems = await createCertificatesTestHelper('address1.onion', 'address2.onion')
const port = await getPort()
const peerId = await createPeerId()
const address = '0.0.0.0'
const peerIdRemote = await createPeerId()
const remoteAddress = createLibp2pAddress(address, peerIdRemote.toString())
-
+ const libp2pKey = Libp2pService.generateLibp2pPSK().fullKey
return {
peerId,
listenAddresses: [createLibp2pListenAddress('localhost')],
@@ -203,6 +203,7 @@ export const libp2pInstanceParams = async (): Promise => {
localAddress: createLibp2pAddress('localhost', peerId.toString()),
targetPort: port,
peers: [remoteAddress],
+ psk: libp2pKey,
}
}
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 8c759f8571..d2b9f28bd7 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
@@ -87,8 +87,6 @@ beforeEach(async () => {
localDbService = await module.resolve(LocalDbService)
registrationService = await module.resolve(RegistrationService)
tor = await module.resolve(Tor)
-
- console.log('tor ', tor)
await tor.init()
const torPassword = crypto.randomBytes(16).toString('hex')
@@ -106,6 +104,9 @@ beforeEach(async () => {
connectionsManagerService.libp2pService = libp2pService
quietDir = await module.resolve(QUIET_DIR)
+
+ const pskBase64 = Libp2pService.generateLibp2pPSK().psk
+ await localDbService.put(LocalDBKeys.PSK, pskBase64)
})
afterEach(async () => {
@@ -117,11 +118,6 @@ afterEach(async () => {
})
describe('Connections manager', () => {
- it('runs tor by default', async () => {
- await connectionsManagerService.init()
- console.log(connectionsManagerService.isTorInit)
- })
-
it('saves peer stats when peer has been disconnected', async () => {
class RemotePeerEventDetail {
peerId: string
@@ -135,7 +131,6 @@ describe('Connections manager', () => {
}
}
const emitSpy = jest.spyOn(libp2pService, 'emit')
- // const emitSpy = jest.spyOn(libp2pService, 'emit')
const launchCommunityPayload: InitCommunityPayload = {
id: community.id,
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 dcef3bdc41..41f61c694d 100644
--- a/packages/backend/src/nest/connections-manager/connections-manager.service.ts
+++ b/packages/backend/src/nest/connections-manager/connections-manager.service.ts
@@ -9,6 +9,7 @@ import { EventEmitter } from 'events'
import getPort from 'get-port'
import PeerId from 'peer-id'
import { removeFilesFromDir } from '../common/utils'
+
import {
AskForMessagesPayload,
ChannelMessagesIdsResponse,
@@ -59,6 +60,7 @@ import { StorageEvents } from '../storage/storage.types'
import { LazyModuleLoader } from '@nestjs/core'
import Logger from '../common/logger'
import { emitError } from '../socket/socket.errors'
+import { isPSKcodeValid } from '@quiet/common'
@Injectable()
export class ConnectionsManagerService extends EventEmitter implements OnModuleInit {
@@ -155,7 +157,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
this.logger('launchCommunityFromStorage')
const community: InitCommunityPayload = await this.localDbService.get(LocalDBKeys.COMMUNITY)
- console.log('launchCommunityFromStorage - community', community)
+ this.logger('launchCommunityFromStorage - community:', community?.id)
if (community) {
const sortedPeers = await this.localDbService.getSortedPeers(community.peers)
if (sortedPeers.length > 0) {
@@ -164,8 +166,6 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
await this.localDbService.put(LocalDBKeys.COMMUNITY, community)
if ([ServiceState.LAUNCHING, ServiceState.LAUNCHED].includes(this.communityState)) return
this.communityState = ServiceState.LAUNCHING
- }
- if (community) {
await this.launchCommunity(community)
}
}
@@ -276,18 +276,43 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
},
network,
}
+ const psk = community.psk
+ if (psk) {
+ this.logger('Creating network: received Libp2p PSK')
+ if (!isPSKcodeValid(psk)) {
+ this.logger.error('Creating network: received Libp2p PSK is not valid')
+ emitError(this.serverIoProvider.io, {
+ type: SocketActionTypes.NETWORK,
+ message: ErrorMessages.NETWORK_SETUP_FAILED,
+ community: community.id,
+ })
+ return
+ }
+ await this.localDbService.put(LocalDBKeys.PSK, psk)
+ }
+
this.serverIoProvider.io.emit(SocketActionTypes.NETWORK, payload)
}
+ private async generatePSK() {
+ const pskBase64 = Libp2pService.generateLibp2pPSK().psk
+ await this.localDbService.put(LocalDBKeys.PSK, pskBase64)
+ this.logger('Generated Libp2p PSK')
+ this.serverIoProvider.io.emit(SocketActionTypes.LIBP2P_PSK_SAVED, { psk: pskBase64 })
+ }
+
public async createCommunity(payload: InitCommunityPayload) {
- console.log('ConnectionsManager.createCommunity peers:', payload.peers)
+ this.logger('Creating community: peers:', payload.peers)
+
+ await this.generatePSK()
+
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.logger('Launching community: peers:', payload.peers)
this.communityState = ServiceState.LAUNCHING
const communityData: InitCommunityPayload = await this.localDbService.get(LocalDBKeys.COMMUNITY)
if (!communityData) {
@@ -331,7 +356,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
targetPort: this.ports.libp2pHiddenService,
privKey: payload.hiddenService.privateKey,
})
- this.logger(`Launching community ${payload.id}, peer: ${payload.peerId.id}`)
+ this.logger(`Launching community ${payload.id}: peer: ${payload.peerId.id}`)
const { Libp2pModule } = await import('../libp2p/libp2p.module')
const moduleRef = await this.lazyModuleLoader.load(() => Libp2pModule)
@@ -343,11 +368,17 @@ 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}`)
+ this.logger(`Launching community ${payload.id}: payload peers: ${peers}`)
if (!peers || peers.length === 0) {
peers = [this.libp2pService.createLibp2pAddress(onionAddress, _peerId.toString())]
}
+ const pskValue: string = await this.localDbService.get(LocalDBKeys.PSK)
+ if (!pskValue) {
+ throw new Error('No psk in local db')
+ }
+ this.logger(`Launching community ${payload.id}: retrieved Libp2p PSK`)
+ const libp2pPSK = Libp2pService.generateLibp2pPSK(pskValue).fullKey
const params: Libp2pNodeParams = {
peerId: _peerId,
listenAddresses: [this.libp2pService.createLibp2pListenAddress(onionAddress)],
@@ -355,16 +386,15 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
localAddress: this.libp2pService.createLibp2pAddress(onionAddress, _peerId.toString()),
targetPort: this.ports.libp2pHiddenService,
peers,
+ psk: libp2pPSK,
}
await this.libp2pService.createInstance(params)
- // KACPER
// Libp2p event listeners
this.libp2pService.on(Libp2pEvents.PEER_CONNECTED, (payload: { peers: string[] }) => {
this.serverIoProvider.io.emit(SocketActionTypes.PEER_CONNECTED, payload)
})
this.libp2pService.on(Libp2pEvents.PEER_DISCONNECTED, async (payload: NetworkDataPayload) => {
- console.log(' this.libp2pService.on(Libp2pEvents.PEER_DISCONNECTED')
const peerPrevStats = await this.localDbService.find(LocalDBKeys.PEERS, payload.peer)
const prev = peerPrevStats?.connectionTime || 0
@@ -381,7 +411,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
this.serverIoProvider.io.emit(SocketActionTypes.PEER_DISCONNECTED, payload)
})
await this.storageService.init(_peerId)
- console.log('storage initialized')
+ this.logger('storage initialized')
}
private attachTorEventsListeners() {
this.logger('attachTorEventsListeners')
@@ -428,6 +458,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
}
})
this.socketService.on(SocketActionTypes.CREATE_NETWORK, async (args: Community) => {
+ this.logger(`socketService - ${SocketActionTypes.CREATE_NETWORK}`)
await this.createNetwork(args)
})
this.socketService.on(SocketActionTypes.CREATE_COMMUNITY, async (args: InitCommunityPayload) => {
@@ -451,7 +482,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
await this.storageService?.updateCommunityMetadata(payload)
})
this.socketService.on(SocketActionTypes.SAVE_USER_CSR, async (payload: SaveCSRPayload) => {
- console.log(`On ${SocketActionTypes.SAVE_USER_CSR}`)
+ this.logger(`socketService - ${SocketActionTypes.SAVE_USER_CSR}`)
await this.storageService?.saveCSR(payload)
this.serverIoProvider.io.emit(SocketActionTypes.SAVED_USER_CSR, payload)
})
@@ -500,7 +531,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
this.socketService.on(
SocketActionTypes.DELETE_FILES_FROM_CHANNEL,
async (payload: DeleteFilesFromChannelSocketPayload) => {
- this.logger('DELETE_FILES_FROM_CHANNEL : payload', payload)
+ this.logger(`socketService - ${SocketActionTypes.DELETE_FILES_FROM_CHANNEL}`, payload)
await this.storageService?.deleteFilesFromChannel(payload)
// await this.deleteFilesFromTemporaryDir() //crashes on mobile, will be fixes in next versions
}
@@ -533,7 +564,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
this.serverIoProvider.io.emit(SocketActionTypes.CHANNEL_SUBSCRIBED, payload)
})
this.storageService.on(StorageEvents.CREATED_CHANNEL, (payload: CreatedChannelResponse) => {
- console.log('created channel in services')
+ this.logger(`Storage - ${StorageEvents.CREATED_CHANNEL}: ${payload.channel.name}`)
this.serverIoProvider.io.emit(SocketActionTypes.CREATED_CHANNEL, payload)
})
this.storageService.on(StorageEvents.REMOVE_DOWNLOAD_STATUS, (payload: RemoveDownloadStatus) => {
@@ -564,19 +595,19 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
this.serverIoProvider.io.emit(SocketActionTypes.CHECK_FOR_MISSING_FILES, payload)
})
this.storageService.on(StorageEvents.CHANNEL_DELETION_RESPONSE, (payload: { channelId: string }) => {
- console.log('emitting deleted channel event back to state manager')
+ this.logger(`Storage - ${StorageEvents.CHANNEL_DELETION_RESPONSE}`)
this.serverIoProvider.io.emit(SocketActionTypes.CHANNEL_DELETION_RESPONSE, payload)
})
this.storageService.on(
StorageEvents.REPLICATED_CSR,
async (payload: { csrs: string[]; certificates: string[]; id: string }) => {
- console.log(`On ${StorageEvents.REPLICATED_CSR}`)
+ console.log(`Storage - ${StorageEvents.REPLICATED_CSR}`)
this.serverIoProvider.io.emit(SocketActionTypes.RESPONSE_GET_CSRS, { csrs: payload.csrs })
this.registrationService.emit(RegistrationEvents.REGISTER_USER_CERTIFICATE, payload)
}
)
this.storageService.on(StorageEvents.REPLICATED_COMMUNITY_METADATA, (payload: CommunityMetadata) => {
- console.log(`On ${StorageEvents.REPLICATED_COMMUNITY_METADATA}: ${payload}`)
+ this.logger(`Storage - ${StorageEvents.REPLICATED_COMMUNITY_METADATA}: ${payload}`)
const communityMetadataPayload: CommunityMetadataPayload = {
rootCa: payload.rootCa,
ownerCertificate: payload.ownerCertificate,
diff --git a/packages/backend/src/nest/libp2p/libp2p.service.spec.ts b/packages/backend/src/nest/libp2p/libp2p.service.spec.ts
index 6775f9a2e2..5fc4e370aa 100644
--- a/packages/backend/src/nest/libp2p/libp2p.service.spec.ts
+++ b/packages/backend/src/nest/libp2p/libp2p.service.spec.ts
@@ -2,8 +2,10 @@ import { Test, TestingModule } from '@nestjs/testing'
import { TestModule } from '../common/test.module'
import { libp2pInstanceParams } from '../common/utils'
import { Libp2pModule } from './libp2p.module'
-import { Libp2pService } from './libp2p.service'
+import { LIBP2P_PSK_METADATA, Libp2pService } from './libp2p.service'
import { Libp2pNodeParams } from './libp2p.types'
+import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
+import validator from 'validator'
describe('Libp2pService', () => {
let module: TestingModule
@@ -45,4 +47,15 @@ describe('Libp2pService', () => {
const libp2pListenAddress = libp2pService.createLibp2pListenAddress('onionAddress')
expect(libp2pListenAddress).toStrictEqual(`/dns4/onionAddress.onion/tcp/80/ws`)
})
+
+ it('Generated libp2p psk matches psk composed from existing key', () => {
+ const generatedKey = Libp2pService.generateLibp2pPSK()
+ const retrievedKey = Libp2pService.generateLibp2pPSK(generatedKey.psk)
+ expect(generatedKey).toEqual(retrievedKey)
+ expect(validator.isBase64(generatedKey.psk)).toBeTruthy()
+
+ const generatedPskBuffer = Buffer.from(generatedKey.psk, 'base64')
+ const expectedFullKeyString = LIBP2P_PSK_METADATA + uint8ArrayToString(generatedPskBuffer, 'base16')
+ expect(uint8ArrayToString(generatedKey.fullKey)).toEqual(expectedFullKeyString)
+ })
})
diff --git a/packages/backend/src/nest/libp2p/libp2p.service.ts b/packages/backend/src/nest/libp2p/libp2p.service.ts
index 311b8f2b47..68a4fd6c7c 100644
--- a/packages/backend/src/nest/libp2p/libp2p.service.ts
+++ b/packages/backend/src/nest/libp2p/libp2p.service.ts
@@ -1,5 +1,4 @@
import { Inject, Injectable } from '@nestjs/common'
-
import { Agent } from 'https'
import { createLibp2p, Libp2p } from 'libp2p'
import { noise } from '@chainsafe/libp2p-noise'
@@ -19,6 +18,13 @@ import Logger from '../common/logger'
import { webSockets } from '../websocketOverTor'
import { all } from '../websocketOverTor/filters'
import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common'
+import { preSharedKey } from 'libp2p/pnet'
+import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
+import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
+import crypto from 'crypto'
+
+const KEY_LENGTH = 32
+export const LIBP2P_PSK_METADATA = '/key/swarm/psk/1.0.0/\n/base16/\n'
@Injectable()
export class Libp2pService extends EventEmitter {
@@ -44,6 +50,24 @@ export class Libp2pService extends EventEmitter {
return createLibp2pListenAddress(address)
}
+ public static generateLibp2pPSK(key?: string) {
+ /**
+ * Based on 'libp2p/pnet' generateKey
+ *
+ * @param key: base64 encoded psk
+ */
+ let psk
+ if (key) {
+ psk = Buffer.from(key, 'base64')
+ } else {
+ psk = crypto.randomBytes(KEY_LENGTH)
+ }
+
+ const base16StringKey = uint8ArrayToString(psk, 'base16')
+ const fullKey = uint8ArrayFromString(LIBP2P_PSK_METADATA + base16StringKey)
+ return { psk: psk.toString('base64'), fullKey }
+ }
+
public async createInstance(params: Libp2pNodeParams): Promise {
if (this.libp2pInstance) {
return this.libp2pInstance
@@ -64,6 +88,9 @@ export class Libp2pService extends EventEmitter {
addresses: {
listen: params.listenAddresses,
},
+ connectionProtector: preSharedKey({
+ psk: params.psk,
+ }),
streamMuxers: [mplex()],
connectionEncryption: [noise()],
relay: {
diff --git a/packages/backend/src/nest/libp2p/libp2p.types.ts b/packages/backend/src/nest/libp2p/libp2p.types.ts
index d32cc0230f..8b80845033 100644
--- a/packages/backend/src/nest/libp2p/libp2p.types.ts
+++ b/packages/backend/src/nest/libp2p/libp2p.types.ts
@@ -14,6 +14,7 @@ export interface Libp2pNodeParams {
localAddress: string
targetPort: number
peers: string[]
+ psk: Uint8Array
}
export interface InitLibp2pParams {
diff --git a/packages/backend/src/nest/local-db/local-db.types.ts b/packages/backend/src/nest/local-db/local-db.types.ts
index a419575730..1834f0a062 100644
--- a/packages/backend/src/nest/local-db/local-db.types.ts
+++ b/packages/backend/src/nest/local-db/local-db.types.ts
@@ -2,5 +2,6 @@ export enum LocalDBKeys {
COMMUNITY = 'community',
REGISTRAR = 'registrar',
PEERS = 'peers',
+ PSK = 'psk',
}
export type LocalDbStatus = 'opening' | 'open' | 'closing' | 'closed'
diff --git a/packages/backend/src/nest/socket/socket.service.ts b/packages/backend/src/nest/socket/socket.service.ts
index 5d687fe2a2..b1218cb7ec 100644
--- a/packages/backend/src/nest/socket/socket.service.ts
+++ b/packages/backend/src/nest/socket/socket.service.ts
@@ -207,6 +207,10 @@ export class SocketService extends EventEmitter implements OnModuleInit {
this.logger('Leaving community')
this.emit(SocketActionTypes.LEAVE_COMMUNITY)
})
+ socket.on(SocketActionTypes.LIBP2P_PSK_SAVED, payload => {
+ this.logger('Saving PSK', payload)
+ this.emit(SocketActionTypes.LIBP2P_PSK_SAVED, payload)
+ })
})
}
diff --git a/packages/backend/src/nest/storage/storage.service.ts b/packages/backend/src/nest/storage/storage.service.ts
index 0068130d95..fdef474a25 100644
--- a/packages/backend/src/nest/storage/storage.service.ts
+++ b/packages/backend/src/nest/storage/storage.service.ts
@@ -382,7 +382,6 @@ export class StorageService extends EventEmitter {
})
this.certificates.events.on('write', async (_address, entry) => {
this.logger('Saved certificate locally')
- this.logger(entry.payload.value)
this.emit(StorageEvents.LOAD_CERTIFICATES, {
certificates: this.getAllEventLogEntries(this.certificates),
})
@@ -526,9 +525,7 @@ export class StorageService extends EventEmitter {
// @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options'
await this.channels.load({ fetchEntryTimeout: 2000 })
- const channels = Object.values(this.channels.all).map(channel => {
- return this.transformChannel(channel)
- })
+ const channels = Object.values(this.channels.all)
const keyValueChannels: {
[key: string]: PublicChannel
@@ -549,10 +546,9 @@ export class StorageService extends EventEmitter {
// @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options'
await this.channels.load({ fetchEntryTimeout: 1000 })
- this.logger('ALL CHANNELS COUNT:', Object.keys(this.channels.all).length)
- this.logger('ALL CHANNELS COUNT:', Object.keys(this.channels.all))
+ this.logger('Channels count:', Object.keys(this.channels.all).length)
+ this.logger('Channels names:', Object.keys(this.channels.all))
Object.values(this.channels.all).forEach(async (channel: PublicChannel) => {
- channel = this.transformChannel(channel)
await this.subscribeToChannel(channel)
})
this.logger('STORAGE: Finished createDbForChannels')
@@ -629,7 +625,7 @@ export class StorageService extends EventEmitter {
db.events.on('replicate.progress', async (address, _hash, entry, progress, total) => {
this.logger(`progress ${progress as string}/${total as string}. Address: ${address as string}`)
- const messages = this.transformMessages([entry.payload.value])
+ const messages = [entry.payload.value]
const verified = await this.verifyMessage(messages[0])
@@ -691,48 +687,14 @@ export class StorageService extends EventEmitter {
})
}
- public transformMessages(msgs: ChannelMessage[]) {
- console.log('---------------- TRANSFORMING MESSAGES ----------------------')
- const messages = msgs.map(msg => {
- console.log('processing message ', msg.id)
- // @ts-ignore
- if (msg.channelAddress) {
- console.log('message before transformation ', msg)
- // @ts-ignore
- msg.channelId = msg.channelAddress
- // @ts-ignore
- delete msg.channelAddress
- console.log('transformed message to new format ', msg)
- return msg
- }
- return msg
- })
- return messages
- }
-
- public transformChannel(channel: PublicChannel) {
- // @ts-ignore
- if (channel.address) {
- console.log('channel before transformation ', channel)
- // @ts-ignore
- channel.id = channel.address
- // @ts-ignore
- delete channel.address
- console.log('transformed channel to new format ', channel)
- return channel
- }
- return channel
- }
-
public async askForMessages(channelId: string, ids: string[]) {
const repo = this.publicChannelsRepos.get(channelId)
if (!repo) return
const messages = this.getAllEventLogEntries(repo.db)
- let filteredMessages: ChannelMessage[] = []
+ const filteredMessages: ChannelMessage[] = []
for (const id of ids) {
filteredMessages.push(...messages.filter(i => i.id === id))
}
- filteredMessages = this.transformMessages(filteredMessages)
this.emit(StorageEvents.LOAD_MESSAGES, {
messages: filteredMessages,
isVerified: true,
@@ -749,8 +711,7 @@ export class StorageService extends EventEmitter {
}
this.logger(`Creating channel ${data.id}`)
- // @ts-ignore
- const channelId = data.id || data.address
+ const channelId = data.id
const db: EventStore = await this.orbitDb.log(`channels.${channelId}`, {
replicate: options.replicate,
@@ -956,7 +917,7 @@ export class StorageService extends EventEmitter {
public getAllUsers(): UserData[] {
const csrs = this.getAllEventLogEntries(this.certificatesRequests)
- console.log('csrs count:', csrs.length)
+ this.logger('CSRs count:', csrs.length)
const allUsers: UserData[] = []
for (const csr of csrs) {
const parsedCert = parseCertificationRequest(csr)
@@ -1044,6 +1005,10 @@ export class StorageService extends EventEmitter {
this.messageThreads = undefined
// @ts-ignore
this.certificates = undefined
+ // @ts-ignore
+ this.certificatesRequests = undefined
+ // @ts-ignore
+ this.communityMetadata = undefined
this.publicChannelsRepos = new Map()
this.directMessagesRepos = new Map()
this.publicKeysMap = new Map()
diff --git a/packages/backend/src/nest/tor/tor.service.ts b/packages/backend/src/nest/tor/tor.service.ts
index 8fbbe162de..be7183103e 100644
--- a/packages/backend/src/nest/tor/tor.service.ts
+++ b/packages/backend/src/nest/tor/tor.service.ts
@@ -205,21 +205,6 @@ export class Tor extends EventEmitter implements OnModuleInit {
return
}
- console.log([
- '--SocksPort',
- this.socksPort.toString(),
- '--HTTPTunnelPort',
- this.configOptions.httpTunnelPort?.toString(),
- '--ControlPort',
- this.controlPort.toString(),
- '--PidFile',
- this.torPidPath,
- '--DataDirectory',
- this.torDataDirectory,
- '--HashedControlPassword',
- this.torPasswordProvider.torHashedPassword,
- // ...this.torProcessParams
- ])
this.process = child_process.spawn(
this.torParamsProvider.torPath,
[
diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md
index de505e8aa5..58019f1a5d 100644
--- a/packages/common/CHANGELOG.md
+++ b/packages/common/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.2-alpha.1](https://github.com/TryQuiet/quiet/compare/@quiet/common@2.0.2-alpha.0...@quiet/common@2.0.2-alpha.1) (2023-11-14)
+
+**Note:** Version bump only for package @quiet/common
+
+
+
+
+
## [2.0.2-alpha.0](https://github.com/TryQuiet/quiet/compare/@quiet/common@2.0.1-alpha.4...@quiet/common@2.0.2-alpha.0) (2023-10-26)
**Note:** Version bump only for package @quiet/common
diff --git a/packages/common/package-lock.json b/packages/common/package-lock.json
index 67dbf3e87e..8e6302db19 100644
--- a/packages/common/package-lock.json
+++ b/packages/common/package-lock.json
@@ -1,20 +1,22 @@
{
"name": "@quiet/common",
- "version": "2.0.2-alpha.0",
+ "version": "2.0.2-alpha.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@quiet/common",
- "version": "2.0.2-alpha.0",
+ "version": "2.0.2-alpha.1",
"license": "ISC",
"dependencies": {
"cross-env": "^5.2.0",
- "debug": "^4.3.1"
+ "debug": "^4.3.1",
+ "validator": "^13.11.0"
},
"devDependencies": {
"@types/jest": "^26.0.23",
"@types/node": "^17.0.21",
+ "@types/validator": "^13.11.5",
"jest": "^26.6.3",
"ts-jest": "^26.5.2",
"typescript": "^4.9.3"
@@ -1116,6 +1118,12 @@
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
"dev": true
},
+ "node_modules/@types/validator": {
+ "version": "13.11.5",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.5.tgz",
+ "integrity": "sha512-xW4qsT4UIYILu+7ZrBnfQdBYniZrMLYYK3wN9M/NdeIHgBN5pZI2/8Q7UfdWIcr5RLJv/OGENsx91JIpUUoC7Q==",
+ "dev": true
+ },
"node_modules/@types/yargs": {
"version": "15.0.15",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz",
@@ -5989,6 +5997,14 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "node_modules/validator": {
+ "version": "13.11.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
+ "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/w3c-hr-time": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
@@ -7098,6 +7114,12 @@
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
"dev": true
},
+ "@types/validator": {
+ "version": "13.11.5",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.5.tgz",
+ "integrity": "sha512-xW4qsT4UIYILu+7ZrBnfQdBYniZrMLYYK3wN9M/NdeIHgBN5pZI2/8Q7UfdWIcr5RLJv/OGENsx91JIpUUoC7Q==",
+ "dev": true
+ },
"@types/yargs": {
"version": "15.0.15",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz",
@@ -10885,6 +10907,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "validator": {
+ "version": "13.11.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
+ "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ=="
+ },
"w3c-hr-time": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
diff --git a/packages/common/package.json b/packages/common/package.json
index 1bd4e547ac..d62236d210 100644
--- a/packages/common/package.json
+++ b/packages/common/package.json
@@ -1,6 +1,6 @@
{
"name": "@quiet/common",
- "version": "2.0.2-alpha.0",
+ "version": "2.0.2-alpha.1",
"description": "Common monorepo utils",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -21,14 +21,17 @@
"@quiet/eslint-config": "^2.0.2-alpha.0",
"@types/jest": "^26.0.23",
"@types/node": "^17.0.21",
+ "@types/validator": "^13.11.5",
"jest": "^26.6.3",
"ts-jest": "^26.5.2",
"typescript": "^4.9.3"
},
"dependencies": {
- "@quiet/types": "^2.0.2-alpha.0",
+ "@quiet/logger": "^2.0.2-alpha.0",
+ "@quiet/types": "^2.0.2-alpha.1",
"cross-env": "^5.2.0",
- "debug": "^4.3.1"
+ "debug": "^4.3.1",
+ "validator": "^13.11.0"
},
"jest": {
"transform": {
diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts
index 67ce8a7b5b..bd92483eb7 100644
--- a/packages/common/src/index.ts
+++ b/packages/common/src/index.ts
@@ -8,3 +8,4 @@ export * from './channelAddress'
export * from './naming'
export * from './fileData'
export * from './libp2p'
+export * from './tests'
diff --git a/packages/common/src/invitationCode.test.ts b/packages/common/src/invitationCode.test.ts
index 28254563bc..7ef34257cb 100644
--- a/packages/common/src/invitationCode.test.ts
+++ b/packages/common/src/invitationCode.test.ts
@@ -1,9 +1,11 @@
+import { InvitationData } from '@quiet/types'
import {
argvInvitationCode,
- invitationDeepUrl,
+ composeInvitationDeepUrl,
invitationShareUrl,
- pairsToInvitationShareUrl,
- retrieveInvitationCode,
+ composeInvitationShareUrl,
+ parseInvitationCodeDeepUrl,
+ PSK_PARAM_KEY,
} from './invitationCode'
import { QUIET_JOIN_PAGE } from './static'
@@ -12,64 +14,102 @@ describe('Invitation code helper', () => {
const address1 = 'gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad'
const peerId2 = 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE'
const address2 = 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd'
+ const psk = 'BNlxfE2WBF7LrlpIX0CvECN5o1oZtA16PkAb7GYiwYw%3D'
+ const pskDecoded = 'BNlxfE2WBF7LrlpIX0CvECN5o1oZtA16PkAb7GYiwYw='
it('retrieves invitation code from argv', () => {
- const expectedCodes = [
- { peerId: peerId1, onionAddress: address1 },
- { peerId: peerId2, onionAddress: address2 },
- ]
+ const expectedCodes: InvitationData = {
+ pairs: [
+ { peerId: peerId1, onionAddress: address1 },
+ { peerId: peerId2, onionAddress: address2 },
+ ],
+ psk: pskDecoded,
+ }
const result = argvInvitationCode([
'something',
'quiet:/invalid',
'zbay://invalid',
'quiet://invalid',
'quiet://?param=invalid',
- invitationDeepUrl(expectedCodes),
+ composeInvitationDeepUrl(expectedCodes),
])
expect(result).toEqual(expectedCodes)
})
- it('builds proper invitation deep url', () => {
+ it('returns null if argv do not contain any valid invitation code', () => {
+ const result = argvInvitationCode([
+ 'something',
+ 'quiet:/invalid',
+ 'zbay://invalid',
+ 'quiet://invalid',
+ 'quiet://?param=invalid',
+ ])
+ expect(result).toBeNull()
+ })
+
+ it('composes proper invitation deep url', () => {
expect(
- invitationDeepUrl([
- { peerId: 'peerID1', onionAddress: 'address1' },
- { peerId: 'peerID2', onionAddress: 'address2' },
- ])
- ).toEqual('quiet://?peerID1=address1&peerID2=address2')
+ composeInvitationDeepUrl({
+ pairs: [
+ { peerId: 'peerID1', onionAddress: 'address1' },
+ { peerId: 'peerID2', onionAddress: 'address2' },
+ ],
+ psk: pskDecoded,
+ })
+ ).toEqual(`quiet://?peerID1=address1&peerID2=address2&${PSK_PARAM_KEY}=${psk}`)
})
- 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('creates invitation share url based on invitation data', () => {
+ const pairs: InvitationData = {
+ pairs: [
+ { peerId: 'peerID1', onionAddress: 'address1' },
+ { peerId: 'peerID2', onionAddress: 'address2' },
+ ],
+ psk: pskDecoded,
+ }
+ const expected = `${QUIET_JOIN_PAGE}#peerID1=address1&peerID2=address2&${PSK_PARAM_KEY}=${psk}`
+ expect(composeInvitationShareUrl(pairs)).toEqual(expected)
})
- it('builds proper invitation share url', () => {
+ it('builds proper invitation share url from peers addresses', () => {
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`
+ expect(invitationShareUrl(peerList, pskDecoded)).toEqual(
+ `${QUIET_JOIN_PAGE}#QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE=gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad&QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSA=somethingElse&${PSK_PARAM_KEY}=${psk}`
)
})
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 },
- ])
+ const codes = parseInvitationCodeDeepUrl(
+ `quiet://?${peerId1}=${address1}&${peerId2}=${address2}&${PSK_PARAM_KEY}=${psk}`
+ )
+ expect(codes).toEqual({
+ pairs: [
+ { peerId: peerId1, onionAddress: address1 },
+ { peerId: peerId2, onionAddress: address2 },
+ ],
+ psk: pskDecoded,
+ })
})
+ it.each([['12345'], ['a2FzemE='], 'a2FycGllIHcgZ2FsYXJlY2llIGVjaWUgcGVjaWUgYWxlIGkgdGFrIHpqZWNpZQ=='])(
+ 'parsing invitation code throws error if psk is invalid: (%s)',
+ (psk: string) => {
+ expect(() => {
+ parseInvitationCodeDeepUrl(`quiet://?${peerId1}=${address1}&${peerId2}=${address2}&${PSK_PARAM_KEY}=${psk}`)
+ }).toThrow()
+ }
+ )
+
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 }])
+ const parsed = parseInvitationCodeDeepUrl(
+ `quiet://?${peerId1}=${address1}&${peerId2}=${address2}&${PSK_PARAM_KEY}=${psk}`
+ )
+ expect(parsed).toEqual({ pairs: [{ peerId: peerId1, onionAddress: address1 }], psk: pskDecoded })
})
})
diff --git a/packages/common/src/invitationCode.ts b/packages/common/src/invitationCode.ts
index e5e6af26f7..10ecf79dd9 100644
--- a/packages/common/src/invitationCode.ts
+++ b/packages/common/src/invitationCode.ts
@@ -1,38 +1,86 @@
-import { InvitationPair } from '@quiet/types'
-import { ONION_ADDRESS_REGEX, Site } from './static'
-import { createLibp2pAddress } from './libp2p'
-export const retrieveInvitationCode = (url: string): InvitationPair[] => {
- /**
- * Extract invitation codes from deep url.
- * Valid format: quiet://?=&=
- */
- let data: URL
+import { InvitationData, InvitationPair } from '@quiet/types'
+import { QUIET_JOIN_PAGE } from './static'
+import { createLibp2pAddress, isPSKcodeValid } from './libp2p'
+import Logger from './logger'
+const logger = Logger('invite')
+
+export const PSK_PARAM_KEY = 'k'
+const DEEP_URL_SCHEME_WITH_SEPARATOR = 'quiet://'
+const DEEP_URL_SCHEME = 'quiet'
+const ONION_ADDRESS_REGEX = /^[a-z0-9]{56}$/g
+const PEER_ID_REGEX = /^[a-zA-Z0-9]{46}$/g
+
+interface ParseDeepUrlParams {
+ url: string
+ expectedProtocol?: string
+}
+
+const parseDeepUrl = ({ url, expectedProtocol = `${DEEP_URL_SCHEME}:` }: ParseDeepUrlParams): InvitationData => {
+ let _url = url
+ let validUrl: URL | null = null
+
+ if (!expectedProtocol) {
+ // Create a full url to be able to use the same URL parsing mechanism
+ expectedProtocol = `${DEEP_URL_SCHEME}:`
+ _url = `${DEEP_URL_SCHEME}://?${url}`
+ }
+
try {
- data = new URL(url)
+ validUrl = new URL(_url)
} catch (e) {
- return []
+ logger.error(`Could not retrieve invitation code from deep url '${url}'. Reason: ${e.message}`)
+ throw e
}
- if (!data || data.protocol !== 'quiet:') return []
- const params = data.searchParams
+ if (!validUrl || validUrl.protocol !== expectedProtocol) {
+ logger.error(`Could not retrieve invitation code from deep url '${url}'`)
+ throw new Error(`Invalid url`)
+ }
+ const params = validUrl.searchParams
const codes: InvitationPair[] = []
- for (const [peerId, onionAddress] of params.entries()) {
- if (!invitationCodeValid(peerId, onionAddress)) continue
+ let psk = params.get(PSK_PARAM_KEY)
+ if (!psk) throw new Error(`No psk found in invitation code '${url}'`)
+
+ psk = decodeURIComponent(psk)
+ if (!isPSKcodeValid(psk)) throw new Error(`Invalid psk in invitation code '${url}'`)
+
+ params.delete(PSK_PARAM_KEY)
+
+ params.forEach((onionAddress, peerId) => {
+ if (!peerDataValid({ peerId, onionAddress })) return
codes.push({
peerId,
onionAddress,
})
+ })
+ logger('Retrieved data:', codes)
+ return {
+ pairs: codes,
+ psk: psk,
}
- console.log('Retrieved codes:', codes)
- return codes
}
-export const invitationShareUrl = (peers: string[] = []): string => {
- /**
- * @arg {string[]} peers - List of peer's p2p addresses
- * @returns {string} - Complete shareable invitation link, e.g. https://tryquiet.org/join/#=&=
- */
- console.log('Invitation share url, peers:', peers)
- const pairs = []
+/**
+ * Extract invitation data from deep url.
+ * Valid format: quiet://?=&=&k=
+ */
+export const parseInvitationCodeDeepUrl = (url: string): InvitationData => {
+ return parseDeepUrl({ url })
+}
+
+/**
+ * @param code =&=&k=
+ */
+export const parseInvitationCode = (code: string): InvitationData => {
+ return parseDeepUrl({ url: code, expectedProtocol: '' })
+}
+
+/**
+ * @arg {string[]} peers - List of peer's p2p addresses
+ * @arg psk - Pre shared key in base64
+ * @returns {string} - Complete shareable invitation link, e.g. https://tryquiet.org/join/#=&=&k=
+ */
+export const invitationShareUrl = (peers: string[] = [], psk: string): string => {
+ const pairs: InvitationPair[] = []
for (const peerAddress of peers) {
let peerId: string
let onionAddress: string
@@ -54,12 +102,10 @@ export const invitationShareUrl = (peers: string[] = []): string => {
continue
}
const rawAddress = onionAddress.endsWith('.onion') ? onionAddress.split('.')[0] : onionAddress
- pairs.push(`${peerId}=${rawAddress}`)
+ pairs.push({ peerId: peerId, onionAddress: rawAddress })
}
- console.log('invitationShareUrl', pairs.join('&'))
- const url = new URL(`${Site.MAIN_PAGE}${Site.JOIN_PAGE}#${pairs.join('&')}`)
- return url.href
+ return composeInvitationShareUrl({ pairs: pairs, psk: psk })
}
export const pairsToP2pAddresses = (pairs: InvitationPair[]): string[] => {
@@ -70,63 +116,52 @@ export const pairsToP2pAddresses = (pairs: InvitationPair[]): string[] => {
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 url.href.replace('?', '#')
+export const composeInvitationShareUrl = (data: InvitationData) => {
+ return composeInvitationUrl(`${QUIET_JOIN_PAGE}`, data).replace('?', '#')
}
-export const invitationDeepUrl = (pairs: InvitationPair[] = []): string => {
- const url = new URL('quiet://')
- for (const pair of pairs) {
+export const composeInvitationDeepUrl = (data: InvitationData): string => {
+ return composeInvitationUrl(`${DEEP_URL_SCHEME_WITH_SEPARATOR}`, data)
+}
+
+const composeInvitationUrl = (baseUrl: string, data: InvitationData): string => {
+ const url = new URL(baseUrl)
+ for (const pair of data.pairs) {
url.searchParams.append(pair.peerId, pair.onionAddress)
}
+ url.searchParams.append(PSK_PARAM_KEY, data.psk)
return url.href
}
-export const argvInvitationCode = (argv: string[]): InvitationPair[] => {
- /**
- * Extract invitation codes from deep url if url is present in argv
- */
- let invitationCodes: InvitationPair[] = []
+/**
+ * Extract invitation codes from deep url if url is present in argv
+ */
+export const argvInvitationCode = (argv: string[]): InvitationData | null => {
+ let invitationData: InvitationData | null = null
for (const arg of argv) {
- invitationCodes = retrieveInvitationCode(arg)
- if (invitationCodes.length > 0) {
+ try {
+ invitationData = parseInvitationCodeDeepUrl(arg)
+ } catch (e) {
+ continue
+ }
+ if (invitationData.pairs.length > 0) {
break
+ } else {
+ invitationData = null
}
}
- return invitationCodes
+ return invitationData
}
-export const invitationCodeValid = (peerId: string, onionAddress: string): boolean => {
- if (!peerId.match(/^[a-zA-Z0-9]{46}$/g)) {
+const peerDataValid = ({ peerId, onionAddress }: { peerId: string; onionAddress: string }): boolean => {
+ if (!peerId.match(PEER_ID_REGEX)) {
// TODO: test it more properly e.g with PeerId.createFromB58String(peerId.trim())
- console.log(`PeerId ${peerId} is not valid`)
+ logger(`PeerId ${peerId} is not valid`)
return false
}
if (!onionAddress.trim().match(ONION_ADDRESS_REGEX)) {
- console.log(`Onion address ${onionAddress} is not valid`)
+ logger(`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/common/src/libp2p.ts b/packages/common/src/libp2p.ts
index e86ee508e2..b2fb70e83f 100644
--- a/packages/common/src/libp2p.ts
+++ b/packages/common/src/libp2p.ts
@@ -1,3 +1,6 @@
+import validator from 'validator'
+export const PSK_LENGTH = 44 // PSK is 256 bits/8 = 32 bytes which encodes to 44 characters base64
+
const ONION = '.onion'
export const createLibp2pAddress = (address: string, peerId: string) => {
@@ -9,3 +12,8 @@ export const createLibp2pListenAddress = (address: string) => {
if (!address.endsWith(ONION)) address += ONION
return `/dns4/${address}/tcp/80/ws`
}
+
+export const isPSKcodeValid = (psk: string): boolean => {
+ const _psk = psk.trim()
+ return validator.isBase64(_psk) && _psk.length === PSK_LENGTH
+}
diff --git a/packages/common/src/logger.ts b/packages/common/src/logger.ts
new file mode 100644
index 0000000000..31360daef5
--- /dev/null
+++ b/packages/common/src/logger.ts
@@ -0,0 +1,7 @@
+import { logger } from '@quiet/logger'
+
+const createLogger = (name: string) => {
+ return logger('utils')(name)
+}
+
+export default createLogger
diff --git a/packages/common/src/static.ts b/packages/common/src/static.ts
index 05b1f35f0a..270bcdee89 100644
--- a/packages/common/src/static.ts
+++ b/packages/common/src/static.ts
@@ -1,5 +1,3 @@
-export const ONION_ADDRESS_REGEX = /^[a-z0-9]{56}$/g
-
export enum Site {
DOMAIN = 'tryquiet.org',
MAIN_PAGE = 'https://tryquiet.org/',
diff --git a/packages/common/src/tests.ts b/packages/common/src/tests.ts
new file mode 100644
index 0000000000..2cd38af1cf
--- /dev/null
+++ b/packages/common/src/tests.ts
@@ -0,0 +1,20 @@
+import { InvitationData } from '@quiet/types'
+import { composeInvitationDeepUrl, composeInvitationShareUrl } from './invitationCode'
+import { QUIET_JOIN_PAGE } from './static'
+
+const validInvitationCodeTestData: InvitationData = {
+ pairs: [
+ {
+ onionAddress: 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd',
+ peerId: 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE',
+ },
+ ],
+ psk: 'BNlxfE2WBF7LrlpIX0CvECN5o1oZtA16PkAb7GYiwYw=',
+}
+
+export const validInvitationUrlTestData = {
+ shareUrl: () => composeInvitationShareUrl(validInvitationCodeTestData),
+ deepUrl: () => composeInvitationDeepUrl(validInvitationCodeTestData),
+ code: () => composeInvitationShareUrl(validInvitationCodeTestData).split(QUIET_JOIN_PAGE + '#')[1],
+ data: validInvitationCodeTestData,
+}
diff --git a/packages/desktop/CHANGELOG.md b/packages/desktop/CHANGELOG.md
index 7508693170..78943be782 100644
--- a/packages/desktop/CHANGELOG.md
+++ b/packages/desktop/CHANGELOG.md
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [2.0.3-alpha.5](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.4...@quiet/desktop@2.0.3-alpha.5) (2023-11-14)
+
+**Note:** Version bump only for package @quiet/desktop
+
+
+
+
+
+## [2.0.3-alpha.4](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.3...@quiet/desktop@2.0.3-alpha.4) (2023-11-14)
+
+
+### Features
+
+* trigger desktop ([713e1b8](https://github.com/TryQuiet/quiet/commit/713e1b822b266c218f71742ed68616b8b1056c75))
+
+
+
+
+
## [2.0.3-alpha.3](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.2...@quiet/desktop@2.0.3-alpha.3) (2023-11-13)
diff --git a/packages/desktop/package-lock.json b/packages/desktop/package-lock.json
index a412d51019..62347f7ab7 100644
--- a/packages/desktop/package-lock.json
+++ b/packages/desktop/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@quiet/desktop",
- "version": "2.0.3-alpha.3",
+ "version": "2.0.3-alpha.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@quiet/desktop",
- "version": "2.0.3-alpha.3",
+ "version": "2.0.3-alpha.5",
"license": "ISC",
"dependencies": {
"@electron/remote": "^2.0.8",
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index c15fe02713..42dd73f5ff 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -80,7 +80,7 @@
},
"homepage": "https://github.com/TryQuiet",
"@comment version": "To build new version for specific platform, just replace platform in version tag to one of following linux, mac, windows",
- "version": "2.0.3-alpha.3",
+ "version": "2.0.3-alpha.5",
"description": "Decentralized team chat",
"main": "dist/main/main.js",
"scripts": {
@@ -111,7 +111,7 @@
"build:renderer:prod": "webpack --config webpack/webpack.config.renderer.prod.js",
"postBuild": "node scripts/postBuild.js",
"prestart": "npm run build:main",
- "start": "cross-env DEBUG='backend*,quiet*,state-manager*,desktop*,libp2p:websockets:listener:backend' npm run start:renderer",
+ "start": "cross-env DEBUG='backend*,quiet*,state-manager*,desktop*,utils*,libp2p:websockets:listener:backend' npm run start:renderer",
"start:main": "cross-env NODE_ENV=development electron .",
"start:renderer": "cross-env NODE_ENV=development webpack-dev-server --config webpack/webpack.config.renderer.dev.js",
"storybook": "export NODE_OPTIONS=--openssl-legacy-provider && start-storybook -p 6006",
@@ -125,9 +125,9 @@
"dependencies": {
"@electron/remote": "^2.0.8",
"@peculiar/webcrypto": "1.4.3",
- "@quiet/common": "^2.0.2-alpha.0",
+ "@quiet/common": "^2.0.2-alpha.1",
"@quiet/logger": "^2.0.2-alpha.0",
- "@quiet/types": "^2.0.2-alpha.0",
+ "@quiet/types": "^2.0.2-alpha.1",
"@sentry/electron": "^2.5.4",
"backend-bundle": "^2.0.1-alpha.4",
"electron-debug": "^3.0.1",
@@ -157,8 +157,8 @@
"@mui/lab": "^5.0.0-alpha.109",
"@mui/material": "~5.10.15",
"@quiet/eslint-config": "^2.0.2-alpha.0",
- "@quiet/identity": "^2.0.2-alpha.0",
- "@quiet/state-manager": "^2.0.2-alpha.1",
+ "@quiet/identity": "^2.0.2-alpha.2",
+ "@quiet/state-manager": "^2.0.2-alpha.3",
"@redux-saga/types": "^1.1.0",
"@reduxjs/toolkit": "^1.9.1",
"@sentry/browser": "^6.19.7",
diff --git a/packages/desktop/src/main/invitation.ts b/packages/desktop/src/main/invitation.ts
index 39bf432962..2616059683 100644
--- a/packages/desktop/src/main/invitation.ts
+++ b/packages/desktop/src/main/invitation.ts
@@ -3,15 +3,15 @@ import path from 'path'
import os from 'os'
import { execSync } from 'child_process'
import { BrowserWindow } from 'electron'
-import { InvitationPair } from '@quiet/types'
+import { InvitationData, InvitationPair } from '@quiet/types'
-export const processInvitationCode = (mainWindow: BrowserWindow, codes: InvitationPair[]) => {
- if (codes.length === 0) {
+export const processInvitationCode = (mainWindow: BrowserWindow, data: InvitationData | null) => {
+ if (!data || data?.pairs.length === 0) {
console.log('No valid invitation codes, not processing')
return
}
mainWindow.webContents.send('invitation', {
- codes,
+ data,
})
}
diff --git a/packages/desktop/src/main/main.test.ts b/packages/desktop/src/main/main.test.ts
index 0239a05cd0..8f136a3e0b 100644
--- a/packages/desktop/src/main/main.test.ts
+++ b/packages/desktop/src/main/main.test.ts
@@ -5,7 +5,8 @@ import { autoUpdater } from 'electron-updater'
import { BrowserWindow, app, ipcMain, Menu } from 'electron'
import { waitFor } from '@testing-library/dom'
import path from 'path'
-import { invitationDeepUrl } from '@quiet/common'
+import { composeInvitationDeepUrl, validInvitationUrlTestData } from '@quiet/common'
+import { InvitationData } from '@quiet/types'
// eslint-disable-next-line
const remote = require('@electron/remote/main')
@@ -237,33 +238,39 @@ describe('other electron app events ', () => {
})
describe('Invitation code', () => {
+ let codes: InvitationData
+
+ beforeEach(() => {
+ codes = { ...validInvitationUrlTestData.data }
+ })
+
it('handles invitation code on open-url event (on macos)', async () => {
expect(mockAppOnCalls[2][0]).toBe('ready')
await mockAppOnCalls[2][1]()
- const codes = [
- {
- peerId: 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE',
- onionAddress: 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd',
- },
- ]
+
+ expect(mockAppOnCalls[1][0]).toBe('open-url')
+ const event = { preventDefault: () => {} }
+ mockAppOnCalls[1][1](event, composeInvitationDeepUrl(codes))
+ expect(mockWindowWebContentsSend).toHaveBeenCalledWith('invitation', { data: codes })
+ })
+
+ it('do not process invitation code on open-url event (on macos) if url is invalid', async () => {
+ codes['psk'] = '12345'
+ expect(mockAppOnCalls[2][0]).toBe('ready')
+ await mockAppOnCalls[2][1]()
+
expect(mockAppOnCalls[1][0]).toBe('open-url')
const event = { preventDefault: () => {} }
- mockAppOnCalls[1][1](event, invitationDeepUrl(codes))
- expect(mockWindowWebContentsSend).toHaveBeenCalledWith('invitation', { codes })
+ mockAppOnCalls[1][1](event, composeInvitationDeepUrl(codes))
+ expect(mockWindowWebContentsSend).not.toHaveBeenCalledWith('invitation', { data: codes })
})
it('process invitation code on second-instance event', async () => {
- const codes = [
- {
- peerId: 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE',
- onionAddress: 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd',
- },
- ]
await mockAppOnCalls[2][1]()
- const commandLine = ['/tmp/.mount_Quiet-TVQc6s/quiet', invitationDeepUrl(codes)]
+ const commandLine = ['/tmp/.mount_Quiet-TVQc6s/quiet', composeInvitationDeepUrl(codes)]
expect(mockAppOnCalls[0][0]).toBe('second-instance')
const event = { preventDefault: () => {} }
mockAppOnCalls[0][1](event, commandLine)
- expect(mockWindowWebContentsSend).toHaveBeenCalledWith('invitation', { codes })
+ expect(mockWindowWebContentsSend).toHaveBeenCalledWith('invitation', { data: codes })
})
})
diff --git a/packages/desktop/src/main/main.ts b/packages/desktop/src/main/main.ts
index b8c20310f4..c6c74db976 100644
--- a/packages/desktop/src/main/main.ts
+++ b/packages/desktop/src/main/main.ts
@@ -11,7 +11,7 @@ import { Crypto } from '@peculiar/webcrypto'
import logger from './logger'
import { DATA_DIR, DEV_DATA_DIR } from '../shared/static'
import { fork, ChildProcess } from 'child_process'
-import { argvInvitationCode, getFilesData, retrieveInvitationCode } from '@quiet/common'
+import { argvInvitationCode, getFilesData, parseInvitationCodeDeepUrl } from '@quiet/common'
import { updateDesktopFile, processInvitationCode } from './invitation'
const ElectronStore = require('electron-store')
ElectronStore.initRenderer()
@@ -147,8 +147,12 @@ app.on('open-url', (event, url) => {
event.preventDefault()
if (mainWindow) {
invitationUrl = null
- const invitationCode = retrieveInvitationCode(url)
- processInvitationCode(mainWindow, invitationCode)
+ try {
+ const invitationCode = parseInvitationCodeDeepUrl(url)
+ processInvitationCode(mainWindow, invitationCode)
+ } catch (e) {
+ console.warn(e.message)
+ }
}
})
@@ -474,13 +478,22 @@ app.on('ready', async () => {
throw new Error(`mainWindow is on unexpected type ${mainWindow}`)
}
if (process.platform === 'darwin' && invitationUrl) {
- const invitationCode = retrieveInvitationCode(invitationUrl)
- processInvitationCode(mainWindow, invitationCode)
- invitationUrl = null
+ try {
+ const invitationCode = parseInvitationCodeDeepUrl(invitationUrl)
+ processInvitationCode(mainWindow, invitationCode)
+ } catch (e) {
+ console.warn(e.message)
+ } finally {
+ invitationUrl = null
+ }
}
if (process.platform !== 'darwin' && process.argv) {
- const invitationCode = argvInvitationCode(process.argv)
- processInvitationCode(mainWindow, invitationCode)
+ try {
+ const invitationCode = argvInvitationCode(process.argv)
+ processInvitationCode(mainWindow, invitationCode)
+ } catch (e) {
+ console.warn(e.message)
+ }
}
await checkForUpdate(mainWindow)
diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.test.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.test.tsx
index a9752d46f8..db777c9a6b 100644
--- a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.test.tsx
+++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.test.tsx
@@ -17,17 +17,12 @@ import PerformCommunityActionComponent from '../PerformCommunityActionComponent'
import { inviteLinkField } from '../../../forms/fields/communityFields'
import { InviteLinkErrors } from '../../../forms/fieldsErrors'
import { CommunityOwnership } from '@quiet/types'
-import { Site, QUIET_JOIN_PAGE } from '@quiet/common'
+import { Site, QUIET_JOIN_PAGE, validInvitationUrlTestData, PSK_PARAM_KEY } from '@quiet/common'
describe('join community', () => {
- const validCode =
- 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE=y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd'
- const validPair = [
- {
- onionAddress: 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd',
- peerId: 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE',
- },
- ]
+ const validCode = validInvitationUrlTestData.code()
+ const validData = validInvitationUrlTestData.data
+ const psk = validInvitationUrlTestData.data.psk
it('users switches from join to create', async () => {
const { store } = await prepareStore({
@@ -106,9 +101,6 @@ describe('join community', () => {
})
it('joins community on submit if connection is ready and registrar url is correct', async () => {
- const registrarUrl =
- 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE=y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd'
-
const handleCommunityAction = jest.fn()
const component = (
@@ -129,20 +121,13 @@ describe('join community', () => {
const textInput = result.queryByPlaceholderText(inviteLinkField().fieldProps.placeholder)
expect(textInput).not.toBeNull()
// @ts-expect-error
- await userEvent.type(textInput, registrarUrl)
+ await userEvent.type(textInput, validCode)
const submitButton = result.getByText('Continue')
expect(submitButton).toBeEnabled()
await userEvent.click(submitButton)
- await waitFor(() =>
- expect(handleCommunityAction).toBeCalledWith([
- {
- peerId: 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE',
- onionAddress: 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd',
- },
- ])
- )
+ await waitFor(() => expect(handleCommunityAction).toBeCalledWith(validData))
})
it.each([[`${QUIET_JOIN_PAGE}#${validCode}`], [`${QUIET_JOIN_PAGE}/#${validCode}`]])(
@@ -176,13 +161,12 @@ describe('join community', () => {
expect(submitButton).toBeEnabled()
await userEvent.click(submitButton)
- await waitFor(() => expect(handleCommunityAction).toBeCalledWith(validPair))
+ await waitFor(() => expect(handleCommunityAction).toBeCalledWith(validData))
}
)
it('trims whitespaces from registrar url', async () => {
- const registrarUrl =
- 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE=y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd '
+ const registrarUrl = validCode + ' '
const handleCommunityAction = jest.fn()
@@ -210,19 +194,12 @@ describe('join community', () => {
expect(submitButton).toBeEnabled()
await userEvent.click(submitButton)
- await waitFor(() =>
- expect(handleCommunityAction).toBeCalledWith([
- {
- peerId: 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE',
- onionAddress: 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd',
- },
- ])
- )
+ await waitFor(() => expect(handleCommunityAction).toBeCalledWith(validData))
})
it.each([
[`http://${validCode}`, InviteLinkErrors.InvalidCode],
- ['QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE=bbb', InviteLinkErrors.InvalidCode],
+ [`QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE=bbb&${PSK_PARAM_KEY}=${psk}`, InviteLinkErrors.InvalidCode],
['bbb=y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd', InviteLinkErrors.InvalidCode],
['QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE= ', InviteLinkErrors.InvalidCode],
['nqnw4kc4c77fb47lk52m5l57h4tc', InviteLinkErrors.InvalidCode],
diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx
index 590c156805..9dd23ee7aa 100644
--- a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx
+++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx
@@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { socketSelectors } from '../../../sagas/socket/socket.selectors'
-import { CommunityOwnership, CreateNetworkPayload, InvitationPair } from '@quiet/types'
-import { communities, identity, connection } from '@quiet/state-manager'
+import { CommunityOwnership, CreateNetworkPayload, InvitationData, InvitationPair } from '@quiet/types'
+import { communities, identity, connection, network } from '@quiet/state-manager'
import PerformCommunityActionComponent from '../../../components/CreateJoinCommunity/PerformCommunityActionComponent'
import { ModalName } from '../../../sagas/modals/modals.types'
import { useModal } from '../../../containers/hooks'
@@ -15,7 +15,9 @@ const JoinCommunity = () => {
const currentCommunity = useSelector(communities.selectors.currentCommunity)
const currentIdentity = useSelector(identity.selectors.currentIdentity)
- const invitationCode = useSelector(communities.selectors.invitationCodes)
+ // Invitation link data should be already available if user joined via deep link
+ const invitationCodes = useSelector(communities.selectors.invitationCodes)
+ const psk = useSelector(communities.selectors.psk)
const joinCommunityModal = useModal(ModalName.joinCommunityModal)
const createCommunityModal = useModal(ModalName.createCommunityModal)
@@ -36,10 +38,11 @@ const JoinCommunity = () => {
}
}, [currentCommunity])
- const handleCommunityAction = (address: InvitationPair[]) => {
+ const handleCommunityAction = (data: InvitationData) => {
const payload: CreateNetworkPayload = {
ownership: CommunityOwnership.User,
- peers: address,
+ peers: data.pairs,
+ psk: data.psk,
}
dispatch(communities.actions.createNetwork(payload))
}
@@ -68,7 +71,8 @@ const JoinCommunity = () => {
hasReceivedResponse={Boolean(currentIdentity && !currentIdentity.userCertificate)}
revealInputValue={revealInputValue}
handleClickInputReveal={handleClickInputReveal}
- invitationCode={invitationCode}
+ invitationCode={invitationCodes}
+ psk={psk}
/>
)
}
diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx
index 3ec4ac037f..144a3b2a9e 100644
--- a/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx
+++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx
@@ -12,7 +12,7 @@ import { LoadingButton } from '../ui/LoadingButton/LoadingButton'
import { CreateCommunityDictionary, JoinCommunityDictionary } from '../CreateJoinCommunity/community.dictionary'
-import { CommunityOwnership, InvitationPair } from '@quiet/types'
+import { CommunityOwnership, InvitationData, InvitationPair } from '@quiet/types'
import { Controller, useForm } from 'react-hook-form'
import { TextInput } from '../../forms/components/textInput'
@@ -20,7 +20,7 @@ import { InviteLinkErrors } from '../../forms/fieldsErrors'
import { IconButton, InputAdornment } from '@mui/material'
import VisibilityOff from '@mui/icons-material/VisibilityOff'
import Visibility from '@mui/icons-material/Visibility'
-import { ONION_ADDRESS_REGEX, pairsToInvitationShareUrl, parseName } from '@quiet/common'
+import { composeInvitationShareUrl, parseName } from '@quiet/common'
import { getInvitationCodes } from '@quiet/state-manager'
const PREFIX = 'PerformCommunityActionComponent'
@@ -138,6 +138,7 @@ export interface PerformCommunityActionProps {
revealInputValue?: boolean
handleClickInputReveal?: () => void
invitationCode?: InvitationPair[]
+ psk?: string
}
export const PerformCommunityActionComponent: React.FC = ({
@@ -152,6 +153,7 @@ export const PerformCommunityActionComponent: React.FC {
const [formSent, setFormSent] = useState(false)
@@ -190,14 +192,20 @@ export const PerformCommunityActionComponent: React.FC {
- if (communityOwnership === CommunityOwnership.User && invitationCode?.length) {
+ if (communityOwnership === CommunityOwnership.User && invitationCode?.length && psk) {
setFormSent(true)
- setValue('name', pairsToInvitationShareUrl(invitationCode))
+ setValue('name', composeInvitationShareUrl({ pairs: invitationCode, psk: psk }))
}
}, [communityOwnership, invitationCode])
diff --git a/packages/desktop/src/renderer/components/Settings/Settings.stories.tsx b/packages/desktop/src/renderer/components/Settings/Settings.stories.tsx
index 4b3e96a4bc..80e8663143 100644
--- a/packages/desktop/src/renderer/components/Settings/Settings.stories.tsx
+++ b/packages/desktop/src/renderer/components/Settings/Settings.stories.tsx
@@ -10,18 +10,21 @@ import { InviteComponent } from './Tabs/Invite/Invite.component'
import { LeaveCommunityComponent } from './Tabs/LeaveCommunity/LeaveCommunityComponent'
import { Typography } from '@mui/material'
import { QRCodeComponent } from './Tabs/QRCode/QRCode.component'
-import { pairsToInvitationShareUrl } from '@quiet/common'
-
-const invitationLink = pairsToInvitationShareUrl([
- {
- peerId: 'QmVTkUad2Gq3MkCa8gf12R1gsWDfk2yiTEqb6YGXDG2iQ3',
- onionAddress: 'p3oqdr53dkgg3n5nuezlzyawhxvit5efxzlunvzp7n7lmva6fj3i43ad',
- },
- {
- peerId: 'Qmd2Un9AynokZrcZGsMuaqgupTtidHGQnUkNVfFFAef97C',
- onionAddress: 'vnywuiyl7p7ig2murcscdyzksko53e4k3dpdm2yoopvvu25p6wwjqbad',
- },
-])
+import { composeInvitationShareUrl } from '@quiet/common'
+
+const invitationLink = composeInvitationShareUrl({
+ pairs: [
+ {
+ peerId: 'QmVTkUad2Gq3MkCa8gf12R1gsWDfk2yiTEqb6YGXDG2iQ3',
+ onionAddress: 'p3oqdr53dkgg3n5nuezlzyawhxvit5efxzlunvzp7n7lmva6fj3i43ad',
+ },
+ {
+ peerId: 'Qmd2Un9AynokZrcZGsMuaqgupTtidHGQnUkNVfFFAef97C',
+ onionAddress: 'vnywuiyl7p7ig2murcscdyzksko53e4k3dpdm2yoopvvu25p6wwjqbad',
+ },
+ ],
+ psk: '12345',
+})
const Template: ComponentStory = args => {
return
diff --git a/packages/desktop/src/renderer/components/Settings/Tabs/Invite/Invite.component.test.tsx b/packages/desktop/src/renderer/components/Settings/Tabs/Invite/Invite.component.test.tsx
index 926d0e39fe..a258ee8331 100644
--- a/packages/desktop/src/renderer/components/Settings/Tabs/Invite/Invite.component.test.tsx
+++ b/packages/desktop/src/renderer/components/Settings/Tabs/Invite/Invite.component.test.tsx
@@ -2,28 +2,31 @@ import '@testing-library/jest-dom'
import React from 'react'
import { renderComponent } from '../../../../testUtils/renderComponent'
import { InviteComponent } from './Invite.component'
-import { pairsToInvitationShareUrl } from '@quiet/common'
+import { composeInvitationShareUrl } from '@quiet/common'
describe('CopyLink', () => {
it('renderComponent - hidden long link', () => {
- const invitationLink = pairsToInvitationShareUrl([
- {
- peerId: 'QmVTkUad2Gq3MkCa8gf12R1gsWDfk2yiTEqb6YGXDG2iQ3',
- onionAddress: 'p3oqdr53dkgg3n5nuezlzyawhxvit5efxzlunvzp7n7lmva6fj3i43ad',
- },
- {
- peerId: 'Qmd2Un9AynokZrcZGsMuaqgupTtidHGQnUkNVfFFAef97C',
- onionAddress: 'vnywuiyl7p7ig2murcscdyzksko53e4k3dpdm2yoopvvu25p6wwjqbad',
- },
- {
- peerId: 'QmXRY4rhAx8Muq8dMGkr9qknJdE6UHZDdGaDRTQEbwFN5b',
- onionAddress: '6vu2bxki777it3cpayv6fq6vpl4ke3kzj7gxicfygm55dhhtphyfdvyd',
- },
- {
- peerId: 'QmT18UvnUBkseMc3SqnfPxpHwN8nzLrJeNSLZtc8rAFXhz',
- onionAddress: 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd',
- },
- ])
+ const invitationLink = composeInvitationShareUrl({
+ pairs: [
+ {
+ peerId: 'QmVTkUad2Gq3MkCa8gf12R1gsWDfk2yiTEqb6YGXDG2iQ3',
+ onionAddress: 'p3oqdr53dkgg3n5nuezlzyawhxvit5efxzlunvzp7n7lmva6fj3i43ad',
+ },
+ {
+ peerId: 'Qmd2Un9AynokZrcZGsMuaqgupTtidHGQnUkNVfFFAef97C',
+ onionAddress: 'vnywuiyl7p7ig2murcscdyzksko53e4k3dpdm2yoopvvu25p6wwjqbad',
+ },
+ {
+ peerId: 'QmXRY4rhAx8Muq8dMGkr9qknJdE6UHZDdGaDRTQEbwFN5b',
+ onionAddress: '6vu2bxki777it3cpayv6fq6vpl4ke3kzj7gxicfygm55dhhtphyfdvyd',
+ },
+ {
+ peerId: 'QmT18UvnUBkseMc3SqnfPxpHwN8nzLrJeNSLZtc8rAFXhz',
+ onionAddress: 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd',
+ },
+ ],
+ psk: '123435',
+ })
const result = renderComponent(
)
@@ -124,12 +127,15 @@ describe('CopyLink', () => {
})
it('renderComponent - revealed short link', () => {
- const invitationLink = pairsToInvitationShareUrl([
- {
- peerId: 'QmVTkUad2Gq3MkCa8gf12R1gsWDfk2yiTEqb6YGXDG2iQ3',
- onionAddress: 'p3oqdr53dkgg3n5nuezlzyawhxvit5efxzlunvzp7n7lmva6fj3i43ad',
- },
- ])
+ const invitationLink = composeInvitationShareUrl({
+ pairs: [
+ {
+ peerId: 'QmVTkUad2Gq3MkCa8gf12R1gsWDfk2yiTEqb6YGXDG2iQ3',
+ onionAddress: 'p3oqdr53dkgg3n5nuezlzyawhxvit5efxzlunvzp7n7lmva6fj3i43ad',
+ },
+ ],
+ psk: '12345',
+ })
const result = renderComponent(
)
@@ -182,7 +188,7 @@ describe('CopyLink', () => {
class="MuiTypography-root MuiTypography-body2 InviteToCommunitylink css-16d47hw-MuiTypography-root"
data-testid="invitation-link"
>
- https://tryquiet.org/join#QmVTkUad2Gq3MkCa8gf12R1gsWDfk2yiTEqb6YGXDG2iQ3=p3oqdr53dkgg3n5nuezlzyawhxvit5efxzlunvzp7n7lmva6fj3i43ad
+ https://tryquiet.org/join#QmVTkUad2Gq3MkCa8gf12R1gsWDfk2yiTEqb6YGXDG2iQ3=p3oqdr53dkgg3n5nuezlzyawhxvit5efxzlunvzp7n7lmva6fj3i43ad&k=12345