Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: add warning before downloading secret data #23260

Merged
merged 9 commits into from
Sep 22, 2023
3 changes: 3 additions & 0 deletions changelog/23260.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui: Adds warning before downloading KV v2 secret values
```
42 changes: 32 additions & 10 deletions ui/lib/core/addon/components/masked-input.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,9 @@
/>
{{/if}}
{{#if @allowDownload}}
<DownloadButton
class="button download-button"
@filename={{or @name "secret-value"}}
@data={{@value}}
@stringify={{true}}
aria-label="Download secret value"
>
<Icon @name="download" />
</DownloadButton>
<button type="button" class="button download-button" {{on "click" (fn (mut this.modalOpen) true)}}>
<Icon data-test-download-icon @name="download" />
</button>
{{/if}}
<button
onclick={{this.toggleMask}}
Expand All @@ -64,4 +58,32 @@
>
<Icon @name={{if this.showValue "eye" "eye-off"}} />
</button>
</div>
</div>

{{! CONFIRM DOWNLOAD MODAL }}
<Modal
@title="Download secret value?"
@onClose={{action (mut this.modalOpen) false}}
@isActive={{this.modalOpen}}
@type="warning"
>
<section class="modal-card-body">
This download is
<strong>unencrypted</strong>. Are you sure you want to download this secret data as plaintext?
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<DownloadButton
class="button is-primary"
@filename={{or @name "secret-value"}}
@data={{@value}}
@stringify={{true}}
aria-label="Download secret value"
{{on "click" (fn (mut this.modalOpen) false)}}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zofskeez - There's a click event in the <DownloadButton> that handles the eventual download. It works fine locally but do you foresee any issue with stacking events like this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to pass the action up from the handleDownload method in DownloadButton. Something like:

async handleDownload() {
  try {
    // download was a success
    if (this.args.onSuccess) {
      this.args.onSuccess();
    }
  } catch {}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! That's what I was thinking 🤔 Will refactor!

>
Download
</DownloadButton>
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.modalOpen) false)}}>
Cancel
</button>
</footer>
</Modal>
2 changes: 2 additions & 0 deletions ui/lib/core/addon/components/masked-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ import autosize from 'autosize';
* @param name {String} - The key correlated to the value. Used for the download file name.
* @param [onChange=Callback] {Function|action} - Callback triggered on change, sends new value. Must set the value of @value
* @param [allowCopy=false] {bool} - Whether or not the input should render with a copy button.
* @param [allowDownload=false] {bool} - Renders a download button that prompts a confirmation modal to download the secret value
* @param [displayOnly=false] {bool} - Whether or not to display the value as a display only `pre` element or as an input.
*
*/
export default class MaskedInputComponent extends Component {
textareaId = 'textarea-' + guidFor(this);
@tracked showValue = false;
@tracked modalOpen = false;

constructor() {
super(...arguments);
Expand Down
12 changes: 9 additions & 3 deletions ui/tests/integration/components/masked-input-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, focus, triggerKeyEvent, typeIn, fillIn } from '@ember/test-helpers';
import { render, focus, triggerKeyEvent, typeIn, fillIn, click } from '@ember/test-helpers';
import { create } from 'ember-cli-page-object';
import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon';
Expand Down Expand Up @@ -51,8 +51,14 @@ module('Integration | Component | masked input', function (hooks) {
});

test('it renders a download button when allowDownload is true', async function (assert) {
await render(hbs`<MaskedInput @allowDownload={{true}} />`);
assert.ok(component.downloadButtonIsPresent);
await render(hbs`<MaskedInput @allowDownload={{true}} /> <div id="modal-wormhole"></div>
`);
assert.ok(component.downloadIconIsPresent);

await click('[data-test-download-icon]');
assert.ok(component.downloadButtonIsPresent, 'clicking download icon opens modal with download button');

assert;
});

test('it shortens all outputs when displayOnly and masked', async function (assert) {
Expand Down
1 change: 1 addition & 0 deletions ui/tests/pages/components/masked-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { clickable, isPresent } from 'ember-cli-page-object';
export default {
textareaIsPresent: isPresent('[data-test-textarea]'),
copyButtonIsPresent: isPresent('[data-test-copy-button]'),
downloadIconIsPresent: isPresent('[data-test-download-icon]'),
downloadButtonIsPresent: isPresent('[data-test-download-button]'),
toggleMasked: clickable('[data-test-button="toggle-masked"]'),
copyValue: clickable('[data-test-copy-button]'),
Expand Down