diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cd7251ca8..d4993c6b71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -[2.1.0] - unreleased +[Unreleased] + +* Handle spaces in tor process path. Run tor process in shell + +[2.0.1] * refactor: Remove SAVE_OWNER_CERTIFICATE event @@ -21,6 +25,7 @@ * Ask push notifications runtime permission on Android app start * Fix for multiplicating "welcome" messages when joining a community + * Fix: base jdenticon on pubkey instead of username - this way unregistered user with duplicated username will have different profile image * Updated old logo of Linux and Windows with rounded ones diff --git a/packages/backend/src/nest/common/test.module.ts b/packages/backend/src/nest/common/test.module.ts index 00e5adcb07..d653c1228c 100644 --- a/packages/backend/src/nest/common/test.module.ts +++ b/packages/backend/src/nest/common/test.module.ts @@ -27,8 +27,8 @@ const torPath = torBinForPlatform() const libPath = torDirForPlatform() export const defaultConfigForTest = { socketIOPort: TEST_DATA_PORT, - torBinaryPath: torBinForPlatform(), - torResourcesPath: torDirForPlatform(), + torBinaryPath: torPath, + torResourcesPath: torPath, torControlPort: await getPort(), options: { env: { diff --git a/packages/backend/src/nest/common/utils.ts b/packages/backend/src/nest/common/utils.ts index 6b1b445486..d2dc00e184 100644 --- a/packages/backend/src/nest/common/utils.ts +++ b/packages/backend/src/nest/common/utils.ts @@ -6,7 +6,7 @@ import { UserData } from '@quiet/types' import createHttpsProxyAgent from 'https-proxy-agent' import PeerId from 'peer-id' import tmp from 'tmp' -import crypto from 'crypto' +import crypto, { sign } from 'crypto' import { type PermsData } from '@quiet/types' import { TestConfig } from '../const' import logger from './logger' @@ -14,6 +14,7 @@ import { Libp2pNodeParams } from '../libp2p/libp2p.types' import { createLibp2pAddress, createLibp2pListenAddress, isDefined } from '@quiet/common' import { Libp2pService } from '../libp2p/libp2p.service' import { CertFieldsTypes, getReqFieldValue, loadCSR } from '@quiet/identity' +import { execFile } from 'child_process' const log = logger('test') @@ -127,7 +128,8 @@ export const torBinForPlatform = (basePath = '', binName = 'tor'): string => { return basePath } const ext = process.platform === 'win32' ? '.exe' : '' - return path.join(torDirForPlatform(basePath), `${binName}`.concat(ext)) + // Wrap path in quotes to handle spaces in path + return `"${path.join(torDirForPlatform(basePath), `${binName}`.concat(ext))}"` } export const torDirForPlatform = (basePath?: string): string => { 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 4cd3c6c417..f545231518 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 @@ -80,7 +80,10 @@ beforeEach(async () => { ], }) .overrideProvider(TOR_PASSWORD_PROVIDER) - .useValue({ torPassword: '', torHashedPassword: '' }) + .useValue({ + torPassword: 'b5e447c10b0d99e7871636ee5e0839b5', + torHashedPassword: '16:FCFFE21F3D9138906021FAADD9E49703CC41848A95F829E0F6E1BDBE63', + }) .compile() connectionsManagerService = await module.resolve(ConnectionsManagerService) diff --git a/packages/backend/src/nest/tor/tor.module.ts b/packages/backend/src/nest/tor/tor.module.ts index f0024e7b49..576a54ec27 100644 --- a/packages/backend/src/nest/tor/tor.module.ts +++ b/packages/backend/src/nest/tor/tor.module.ts @@ -19,7 +19,7 @@ const torParamsProvider = { LD_LIBRARY_PATH: configOptions.torResourcesPath, HOME: os.homedir(), }, - detached: true, + // detached: true, // TODO: check if this is needed } return { torPath, options } diff --git a/packages/backend/src/nest/tor/tor.service.tor.spec.ts b/packages/backend/src/nest/tor/tor.service.tor.spec.ts index 59e188463b..cab5e0d919 100644 --- a/packages/backend/src/nest/tor/tor.service.tor.spec.ts +++ b/packages/backend/src/nest/tor/tor.service.tor.spec.ts @@ -9,9 +9,10 @@ import { type DirResult } from 'tmp' import { jest } from '@jest/globals' import { TorControlAuthType } from './tor.types' import { TorControl } from './tor-control.service' -import crypto from 'crypto' import { sleep } from '../common/sleep' + jest.setTimeout(200_000) + describe('TorControl', () => { let module: TestingModule let torService: Tor @@ -19,7 +20,8 @@ describe('TorControl', () => { let tmpDir: DirResult let tmpAppDataPath: string - const torPassword = crypto.randomBytes(16).toString('hex') + const torPassword = 'b5e447c10b0d99e7871636ee5e0839b5' + const torHashedPassword = '16:FCFFE21F3D9138906021FAADD9E49703CC41848A95F829E0F6E1BDBE63' beforeEach(async () => { jest.clearAllMocks() @@ -29,7 +31,7 @@ describe('TorControl', () => { imports: [TestModule, TorModule], }) .overrideProvider(TOR_PASSWORD_PROVIDER) - .useValue({ torPassword: torPassword, torHashedPassword: '' }) + .useValue({ torPassword, torHashedPassword }) .overrideProvider(TOR_PARAMS_PROVIDER) .useValue({ torPath: torBinForPlatform(), @@ -148,19 +150,19 @@ describe('TorControl', () => { expect(status).toBe(false) }) - it('should find hanging tor process and kill it', async () => { + it('should find hanging tor processes and kill them', async () => { const processKill = jest.spyOn(process, 'kill') await torService.init() torService.clearHangingTorProcess() - expect(processKill).toHaveBeenCalledTimes(1) + expect(processKill).toHaveBeenCalledTimes(2) // Spawning with {shell:true} starts 2 processes so we need to kill 2 processes }) - it('should find hanging tor process and kill it if Quiet path includes space', async () => { + it('should find hanging tor processes and kill them if Quiet path includes space', async () => { tmpDir = createTmpDir('quietTest Tmp_') // On MacOS quiet data lands in '(...)/Application Support/(...)' which caused problems with grep tmpAppDataPath = tmpQuietDirPath(tmpDir.name) const processKill = jest.spyOn(process, 'kill') await torService.init() torService.clearHangingTorProcess() - expect(processKill).toHaveBeenCalledTimes(1) + expect(processKill).toHaveBeenCalledTimes(2) // Spawning with {shell:true} starts 2 processes so we need to kill 2 processes }) }) diff --git a/packages/backend/src/nest/tor/tor.service.ts b/packages/backend/src/nest/tor/tor.service.ts index 8305b3ebd1..ff1f1fce0a 100644 --- a/packages/backend/src/nest/tor/tor.service.ts +++ b/packages/backend/src/nest/tor/tor.service.ts @@ -1,11 +1,10 @@ import * as child_process from 'child_process' -import crypto from 'crypto' import * as fs from 'fs' import path from 'path' import getPort from 'get-port' import { removeFilesFromDir } from '../common/utils' import { EventEmitter } from 'events' -import { ConnectionProcessInfo, SocketActionTypes, SupportedPlatform } from '@quiet/types' +import { SocketActionTypes, SupportedPlatform } from '@quiet/types' import { Inject, OnModuleInit } from '@nestjs/common' import { ConfigOptions, ServerIoProviderTypes } from '../types' import { CONFIG_OPTIONS, QUIET_DIR, SERVER_IO_PROVIDER, TOR_PARAMS_PROVIDER, TOR_PASSWORD_PROVIDER } from '../const' @@ -16,7 +15,7 @@ import Logger from '../common/logger' export class Tor extends EventEmitter implements OnModuleInit { socksPort: number - process: child_process.ChildProcessWithoutNullStreams | any = null + process: child_process.ChildProcessWithoutNullStreams | null = null torDataDirectory: string torPidPath: string extraTorProcessParams: TorParams @@ -93,7 +92,7 @@ export class Tor extends EventEmitter implements OnModuleInit { try { this.clearHangingTorProcess() } catch (e) { - this.logger('Error occured while trying to clear hanging tor processes') + this.logger('Error occured while trying to clear hanging tor processes', e) } try { @@ -113,7 +112,7 @@ export class Tor extends EventEmitter implements OnModuleInit { resolve() } catch { this.logger('Killing tor') - await this.process.kill() + this.clearHangingTorProcess() removeFilesFromDir(this.torDataDirectory) // eslint-disable-next-line @@ -159,11 +158,15 @@ export class Tor extends EventEmitter implements OnModuleInit { public clearHangingTorProcess() { const torProcessId = child_process.execSync(this.hangingTorProcessCommand()).toString('utf8').trim() if (!torProcessId) return - this.logger(`Found tor process with pid ${torProcessId}. Killing...`) - try { - process.kill(Number(torProcessId), 'SIGTERM') - } catch (e) { - this.logger.error(`Tried killing hanging tor process. Failed. Reason: ${e.message}`) + const ids = torProcessId.split('\n') // Spawning with {shell:true} starts 2 processes + this.logger(`Found tor process(es) with pid(s) ${ids}. Killing...`) + + for (const id of ids) { + try { + process.kill(Number(id.trim())) + } catch (e) { + this.logger.error(`Tried killing hanging tor process with id ${id}. Failed. Reason: ${e.message}`) + } } } @@ -204,6 +207,10 @@ export class Tor extends EventEmitter implements OnModuleInit { reject(new Error("Can't spawn tor - no controlPort")) return } + const options: child_process.SpawnOptionsWithoutStdio = { + ...this.torParamsProvider.options, + shell: true, + } this.process = child_process.spawn( this.torParamsProvider.torPath, @@ -215,15 +222,26 @@ export class Tor extends EventEmitter implements OnModuleInit { '--ControlPort', this.controlPort.toString(), '--PidFile', - this.torPidPath, + `"${this.torPidPath}"`, '--DataDirectory', - this.torDataDirectory, + `"${this.torDataDirectory}"`, '--HashedControlPassword', this.torPasswordProvider.torHashedPassword, // ...this.torProcessParams ], - this.torParamsProvider.options + options ) + this.process.stderr.on('data', e => { + this.logger.error('Tor process. Stderr:', e) + }) + + this.process.on('exit', (code, signal) => { + this.logger(`Tor exited with code ${code} and signal ${signal}`) + }) + + this.process.on('error', err => { + this.logger.error(`Tor process. Error occurred: ${err.message}`) + }) this.process.stdout.on('data', (data: any) => { this.logger(data.toString()) @@ -326,7 +344,7 @@ export class Tor extends EventEmitter implements OnModuleInit { public async kill(): Promise { return await new Promise((resolve, reject) => { - this.logger('Killing tor...') + this.logger('Killing tor... with pid', this.process?.pid) if (this.process === null) { this.logger('TOR: Process is not initalized.') resolve() @@ -342,7 +360,7 @@ export class Tor extends EventEmitter implements OnModuleInit { this.process?.on('error', () => { reject(new Error('TOR: Something went wrong with killing tor process')) }) - this.process?.kill() + this.clearHangingTorProcess() }) } } diff --git a/packages/e2e-tests/src/tests/oneClient.test.ts b/packages/e2e-tests/src/tests/oneClient.test.ts index b02fa17551..05d22a077f 100644 --- a/packages/e2e-tests/src/tests/oneClient.test.ts +++ b/packages/e2e-tests/src/tests/oneClient.test.ts @@ -59,11 +59,14 @@ describe('One Client', () => { const isRegisterModal = await registerModal.element.isDisplayed() expect(isRegisterModal).toBeTruthy() + console.log('Registration - vefore typeUsername') await registerModal.typeUsername('testuser') + console.log('Registration - before submit') await registerModal.submit() + console.log('Registration - after submit') }) - it('User waits for the modal JoiningLoadingPanel to disappear', async () => { + it.skip('User waits for the modal JoiningLoadingPanel to disappear', async () => { const loadingPanelCommunity = new JoiningLoadingPanel(app.driver) const isLoadingPanelCommunity = await loadingPanelCommunity.element.isDisplayed() expect(isLoadingPanelCommunity).toBeTruthy()