From e11567be823e67206f8dcf7f993aa6d6ec76a90f Mon Sep 17 00:00:00 2001 From: Noelle Daley Date: Mon, 16 Mar 2020 16:48:11 -0600 Subject: [PATCH] Ui/transit modal (#8575) * wip -- add modal component using ember-wormhole, add static content but still need to enable onClose * add onClose to modal * WIP * add copy and close button * add copy and close button * and copy and close button to modal * use modal on each key action page * make text copied text more generic * update datakey textareas to codemirror * only show user input on encrypt and decrypt * only show user input on all key actions * separate copy ciphertext, plaintext, and close button on datakey modal * style ciphertext and plaintext as code * only show separate copy buttons on datakey modal if both outputs are shown * update modal styling * style modal * add descriptions to each key action * remove conditional from hmac modal since we only ever show hmac output * add modal for export key action * make output scroll horizontally with copy button next to it * make output scroll horizontally with copy button next to it * escape & in copy and close button, format text output so it scrolls horizontally * fix formatting of key action descriptions * Ui/add transit modal tests (#8523) * Fix tests for updated transit with modals workflow * WIP // remove box shadow from key actions descriptions * WIP // flash messages on successful action match mocks * WIP // remove ciphertext view after datakey created * WIP // make flash messages when copy & closing less generic, and match copy flash message * WIP // Optionally show close button on modal, with tests * remove unused deps from modal test * WIP // Fix verify modal styling and content * Add modal for sign action * Fix output of non-wrapped export key * Fix output of non-wrapped export key * Add description to JSDOCS about modal component * Add help text about plaintext encoded in base64 * add flash msgs for datakey and export * flash success msg when closing modal on export page * clarify sign success msg * address PR feedback * add indentation for export key json * Fix modal tests pt 2 * Remove decode after decrypt in transit tests Co-authored-by: Chelsea Shaw --- ui/app/components/modal.js | 21 ++ ui/app/components/transit-key-actions.js | 29 ++- ui/app/styles/components/modal.scss | 44 ++++ ui/app/styles/components/transit-card.scss | 3 +- ui/app/styles/core.scss | 1 + ui/app/templates/components/modal.hbs | 14 ++ .../components/transit-key-action/datakey.hbs | 218 +++++++++--------- .../components/transit-key-action/decrypt.hbs | 150 ++++++------ .../components/transit-key-action/encrypt.hbs | 73 +++--- .../components/transit-key-action/export.hbs | 165 ++++++------- .../components/transit-key-action/hmac.hbs | 161 ++++++------- .../components/transit-key-action/rewrap.hbs | 24 +- .../components/transit-key-action/sign.hbs | 216 +++++++++-------- .../components/transit-key-action/verify.hbs | 31 +-- .../components/transit-key-actions.hbs | 1 + ui/package.json | 1 + ui/tests/acceptance/transit-test.js | 56 +++-- ui/tests/integration/components/modal-test.js | 29 +++ .../components/transit-key-actions-test.js | 82 ++++--- ui/yarn.lock | 8 + 20 files changed, 748 insertions(+), 579 deletions(-) create mode 100644 ui/app/components/modal.js create mode 100644 ui/app/styles/components/modal.scss create mode 100644 ui/app/templates/components/modal.hbs create mode 100644 ui/tests/integration/components/modal-test.js diff --git a/ui/app/components/modal.js b/ui/app/components/modal.js new file mode 100644 index 000000000..55c5dc1f4 --- /dev/null +++ b/ui/app/components/modal.js @@ -0,0 +1,21 @@ +/** + * @module Modal + * Modal components are used to overlay content on top of the page. Has a darkened background, + * a title, and in order to close it you must pass an onClose function. + * + * @example + * ```js + * {}}/> + * ``` + * @param {function} onClose - onClose is the action taken when someone clicks the modal background or close button (if shown). + * @param {string} [title] - This text shows up in the header section of the modal. + * @param {boolean} [showCloseButton=false] - controls whether the close button in the top right corner shows. + */ + +import Component from '@ember/component'; + +export default Component.extend({ + title: null, + showCloseButton: false, + onClose: () => {}, +}); diff --git a/ui/app/components/transit-key-actions.js b/ui/app/components/transit-key-actions.js index 64a5acefe..3a4294f8d 100644 --- a/ui/app/components/transit-key-actions.js +++ b/ui/app/components/transit-key-actions.js @@ -43,12 +43,24 @@ const PARAMS_FOR_ACTION = { decrypt: ['ciphertext', 'context', 'nonce'], rewrap: ['ciphertext', 'context', 'nonce', 'key_version'], }; +const SUCCESS_MESSAGE_FOR_ACTION = { + 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', +}; export default Component.extend(TRANSIT_PARAMS, { store: service(), + flashMessages: service(), // public attrs selectedAction: null, key: null, + isModalActive: false, onRefresh() {}, init() { @@ -137,6 +149,12 @@ export default Component.extend(TRANSIT_PARAMS, { this.set('errors', null); }, + triggerSuccessMessage(action) { + const message = SUCCESS_MESSAGE_FOR_ACTION[action]; + if (!message) return; + this.get('flashMessages').success(message); + }, + handleSuccess(resp, options, action) { let props = {}; if (resp && resp.data) { @@ -149,10 +167,12 @@ export default Component.extend(TRANSIT_PARAMS, { if (options.wrapTTL) { props = assign({}, props, { wrappedToken: resp.wrap_info.token }); } + this.toggleProperty('isModalActive'); this.setProperties(props); if (action === 'rotate') { this.get('onRefresh')(); } + this.triggerSuccessMessage(action); }, compactData(data) { @@ -184,6 +204,13 @@ export default Component.extend(TRANSIT_PARAMS, { arr.forEach(param => this.set(param, null)); }, + toggleModal(successMessage) { + if (!!successMessage && typeof successMessage === 'string') { + this.get('flashMessages').success(successMessage); + } + this.toggleProperty('isModalActive'); + }, + doSubmit(data, options = {}) { const { backend, id } = this.getModelInfo(); const action = this.get('selectedAction'); @@ -192,7 +219,7 @@ export default Component.extend(TRANSIT_PARAMS, { if (action === 'encrypt' && !!formData.plaintext) { formData.plaintext = encodeString(formData.plaintext); } - if ((action === 'hmac' || action === 'verify') && !!formData.input) { + if ((action === 'hmac' || action === 'verify' || action === 'sign') && !!formData.input) { formData.input = encodeString(formData.input); } } diff --git a/ui/app/styles/components/modal.scss b/ui/app/styles/components/modal.scss new file mode 100644 index 000000000..fe784140d --- /dev/null +++ b/ui/app/styles/components/modal.scss @@ -0,0 +1,44 @@ +.modal-background { + background: rgb(235, 238, 242, 0.9); +} + +.modal-card { + box-shadow: $box-shadow-highest; + border: 1px solid $grey-light; + + &-head { + border-radius: 0; + background-color: $grey-lightest; + border-bottom: 1px solid $grey-light; + } + + &-foot { + border-radius: 0; + border: 0; + background-color: $white; + } + + &-title.title { + margin: 0; + } + + .copy-text { + background-color: $grey-lightest; + padding: $spacing-s; + margin-bottom: $spacing-s; + + code { + overflow: scroll; + max-width: calc(100% - 36px); + background-color: inherit; + } + } + + .copy-close { + margin-top: $spacing-s; + } +} + +pre { + background-color: inherit; +} diff --git a/ui/app/styles/components/transit-card.scss b/ui/app/styles/components/transit-card.scss index 1fb0cec04..82a6628ca 100644 --- a/ui/app/styles/components/transit-card.scss +++ b/ui/app/styles/components/transit-card.scss @@ -9,10 +9,11 @@ .transit-card { border-radius: $radius; - box-shadow: 0 0 0 1px rgba($grey-dark, 0.3), $box-shadow-middle; + box-shadow: 0 0 0 1px rgba($grey-light, 0.4); display: grid; grid-template-columns: 0.45fr 2fr; padding: $spacing-m; + border: none; .transit-icon { justify-self: center; diff --git a/ui/app/styles/core.scss b/ui/app/styles/core.scss index 9ae37b811..e265d4cd7 100644 --- a/ui/app/styles/core.scss +++ b/ui/app/styles/core.scss @@ -68,6 +68,7 @@ @import './components/loader'; @import './components/login-form'; @import './components/masked-input'; +@import './components/modal'; @import './components/namespace-picker'; @import './components/namespace-reminder'; @import './components/navigate-input'; diff --git a/ui/app/templates/components/modal.hbs b/ui/app/templates/components/modal.hbs new file mode 100644 index 000000000..30bf51073 --- /dev/null +++ b/ui/app/templates/components/modal.hbs @@ -0,0 +1,14 @@ +{{#ember-wormhole to="modal-wormhole"}} + +{{/ember-wormhole}} diff --git a/ui/app/templates/components/transit-key-action/datakey.hbs b/ui/app/templates/components/transit-key-action/datakey.hbs index 54540edc9..b79fbac46 100644 --- a/ui/app/templates/components/transit-key-action/datakey.hbs +++ b/ui/app/templates/components/transit-key-action/datakey.hbs @@ -1,124 +1,126 @@
- {{#if ciphertext}}
- {{#if (eq param 'plaintext')}} + +
+

Generate a new high-entropy key and value using {{key.name}} as the encryption key.

+
- -
- + +
+
+ +
+ {{#if key.derived}} +
+ +
+
+ {{input type="text" id="context" value=context class="input" data-test-transit-input="context"}} +
+
+ {{b64-toggle value=context data-test-transit-b64-toggle="context"}} +
+
+
+ {{/if}} + {{#if (eq key.convergentEncryptionVersion 1)}} +
+ +
+
+ {{input type="text" id="nonce" value=nonce class="input" data-test-transit-input="nonce"}} +
+
+ {{b64-toggle value=nonce data-test-transit-b64-toggle="nonce"}} +
+
+
{{/if}}
- -
- + +
+
+ +
- {{#if (eq param 'plaintext')}} -
- {{#copy-button - clipboardTarget="#plaintext" - class="button is-primary" - buttonType="button" - success=(action (set-flash-message 'Plaintext copied!')) - }} - Copy plaintext - {{/copy-button}} -
- {{/if}}
- {{#copy-button - clipboardTarget="#ciphertext" - class=(concat "button is-primary " (if (eq param "plaintext") "is-outlined" "")) - buttonType="button" - success=(action (set-flash-message 'Ciphertext copied!')) - }} - Copy ciphertext - {{/copy-button}} -
-
-
- {{else}} -
- -
- -
-
- -
-
-
- {{#if key.derived}} -
- -
-
- {{input type="text" id="context" value=context class="input" data-test-transit-input="context"}} -
-
- {{b64-toggle value=context data-test-transit-b64-toggle="context"}} -
-
-
- {{/if}} - {{#if (eq key.convergentEncryptionVersion 1)}} -
- -
-
- {{input type="text" id="nonce" value=nonce class="input" data-test-transit-input="nonce"}} -
-
- {{b64-toggle value=nonce data-test-transit-b64-toggle="nonce"}} -
-
-
- {{/if}} -
- -
-
- -
-
-
-
-
-
- -
-
- {{/if}} + + + diff --git a/ui/app/templates/components/transit-key-action/decrypt.hbs b/ui/app/templates/components/transit-key-action/decrypt.hbs index 417b7add3..ec4c28d5f 100644 --- a/ui/app/templates/components/transit-key-action/decrypt.hbs +++ b/ui/app/templates/components/transit-key-action/decrypt.hbs @@ -1,96 +1,80 @@
- {{#if (and plaintext ciphertext)}} -
-
- -
- {{ivy-codemirror - value=plaintext - options=(hash - readOnly=true - lineNumbers=true - tabSize=2 - mode='ruby' - theme='hashi' - ) - data-test-transit-input="plaintext" - }} - {{b64-toggle value=plaintext isInput=false initialEncoding="base64" data-test-transit-b64-toggle="plaintext"}} -
-
+
+
+

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

-
-
- {{#copy-button - clipboardTarget="#plaintext" - class="button is-primary" - buttonType="button" - success=(action (set-flash-message 'Plaintext copied!')) +
+ +
+ {{ivy-codemirror + valueUpdated=(action (mut ciphertext)) + options=(hash + lineNumbers=true + tabSize=2 + mode='ruby' + theme='hashi' + ) + data-test-transit-input="ciphertext" }} - Copy - {{/copy-button}} -
-
-
- {{else}} -
+ {{#if key.derived}}
- -
- {{ivy-codemirror - value=ciphertext - valueUpdated=(action (mut ciphertext)) - options=(hash - lineNumbers=true - tabSize=2 - mode='ruby' - theme='hashi' - ) - data-test-transit-input="ciphertext" - }} -
-
- {{#if key.derived}} -
- -
-
- {{input type="text" id="context" value=context class="input" data-test-transit-input="context"}} -
-
- {{b64-toggle value=context data-test-transit-b64-toggle="context"}} -
+ +
+
+ {{input type="text" id="context" value=context class="input" data-test-transit-input="context"}} +
+
+ {{b64-toggle value=context data-test-transit-b64-toggle="context"}}
- {{/if}} - {{#if (eq key.convergentEncryptionVersion 1)}} -
- -
-
- {{input type="text" id="nonce" value=nonce class="input" data-test-transit-input="nonce"}} -
-
- {{b64-toggle value=nonce data-test-transit-b64-toggle="nonce"}} -
+
+ {{/if}} + {{#if (eq key.convergentEncryptionVersion 1)}} +
+ +
+
+ {{input type="text" id="nonce" value=nonce class="input" data-test-transit-input="nonce"}} +
+
+ {{b64-toggle value=nonce data-test-transit-b64-toggle="nonce"}}
- {{/if}} -
-
-
-
+ {{/if}} +
+
+
+
- {{/if}} +
+{{#if isModalActive}} + + +
+ + Copy & Close + +
+
+{{/if}} diff --git a/ui/app/templates/components/transit-key-action/encrypt.hbs b/ui/app/templates/components/transit-key-action/encrypt.hbs index 1bc29b47b..4ef35eef9 100644 --- a/ui/app/templates/components/transit-key-action/encrypt.hbs +++ b/ui/app/templates/components/transit-key-action/encrypt.hbs @@ -1,43 +1,9 @@
- {{#if (and plaintext ciphertext)}} -
-
- -
- {{ivy-codemirror - value=ciphertext - options=(hash - readOnly=true - lineNumbers=true - tabSize=2 - mode='ruby' - theme='hashi' - ) - data-test-transit-input="ciphertext" - }} -
-
-
-
-
- {{#copy-button - clipboardText=ciphertext - class="button is-primary" - buttonType="button" - success=(action (set-flash-message 'Ciphertext copied!')) - }} - Copy - {{/copy-button}} -
-
- -
-
- {{else}}
+
+

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

+
{{key-version-select key=key onVersionChange=(action (mut key_version)) @@ -103,5 +69,36 @@
- {{/if}} + + +
+ + Copy & Close + +
+
diff --git a/ui/app/templates/components/transit-key-action/export.hbs b/ui/app/templates/components/transit-key-action/export.hbs index 3b1bc8e67..8c3970ae9 100644 --- a/ui/app/templates/components/transit-key-action/export.hbs +++ b/ui/app/templates/components/transit-key-action/export.hbs @@ -3,101 +3,86 @@ (hash wrapTTL=wrapTTL) on="submit" }} > - {{#if (or keys wrappedToken) }} -
-
- {{#if wrapTTL}} - -
- -
- {{else}} - - {{json-editor - value=(stringify keys) - options=(hash - readOnly=true - ) - }} - {{/if}} -
+
+
+

Export a key using {{key.name}} as the encryption key.

-
-
- {{#copy-button - clipboardText=(if wrapTTL wrappedToken (stringify keys)) - class="button is-primary" - buttonType="button" - success=(action (set-flash-message (if wrapTTL 'Wrapped key copied!' 'Exported key copied!'))) - }} - Copy - {{/copy-button}} -
-
- -
-
- {{else}} -
-
- -
-
- -
+
+ +
+
+
-
-
- {{input type="checkbox" name="exportVersion" id="exportVersion" class="styled" checked=exportVersion}} - -
- {{#if exportVersion}} -
- -
-
- -
+
+
+
+ {{input type="checkbox" name="exportVersion" id="exportVersion" class="styled" checked=exportVersion}} + +
+ {{#if exportVersion}} +
+ +
+
+
- {{/if}} -
- {{wrap-ttl onChange=(action (mut wrapTTL))}} +
+ {{/if}}
-
-
- -
+ {{wrap-ttl onChange=(action (mut wrapTTL))}} +
+
+
+
- {{/if}} +
+ + +
+ + Copy & Close + +
+
diff --git a/ui/app/templates/components/transit-key-action/hmac.hbs b/ui/app/templates/components/transit-key-action/hmac.hbs index e1638bc6e..bb965117e 100644 --- a/ui/app/templates/components/transit-key-action/hmac.hbs +++ b/ui/app/templates/components/transit-key-action/hmac.hbs @@ -1,98 +1,83 @@
- {{#if hmac}} -
-
- -
- {{ivy-codemirror - value=hmac - options=(hash - readOnly=true - lineNumbers=true - tabSize=2 - mode='ruby' - theme='hashi' - ) - }} -
-
+
+ +
+

Generate the digest of given data using the specified hash algorithm and {{key.name}} as the named key.

-
-
- {{#copy-button - clipboardText=hmac - class="button is-primary" - buttonType="button" - success=(action (set-flash-message 'HMAC copied!')) + {{key-version-select + key=key + onVersionChange=(action (mut key_version)) + key_version=key_version + }} +
+ +
+ {{ivy-codemirror + valueUpdated=(action (mut input)) + options=(hash + lineNumbers=true + tabSize=2 + mode='ruby' + theme='hashi' + ) + data-test-transit-input="input" }} - Copy - {{/copy-button}} -
-
-
- {{else}} -
- - {{key-version-select - key=key - onVersionChange=(action (mut key_version)) - key_version=key_version - }} -
- -
- {{ivy-codemirror - value=input - valueUpdated=(action (mut input)) - options=(hash - lineNumbers=true - tabSize=2 - mode='ruby' - theme='hashi' - ) - data-test-transit-input="input" - }} -
-
-
- {{input - type="checkbox" - id="encodedBase64" - checked=encodedBase64 - data-test-transit-input="encodedBase64" }} - -
-
- -
-
- -
+
+ {{input + type="checkbox" + id="encodedBase64" + checked=encodedBase64 + data-test-transit-input="encodedBase64" }} + +
+
+ +
+
+
-
-
- -
+
+
+
+
- {{/if}} +
+ + +
+ + Copy & Close + +
+
diff --git a/ui/app/templates/components/transit-key-action/rewrap.hbs b/ui/app/templates/components/transit-key-action/rewrap.hbs index 4890d5dd9..a70e9735b 100644 --- a/ui/app/templates/components/transit-key-action/rewrap.hbs +++ b/ui/app/templates/components/transit-key-action/rewrap.hbs @@ -1,6 +1,9 @@
+
+

You can rewrap the provided ciphertext using the latest version of {{key.name}} as the encryption key.

+
{{key-version-select key=key onVersionChange=(action (mut key_version)) @@ -10,7 +13,6 @@
{{ivy-codemirror - value=ciphertext valueUpdated=(action (mut ciphertext)) options=(hash lineNumbers=true @@ -63,3 +65,23 @@
+ + +
+ + Copy & Close + +
+
diff --git a/ui/app/templates/components/transit-key-action/sign.hbs b/ui/app/templates/components/transit-key-action/sign.hbs index 863fa52f2..98a4b07ae 100644 --- a/ui/app/templates/components/transit-key-action/sign.hbs +++ b/ui/app/templates/components/transit-key-action/sign.hbs @@ -1,118 +1,138 @@ -
- {{#if signature}} -
-
- -
- -
+ +
+ +
+

Return the cryptographic signature of the given data using {{key.name}} as the encryption key and the specified hash algorithm.

+
+ {{key-version-select + key=key + onVersionChange=(action (mut key_version)) + key_version=key_version + }} +
+ +
+ {{ivy-codemirror + value=input + valueUpdated=(action (mut input)) + options=(hash + lineNumbers=true + tabSize=2 + mode='ruby' + theme='hashi' + ) + data-test-transit-input="input" + }}
-
-
- {{#copy-button - clipboardTarget="#signature" - class="button is-primary" - buttonType="button" - success=(action (set-flash-message 'Signature copied!')) - }} - Copy - {{/copy-button}} -
-
- -
+
+ {{input type="checkbox" id="encodedBase64" checked=encodedBase64 data-test-transit-input="encodedBase64"}} +
- {{else}} -
- - {{key-version-select - key=key - onVersionChange=(action (mut key_version)) - key_version=key_version - }} + {{#if key.derived}}
-