diff --git a/addons/api/addon/handlers/client-daemon-handler.js b/addons/api/addon/handlers/client-daemon-handler.js index 573bc42ba..cdaf83e09 100644 --- a/addons/api/addon/handlers/client-daemon-handler.js +++ b/addons/api/addon/handlers/client-daemon-handler.js @@ -106,10 +106,12 @@ export default class ClientDaemonHandler { } const sessionData = this.session.data?.authenticated; const auth_token_id = sessionData?.id; + const token = sessionData?.token; remainingQuery = { ...remainingQuery, query: searchQuery, auth_token_id, + token, resource: pluralize(type), }; @@ -125,7 +127,7 @@ export default class ClientDaemonHandler { if (e.statusCode === 403 || e.statusCode === 401) { await this.ipc.invoke('addTokenToClientDaemon', { tokenId: auth_token_id, - token: sessionData?.token, + token, }); try { clientDaemonResults = await this.ipc.invoke( diff --git a/ui/desktop/electron-app/src/cli/index.js b/ui/desktop/electron-app/src/cli/index.js index 436d4e710..0d30081d1 100644 --- a/ui/desktop/electron-app/src/cli/index.js +++ b/ui/desktop/electron-app/src/cli/index.js @@ -13,9 +13,9 @@ module.exports = { // Returns JSON-formatted version information from the CLI version: () => { const command = ['-v']; - const rawOutput = spawnSync(command); - let gitRevision = /Git Revision:\s*(?.*)\n/.exec(rawOutput); - let versionNumber = /Version Number:\s*(?.*)\n/.exec(rawOutput); + const { stdout } = spawnSync(command); + let gitRevision = /Git Revision:\s*(?.*)\n/.exec(stdout); + let versionNumber = /Version Number:\s*(?.*)\n/.exec(stdout); if (gitRevision) gitRevision = gitRevision.groups.rev; if (versionNumber) versionNumber = versionNumber.groups.ver; const formatted = versionNumber diff --git a/ui/desktop/electron-app/src/helpers/spawn-promise.js b/ui/desktop/electron-app/src/helpers/spawn-promise.js index 306942070..b3b5fa8eb 100644 --- a/ui/desktop/electron-app/src/helpers/spawn-promise.js +++ b/ui/desktop/electron-app/src/helpers/spawn-promise.js @@ -74,12 +74,19 @@ module.exports = { * Spawn child process and return output immediately. * This function is intended for non-connection related tasks. * @param {string} command - * @return {string} - */ - spawnSync(command) { - const childProcess = spawnSync(path(), command); - const rawOutput = childProcess.stdout.toString(); - return rawOutput; + * @param {object} envVars + * @returns {{stdout: string | undefined, stderr: string | undefined}} */ + spawnSync(command, envVars = {}) { + const childProcess = spawnSync(path(), command, { + env: { + ...process.env, + ...envVars, + }, + }); + + const { stdout, stderr } = childProcess; + + return { stdout: stdout?.toString(), stderr: stderr?.toString() }; }, /** diff --git a/ui/desktop/electron-app/src/services/client-daemon-manager.js b/ui/desktop/electron-app/src/services/client-daemon-manager.js index dc5f3afa4..649c34988 100644 --- a/ui/desktop/electron-app/src/services/client-daemon-manager.js +++ b/ui/desktop/electron-app/src/services/client-daemon-manager.js @@ -3,10 +3,16 @@ * SPDX-License-Identifier: MPL-2.0 */ -const { spawnSync, spawn } = require('../helpers/spawn-promise'); +const { + spawnSync, + spawn, + spawnAsyncJSONPromise, +} = require('../helpers/spawn-promise'); const jsonify = require('../utils/jsonify.js'); const { unixSocketRequest } = require('../helpers/request-promise'); const runtimeSettings = require('./runtime-settings'); +const sanitizer = require('../utils/sanitizer.js'); +const { isWindows } = require('../helpers/platform.js'); class ClientDaemonManager { #socketPath; @@ -22,9 +28,9 @@ class ClientDaemonManager { */ status() { const daemonStatusCommand = ['daemon', 'status', '-format=json']; - const data = spawnSync(daemonStatusCommand); + const { stdout } = spawnSync(daemonStatusCommand); - const status = jsonify(data); + const status = jsonify(stdout); this.#socketPath = status?.item?.socket_address; return status; } @@ -35,6 +41,7 @@ class ClientDaemonManager { */ async start() { const startDaemonCommand = ['daemon', 'start']; + // We use spawn here because we want to check the stderr for specific logs const { stderr } = await spawn(startDaemonCommand); // If we get a null/undefined, err on safe side and don't stop daemon when @@ -65,11 +72,16 @@ class ClientDaemonManager { * @param tokenId * @returns {Promise} */ - addToken({ token, tokenId }) { + async addToken({ token, tokenId }) { + // TODO: Should this and search just always call CLI even for non-Windows? + if (isWindows()) { + return addTokenCliCommand(token); + } + const postBody = { - auth_token_id: tokenId, + auth_token_id: sanitizer.base62EscapeAndValidate(tokenId), boundary_addr: runtimeSettings.clusterUrl, - auth_token: token, + auth_token: sanitizer.base62EscapeAndValidate(token), }; const request = { method: 'POST', @@ -80,6 +92,11 @@ class ClientDaemonManager { } search(requestData) { + if (isWindows()) { + return searchCliCommand(requestData); + } + + delete requestData.token; const queryString = new URLSearchParams(requestData); const request = { @@ -91,5 +108,65 @@ class ClientDaemonManager { } } +const addTokenCliCommand = (token) => { + const addTokenCommand = [ + 'daemon', + 'add-token', + '-format=json', + '-token=env://BOUNDARY_TOKEN', + '-keyring-type=none', + ]; + const sanitizedToken = sanitizer.base62EscapeAndValidate(token); + + // TODO: Is setting an env var for the address the only way? I'm surprised to not see an -addr flag + const { stdout, stderr } = spawnSync(addTokenCommand, { + BOUNDARY_TOKEN: sanitizedToken, + BOUNDARY_ADDR: runtimeSettings.clusterUrl, + }); + let parsedResponse = jsonify(stdout); + + if (parsedResponse?.status_code === 204) { + // 204 has no response body + return; + } + + parsedResponse = jsonify(stderr); + return Promise.reject({ + statusCode: parsedResponse?.status_code, + ...parsedResponse?.api_error, + }); +}; + +const searchCliCommand = (requestData) => { + const searchCommand = [ + 'search', + '-format=json', + '-token=env://BOUNDARY_TOKEN', + `-resource=${requestData.resource}`, + ]; + if (requestData.query) { + searchCommand.push(`-query=${requestData.query}`); + } + if (requestData.filter) { + searchCommand.push(`-filter=${requestData.filter}`); + } + const sanitizedToken = sanitizer.base62EscapeAndValidate(requestData.token); + + const { stdout, stderr } = spawnSync(searchCommand, { + BOUNDARY_TOKEN: sanitizedToken, + }); + let parsedResponse = jsonify(stdout); + + if (parsedResponse?.status_code === 200) { + return parsedResponse?.item; + } + + parsedResponse = jsonify(stderr); + return Promise.reject({ + statusCode: parsedResponse?.status_code, + ...parsedResponse?.api_error, + }); +}; + // Export an instance so we get a singleton module.exports = new ClientDaemonManager();