diff --git a/ui/desktop/app/routes/scopes/scope/projects/sessions.js b/ui/desktop/app/routes/scopes/scope/projects/sessions.js index 908636f1a..cd11b0569 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/sessions.js +++ b/ui/desktop/app/routes/scopes/scope/projects/sessions.js @@ -84,6 +84,6 @@ export default class ScopesScopeProjectsSessionsRoute extends Route { @notifySuccess('notifications.canceled-success') async cancelSession(session) { await session.cancelSession(); - await this.ipc.invoke('cancel', { session_id: session.id }); + await this.ipc.invoke('stop', { session_id: session.id }); } } diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets/target/sessions.js b/ui/desktop/app/routes/scopes/scope/projects/targets/target/sessions.js index 2263aa448..f84af4eac 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets/target/sessions.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets/target/sessions.js @@ -19,6 +19,6 @@ export default class ScopesScopeProjectsTargetsTargetSessionsRoute extends Route @notifySuccess('notifications.canceled-success') async cancelSession(session) { await session.cancelSession(); - await this.ipc.invoke('cancel', { session_id: session.id }); + await this.ipc.invoke('stop', { session_id: session.id }); } } diff --git a/ui/desktop/electron-app/src/helpers/spawn-promise.js b/ui/desktop/electron-app/src/helpers/spawn-promise.js index 86ac21f68..b8ff5d980 100644 --- a/ui/desktop/electron-app/src/helpers/spawn-promise.js +++ b/ui/desktop/electron-app/src/helpers/spawn-promise.js @@ -37,7 +37,7 @@ module.exports = { outputStream += data.toString(); const jsonData = jsonify(outputStream); if (jsonData) { - childProcess.removeListener('data', processData); + childProcess.removeAllListeners(); resolve({ childProcess, response: jsonData }); } }; @@ -50,12 +50,12 @@ module.exports = { const jsonData = jsonify(errorStream); if (jsonData) { const error = jsonData.api_error || jsonData.error; - childProcess.removeListener('error', processError); + childProcess.removeAllListeners(); reject(new Error(error.message)); } }; - childProcess.stderr.on('data', processError); + childProcess.stderr.on('error', processError); }); }, diff --git a/ui/desktop/electron-app/src/ipc/handlers.js b/ui/desktop/electron-app/src/ipc/handlers.js index d3a8d4efd..e727a4b48 100644 --- a/ui/desktop/electron-app/src/ipc/handlers.js +++ b/ui/desktop/electron-app/src/ipc/handlers.js @@ -61,4 +61,4 @@ handle('connect', ({ target_id, token, host_id }) => /** * Cancel an established boundary session spawned process. */ -handle('cancel', ({ session_id }) => sessionManager.stopById(session_id)); +handle('stop', ({ session_id }) => sessionManager.stopById(session_id)); diff --git a/ui/desktop/electron-app/src/models/session-manager.js b/ui/desktop/electron-app/src/models/session-manager.js new file mode 100644 index 000000000..dfcfd923a --- /dev/null +++ b/ui/desktop/electron-app/src/models/session-manager.js @@ -0,0 +1,33 @@ +const Session = require('./session.js'); + +class SessionManager { + #sessions = []; + + /** + * Start a session and track it. + * Returns session proxy details if successfully started. + * @param {string} addr + * @param {string} target_id + * @param {string} token + * @param {string} host_id + */ + start(addr, target_id, token, host_id) { + const session = new Session(addr, target_id, token, host_id); + this.#sessions.push(session); + return session.start(); + } + + /** + * Stop a session using identifier. + * @param {string} session_id + */ + stopById(session_id) { + const session = this.#sessions.find((session) => session.id === session_id); + return session.stop(); + } + + stopAll() { + } +} + +module.exports = SessionManager; diff --git a/ui/desktop/electron-app/src/session/index.js b/ui/desktop/electron-app/src/models/session.js similarity index 78% rename from ui/desktop/electron-app/src/session/index.js rename to ui/desktop/electron-app/src/models/session.js index 34f4440b7..105b7d723 100644 --- a/ui/desktop/electron-app/src/session/index.js +++ b/ui/desktop/electron-app/src/models/session.js @@ -1,16 +1,5 @@ const { spawnAsyncJSONPromise } = require('../helpers/spawn-promise.js'); -/** - * Super paranoid shell quote/escape and validation. Input must be base62. - * @param {string} str - */ -const escapeAndValidateBase62 = (str) => { - const candidate = str.toString(); - if (candidate.match(/^[A-Za-z0-9_]*$/)) return candidate; - throw new Error(` - Could not invoke command: - input contained unsafe characters. - `); -}; +const base62 = require('../utils/base62.js'); class Session { #id; @@ -88,20 +77,21 @@ class Session { */ cliCommand() { const sanitized = { - target_id: escapeAndValidateBase62(this.#targetId), - token: escapeAndValidateBase62(this.#token), + target_id: base62.escapeAndValidate(this.#targetId), + token: base62.escapeAndValidate(this.#token), + addr: encodeURI(this.#addr), }; const command = [ 'connect', `-target-id=${sanitized.target_id}`, `-token=${sanitized.token}`, - `-addr=${this.#addr}`, + `-addr=${sanitized.addr}`, '-format=json', ]; if (this.#hostId) { - sanitized.host_id = escapeAndValidateBase62(this.#hostId); + sanitized.host_id = base62.escapeAndValidate(this.#hostId); command.push(`-host-id=${sanitized.host_id}`); } return command; diff --git a/ui/desktop/electron-app/src/session/manager.js b/ui/desktop/electron-app/src/session/manager.js index 63b3a8943..dc96e096f 100644 --- a/ui/desktop/electron-app/src/session/manager.js +++ b/ui/desktop/electron-app/src/session/manager.js @@ -1,47 +1,7 @@ -const Session = require('./index.js'); - -class SessionManager { - #sessions; - - constructor() { - this.#sessions = []; - } - - /** - * Checks for active sessions - * @returns {boolean} - */ - get hasActiveSessions() { - return Boolean(this.#sessions.find((session) => session.isActive)); - } - - /** - * Start a session and track it. - * Returns session proxy details if successfully started. - * @param {string} addr - * @param {string} target_id - * @param {string} token - * @param {string} host_id - */ - async start(addr, target_id, token, host_id) { - const session = new Session(addr, target_id, token, host_id); - this.#sessions.push(session); - return session.start(); - } - - /** - * Stop a session using identifier. - * @param {string} session_id - */ - async stopById(session_id) { - const session = this.#sessions.find((session) => session.id === session_id); - return session && session.stop(); - } - - stopAll() { - this.#sessions.forEach((session) => session.stop()); - } -} +const SessionManager = require('../models/session-manager.js'); +/** + * Singleton for SessionManager + */ const sessionManager = new SessionManager(); module.exports = sessionManager; diff --git a/ui/desktop/electron-app/src/utils/base62.js b/ui/desktop/electron-app/src/utils/base62.js new file mode 100644 index 000000000..0c844b790 --- /dev/null +++ b/ui/desktop/electron-app/src/utils/base62.js @@ -0,0 +1,14 @@ +module.exports = { + /** + * Super paranoid shell quote/escape and validation. Input must be base62. + * @param {string} str + */ + escapeAndValidate: (str) => { + const candidate = str.toString(); + if (candidate.match(/^[A-Za-z0-9_]*$/)) return candidate; + throw new Error(` + Could not invoke command: + input contained unsafe characters. + `); + }, +}; diff --git a/ui/desktop/tests/acceptance/projects/sessions-test.js b/ui/desktop/tests/acceptance/projects/sessions-test.js index 012db14cb..2cdbafc16 100644 --- a/ui/desktop/tests/acceptance/projects/sessions-test.js +++ b/ui/desktop/tests/acceptance/projects/sessions-test.js @@ -245,7 +245,7 @@ module('Acceptance | projects | sessions', function (hooks) { test('cancelling a session', async function (assert) { assert.expect(1); - stubs.ipcService.withArgs('cancel'); + stubs.ipcService.withArgs('stop'); later(async () => { run.cancelTimers(); await click('tbody tr:first-child td:last-child button'); @@ -267,7 +267,7 @@ module('Acceptance | projects | sessions', function (hooks) { test('cancelling a session with ipc error shows notification', async function (assert) { assert.expect(1); - stubs.ipcService.withArgs('cancel').throws(); + stubs.ipcService.withArgs('stop').throws(); later(async () => { run.cancelTimers(); await click('tbody tr:first-child td:last-child button'); diff --git a/ui/desktop/tests/acceptance/projects/targets/sessions-test.js b/ui/desktop/tests/acceptance/projects/targets/sessions-test.js index 9fde4b210..e6031ab88 100644 --- a/ui/desktop/tests/acceptance/projects/targets/sessions-test.js +++ b/ui/desktop/tests/acceptance/projects/targets/sessions-test.js @@ -271,7 +271,7 @@ module('Acceptance | projects | targets | sessions', function (hooks) { test('cancelling a session with ipc error shows notification', async function (assert) { assert.expect(1); - stubs.ipcService.withArgs('cancel').throws(); + stubs.ipcService.withArgs('stop').throws(); later(async () => { run.cancelTimers(); await click('tbody tr:first-child td:last-child button');