diff --git a/ui-v2/app/adapters/oidc-provider.js b/ui-v2/app/adapters/oidc-provider.js index 7d1da6df3c8a..f2b65da2e220 100644 --- a/ui-v2/app/adapters/oidc-provider.js +++ b/ui-v2/app/adapters/oidc-provider.js @@ -33,7 +33,7 @@ export default Adapter.extend({ ${{ ...Namespace(ns), AuthMethod: id, - RedirectURI: `${this.env.var('CONSUL_BASE_UI_URL')}/torii/redirect.html`, + RedirectURI: `${this.env.var('CONSUL_BASE_UI_URL')}/oidc/callback`, }} `; }, diff --git a/ui-v2/app/components/auth-dialog/README.mdx b/ui-v2/app/components/auth-dialog/README.mdx new file mode 100644 index 000000000000..676d55379661 --- /dev/null +++ b/ui-v2/app/components/auth-dialog/README.mdx @@ -0,0 +1,56 @@ +## AuthDialog + +```handlebars + + {{#let components.AuthForm components.AuthProfile as |AuthForm AuthProfile|}} + + Here's the login form: + + + + Here's your profile: + + + Contact your administrator for login credentials. + +{{#if (env 'CONSUL_SSO_ENABLED')}} + + {{#if (gt providers.length 0)}} +

+ or +

+ {{/if}} + +{{/if}} + + + + + \ No newline at end of file diff --git a/ui-v2/app/components/auth-form/index.js b/ui-v2/app/components/auth-form/index.js new file mode 100644 index 000000000000..c6a95e277591 --- /dev/null +++ b/ui-v2/app/components/auth-form/index.js @@ -0,0 +1,21 @@ +import Component from '@ember/component'; + +import chart from './chart.xstate'; + +export default Component.extend({ + tagName: '', + onsubmit: function(e) {}, + onchange: function(e) {}, + init: function() { + this._super(...arguments); + this.chart = chart; + }, + actions: { + hasValue: function(context, event, meta) { + return this.value !== '' && typeof this.value !== 'undefined'; + }, + focus: function() { + this.input.focus(); + }, + }, +}); diff --git a/ui-v2/app/components/auth-profile/README.mdx b/ui-v2/app/components/auth-profile/README.mdx new file mode 100644 index 000000000000..f4fa59771d18 --- /dev/null +++ b/ui-v2/app/components/auth-profile/README.mdx @@ -0,0 +1,20 @@ +## AuthProfile + +```handlebars + +``` + +A straightforward partial-like component for rendering a user profile. + +### Arguments + +| Argument | Type | Default | Description | +| --- | --- | --- | --- | +| `item` | `Object` | | A Consul shaped token object (currently only requires an AccessorID property to be set | + +### See + +- [Component Source Code](./index.js) +- [Template Source Code](./index.hbs) + +--- diff --git a/ui-v2/app/components/auth-profile/index.hbs b/ui-v2/app/components/auth-profile/index.hbs new file mode 100644 index 000000000000..6f46fd37cbd9 --- /dev/null +++ b/ui-v2/app/components/auth-profile/index.hbs @@ -0,0 +1,9 @@ +
+
+ My ACL Token
+ AccessorID +
+
+ {{substr item.AccessorID -8}} +
+
\ No newline at end of file diff --git a/ui-v2/app/components/auth-profile/index.js b/ui-v2/app/components/auth-profile/index.js new file mode 100644 index 000000000000..4798652642ba --- /dev/null +++ b/ui-v2/app/components/auth-profile/index.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', +}); diff --git a/ui-v2/app/components/child-selector/index.hbs b/ui-v2/app/components/child-selector/index.hbs index 51fd38ef93ab..1b13235f0e6b 100644 --- a/ui-v2/app/components/child-selector/index.hbs +++ b/ui-v2/app/components/child-selector/index.hbs @@ -1,3 +1,4 @@ +
{{yield}} {{yield}}
\ No newline at end of file diff --git a/ui-v2/app/components/child-selector/index.js b/ui-v2/app/components/child-selector/index.js index cf01b8138a8e..c1f58ef5b8ac 100644 --- a/ui-v2/app/components/child-selector/index.js +++ b/ui-v2/app/components/child-selector/index.js @@ -8,6 +8,7 @@ import WithListeners from 'consul-ui/mixins/with-listeners'; export default Component.extend(SlotsMixin, WithListeners, { onchange: function() {}, + tagName: '', error: function() {}, type: '', diff --git a/ui-v2/app/components/dom-buffer/index.js b/ui-v2/app/components/dom-buffer/index.js index 63c4d62f8339..eb2706e88164 100644 --- a/ui-v2/app/components/dom-buffer/index.js +++ b/ui-v2/app/components/dom-buffer/index.js @@ -9,10 +9,11 @@ export default Component.extend({ }, didInsertElement: function() { this._super(...arguments); - this.buffer.add(this.getBufferName(), this.element); + this._element = this.buffer.add(this.getBufferName(), this.element); }, didDestroyElement: function() { this._super(...arguments); - this.buffer.remove(this.getBufferName()); + this.buffer.remove(this.getBufferName(), this._element); + this._element = null; }, }); diff --git a/ui-v2/app/components/form-component/index.js b/ui-v2/app/components/form-component/index.js index 0be3328b03f0..520cb2148e09 100644 --- a/ui-v2/app/components/form-component/index.js +++ b/ui-v2/app/components/form-component/index.js @@ -6,6 +6,7 @@ import WithListeners from 'consul-ui/mixins/with-listeners'; // match anything that isn't a [ or ] into multiple groups const propRe = /([^[\]])+/g; export default Component.extend(WithListeners, SlotsMixin, { + tagName: '', onreset: function() {}, onchange: function() {}, onerror: function() {}, diff --git a/ui-v2/app/components/hashicorp-consul/index.hbs b/ui-v2/app/components/hashicorp-consul/index.hbs index cbf153e0cd34..2ca5f255ed59 100644 --- a/ui-v2/app/components/hashicorp-consul/index.hbs +++ b/ui-v2/app/components/hashicorp-consul/index.hbs @@ -1,4 +1,4 @@ - +
Consul @@ -100,7 +100,7 @@ @@ -172,5 +185,4 @@

Consul {{env 'CONSUL_VERSION'}}

Documentation {{{concat ''}}} - - \ No newline at end of file + \ No newline at end of file diff --git a/ui-v2/app/components/hashicorp-consul/index.js b/ui-v2/app/components/hashicorp-consul/index.js index 6a6767a673ba..72f5f9f90e14 100644 --- a/ui-v2/app/components/hashicorp-consul/index.js +++ b/ui-v2/app/components/hashicorp-consul/index.js @@ -1,19 +1,12 @@ import Component from '@ember/component'; import { inject as service } from '@ember/service'; -import { get, set, computed } from '@ember/object'; -import { getOwner } from '@ember/application'; +import { computed } from '@ember/object'; export default Component.extend({ dom: service('dom'), - env: service('env'), - feedback: service('feedback'), - router: service('router'), - http: service('repository/type/event-source'), - client: service('client/http'), - store: service('store'), - settings: service('settings'), didInsertElement: function() { + this._super(...arguments); this.dom.root().classList.remove('template-with-vertical-menu'); }, // TODO: Right now this is the only place where we need permissions @@ -25,108 +18,15 @@ export default Component.extend({ }) !== 'undefined' ); }), - forwardForACL: function(token) { - let routeName = this.router.currentRouteName; - const route = getOwner(this).lookup(`route:${routeName}`); - // a null AccessorID means we are in legacy mode - // take the user to the legacy acls - // otherwise just refresh the page - if (get(token, 'AccessorID') === null) { - // returning false for a feedback action means even though - // its successful, please skip this notification and don't display it - return route.transitionTo('dc.acls'); - } else { - // TODO: Ideally we wouldn't need to use env() at a component level - // transitionTo should probably remove it instead if NSPACES aren't enabled - if (this.env.var('CONSUL_NSPACES_ENABLED') && get(token, 'Namespace') !== this.nspace.Name) { - if (!routeName.startsWith('nspace')) { - routeName = `nspace.${routeName}`; - } - const nspace = get(token, 'Namespace'); - // you potentially have a new namespace - if (typeof nspace !== 'undefined') { - return route.transitionTo(`${routeName}`, `~${nspace}`, this.dc.Name); - } - // you are logging out, just refresh - return route.refresh(); - } else { - if (route.routeName === 'dc.acls.index') { - return route.transitionTo('dc.acls.tokens.index'); - } - return route.refresh(); - } - } - }, actions: { - send: function(el, method, ...rest) { - const component = this.dom.component(el); - component.actions[method].apply(component, rest || []); + keypressClick: function(e) { + e.target.dispatchEvent(new MouseEvent('click')); }, - changeToken: function(token = {}) { - const prev = this.token; - if (token === '') { - token = {}; - } - set(this, 'token', token); - // if this is just the initial 'find out what the current token is' - // then don't do anything - if (typeof prev === 'undefined') { - return; - } - let notification; - let action = () => this.forwardForACL(token); - switch (true) { - case get(this, 'token.AccessorID') === null && get(this, 'token.SecretID') === null: - // 'everything is null, 403 this needs deleting' token - this.settings.delete('token'); - return; - case get(prev, 'AccessorID') === null && get(prev, 'SecretID') === null: - // we just had an 'everything is null, this needs deleting' token - // reject and break so this acts differently to just logging out - action = () => Promise.reject({}); - notification = 'authorize'; - break; - case typeof get(prev, 'AccessorID') !== 'undefined' && - typeof get(this, 'token.AccessorID') !== 'undefined': - // change of both Accessor and Secret, means use - notification = 'use'; - break; - case get(this, 'token.AccessorID') === null && - typeof get(this, 'token.SecretID') !== 'undefined': - // legacy login, don't do anything as we don't use self for auth here but the endpoint itself - // self is successful, but skip this notification and don't display it - return this.forwardForACL(token); - case typeof get(prev, 'AccessorID') === 'undefined' && - typeof get(this, 'token.AccessorID') !== 'undefined': - // normal login - notification = 'authorize'; - break; - case (typeof get(prev, 'AccessorID') !== 'undefined' || get(prev, 'AccessorID') === null) && - typeof get(this, 'token.AccessorID') === 'undefined': - //normal logout - notification = 'logout'; - break; - } - this.actions.reauthorize.apply(this, [ - { - type: notification, - action: action, - }, - ]); + open: function() { + this.authForm.focus(); }, - reauthorize: function(e) { - this.client.abort(); - this.http.resetCache(); - this.store.init(); - const type = get(e, 'type'); - this.feedback.execute( - e.action, - type, - function(type, e) { - return type; - }, - {} - ); + close: function() { + this.authForm.reset(); }, change: function(e) { const win = this.dom.viewport(); diff --git a/ui-v2/app/components/jwt-source/README.mdx b/ui-v2/app/components/jwt-source/README.mdx new file mode 100644 index 000000000000..51b1a12f2293 --- /dev/null +++ b/ui-v2/app/components/jwt-source/README.mdx @@ -0,0 +1,24 @@ +## JwtSource + +```handlebars + +``` + +### Arguments + +| Argument | Type | Default | Description | +| --- | --- | --- | --- | +| `src` | `String` | | The source to subscribe to updates to, this should map to a string based URI | +| `onchange` | `Function` | | The action to fire when the data changes. Emits an Event-like object with a `data` property containing the jwt data, in this case the autorizationCode and the status | +| `onerror` | `Function` | | The action to fire when an error occurs. Emits ErrorEvent object with an `error` property containing the Error. | + +This component will go through the steps of requesting a JWT token from a third party oauth provider. `src` should contain the full URL of the authorization URL for the 3rd party provider. Once the user has logged into the 3rd party provider the `onchange` event will be fired containing an event-like object whose data contains the JWT information. + +The component need only be place into the DOM in order to begin the OAuth dance. + +### See + +- [Component Source Code](./index.js) +- [Template Source Code](./index.hbs) + +--- diff --git a/ui-v2/app/components/jwt-source/index.js b/ui-v2/app/components/jwt-source/index.js new file mode 100644 index 000000000000..da84dc672caa --- /dev/null +++ b/ui-v2/app/components/jwt-source/index.js @@ -0,0 +1,31 @@ +import Component from '@ember/component'; +import { inject as service } from '@ember/service'; +import { fromPromise } from 'consul-ui/utils/dom/event-source'; + +export default Component.extend({ + repo: service('repository/oidc-provider'), + dom: service('dom'), + tagName: '', + onchange: function(e) {}, + onerror: function(e) {}, + init: function() { + this._super(...arguments); + this._listeners = this.dom.listeners(); + }, + willDestroy: function() { + this._super(...arguments); + this.repo.close(); + this._listeners.remove(); + }, + didInsertElement: function() { + if (this.source) { + this.source.close(); + } + // TODO: Could this use once? Double check but I don't think it can + this.source = fromPromise(this.repo.findCodeByURL(this.src)); + this._listeners.add(this.source, { + message: e => this.onchange(e), + error: e => this.onerror(e), + }); + }, +}); diff --git a/ui-v2/app/components/oidc-select/chart.xstate.js b/ui-v2/app/components/oidc-select/chart.xstate.js new file mode 100644 index 000000000000..08e65961d8b8 --- /dev/null +++ b/ui-v2/app/components/oidc-select/chart.xstate.js @@ -0,0 +1,16 @@ +export default { + id: 'oidc-select', + initial: 'loading', + states: { + loaded: {}, + loading: { + on: { + SUCCESS: [ + { + target: 'loaded', + }, + ], + }, + }, + }, +}; diff --git a/ui-v2/app/components/oidc-select/index.hbs b/ui-v2/app/components/oidc-select/index.hbs new file mode 100644 index 000000000000..99b173be5fba --- /dev/null +++ b/ui-v2/app/components/oidc-select/index.hbs @@ -0,0 +1,46 @@ + + + +
+ {{#if (lt items.length 3)}} +
    + {{#each items as |item|}} +
  • + +
  • + {{/each}} +
+ {{else}} + {{#let (or provider (object-at 0 items)) as |item|}} + + + {{/let}} + {{/if}} +
+
+ +
+
+
\ No newline at end of file diff --git a/ui-v2/app/components/oidc-select/index.js b/ui-v2/app/components/oidc-select/index.js new file mode 100644 index 000000000000..a0b5b164d9ae --- /dev/null +++ b/ui-v2/app/components/oidc-select/index.js @@ -0,0 +1,20 @@ +import Component from '@ember/component'; +import chart from './chart.xstate'; + +export default Component.extend({ + tagName: '', + onchange: function() {}, + onerror: function() {}, + init: function() { + this._super(...arguments); + this.chart = chart; + }, + didReceiveAttrs: function() { + // This gets called no matter which attr has changed + // such as disabled, thing is once we are in loaded state + // it doesn't do anything anymore + if (typeof this.items !== 'undefined') { + this.dispatch('SUCCESS'); + } + }, +}); diff --git a/ui-v2/app/components/policy-selector/index.hbs b/ui-v2/app/components/policy-selector/index.hbs index 8b702a2b86f6..6e2b7198acc2 100644 --- a/ui-v2/app/components/policy-selector/index.hbs +++ b/ui-v2/app/components/policy-selector/index.hbs @@ -1,4 +1,4 @@ - + {{yield}} Apply an existing policy diff --git a/ui-v2/app/components/role-form/index.hbs b/ui-v2/app/components/role-form/index.hbs index c5a7ecaa1298..460c6d6904a1 100644 --- a/ui-v2/app/components/role-form/index.hbs +++ b/ui-v2/app/components/role-form/index.hbs @@ -1,5 +1,5 @@ {{yield}} -
+