mirror of https://github.com/status-im/consul.git
ui: Upgrades token sourcing related components to Glimmer+docs (#11592)
This commit is contained in:
parent
b4a7278e72
commit
392dedc3e2
|
@ -1,27 +1,93 @@
|
|||
---
|
||||
class: ember
|
||||
---
|
||||
# JwtSource
|
||||
|
||||
```hbs
|
||||
<JwtSource @src={{url}} @onchange={{action 'change'}} @onerror={{action 'error'}} />
|
||||
This is a Source-like component and with most of our Source components, the
|
||||
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 |
|
||||
| --- | --- | --- | --- |
|
||||
| `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 |
|
||||
| `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 `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. |
|
||||
|
||||
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)
|
||||
- [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 { 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();
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
this.repo.close();
|
||||
this._listeners.remove();
|
||||
},
|
||||
didInsertElement: function() {
|
||||
// TODO: We could probably update this to be a template only component now
|
||||
// rather than a JS only one.
|
||||
export default class JWTSource extends Component {
|
||||
@service('repository/oidc-provider') repo;
|
||||
@service('dom') dom;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
if (this.source) {
|
||||
this.source.close();
|
||||
}
|
||||
this._listeners = this.dom.listeners();
|
||||
// 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, {
|
||||
message: e => this.onchange(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 @@
|
|||
---
|
||||
class: ember
|
||||
---
|
||||
## TokenSource
|
||||
# TokenSource
|
||||
|
||||
```hbs
|
||||
<TokenSource
|
||||
@dc={{dc}}
|
||||
@nspace={{nspace}}
|
||||
@partition={{partition}}
|
||||
@type={{or 'oidc' 'secret'}}
|
||||
@value={{or identifierForProvider secret}}
|
||||
@onchange={{action 'change'}}
|
||||
@onerror={{action 'error'}}
|
||||
This is a Source-like component that goes through either traditional 'secret'
|
||||
based login or a 'oidc' flow depending on the `type` argument given.
|
||||
|
||||
As with most of our Source components, the 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 />`). It is also loosely based on a HTML `<input />`
|
||||
element (it has `type` and `value` arguments/attributes)
|
||||
|
||||
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 |
|
||||
| --- | --- | --- | --- |
|
||||
| `dc` | `String` | | The name of the current datacenter |
|
||||
| `nspace` | `String` | | The name of the current namespace |
|
||||
| `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 |
|
||||
| `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)
|
||||
- [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|>
|
||||
<Guard @name="isSecret" @cond={{action 'isSecret'}} />
|
||||
{{!-- This `or` can be completely removed post 1.10 as 1.10 has optional params with default values --}}
|
||||
{{#let (concat '/' (or partition '') '/' (or nspace '') '/' dc) as |path|}}
|
||||
<StateChart
|
||||
@src={{this.chart}}
|
||||
@initial={{if (eq @type 'oidc') 'provider' 'secret'}}
|
||||
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">
|
||||
<DataSource
|
||||
@src={{uri (concat path '/token/self/${value}')
|
||||
(hash
|
||||
value=value
|
||||
value=@value
|
||||
)
|
||||
}}
|
||||
@onchange={{action 'change'}}
|
||||
@onerror={{action onerror}}
|
||||
@onchange={{this.change}}
|
||||
@onerror={{@onerror}}
|
||||
/>
|
||||
</State>
|
||||
<State @matches="provider">
|
||||
<DataSource
|
||||
@src={{concat path '/oidc/provider/' value}}
|
||||
@onchange={{queue (action (mut provider) value="data") (action dispatch "SUCCESS")}}
|
||||
@onerror={{action onerror}}
|
||||
@src={{uri (concat path '/oidc/provider/${value}')
|
||||
(hash
|
||||
value=@value
|
||||
)
|
||||
}}
|
||||
@onchange={{queue (action (mut this.provider) value="data") (action dispatch "SUCCESS")}}
|
||||
@onerror={{@onerror}}
|
||||
/>
|
||||
</State>
|
||||
<State @matches="jwt">
|
||||
<JwtSource
|
||||
@src={{provider.AuthURL}}
|
||||
@onchange={{queue (action (mut jwt) value="data") (action dispatch "SUCCESS")}}
|
||||
@onerror={{action onerror}}
|
||||
@src={{this.provider.AuthURL}}
|
||||
@onchange={{queue (action (mut this.jwt) value="data") (action dispatch "SUCCESS")}}
|
||||
@onerror={{@onerror}}
|
||||
/>
|
||||
</State>
|
||||
<State @matches="token">
|
||||
<DataSource
|
||||
@src={{uri
|
||||
(concat path '/oidc/authorize/${provider}/${code}/${state}')
|
||||
@src={{uri (concat path '/oidc/authorize/${provider}/${code}/${state}')
|
||||
(hash
|
||||
provider=provider.Name
|
||||
code=jwt.authorizationCode
|
||||
state=(or jwt.authorizationState '')
|
||||
provider=this.provider.Name
|
||||
code=this.jwt.authorizationCode
|
||||
state=(or this.jwt.authorizationState '')
|
||||
)
|
||||
}}
|
||||
@onchange={{action 'change'}}
|
||||
@onerror={{action onerror}}
|
||||
@onchange={{this.change}}
|
||||
@onerror={{@onerror}}
|
||||
/>
|
||||
</State>
|
||||
{{/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';
|
||||
export default Component.extend({
|
||||
onchange: function() {},
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
|
||||
export default class TokenSource extends Component {
|
||||
@tracked provider;
|
||||
@tracked jwt;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.chart = chart;
|
||||
},
|
||||
actions: {
|
||||
isSecret: function() {
|
||||
return this.type === 'secret';
|
||||
},
|
||||
change: function(e) {
|
||||
e.data.toJSON = function() {
|
||||
return {
|
||||
AccessorID: this.AccessorID,
|
||||
// TODO: In the past we've always ignored the SecretID returned
|
||||
// from the server and used what the user typed in instead, now
|
||||
// as we don't know the SecretID when we use SSO we use the SecretID
|
||||
// in the response
|
||||
SecretID: this.SecretID,
|
||||
Namespace: this.Namespace,
|
||||
Partition: this.Partition,
|
||||
...{
|
||||
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
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
isSecret() {
|
||||
return this.args.type === 'secret';
|
||||
}
|
||||
|
||||
@action
|
||||
change(e) {
|
||||
e.data.toJSON = function() {
|
||||
return {
|
||||
AccessorID: this.AccessorID,
|
||||
// TODO: In the past we've always ignored the SecretID returned
|
||||
// from the server and used what the user typed in instead, now
|
||||
// as we don't know the SecretID when we use SSO we use the SecretID
|
||||
// in the response
|
||||
SecretID: this.SecretID,
|
||||
Namespace: this.Namespace,
|
||||
Partition: this.Partition,
|
||||
...{
|
||||
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