feat: 🎸 add origin flow

This commit is contained in:
Randall Morey committed 2020-11-13 15:20:26 -05:00
1 parent baca882299
commit aa40a3c917
14 files changed
+285 -12

No files matched your search

View File
Whitespace-only changes.
+13
View File
@@ -0,0 +1,13 @@
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import config from '../config/environment';
export default class ApplicationController extends Controller {
// =services
@service session;
// =attributes
notifyTimeout = config.notifyTimeout;
}
+1
View File
@@ -15,4 +15,5 @@ Router.map(function () {
});
});
});
this.route('origin');
});
+26
View File
@@ -16,11 +16,37 @@ export default class ApplicationRoute extends Route.extend(
// =services
@service session;
@service ipc;
// =attributes
/**
* @type {string}
*/
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);
}
}
/**
* After becoming authenticated, does nothing. This overrides the default
* behavior of the ApplicationRouteMixin, which is to redirect after auth.
+13 -2
View File
@@ -1,12 +1,23 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default class IndexRoute extends Route {
// =services
@service session;
// =methods
/**
* Redirects to scopes route for further processing.
* If no origin is specified yet, redirects to origin, otherwise scopes.
*/
redirect() {
this.replaceWith('scopes');
const rendererOrigin = this.session.data.origin;
if (!rendererOrigin) {
this.replaceWith('origin');
} else {
this.replaceWith('scopes');
}
}
}
+49
View File
@@ -0,0 +1,49 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
export default class OriginRoute extends Route {
// =services
@service session;
@service ipc;
@service intl;
@service notify;
// =attributes
/**
* @type {ApplicationAdapter}
*/
get adapter() {
return this.store.adapterFor('application');
}
// =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.
* @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);
this.replaceWith('index');
} catch (e) {
// If scopes don't load, we assume this is not a Boundary API
const errorMessage =
this.intl.t('errors.origin-verification-failed.description');
this.notify.error(errorMessage, { closeAfter: null });
}
}
}
+17 -3
View File
@@ -6,9 +6,12 @@
<Rose::Header class="{{if (is-loading) " loading"}}" as |header|>
<header.brand @logo="logo-app" @text={{app-name}} />
<header.nav>
{{outlet "header-nav"}}
</header.nav>
{{#if this.session.isAuthenticated}}
<header.nav>
{{outlet "header-nav"}}
</header.nav>
{{/if}}
</Rose::Header>
</layout.header>
@@ -17,3 +20,14 @@
</layout.body>
</Rose::Layout::Global>
<EmberNotify @closeAfter={{this.notifyTimeout}} as |message close|>
<Rose::Notification
@style={{message.type}}
@heading={{t (concat 'states.' message.type)}}
@dismiss={{close}}
@dismissText={{t 'actions.dismiss'}}
>
{{message.text}}
</Rose::Notification>
</EmberNotify>
+19
View File
@@ -0,0 +1,19 @@
<Rose::Form
class="full-width"
@onSubmit={{route-action "setOrigin" this.origin}}
@cancel={{false}}
as |form|
>
<form.input
@name="host"
@type="text"
@value={{this.origin}}
@label={{t "form.origin.label"}}
@helperText={{t "form.origin.description"}} />
<form.actions
@submitText="Set Origin"
@showCancel={{false}} />
</Rose::Form>
+94
View File
@@ -0,0 +1,94 @@
/*jshint node:true*/
module.exports = function (/* environment */) {
return {
/**
* Merges the fallback locale's translations into all other locales as a
* build-time fallback strategy.
*
* This will **not** prevent missing translation warnings or errors from occurring.
* It's meant as safety net when warning are enabled.
* When enabled along with `errorOnMissingTranslations` any fallback attempts will result in an error.
*
* @property fallbackLocale
* @type {String?}
* @default "null"
*/
fallbackLocale: null,
/**
* Path where translations are kept. This is relative to the project root.
* For example, if your translations are an npm dependency, set this to:
*`'./node_modules/path/to/translations'`
*
* @property inputPath
* @type {String}
* @default "'translations'"
*/
inputPath: 'translations',
/**
* Prevents the translations from being bundled with the application code.
* This enables asynchronously loading the translations for the active locale
* by fetching them from the asset folder of the build.
*
* See: https://ember-intl.github.io/ember-intl/docs/guide/asynchronously-loading-translations
*
* @property publicOnly
* @type {Boolean}
* @default "false"
*/
publicOnly: false,
/**
* Add the subdirectories of the translations as a namespace for all keys.
*
* @property wrapTranslationsWithNamespace
* @type {Boolean}
* @default "false"
*/
wrapTranslationsWithNamespace: true,
/**
* Cause a build error if ICU argument mismatches are detected.
*
* @property errorOnNamedArgumentMismatch
* @type {Boolean}
* @default "false"
*/
errorOnNamedArgumentMismatch: false,
/**
* Cause a build error if missing translations are detected.
*
* See https://ember-intl.github.io/ember-intl/docs/guide/missing-translations#throwing-a-build-error-on-missing-required-translation
*
* @property errorOnMissingTranslations
* @type {Boolean}
* @default "false"
*/
errorOnMissingTranslations: false,
/**
* removes empty translations from the build output.
*
* @property stripEmptyTranslations
* @type {Boolean}
* @default "false"
*/
stripEmptyTranslations: false,
/**
* Filter missing translations to ignore expected missing translations.
*
* See https://ember-intl.github.io/ember-intl/docs/guide/missing-translations#requiring-translations
*
* @property requiresTranslation
* @type {Function}
* @default "function(key,locale) { return true }"
*/
requiresTranslation(/* key, locale */) {
return true;
},
};
};
+7
View File
@@ -26,12 +26,16 @@ module.exports = function (environment) {
// when it is created
},
isElectron: Boolean(process.env.EMBER_CLI_ELECTRON),
api: {
host: API_HOST,
namespace: 'v1',
},
appName: APP_NAME,
notifyTimeout: 4000,
};
if (environment === 'development') {
@@ -52,6 +56,9 @@ module.exports = function (environment) {
ENV.APP.rootElement = '#ember-testing';
ENV.APP.autoboot = false;
// Notification timeout should be 0 for fast tests
ENV.notifyTimeout = 0;
}
if (environment === 'production') {
+20 -7
View File
@@ -1,17 +1,30 @@
const { ipcMain } = require('electron');
const isDev = require('electron-is-dev');
const log = (method, request) => {
if (isDev) console.log(`[ipc-main](${method}): `, request);
}
let origin = 'serve://boundary';
/**
* A demonstration method `getFoobars` which returns an object with a message.
* Returns the current runtime origin, which is used by the main thread to
* rewrite the CSP to allow requests.
*/
ipcMain.handle('getFoobars', async (event, request) => {
log('getFoobars', request);
return {
message: 'this is a response from an ipcMain handler'
};
ipcMain.handle('getOrigin', async (event, request) => {
log('getOrigin', request);
return origin;
});
/**
* TODO: sets the main process origin to the specified value. This value will
* be used to rewrite the CSP to allow requests to that origin. If the origin
* was not previously set, the main process will set it and restart
* the renderer.
*
* For now, we update the internal origin but do not yet rewrite the CSP.
*/
ipcMain.handle('setOrigin', async (event, request) => {
log('setOrigin', request);
origin = request.data;
return origin;
});
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | application', function(hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function(assert) {
let controller = this.owner.lookup('controller:application');
assert.ok(controller);
});
});
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | origin', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:origin');
assert.ok(route);
});
});
+3
View File
@@ -11,3 +11,6 @@ type:
help: The type of the resource, to help differentiate schemas
project:
label: Project
origin:
label: Origin
description: The Boundary server origin (e.g. protocol://host)