From 98bd45a9992a0028b47fbf509ce942907cd0d0db Mon Sep 17 00:00:00 2001 From: Angel Garbarino Date: Tue, 20 Feb 2024 15:36:56 -0700 Subject: [PATCH] Glimmerize Transit Key Actions (#25365) * wip, all actions wired up, not finished or tested. * move the rotate action to it's own component to clarify scope * clean up and refresh component on queryParam change without forcing a refresh from component as before * solve issue of carrying over props we want to keep * clean up and add clearProps action * transit key actions passing * update assert and doc * remove unecessary changes * Address pr comments * replace perform in submit action and instead pass it in as perform * address claire's pr comments * welp * trying to rearrange closer to original * addressing pr comments * move things around * pr comments * remove ciphertext when last action was rewrap * add args and istruncated and isfullwidth --- ui/app/components/transit-form-show.js | 27 ++ .../components/transit-key-action/export.js | 5 +- ui/app/components/transit-key-actions.hbs | 134 ++++++++ ui/app/components/transit-key-actions.js | 290 ++++++++---------- .../components/transit-form-show.hbs | 13 +- .../components/transit-key-action/datakey.hbs | 8 +- .../components/transit-key-action/decrypt.hbs | 8 +- .../components/transit-key-action/encrypt.hbs | 20 +- .../components/transit-key-action/export.hbs | 18 +- .../components/transit-key-action/hmac.hbs | 21 +- .../components/transit-key-action/rewrap.hbs | 11 +- .../components/transit-key-action/sign.hbs | 42 ++- .../components/transit-key-action/verify.hbs | 53 ++-- .../components/transit-key-actions.hbs | 55 ---- .../backend/transit-actions-layout.hbs | 8 +- ui/tests/acceptance/transit-test.js | 3 +- .../components/transit-key-actions-test.js | 17 +- 17 files changed, 410 insertions(+), 323 deletions(-) create mode 100644 ui/app/components/transit-form-show.js create mode 100644 ui/app/components/transit-key-actions.hbs delete mode 100644 ui/app/templates/components/transit-key-actions.hbs diff --git a/ui/app/components/transit-form-show.js b/ui/app/components/transit-form-show.js new file mode 100644 index 0000000000..bde37c9276 --- /dev/null +++ b/ui/app/components/transit-form-show.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; +import { service } from '@ember/service'; +import { action } from '@ember/object'; +import errorMessage from 'vault/utils/error-message'; + +export default class TransitFormShow extends Component { + @service store; + @service router; + @service flashMessages; + + @action async rotateKey() { + const { backend, id } = this.args.key; + try { + await this.store.adapterFor('transit-key').keyAction('rotate', { backend, id }); + this.flashMessages.success('Key rotated.'); + // must refresh to see the updated versions, a model refresh does not trigger the change. + await this.router.refresh(); + } catch (e) { + this.flashMessages.danger(errorMessage(e)); + } + } +} diff --git a/ui/app/components/transit-key-action/export.js b/ui/app/components/transit-key-action/export.js index f521170adf..bb4bc33d4b 100644 --- a/ui/app/components/transit-key-action/export.js +++ b/ui/app/components/transit-key-action/export.js @@ -7,8 +7,5 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; export default class ExportComponent extends Component { - @tracked - wrapTTL = null; - @tracked - exportVersion = false; + @tracked exportVersion = false; } diff --git a/ui/app/components/transit-key-actions.hbs b/ui/app/components/transit-key-actions.hbs new file mode 100644 index 0000000000..26e394ee49 --- /dev/null +++ b/ui/app/components/transit-key-actions.hbs @@ -0,0 +1,134 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +~}} + +
+ + + {{#if (eq @selectedAction "encrypt")}} + + {{else if (eq @selectedAction "decrypt")}} + + {{else if (eq @selectedAction "datakey")}} + + {{else if (eq @selectedAction "rewrap")}} + + {{else if (eq @selectedAction "hmac")}} + + {{else if (eq @selectedAction "verify")}} + + {{else if (eq @selectedAction "sign")}} + + {{else if (or (eq @selectedAction "export") (eq @key.supportedActions.firstObject "export"))}} + + {{/if}} +
\ No newline at end of file diff --git a/ui/app/components/transit-key-actions.js b/ui/app/components/transit-key-actions.js index 94acac0a25..6305e6b75a 100644 --- a/ui/app/components/transit-key-actions.js +++ b/ui/app/components/transit-key-actions.js @@ -3,15 +3,31 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { assign } from '@ember/polyfills'; -import { copy } from 'ember-copy'; -import { assert } from '@ember/debug'; +import Component from '@glimmer/component'; import { service } from '@ember/service'; -import Component from '@ember/component'; -import { set, get, computed } from '@ember/object'; +import { action } from '@ember/object'; +import { assert } from '@ember/debug'; +import { tracked } from '@glimmer/tracking'; +import { task } from 'ember-concurrency'; +import { waitFor } from '@ember/test-waiters'; import { encodeString } from 'vault/utils/b64'; +import errorMessage from 'vault/utils/error-message'; -const TRANSIT_PARAMS = { +/** + * @module TransitKeyActions + * TransitKeyActions component handles the actions a user can take on a transit key model. The model and props are updated on every tab change + * + * @example + * + * + * @param {string} selectedAction - This is the query param "action" value. Ex: hmac, verify, decrypt, etc. The only time this param can be empty is if a user is exporting a key + * @param {object} key - This is the transit key model. + */ + +const STARTING_TRANSIT_PROPS = { hash_algorithm: 'sha2-256', algorithm: 'sha2-256', signature_algorithm: 'pss', @@ -35,161 +51,101 @@ const TRANSIT_PARAMS = { exportKeyType: null, exportKeyVersion: null, wrappedToken: null, + wrappedTTL: '30m', valid: null, plaintextOriginal: null, didDecode: false, verification: 'Signature', }; -const PARAMS_FOR_ACTION = { + +const PROPS_TO_KEEP = { + encrypt: ['plaintext', 'context', 'nonce', 'key_version'], + decrypt: ['ciphertext', 'context', 'nonce'], sign: ['input', 'hash_algorithm', 'key_version', 'prehashed', 'signature_algorithm'], verify: ['input', 'hmac', 'signature', 'hash_algorithm', 'prehashed'], hmac: ['input', 'algorithm', 'key_version'], - encrypt: ['plaintext', 'context', 'nonce', 'key_version'], - decrypt: ['ciphertext', 'context', 'nonce'], rewrap: ['ciphertext', 'context', 'nonce', 'key_version'], + datakey: [], }; + const SUCCESS_MESSAGE_FOR_ACTION = { - sign: 'Signed your data', + sign: 'Signed your data.', // the verify action doesn't trigger a success message - hmac: 'Created your hash output', - encrypt: 'Created a wrapped token for your data', - decrypt: 'Decrypted the data from your token', - rewrap: 'Created a new token for your data', - datakey: 'Generated your key', - export: 'Exported your key', + hmac: 'Created your hash output.', + encrypt: 'Created a wrapped token for your data.', + decrypt: 'Decrypted the data from your token.', + rewrap: 'Created a new token for your data.', + datakey: 'Generated your key.', + export: 'Exported your key.', }; -export default Component.extend(TRANSIT_PARAMS, { - store: service(), - flashMessages: service(), - // public attrs - selectedAction: null, - key: null, - isModalActive: false, +export default class TransitKeyActions extends Component { + @service store; + @service flashMessages; + @service router; - onRefresh() {}, - init() { - this._super(...arguments); - // TODO figure out why get is needed here Ember Upgrade - // eslint-disable-next-line ember/no-get - if (this.selectedAction) { - return; + @tracked isModalActive = false; + @tracked errors = null; + @tracked props = { ...STARTING_TRANSIT_PROPS }; // Shallow copy of the object. We don't want to mutate the original. + + constructor() { + super(...arguments); + assert('@key is required for TransitKeyActions components', this.args.key); + + if (this.firstSupportedAction === 'export' || this.args.selectedAction === 'export') { + this.props.exportKeyType = this.args.key.exportKeyTypes[0]; + this.props.exportKeyVersion = this.args.key.validKeyVersions.lastObject; } - // eslint-disable-next-line ember/no-get - set(this, 'selectedAction', get(this, 'key.supportedActions.firstObject')); - assert('`key` is required for `' + this.toString() + '`.', this.getModelInfo()); - }, + } - didReceiveAttrs() { - this._super(...arguments); - this.checkAction(); - if (this.selectedAction === 'export') { - this.setExportKeyDefaults(); - } - }, - - setExportKeyDefaults() { - const exportKeyType = this.key.exportKeyTypes.firstObject; - const exportKeyVersion = this.key.validKeyVersions.lastObject; - this.setProperties({ - exportKeyType, - exportKeyVersion, - }); - }, - - keyIsRSA: computed('key.type', function () { - const type = this.key.type; + get keyIsRSA() { + const { type } = this.args.key; return type === 'rsa-2048' || type === 'rsa-3072' || type === 'rsa-4096'; - }), + } - getModelInfo() { - const model = this.key || this.backend; - if (!model) { - return null; - } - const backend = model.backend || model.id; - const id = model.id; - - return { - backend, - id, - }; - }, - - checkAction() { - const currentAction = this.selectedAction; - const oldAction = this.oldSelectedAction; - - this.resetParams(oldAction, currentAction); - set(this, 'oldSelectedAction', currentAction); - }, - - resetParams(oldAction, action) { - const params = copy(TRANSIT_PARAMS); - let paramsToKeep; - const clearWithoutCheck = - !oldAction || - // don't save values from datakey - oldAction === 'datakey' || - // can rewrap signatures — using that as a ciphertext later would be problematic - (oldAction === 'rewrap' && !this.key.supportsEncryption); - - if (!clearWithoutCheck && action) { - paramsToKeep = PARAMS_FOR_ACTION[action]; - } - - if (paramsToKeep) { - paramsToKeep.forEach((param) => delete params[param]); - } - //resets params still left in the object to defaults - this.clearErrors(); - this.setProperties(params); - if (action === 'export') { - this.setExportKeyDefaults(); - } - }, - - handleError(e) { - this.set('errors', e.errors); - }, - - clearErrors() { - this.set('errors', null); - }, - - triggerSuccessMessage(action) { - const message = SUCCESS_MESSAGE_FOR_ACTION[action]; - if (!message) return; - this.flashMessages.success(message); - }, + get firstSupportedAction() { + return this.args.key.supportedActions[0]; + } handleSuccess(resp, options, action) { - let props = {}; if (resp && resp.data) { if (action === 'export' && resp.data.keys) { const { keys, type, name } = resp.data; resp.data.keys = { keys, type, name }; } - props = assign({}, props, resp.data); + this.props = { ...this.props, ...resp.data }; + + // While we do not pass ciphertext as a value to the JsonEditor, so that navigating from rewrap to decrypt will not show ciphertext in the editor, we still want to clear it from the props after rewrapping. + if (action === 'rewrap' && !this.args.key.supportsEncryption) { + this.props.ciphertext = null; + } } if (options.wrapTTL) { - props = assign({}, props, { wrappedToken: resp.wrap_info.token }); + this.props = { ...this.props, ...{ wrappedToken: resp.wrap_info.token } }; } - if (!this.isDestroyed && !this.isDestroying) { - this.toggleProperty('isModalActive'); - this.setProperties(props); + this.isModalActive = true; + // verify doesn't trigger a success message + if (this.args.selectedAction !== 'verify') { + this.flashMessages.success(SUCCESS_MESSAGE_FOR_ACTION[action]); } - if (action === 'rotate') { - this.onRefresh(); - } - this.triggerSuccessMessage(action); - }, + } + + @action updateProps() { + this.errors = null; + // There are specific props we want to carry over from the previous tab. + // Ex: carrying over this.props.context from the encrypt tab to the decrypt tab, but not carrying over this.props.plaintext. + // To do this, we make a new object that contains the old this.props key/values from the previous tab that we want to keep. We then merge that new object into the STARTING_TRANSIT_PROPS object to come up with our new this.props tracked property. + // This action is passed to did-update in the component. + const transferredProps = PROPS_TO_KEEP[this.args.selectedAction]?.reduce( + (obj, key) => ({ ...obj, [key]: this.props[key] }), + {} + ); + this.props = { ...STARTING_TRANSIT_PROPS, ...transferredProps }; + } compactData(data) { - const type = this.key.type; - const isRSA = type === 'rsa-2048' || type === 'rsa-3072' || type === 'rsa-4096'; return Object.keys(data).reduce((result, key) => { - if (key === 'signature_algorithm' && !isRSA) { + if (key === 'signature_algorithm' && !this.keyIsRSA) { return result; } if (data[key]) { @@ -197,51 +153,45 @@ export default Component.extend(TRANSIT_PARAMS, { } return result; }, {}); - }, + } - actions: { - onActionChange(action) { - set(this, 'selectedAction', action); - this.checkAction(); - }, + @action toggleEncodeBase64() { + this.props.encodedBase64 = !this.props.encodedBase64; + } - onClear() { - this.resetParams(null, this.selectedAction); - }, + @action clearSpecificProps(arrayToClear) { + arrayToClear.forEach((prop) => (this.props[prop] = null)); + } - clearParams(params) { - const arr = Array.isArray(params) ? params : [params]; - arr.forEach((param) => this.set(param, null)); - }, - - doSubmit(data, options = {}, maybeEvent) { - const event = options.type === 'submit' ? options : maybeEvent; - if (event) { - event.preventDefault(); + @task + @waitFor + *doSubmit(data, options = {}, maybeEvent) { + this.errors = null; + const event = options.type === 'submit' ? options : maybeEvent; + if (event) { + event.preventDefault(); + } + const { backend, id } = this.args.key; + const action = this.args.selectedAction || this.firstSupportedAction; + const { ...formData } = data || {}; + if (!this.props.encodedBase64) { + if (action === 'encrypt' && !!formData.plaintext) { + formData.plaintext = encodeString(formData.plaintext); } - const { backend, id } = this.getModelInfo(); - const action = this.selectedAction; - const { encodedBase64, ...formData } = data || {}; - if (!encodedBase64) { - if (action === 'encrypt' && !!formData.plaintext) { - formData.plaintext = encodeString(formData.plaintext); - } - if ((action === 'hmac' || action === 'verify' || action === 'sign') && !!formData.input) { - formData.input = encodeString(formData.input); - } + if ((action === 'hmac' || action === 'verify' || action === 'sign') && !!formData.input) { + formData.input = encodeString(formData.input); } - const payload = formData ? this.compactData(formData) : null; - this.setProperties({ - errors: null, - result: null, - }); - this.store + } + const payload = formData ? this.compactData(formData) : null; + + try { + const resp = yield this.store .adapterFor('transit-key') - .keyAction(action, { backend, id, payload }, options) - .then( - (resp) => this.handleSuccess(resp, options, action), - (...errArgs) => this.handleError(...errArgs) - ); - }, - }, -}); + .keyAction(action, { backend, id, payload }, options); + + this.handleSuccess(resp, options, action); + } catch (e) { + this.errors = errorMessage(e); + } + } +} diff --git a/ui/app/templates/components/transit-form-show.hbs b/ui/app/templates/components/transit-form-show.hbs index 559be499fc..a22f1b5162 100644 --- a/ui/app/templates/components/transit-form-show.hbs +++ b/ui/app/templates/components/transit-form-show.hbs @@ -47,8 +47,17 @@ {{#if (not-eq @tab "actions")}} - {{#if (eq @tab "versions")}} - + {{#if (and (eq @tab "versions") @key.canRotate)}} + {{/if}} {{#if (eq @mode "show")}} {{#if (or @capabilities.canUpdate @capabilities.canDelete)}} diff --git a/ui/app/templates/components/transit-key-action/datakey.hbs b/ui/app/templates/components/transit-key-action/datakey.hbs index 787c1ed2a1..e957d56a75 100644 --- a/ui/app/templates/components/transit-key-action/datakey.hbs +++ b/ui/app/templates/components/transit-key-action/datakey.hbs @@ -3,7 +3,7 @@ SPDX-License-Identifier: BUSL-1.1 ~}} -
+
@@ -78,7 +78,7 @@ Copy your generated key - {{#if (eq @param "plaintext")}} + {{#if @plaintext}}

Plaintext

Plaintext is base64 encoded

+

You can decrypt ciphertext using {{@key.name}} as the encryption key.

@@ -12,7 +12,7 @@
@@ -60,11 +60,13 @@

Plaintext

-

Plaintext is base64 encoded

+

Plaintext is base64 encoded.

You can encrypt plaintext data using {{@key.name}} as the encryption key.

- +
- +
{{#if @key.derived}} @@ -78,6 +82,8 @@ @textToCopy={{@ciphertext}} @color="secondary" @container="#transit-encrypt-modal" + @isFullWidth={{true}} + @isTruncated={{true}} @onError={{(fn (set-flash-message "Clipboard copy failed. Please make sure the browser Clipboard API is allowed." "danger") )}} diff --git a/ui/app/templates/components/transit-key-action/export.hbs b/ui/app/templates/components/transit-key-action/export.hbs index 83e7c2bf2d..0dc773fe2b 100644 --- a/ui/app/templates/components/transit-key-action/export.hbs +++ b/ui/app/templates/components/transit-key-action/export.hbs @@ -4,11 +4,15 @@ ~}}
@@ -55,7 +59,7 @@
{{/if}}
- +
@@ -70,12 +74,14 @@

Wrapped key

- {{#if this.wrapTTL}} + {{#if @wrappedTTL}} +
@@ -18,14 +13,20 @@ as the named key.

- +
- +
- +
@@ -60,6 +61,8 @@ @textToCopy={{@hmac}} @color="secondary" @container="#transit-hmac-modal" + @isFullWidth={{true}} + @isTruncated={{true}} @onError={{(fn (set-flash-message "Clipboard copy failed. Please make sure the browser Clipboard API is allowed." "danger") )}} diff --git a/ui/app/templates/components/transit-key-action/rewrap.hbs b/ui/app/templates/components/transit-key-action/rewrap.hbs index 1bdf549a7a..4ccd942b8d 100644 --- a/ui/app/templates/components/transit-key-action/rewrap.hbs +++ b/ui/app/templates/components/transit-key-action/rewrap.hbs @@ -3,7 +3,10 @@ SPDX-License-Identifier: BUSL-1.1 ~}} - +
@@ -13,10 +16,10 @@ as the encryption key.

- +
- +
{{#if @key.derived}} @@ -72,6 +75,8 @@ @textToCopy={{@ciphertext}} @color="secondary" @container="#transit-rewrap-modal" + @isFullWidth={{true}} + @isTruncated={{true}} @onError={{(fn (set-flash-message "Clipboard copy failed. Please make sure the browser Clipboard API is allowed." "danger") )}} diff --git a/ui/app/templates/components/transit-key-action/sign.hbs b/ui/app/templates/components/transit-key-action/sign.hbs index 83ad590ef3..8f583236a9 100644 --- a/ui/app/templates/components/transit-key-action/sign.hbs +++ b/ui/app/templates/components/transit-key-action/sign.hbs @@ -4,18 +4,22 @@ ~}}
@@ -26,20 +30,26 @@ as the encryption key and the specified hash algorithm.

- +
- +
{{#if @key.derived}} @@ -114,7 +124,7 @@
- +
@@ -129,6 +139,8 @@ @textToCopy={{@signature}} @color="secondary" @container="#transit-sign-modal" + @isFullWidth={{true}} + @isTruncated={{true}} @onError={{(fn (set-flash-message "Clipboard copy failed. Please make sure the browser Clipboard API is allowed." "danger") )}} diff --git a/ui/app/templates/components/transit-key-action/verify.hbs b/ui/app/templates/components/transit-key-action/verify.hbs index fcb52181c0..ff0bc1cfff 100644 --- a/ui/app/templates/components/transit-key-action/verify.hbs +++ b/ui/app/templates/components/transit-key-action/verify.hbs @@ -4,19 +4,23 @@ ~}}
@@ -26,15 +30,21 @@
- +
{{#if (and @key.supportsSigning @key.derived (not @hmac))}} @@ -64,7 +74,7 @@ id="verification" onchange={{queue (action (mut @verification) value="target.value") - (action @clearParams (array "hmac" "signature")) + (fn @clearSpecificProps (array "hmac" "signature")) }} > {{#each (array "Signature" "HMAC") as |type|}} @@ -145,18 +155,13 @@ {{#if (or (and @verification (eq @verification "HMAC")) @hmac)}}
- +
{{else}}
- +
{{/if}} @@ -165,7 +170,7 @@ {{else}}
- +
@@ -186,7 +191,7 @@
- +
diff --git a/ui/app/templates/components/transit-key-actions.hbs b/ui/app/templates/components/transit-key-actions.hbs deleted file mode 100644 index be489d67d4..0000000000 --- a/ui/app/templates/components/transit-key-actions.hbs +++ /dev/null @@ -1,55 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -~}} - -{{#if (eq this.selectedAction "rotate")}} - {{#if this.key.canRotate}} - - {{/if}} -{{else}} - - {{#if this.selectedAction}} -
- {{! template-lint-disable no-passed-in-event-handlers }} - {{component - (concat "transit-key-action/" this.selectedAction) - key=this.key - keys=this.keys - keyIsRSA=this.keyIsRSA - verification=this.verification - hmac=this.hmac - param=this.param - key_version=this.key_version - isModalActive=this.isModalActive - ciphertext=this.ciphertext - plaintext=this.plaintext - context=this.context - nonce=this.nonce - input=this.input - bits=this.bits - algorithm=this.algorithm - signature=this.signature - signature_algorithm=this.signature_algorithm - hash_algorithm=this.hash_algorithm - prehashed=this.prehashed - wrappedToken=this.wrappedToken - exportKeyType=this.exportKeyType - exportKeyVersion=this.exportKeyVersion - encodedBase64=this.encodedBase64 - valid=this.valid - doSubmit=(action "doSubmit") - clearParams=(action "clearParams") - }} -
- {{/if}} -{{/if}} \ No newline at end of file diff --git a/ui/app/templates/vault/cluster/secrets/backend/transit-actions-layout.hbs b/ui/app/templates/vault/cluster/secrets/backend/transit-actions-layout.hbs index b3fcbb42b8..9406dbfcef 100644 --- a/ui/app/templates/vault/cluster/secrets/backend/transit-actions-layout.hbs +++ b/ui/app/templates/vault/cluster/secrets/backend/transit-actions-layout.hbs @@ -57,12 +57,6 @@
- +
\ No newline at end of file diff --git a/ui/tests/acceptance/transit-test.js b/ui/tests/acceptance/transit-test.js index 4587150a25..4e1f51f423 100644 --- a/ui/tests/acceptance/transit-test.js +++ b/ui/tests/acceptance/transit-test.js @@ -23,7 +23,7 @@ const SELECTORS = { form: (item) => `[data-test-transit-key="${item}"]`, versionRow: (version) => `[data-test-transit-version="${version}"]`, rotate: { - trigger: '[data-test-confirm-action-trigger]', + trigger: '[data-test-transit-key-rotate]', confirm: '[data-test-confirm-button]', }, }; @@ -344,6 +344,7 @@ module('Acceptance | transit (flaky)', function (hooks) { await click(SELECTORS.versionsTab); assert.dom(SELECTORS.versionRow(1)).hasTextContaining('Version 1', `${name}: only one key version`); + await waitUntil(() => find(SELECTORS.rotate.trigger)); await click(SELECTORS.rotate.trigger); await click(SELECTORS.rotate.confirm); diff --git a/ui/tests/integration/components/transit-key-actions-test.js b/ui/tests/integration/components/transit-key-actions-test.js index b1cea6e73b..b14db3bb25 100644 --- a/ui/tests/integration/components/transit-key-actions-test.js +++ b/ui/tests/integration/components/transit-key-actions-test.js @@ -60,7 +60,7 @@ module('Integration | Component | transit key actions', function (hooks) { render(hbs` `); const err = await promise; - assert.ok(err.message.includes('`key` is required for'), 'asserts without key'); + assert.ok(err.message.includes('@key is required for'), 'asserts without key'); }); test('it renders', async function (assert) { @@ -102,19 +102,6 @@ module('Integration | Component | transit key actions', function (hooks) { .exists({ count: 1 }, 'renders signature_algorithm field on verify with rsa key'); }); - test('it renders: rotate', async function (assert) { - this.set('key', { backend: 'transit', id: 'akey', supportedActions: ['rotate'] }); - await render(hbs` - `); - - assert.dom('*').hasText('', 'renders an empty div'); - - this.set('key.canRotate', true); - assert - .dom('button') - .hasText('Rotate encryption key', 'renders confirm-button when key.canRotate is true'); - }); - async function doEncrypt(assert, actions = [], keyattrs = {}) { const keyDefaults = { backend: 'transit', id: 'akey', supportedActions: ['encrypt'].concat(actions) }; @@ -312,7 +299,7 @@ module('Integration | Component | transit key actions', function (hooks) { validKeyVersions: [1], }); await render(hbs` - `); + `); await fillIn('#algorithm', 'sha2-384'); await blur('#algorithm'); await fillIn('[data-test-component="code-mirror-modifier"] textarea', 'plaintext');