backport UI: show token expiring warning (#23762)

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
This commit is contained in:
hc-github-team-secure-vault-core
2023-10-20 10:44:22 -04:00
committed by GitHub
parent cd1d91dbf3
commit 3e8b670f18
6 changed files with 125 additions and 24 deletions

3
changelog/23143.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
ui: Surface warning banner if UI has stopped auto-refreshing token
```

View File

@@ -0,0 +1,55 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
{{#if (and this.showWarning (is-after (now interval=1000) @expirationDate))}}
<Hds::Alert @type="page" @color="critical" data-test-token-expired-banner as |A|>
<A.Title>Error</A.Title>
<A.Description>
Your auth token expired on
{{date-format @expirationDate "MMMM do yyyy, h:mm:ss a"}}. You will need to re-authenticate.
</A.Description>
<A.Link::Standalone @icon="arrow-right" @iconPosition="trailing" @text="Reauthenticate" @route="vault.cluster.logout" />
</Hds::Alert>
{{else}}
<section class="section">
<div class="container is-widescreen">
{{#if (and this.showWarning @allowingExpiration this.canDismiss)}}
<Hds::Alert
@type="inline"
@color="warning"
@onDismiss={{fn (mut this.canDismiss) false}}
class="has-top-margin-s"
data-test-token-expiring-banner
as |A|
>
<A.Title>Session will expire</A.Title>
<A.Description>
We've stopped auto-renewing your token due to inactivity. It will expire in
{{date-from-now @expirationDate}}
on
{{date-format @expirationDate "MMMM do yyyy, h:mm:ss a O"}}.
</A.Description>
<A.Button
@text="Renew token"
@color="secondary"
@icon={{if this.renewToken.isRunning "loading" "reload"}}
@iconPosition="trailing"
disabled={{this.renewToken.isRunning}}
{{on "click" (perform this.renewToken)}}
data-test-renew-token-button
/>
<A.Link::Standalone
@icon="arrow-right"
@iconPosition="trailing"
@color="secondary"
@text="Log out"
@route="vault.cluster.logout"
/>
</Hds::Alert>
{{/if}}
{{yield}}
</div>
</section>
{{/if}}

View File

@@ -5,9 +5,36 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { later } from '@ember/runloop';
import { task } from 'ember-concurrency';
export default class TokenExpireWarning extends Component {
@service auth;
@service router;
@tracked canDismiss = true;
handleRenew() {
return new Promise((resolve) => {
later(() => {
this.auth
.renew()
.then(() => {
// This renewal was triggered by an explicit user action,
// so this will reset the time inactive calculation
this.auth.setLastFetch(Date.now());
})
.finally(() => {
resolve();
});
}, 200);
});
}
@task
*renewToken() {
yield this.handleRenew();
}
get showWarning() {
const currentRoute = this.router.currentRouteName;

View File

@@ -1,21 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
{{#if (and this.showWarning (is-after (now interval=1000) @expirationDate))}}
<Hds::Alert @type="page" @color="critical" as |A|>
<A.Title>Error</A.Title>
<A.Description>
Your auth token expired on
{{date-format @expirationDate "MMMM do yyyy, h:mm:ss a"}}. You will need to re-authenticate.
</A.Description>
<A.Link::Standalone @icon="arrow-right" @iconPosition="trailing" @text="Reauthenticate" @route="vault.cluster.logout" />
</Hds::Alert>
{{else}}
<section class="section">
<div class="container is-widescreen">
{{yield}}
</div>
</section>
{{/if}}

View File

@@ -41,7 +41,7 @@
</div>
{{#if this.auth.isActiveSession}}
<TokenExpireWarning @expirationDate={{this.auth.tokenExpirationDate}}>
<TokenExpireWarning @expirationDate={{this.auth.tokenExpirationDate}} @allowingExpiration={{this.auth.allowExpiration}}>
{{outlet}}
</TokenExpireWarning>
{{else}}

View File

@@ -18,7 +18,7 @@ module('Integration | Component | token-expire-warning', function (hooks) {
await render(hbs`<TokenExpireWarning @expirationDate={{this.expirationDate}}/>`);
await waitUntil(() => find('#modal-overlays'));
assert.dom().includesText('Your auth token expired on');
assert.dom('[data-test-token-expired-banner]').includesText('Your auth token expired on');
});
test('it does not render a warning when the token is not expired', async function (assert) {
@@ -30,8 +30,45 @@ module('Integration | Component | token-expire-warning', function (hooks) {
<p>Do not worry, your token has not expired.</p>
</TokenExpireWarning>
`);
await waitUntil(() => find('#modal-overlays'));
assert.dom().doesNotIncludeText('Your auth token expired on');
assert.dom().includesText('Do not worry');
});
test('it renders a warning when the token is no longer renewing', async function (assert) {
const expirationDate = addMinutes(Date.now(), 3);
this.set('expirationDate', expirationDate);
this.set('allowingExpiration', false);
await render(
hbs`
<TokenExpireWarning @expirationDate={{this.expirationDate}} @allowingExpiration={{this.allowingExpiration}}>
<p data-test-content>This is the content</p>
</TokenExpireWarning>
`
);
assert.dom('[data-test-token-expired-banner]').doesNotExist('Does not show token expired banner');
assert.dom('[data-test-token-expiring-banner]').doesNotExist('Does not show token expiring banner');
assert.dom('[data-test-content]').hasText('This is the content');
await this.set('allowingExpiration', true);
assert.dom('[data-test-token-expired-banner]').doesNotExist('Does not show token expired banner');
assert.dom('[data-test-token-expiring-banner]').exists('Shows token expiring banner');
assert.dom('[data-test-content]').hasText('This is the content');
});
test('Does not render a warning if no expiration date', async function (assert) {
this.set('expirationDate', null);
this.set('allowingExpiration', true);
await render(
hbs`
<TokenExpireWarning @expirationDate={{this.expirationDate}} @allowingExpiration={{this.allowingExpiration}}>
<p data-test-content>This is the content</p>
</TokenExpireWarning>
`
);
assert.dom('[data-test-token-expired-banner]').doesNotExist('Does not show token expired banner');
assert.dom('[data-test-token-expiring-banner]').doesNotExist('Does not show token expiring banner');
assert.dom('[data-test-content]').hasText('This is the content');
});
});