Merge pull request #397 from hashicorp/init-connect-ipc-event
Init connect ipc event
This commit is contained in:
15 files changed
+207
-56
No files matched your search
@@ -5,6 +5,14 @@ auth:
|
||||
no-scopes: No scopes were found. To authenticate, ask your administrator to create an scope and grant you access.
|
||||
choose-org: Choose a scope to authenticate.
|
||||
authenticating-with-org: "You are authenticating with {name}."
|
||||
origin:
|
||||
titles:
|
||||
welcome: Welcome to Boundary
|
||||
descriptions:
|
||||
welcome:
|
||||
get-started: To get started, you'll need to
|
||||
step-1: Enter your controller URL
|
||||
step-2: Use your credentials to authenticate in
|
||||
titles:
|
||||
home: Home
|
||||
iam: IAM
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Ember from 'ember';
|
||||
import { assert } from '@ember/debug';
|
||||
import config from '../config/environment';
|
||||
|
||||
/**
|
||||
@@ -25,6 +24,8 @@ class MockIPC {
|
||||
this.origin = origin;
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
connect() {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,9 +41,10 @@ export function initialize() {
|
||||
window.addEventListener('message', async function (event) {
|
||||
if (event.origin !== window.location.origin) return;
|
||||
const { method, payload } = event?.data ?? {};
|
||||
assert(`IPC requires a method, you passed '${method}'.`, method);
|
||||
const response = await mockIPC.invoke(method, payload);
|
||||
event.ports[0].postMessage(response);
|
||||
if (method) {
|
||||
const response = await mockIPC.invoke(method, payload);
|
||||
event.ports[0].postMessage(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export default class ApplicationRoute extends Route.extend(
|
||||
// =services
|
||||
|
||||
@service session;
|
||||
@service ipc;
|
||||
@service origin;
|
||||
|
||||
// =attributes
|
||||
|
||||
@@ -24,26 +24,13 @@ export default class ApplicationRoute extends Route.extend(
|
||||
*/
|
||||
routeIfUnauthenticated = 'index';
|
||||
|
||||
/**
|
||||
* @type {ApplicationAdapter}
|
||||
*/
|
||||
get adapter() {
|
||||
return this.store.adapterFor('application');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the origin specified in the renderer matches the origin
|
||||
* reported by the main process. If they differ, update the main process
|
||||
* origin so that the renderer's CSP can be rewritten to allow requests.
|
||||
*/
|
||||
async beforeModel() {
|
||||
const rendererOrigin = this.session.data.origin;
|
||||
const mainOrigin = await this.ipc.invoke('getOrigin');
|
||||
const isMatch = rendererOrigin === mainOrigin;
|
||||
this.adapter.host = rendererOrigin;
|
||||
if (rendererOrigin && !isMatch) {
|
||||
await this.ipc.invoke('setOrigin', rendererOrigin);
|
||||
}
|
||||
beforeModel() {
|
||||
return this.origin.updateOrigin();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,8 +5,7 @@ import { action } from '@ember/object';
|
||||
export default class OriginRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service session;
|
||||
@service ipc;
|
||||
@service origin;
|
||||
@service intl;
|
||||
@service notify;
|
||||
|
||||
@@ -22,23 +21,17 @@ export default class OriginRoute extends Route {
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Points the API to the specified origin and persists it to the session,
|
||||
* as well as the main process. When the main process receives the origin,
|
||||
* it is expected that the renderer will be restarted.
|
||||
* Points the API to the specified origin. When the main process receives
|
||||
* the origin, it is expected that the renderer will be restarted.
|
||||
* @param {string} origin
|
||||
*/
|
||||
@action
|
||||
async setOrigin(origin) {
|
||||
this.adapter.host = origin;
|
||||
// Check if we have a Boundary endpoint...
|
||||
try {
|
||||
// If scopes load, this could be a Boundary API
|
||||
await this.store.query('scope', {});
|
||||
this.session.set('data.origin', origin);
|
||||
await this.ipc.invoke('setOrigin', origin);
|
||||
await this.origin.setOrigin(origin);
|
||||
this.replaceWith('index');
|
||||
} catch (e) {
|
||||
// If scopes don't load, we assume this is not a Boundary API
|
||||
// If scopes do not load, we assume this is not a Boundary API
|
||||
const errorMessage = this.intl.t(
|
||||
'errors.origin-verification-failed.description'
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service session;
|
||||
@service origin;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -26,4 +27,8 @@ export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
authMethods: this.store.query('auth-method', { scope_id }),
|
||||
});
|
||||
}
|
||||
|
||||
redirect() {
|
||||
if (!this.origin.rendererOrigin) this.replaceWith('index');
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { all } from 'rsvp';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class ScopesScopeProjectsTargetsRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service ipc;
|
||||
@service session;
|
||||
@service notify;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -35,4 +38,21 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Establish a session to current target.
|
||||
*/
|
||||
@action
|
||||
async connect(model) {
|
||||
try {
|
||||
await this.ipc.invoke('connect', {
|
||||
target_id: model.target.id,
|
||||
auth_token: this.session.data.authenticated.token,
|
||||
});
|
||||
} catch(e) {
|
||||
this.notify.error(e.toString(), { closeAfter: null });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import Service from '@ember/service';
|
||||
import { assert } from '@ember/debug';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class OriginService extends Service {
|
||||
|
||||
// =services
|
||||
|
||||
@service ipc;
|
||||
@service store;
|
||||
@service session;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* @type {ApplicationAdapter}
|
||||
*/
|
||||
get adapter() {
|
||||
return this.store.adapterFor('application');
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {?string}
|
||||
*/
|
||||
get rendererOrigin() {
|
||||
return this.session.data.origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?string} origin
|
||||
*/
|
||||
set rendererOrigin(origin) {
|
||||
this.session.set('data.origin', origin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Promise{?string}}
|
||||
*/
|
||||
get mainOrigin() {
|
||||
return this.ipc.invoke('getOrigin');
|
||||
}
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Sets the main origin equal to the current renderer origin (if any).
|
||||
*/
|
||||
async updateOrigin() {
|
||||
const rendererOrigin = this.rendererOrigin;
|
||||
if (rendererOrigin) await this.setOrigin(rendererOrigin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the specified origin is a Boundary API. If so, the app is
|
||||
* updated to use the origin. If it is not a Boundary API, or an origin
|
||||
* wasn't specified, throws an error.
|
||||
* @param {string} origin - protocol://host:port
|
||||
*/
|
||||
async setOrigin(origin) {
|
||||
const originalHost = this.adapter.host;
|
||||
assert(`setOrigin expects a string, you passed ${origin}`, typeof origin === 'string');
|
||||
try {
|
||||
this.adapter.host = origin;
|
||||
await this.store.query('scope', {});
|
||||
this.rendererOrigin = origin;
|
||||
if (origin !== await this.mainOrigin) {
|
||||
await this.ipc.invoke('setOrigin', origin);
|
||||
}
|
||||
} catch (e) {
|
||||
this.adapter.host = originalHost;
|
||||
this.rendererOrigin = null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -197,3 +197,9 @@
|
||||
.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.centered {
|
||||
display: inline-block;
|
||||
margin: auto;
|
||||
transform: translate(0%, -20%);
|
||||
}
|
||||
@@ -1,19 +1,30 @@
|
||||
<Rose::Form
|
||||
class="full-width"
|
||||
@onSubmit={{route-action "setOrigin" this.origin}}
|
||||
@cancel={{false}}
|
||||
as |form|
|
||||
>
|
||||
<Rose::Layout::Centered class="centered">
|
||||
<h2>{{t 'origin.titles.welcome'}}</h2>
|
||||
<p>{{t 'origin.descriptions.welcome.get-started'}}</p>
|
||||
<p>
|
||||
<ol>
|
||||
<li>{{t 'origin.descriptions.welcome.step-1'}}</li>
|
||||
<li>{{t 'origin.descriptions.welcome.step-2'}}</li>
|
||||
</ol>
|
||||
</p>
|
||||
|
||||
<form.input
|
||||
@name="host"
|
||||
@type="text"
|
||||
@value={{this.origin}}
|
||||
@label={{t "form.origin.label"}}
|
||||
@helperText={{t "form.origin.description"}} />
|
||||
<Rose::Form
|
||||
class="full-width"
|
||||
@onSubmit={{route-action "setOrigin" this.origin}}
|
||||
@cancel={{false}}
|
||||
as |form|
|
||||
>
|
||||
|
||||
<form.actions
|
||||
@submitText="Set Origin"
|
||||
@showCancel={{false}} />
|
||||
<form.input
|
||||
@name="host"
|
||||
@type="text"
|
||||
@value={{this.origin}}
|
||||
@label={{t "form.origin.label"}}
|
||||
@helperText={{t "form.origin.description"}} />
|
||||
|
||||
</Rose::Form>
|
||||
<form.actions
|
||||
@submitText="Set Origin"
|
||||
@showCancel={{false}} />
|
||||
|
||||
</Rose::Form>
|
||||
</Rose::Layout::Centered>
|
||||
@@ -36,7 +36,7 @@
|
||||
</Rose::Badge>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Button @style="secondary">
|
||||
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model)}}>
|
||||
{{t "resources.session.actions.connect"}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const APP_NAME = process.env.APP_NAME || 'Boundary';
|
||||
const API_HOST = process.env.API_HOST || '';
|
||||
const ENABLE_MIRAGE = process.env.API_HOST ? false : true;
|
||||
|
||||
module.exports = function (environment) {
|
||||
let ENV = {
|
||||
@@ -29,6 +30,7 @@ module.exports = function (environment) {
|
||||
isElectron: Boolean(process.env.EMBER_CLI_ELECTRON),
|
||||
|
||||
'ember-cli-mirage': {
|
||||
enabled: ENABLE_MIRAGE,
|
||||
directory: '../../addons/api/mirage'
|
||||
},
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ const isDev = require('electron-is-dev');
|
||||
const log = (method, request) => {
|
||||
if (isDev) console.log(`[ipc-main](${method}): `, request);
|
||||
};
|
||||
const { lookpath } = require('lookpath');
|
||||
const { exec } = require('child_process');
|
||||
|
||||
let origin = 'serve://boundary';
|
||||
|
||||
@@ -28,3 +30,24 @@ ipcMain.handle('setOrigin', async (event, request) => {
|
||||
origin = request?.data;
|
||||
return origin;
|
||||
});
|
||||
|
||||
/**
|
||||
* Establishes a boundary session and returns session details.
|
||||
* TODO: Return session details.
|
||||
*/
|
||||
ipcMain.handle('connect', async (event, request) => {
|
||||
log('connect payload', request);
|
||||
|
||||
const cliAvailable = await lookpath('boundary');
|
||||
log('cli check', cliAvailable);
|
||||
if(!cliAvailable) { throw new Error('CLI unavailable.'); }
|
||||
|
||||
const connectCmd = `boundary connect -target-id=${request.target_id} -token=${request.auth_token}`;
|
||||
log('connect command', connectCmd);
|
||||
|
||||
exec(connectCmd, (output) => {
|
||||
log('connect output', output);
|
||||
}, (error) => {
|
||||
log('connect error', error);
|
||||
});
|
||||
});
|
||||
@@ -33,11 +33,10 @@
|
||||
"@ember/optional-features": "^1.3.0",
|
||||
"@glimmer/component": "^1.0.1",
|
||||
"@glimmer/tracking": "^1.0.0",
|
||||
"core": "*",
|
||||
"rose": "*",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"core": "*",
|
||||
"doctoc": "^1.4.0",
|
||||
"ember-auto-import": "^1.6.0",
|
||||
"ember-cli": "~3.20.2",
|
||||
@@ -67,9 +66,11 @@
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"faker": "^5.1.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"lookpath": "^1.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.1.2",
|
||||
"qunit-dom": "^1.2.0",
|
||||
"rose": "*",
|
||||
"sass": "^1.29.0",
|
||||
"sass-lint": "^1.13.1",
|
||||
"sinon": "^9.2.1"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Service | origin', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let service = this.owner.lookup('service:origin');
|
||||
assert.ok(service);
|
||||
});
|
||||
});
|
||||
@@ -14044,6 +14044,11 @@ longest@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/longest/-/longest-2.0.1.tgz#781e183296aa94f6d4d916dc335d0d17aefa23f8"
|
||||
integrity sha1-eB4YMpaqlPbU2RbcM10NF676I/g=
|
||||
|
||||
lookpath@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lookpath/-/lookpath-1.1.0.tgz#932d68371a2f0b4a5644f03d6a2b4728edba96d2"
|
||||
integrity sha512-B9NM7XpVfkyWqfOBI/UW0kVhGw7pJztsduch+1wkbYDi90mYK6/InFul3lG0hYko/VEcVMARVBJ5daFRc5aKCw==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
@@ -15332,15 +15337,15 @@ npm-run-path@^4.0.0:
|
||||
dependencies:
|
||||
path-key "^3.0.0"
|
||||
|
||||
npm-user-validate@^1.0.1, npm-user-validate@~1.0.0:
|
||||
npm-user-validate@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.1.tgz#31428fc5475fe8416023f178c0ab47935ad8c561"
|
||||
integrity sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw==
|
||||
|
||||
npm@5.1.0, npm@~6.14.6:
|
||||
version "6.14.8"
|
||||
resolved "https://registry.yarnpkg.com/npm/-/npm-6.14.8.tgz#64ef754345639bc035982ec3f609353c8539033c"
|
||||
integrity sha512-HBZVBMYs5blsj94GTeQZel7s9odVuuSUHy1+AlZh7rPVux1os2ashvEGLy/STNK7vUjbrCg5Kq9/GXisJgdf6A==
|
||||
version "6.14.9"
|
||||
resolved "https://registry.yarnpkg.com/npm/-/npm-6.14.9.tgz#d2b4237562bfd95689249e2c2874700ed952ed82"
|
||||
integrity sha512-yHi1+i9LyAZF1gAmgyYtVk+HdABlLy94PMIDoK1TRKWvmFQAt5z3bodqVwKvzY0s6dLqQPVsRLiwhJfNtiHeCg==
|
||||
dependencies:
|
||||
JSONStream "^1.3.5"
|
||||
abbrev "~1.1.1"
|
||||
@@ -15414,7 +15419,7 @@ npm@5.1.0, npm@~6.14.6:
|
||||
npm-pick-manifest "^3.0.2"
|
||||
npm-profile "^4.0.4"
|
||||
npm-registry-fetch "^4.0.7"
|
||||
npm-user-validate "~1.0.0"
|
||||
npm-user-validate "^1.0.1"
|
||||
npmlog "~4.1.2"
|
||||
once "~1.4.0"
|
||||
opener "^1.5.1"
|
||||
|
||||
Reference in new issue
Block a user