ui: Upgrades token sourcing related components to Glimmer+docs (#11592)
This commit is contained in:
parent
764c22a6c4
commit
07e7fdcd09
|
@ -1,27 +1,93 @@
|
||||||
---
|
|
||||||
class: ember
|
|
||||||
---
|
|
||||||
# JwtSource
|
# JwtSource
|
||||||
|
|
||||||
```hbs
|
This is a Source-like component and with most of our Source components, the
|
||||||
<JwtSource @src={{url}} @onchange={{action 'change'}} @onerror={{action 'error'}} />
|
component only needs to be added to the page in order to start the flow and is
|
||||||
|
meant to be used as such (think 'this is like an `<img />`).
|
||||||
|
|
||||||
|
The 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 including `redirect_uri`. 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.
|
||||||
|
|
||||||
|
During development you can use the `CONSUL_OIDC_PROVIDER_URL`
|
||||||
|
environment/cookie value to inject/mock the provider URL of your choice. This
|
||||||
|
var/cookie value **does not** need the `redirect_uri` parameter adding as that
|
||||||
|
will be automatically added by the UI . This URL will then be returned by our
|
||||||
|
mock API when requesting the AuthURL for **all** OIDC AuthMethods.
|
||||||
|
|
||||||
|
This component **does not store the resulting JWT**, it only emits it via
|
||||||
|
its `onchange` argument/event handler. Errors are emitted via the `onerror`
|
||||||
|
argument/event handler.
|
||||||
|
|
||||||
|
|
||||||
|
```hbs preview-template
|
||||||
|
<figure>
|
||||||
|
<figcaption>Provide a widget to set the URL</figcaption>
|
||||||
|
|
||||||
|
<input
|
||||||
|
oninput={{action (mut this.value) value="target.value"}}
|
||||||
|
type="text"
|
||||||
|
value={{this.value}}
|
||||||
|
{{did-insert (set this 'value'
|
||||||
|
(concat
|
||||||
|
(or
|
||||||
|
(env 'CONSUL_OIDC_PROVIDER_URL')
|
||||||
|
(concat (env 'CONSUL_BASE_UI_URL') '/oauth-provider-debug?client_id=oauth-double&nonce=1&state=123456789abc')
|
||||||
|
)
|
||||||
|
(concat '&redirect_uri=' (env 'CONSUL_BASE_UI_URL') '/oidc/callback&response_type=code&scope=openid')
|
||||||
|
)
|
||||||
|
)}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
{{on 'click' (fn (mut this.state) 'authenticating')}}
|
||||||
|
>
|
||||||
|
Go
|
||||||
|
</button>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<figcaption>When the button is clicked add TokenSource to the page</figcaption>
|
||||||
|
|
||||||
|
{{#if (eq this.state 'authenticating')}}
|
||||||
|
<JwtSource
|
||||||
|
@src={{this.value}}
|
||||||
|
@onchange={{queue (action (mut this.jwt) value="data") (fn (mut this.state) '')}}
|
||||||
|
@onerror={{queue (action (mut this.error) value="error.errors.firstObject") (fn (mut this.state) '')}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<figcaption>Show the results</figcaption>
|
||||||
|
<dl>
|
||||||
|
{{#if this.jwt}}
|
||||||
|
<dt>authorizationState</dt>
|
||||||
|
<dd>{{this.jwt.authorizationState}}</dd>
|
||||||
|
<dt>authorizationCode</dt>
|
||||||
|
<dd>{{this.jwt.authorizationCode}}</dd>
|
||||||
|
<dt>provider</dt>
|
||||||
|
<dd>{{this.jwt.provider}}</dd>
|
||||||
|
{{/if}}
|
||||||
|
{{#if this.error}}
|
||||||
|
<dt>Error</dt>
|
||||||
|
<dd>{{this.error.detail}}</dd>
|
||||||
|
{{/if}}
|
||||||
|
</dl>
|
||||||
|
</figure>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Arguments
|
## Arguments
|
||||||
|
|
||||||
| Argument | Type | Default | Description |
|
| Argument | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `src` | `String` | | The source to subscribe to updates to, this should map to a string based URI |
|
| `src` | `String` | | The **full** AuthURL for the 3rd party OIDC provider as provided by Consul (including `redirect_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 |
|
| `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 `autorizationStatus`, `autorizationCode`, plus the UI added `provider` name |
|
||||||
| `onerror` | `Function` | | The action to fire when an error occurs. Emits ErrorEvent object with an `error` property containing the Error. |
|
| `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
|
||||||
|
|
||||||
### See
|
|
||||||
|
|
||||||
- [Component Source Code](./index.js)
|
- [Component Source Code](./index.js)
|
||||||
- [Template Source Code](./index.hbs)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,31 +1,46 @@
|
||||||
import Component from '@ember/component';
|
import Component from '@glimmer/component';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
import { fromPromise } from 'consul-ui/utils/dom/event-source';
|
import { fromPromise } from 'consul-ui/utils/dom/event-source';
|
||||||
|
|
||||||
export default Component.extend({
|
// TODO: We could probably update this to be a template only component now
|
||||||
repo: service('repository/oidc-provider'),
|
// rather than a JS only one.
|
||||||
dom: service('dom'),
|
export default class JWTSource extends Component {
|
||||||
tagName: '',
|
@service('repository/oidc-provider') repo;
|
||||||
onchange: function(e) {},
|
@service('dom') dom;
|
||||||
onerror: function(e) {},
|
|
||||||
init: function() {
|
constructor() {
|
||||||
this._super(...arguments);
|
super(...arguments);
|
||||||
this._listeners = this.dom.listeners();
|
|
||||||
},
|
|
||||||
willDestroyElement: function() {
|
|
||||||
this._super(...arguments);
|
|
||||||
this.repo.close();
|
|
||||||
this._listeners.remove();
|
|
||||||
},
|
|
||||||
didInsertElement: function() {
|
|
||||||
if (this.source) {
|
if (this.source) {
|
||||||
this.source.close();
|
this.source.close();
|
||||||
}
|
}
|
||||||
|
this._listeners = this.dom.listeners();
|
||||||
// TODO: Could this use once? Double check but I don't think it can
|
// TODO: Could this use once? Double check but I don't think it can
|
||||||
this.source = fromPromise(this.repo.findCodeByURL(this.src));
|
this.source = fromPromise(this.repo.findCodeByURL(this.args.src));
|
||||||
this._listeners.add(this.source, {
|
this._listeners.add(this.source, {
|
||||||
message: e => this.onchange(e),
|
message: e => this.onchange(e),
|
||||||
error: e => this.onerror(e),
|
error: e => this.onerror(e),
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
onchange(e) {
|
||||||
|
if(typeof this.args.onchange === 'function') {
|
||||||
|
this.args.onchange(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onerror(e) {
|
||||||
|
if(typeof this.args.onerror === 'function') {
|
||||||
|
this.args.onerror(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
super.willDestroy(...arguments);
|
||||||
|
if (this.source) {
|
||||||
|
this.source.close();
|
||||||
|
}
|
||||||
|
this.repo.close();
|
||||||
|
this._listeners.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,36 +1,89 @@
|
||||||
---
|
# TokenSource
|
||||||
class: ember
|
|
||||||
---
|
|
||||||
## TokenSource
|
|
||||||
|
|
||||||
```hbs
|
This is a Source-like component that goes through either traditional 'secret'
|
||||||
<TokenSource
|
based login or a 'oidc' flow depending on the `type` argument given.
|
||||||
@dc={{dc}}
|
|
||||||
@nspace={{nspace}}
|
As with most of our Source components, the component only needs to be added to
|
||||||
@partition={{partition}}
|
the page in order to start the flow and is meant to be used as such (think
|
||||||
@type={{or 'oidc' 'secret'}}
|
'this is like an `<img />`). It is also loosely based on a HTML `<input />`
|
||||||
@value={{or identifierForProvider secret}}
|
element (it has `type` and `value` arguments/attributes)
|
||||||
@onchange={{action 'change'}}
|
|
||||||
@onerror={{action 'error'}}
|
When using the `oidc` type the component will go through the steps of
|
||||||
|
requesting the OIDC providers authorization URL from Consul, go through the
|
||||||
|
OIDC flow with the user and the 3rd party provider (via our `<JwtSource />`
|
||||||
|
component), then lastly exchanging the resulting JWT with Consul for a normal
|
||||||
|
Consul token.
|
||||||
|
|
||||||
|
When using the `secret` type, the component simply exchanges the users secret
|
||||||
|
for a normal Consul token.
|
||||||
|
|
||||||
|
This component **does not store the resulting token**, it only emits it via
|
||||||
|
its `onchange` argument/event handler. Errors are emitted via the `onerror`
|
||||||
|
argument/event handler.
|
||||||
|
|
||||||
|
```hbs preview-template
|
||||||
|
<figure>
|
||||||
|
<figcaption>Provide a widget to login with</figcaption>
|
||||||
|
|
||||||
|
<input
|
||||||
|
oninput={{action (mut this.value) value="target.value"}}
|
||||||
|
type="text"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
{{on 'click' (fn (mut this.state) 'authenticating')}}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<figcaption>When the button is clicked add TokenSource to the page</figcaption>
|
||||||
|
|
||||||
|
{{#if (eq this.state 'authenticating')}}
|
||||||
|
<TokenSource
|
||||||
|
@dc={{'dc-1'}}
|
||||||
|
@nspace={{''}}
|
||||||
|
@partition={{''}}
|
||||||
|
@type={{or 'secret' 'oidc'}}
|
||||||
|
@value={{this.value}}
|
||||||
|
@onchange={{queue (action (mut this.token) value="data") (fn (mut this.state) '')}}
|
||||||
|
@onerror={{queue (action (mut this.error) value="error.errors.firstObject") (fn (mut this.state) '')}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<figcaption>Show the results</figcaption>
|
||||||
|
<dl>
|
||||||
|
{{#if this.token}}
|
||||||
|
<dt>AccessorID</dt>
|
||||||
|
<dd>{{this.token.AccessorID}}</dd>
|
||||||
|
{{/if}}
|
||||||
|
{{#if this.error}}
|
||||||
|
<dt>Error</dt>
|
||||||
|
<dd>{{this.error.detail}}</dd>
|
||||||
|
{{/if}}
|
||||||
|
</dl>
|
||||||
|
</figure>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Arguments
|
## Arguments
|
||||||
|
|
||||||
| Argument | Type | Default | Description |
|
| Argument | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `dc` | `String` | | The name of the current datacenter |
|
| `dc` | `String` | | The name of the current datacenter |
|
||||||
| `nspace` | `String` | | The name of the current namespace |
|
| `nspace` | `String` | | The name of the current namespace |
|
||||||
| `partition` | `String` | | The name of the current partition |
|
| `partition` | `String` | | The name of the current partition |
|
||||||
|
| `type` | `String` | | `secret` or `oidc`. `secret` is just traditional login, whereas `oidc` uses the users OIDC provider |
|
||||||
|
| `value` | `String` | | When `type` is `secret` this should be the users secret. When `type` is `oidc` this should be the name of the `AuthMethod` to use for authentication |
|
||||||
| `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 |
|
| `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. |
|
| `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
|
||||||
|
|
||||||
### See
|
|
||||||
|
|
||||||
|
- [JwtSource Component](../jwt-source/README.mdx)
|
||||||
|
- [StateChart](./chart.xstate.js)
|
||||||
- [Component Source Code](./index.js)
|
- [Component Source Code](./index.js)
|
||||||
- [Template Source Code](./index.hbs)
|
- [Template Source Code](./index.hbs)
|
||||||
|
|
||||||
|
|
|
@ -1,44 +1,60 @@
|
||||||
<StateChart @src={{chart}} @initial={{if (eq type 'oidc') 'provider' 'secret'}} as |State Guard Action dispatch state|>
|
<StateChart
|
||||||
<Guard @name="isSecret" @cond={{action 'isSecret'}} />
|
@src={{this.chart}}
|
||||||
{{!-- This `or` can be completely removed post 1.10 as 1.10 has optional params with default values --}}
|
@initial={{if (eq @type 'oidc') 'provider' 'secret'}}
|
||||||
{{#let (concat '/' (or partition '') '/' (or nspace '') '/' dc) as |path|}}
|
as |State Guard Action dispatch state|>
|
||||||
|
<Guard
|
||||||
|
@name="isSecret"
|
||||||
|
@cond={{this.isSecret}}
|
||||||
|
/>
|
||||||
|
{{#let
|
||||||
|
(uri '/${partition}/{$nspace}/${dc}'
|
||||||
|
(hash
|
||||||
|
partition=@partition
|
||||||
|
nspace=@nspace
|
||||||
|
dc=@dc
|
||||||
|
)
|
||||||
|
)
|
||||||
|
as |path|}}
|
||||||
<State @matches="secret">
|
<State @matches="secret">
|
||||||
<DataSource
|
<DataSource
|
||||||
@src={{uri (concat path '/token/self/${value}')
|
@src={{uri (concat path '/token/self/${value}')
|
||||||
(hash
|
(hash
|
||||||
value=value
|
value=@value
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@onchange={{action 'change'}}
|
@onchange={{this.change}}
|
||||||
@onerror={{action onerror}}
|
@onerror={{@onerror}}
|
||||||
/>
|
/>
|
||||||
</State>
|
</State>
|
||||||
<State @matches="provider">
|
<State @matches="provider">
|
||||||
<DataSource
|
<DataSource
|
||||||
@src={{concat path '/oidc/provider/' value}}
|
@src={{uri (concat path '/oidc/provider/${value}')
|
||||||
@onchange={{queue (action (mut provider) value="data") (action dispatch "SUCCESS")}}
|
(hash
|
||||||
@onerror={{action onerror}}
|
value=@value
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@onchange={{queue (action (mut this.provider) value="data") (action dispatch "SUCCESS")}}
|
||||||
|
@onerror={{@onerror}}
|
||||||
/>
|
/>
|
||||||
</State>
|
</State>
|
||||||
<State @matches="jwt">
|
<State @matches="jwt">
|
||||||
<JwtSource
|
<JwtSource
|
||||||
@src={{provider.AuthURL}}
|
@src={{this.provider.AuthURL}}
|
||||||
@onchange={{queue (action (mut jwt) value="data") (action dispatch "SUCCESS")}}
|
@onchange={{queue (action (mut this.jwt) value="data") (action dispatch "SUCCESS")}}
|
||||||
@onerror={{action onerror}}
|
@onerror={{@onerror}}
|
||||||
/>
|
/>
|
||||||
</State>
|
</State>
|
||||||
<State @matches="token">
|
<State @matches="token">
|
||||||
<DataSource
|
<DataSource
|
||||||
@src={{uri
|
@src={{uri (concat path '/oidc/authorize/${provider}/${code}/${state}')
|
||||||
(concat path '/oidc/authorize/${provider}/${code}/${state}')
|
|
||||||
(hash
|
(hash
|
||||||
provider=provider.Name
|
provider=this.provider.Name
|
||||||
code=jwt.authorizationCode
|
code=this.jwt.authorizationCode
|
||||||
state=(or jwt.authorizationState '')
|
state=(or this.jwt.authorizationState '')
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@onchange={{action 'change'}}
|
@onchange={{this.change}}
|
||||||
@onerror={{action onerror}}
|
@onerror={{@onerror}}
|
||||||
/>
|
/>
|
||||||
</State>
|
</State>
|
||||||
{{/let}}
|
{{/let}}
|
||||||
|
|
|
@ -1,37 +1,47 @@
|
||||||
import Component from '@ember/component';
|
import Component from '@glimmer/component';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
import chart from './chart.xstate';
|
import chart from './chart.xstate';
|
||||||
export default Component.extend({
|
|
||||||
onchange: function() {},
|
export default class TokenSource extends Component {
|
||||||
init: function() {
|
@tracked provider;
|
||||||
this._super(...arguments);
|
@tracked jwt;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
this.chart = chart;
|
this.chart = chart;
|
||||||
},
|
}
|
||||||
actions: {
|
|
||||||
isSecret: function() {
|
@action
|
||||||
return this.type === 'secret';
|
isSecret() {
|
||||||
},
|
return this.args.type === 'secret';
|
||||||
change: function(e) {
|
}
|
||||||
e.data.toJSON = function() {
|
|
||||||
return {
|
@action
|
||||||
AccessorID: this.AccessorID,
|
change(e) {
|
||||||
// TODO: In the past we've always ignored the SecretID returned
|
e.data.toJSON = function() {
|
||||||
// from the server and used what the user typed in instead, now
|
return {
|
||||||
// as we don't know the SecretID when we use SSO we use the SecretID
|
AccessorID: this.AccessorID,
|
||||||
// in the response
|
// TODO: In the past we've always ignored the SecretID returned
|
||||||
SecretID: this.SecretID,
|
// from the server and used what the user typed in instead, now
|
||||||
Namespace: this.Namespace,
|
// as we don't know the SecretID when we use SSO we use the SecretID
|
||||||
Partition: this.Partition,
|
// in the response
|
||||||
...{
|
SecretID: this.SecretID,
|
||||||
AuthMethod: typeof this.AuthMethod !== 'undefined' ? this.AuthMethod : undefined,
|
Namespace: this.Namespace,
|
||||||
// TODO: We should be able to only set namespaces if they are enabled
|
Partition: this.Partition,
|
||||||
// but we might be testing for nspaces everywhere
|
...{
|
||||||
// Namespace: typeof this.Namespace !== 'undefined' ? this.Namespace : undefined
|
AuthMethod: typeof this.AuthMethod !== 'undefined' ? this.AuthMethod : undefined,
|
||||||
},
|
// TODO: We should be able to only set namespaces if they are enabled
|
||||||
};
|
// but we might be testing for nspaces everywhere
|
||||||
|
// Namespace: typeof this.Namespace !== 'undefined' ? this.Namespace : undefined
|
||||||
|
},
|
||||||
};
|
};
|
||||||
// TODO: We should probably put the component into idle state
|
};
|
||||||
this.onchange(e);
|
// TODO: We should probably put the component into idle state
|
||||||
},
|
if(typeof this.args.onchange === 'function') {
|
||||||
},
|
this.args.onchange(e);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue