Merge pull request #397 from hashicorp/init-connect-ipc-event

Init connect ipc event
This commit is contained in:
Randall Morey authored and GitHub committed 2020-11-25 11:30:00 -05:00
commit f3011416a5
15 files changed
+207 -56

No files matched your search

+8
View File
@@ -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
+6 -4
View File
@@ -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);
}
});
}
}
+3 -16
View File
@@ -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 -12
View File
@@ -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 });
}
}
}
+76
View File
@@ -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;
}
}
}
+6
View File
@@ -197,3 +197,9 @@
.nowrap {
white-space: nowrap;
}
.centered {
display: inline-block;
margin: auto;
transform: translate(0%, -20%);
}
+27 -16
View File
@@ -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
View File
@@ -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'
},
+23
View File
@@ -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);
});
});
+3 -2
View File
@@ -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);
});
});
+10 -5
View File
@@ -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"