diff --git a/changelog/11284.txt b/changelog/11284.txt
new file mode 100644
index 000000000..8c0332a3b
--- /dev/null
+++ b/changelog/11284.txt
@@ -0,0 +1,3 @@
+```release-note:improvement
+ui: Obscure secret values on input and displayOnly fields like certificates.
+```
\ No newline at end of file
diff --git a/ui/app/components/text-file.js b/ui/app/components/text-file.js
index e1d72a078..0f82d11cb 100644
--- a/ui/app/components/text-file.js
+++ b/ui/app/components/text-file.js
@@ -1,84 +1,84 @@
-import Component from '@ember/component';
+import Component from '@glimmer/component';
import { set } from '@ember/object';
+import { action } from '@ember/object';
+import { tracked } from '@glimmer/tracking';
+import { guidFor } from '@ember/object/internals';
-export default Component.extend({
- 'data-test-component': 'text-file',
- classNames: ['box', 'is-fullwidth', 'is-marginless', 'is-shadowless'],
- classNameBindings: ['inputOnly:is-paddingless'],
+/**
+ * @module TextFile
+ * `TextFile` components are file upload components where you can either toggle to upload a file or enter text.
+ *
+ * @example
+ *
+ *
+ * @param [inputOnly] {bool} - When true, only the file input will be rendered
+ * @param [helpText] {string} - Text underneath label.
+ * @param file {object} - * Object in the shape of:
+ * {
+ * value: 'file contents here',
+ * fileName: 'nameOfFile.txt',
+ * enterAsText: boolean ability to enter as text
+ * }
+ * @param [onChange=Function.prototype] {Function|action} - A function to call when the value of the input changes.
+ * @param [label=null] {string} - Text to use as the label for the file input. If null, a default will be rendered.
+ */
- /*
- * @public
- * @param Object
- * Object in the shape of:
- * {
- * value: 'file contents here',
- * fileName: 'nameOfFile.txt',
- * enterAsText: bool
- * }
- */
- file: null,
+export default class TextFile extends Component {
+ fileHelpText = 'Select a file from your computer';
+ textareaHelpText = 'Enter the value as text';
+ elementId = guidFor(this);
+ index = '';
- index: null,
- onChange: () => {},
+ @tracked file = null;
+ @tracked showValue = false;
- /*
- * @public
- * @param Boolean
- * When true, only the file input will be rendered
- */
- inputOnly: false,
-
- /*
- * @public
- * @param String
- * Text to use as the label for the file input
- * If null, a default will be rendered
- */
- label: null,
-
- /*
- * @public
- * @param String
- * Text to use as help under the file input
- * If null, a default will be rendered
- */
- fileHelpText: 'Select a file from your computer',
-
- /*
- * @public
- * @param String
- * Text to use as help under the textarea in text-input mode
- * If null, a default will be rendered
- */
- textareaHelpText: 'Enter the value as text',
+ get inputOnly() {
+ return this.args.inputOnly || false;
+ }
+ get label() {
+ return this.args.label || null;
+ }
readFile(file) {
const reader = new FileReader();
reader.onload = () => this.setFile(reader.result, file.name);
reader.readAsText(file);
- },
+ }
setFile(contents, filename) {
- this.onChange(this.index, { value: contents, fileName: filename });
- },
+ this.args.onChange(this.index, { value: contents, fileName: filename });
+ }
- actions: {
- pickedFile(e) {
- const { files } = e.target;
- if (!files.length) {
- return;
- }
- for (let i = 0, len = files.length; i < len; i++) {
- this.readFile(files[i]);
- }
- },
- updateData(e) {
- const file = this.file;
- set(file, 'value', e.target.value);
- this.onChange(this.index, this.file);
- },
- clearFile() {
- this.onChange(this.index, { value: '' });
- },
- },
-});
+ @action
+ pickedFile(e) {
+ e.preventDefault();
+ const { files } = e.target;
+ if (!files.length) {
+ return;
+ }
+ for (let i = 0, len = files.length; i < len; i++) {
+ this.readFile(files[i]);
+ }
+ }
+ @action
+ updateData(e) {
+ e.preventDefault();
+ let file = this.args.file;
+ set(file, 'value', e.target.value);
+ this.args.onChange(this.index, file);
+ }
+ @action
+ clearFile() {
+ this.args.onChange(this.index, { value: '' });
+ }
+ @action
+ toggleMask() {
+ this.showValue = !this.showValue;
+ }
+}
diff --git a/ui/app/models/auth-config/ldap.js b/ui/app/models/auth-config/ldap.js
index d28130f22..18b801474 100644
--- a/ui/app/models/auth-config/ldap.js
+++ b/ui/app/models/auth-config/ldap.js
@@ -9,7 +9,7 @@ export default AuthConfig.extend({
useOpenAPI: true,
certificate: attr({
label: 'Certificate',
- editType: 'textarea',
+ editType: 'file',
}),
fieldGroups: computed('newFields', function() {
let groups = [
diff --git a/ui/app/models/pki-ca-certificate.js b/ui/app/models/pki-ca-certificate.js
index 08ebd372b..255df6f02 100644
--- a/ui/app/models/pki-ca-certificate.js
+++ b/ui/app/models/pki-ca-certificate.js
@@ -143,6 +143,7 @@ export default Certificate.extend({
csr: attr('string', {
editType: 'textarea',
label: 'CSR',
+ masked: true,
}),
expiration: attr(),
diff --git a/ui/app/models/pki-certificate.js b/ui/app/models/pki-certificate.js
index 73bd6895e..9a5a481cf 100644
--- a/ui/app/models/pki-certificate.js
+++ b/ui/app/models/pki-certificate.js
@@ -63,14 +63,20 @@ export default Model.extend({
defaultValue: false,
}),
- certificate: attr('string'),
+ certificate: attr('string', {
+ masked: true,
+ }),
issuingCa: attr('string', {
label: 'Issuing CA',
+ masked: true,
}),
caChain: attr('string', {
label: 'CA chain',
+ masked: true,
+ }),
+ privateKey: attr('string', {
+ masked: true,
}),
- privateKey: attr('string'),
privateKeyType: attr('string'),
serialNumber: attr('string'),
diff --git a/ui/app/styles/app.scss b/ui/app/styles/app.scss
index 7f8fbdb71..d8385bb21 100644
--- a/ui/app/styles/app.scss
+++ b/ui/app/styles/app.scss
@@ -1,3 +1,12 @@
@import 'ember-basic-dropdown';
@import 'ember-power-select';
@import './core';
+
+@mixin font-face($name) {
+ @font-face {
+ font-family: $name;
+ src: url('/ui/fonts/#{$name}.woff2') format('woff2'), url('/ui/fonts/#{$name}.woff') format('woff');
+ }
+}
+
+@include font-face('obscure');
diff --git a/ui/app/styles/components/masked-input.scss b/ui/app/styles/components/masked-input.scss
index 124f2a164..65f03e0cb 100644
--- a/ui/app/styles/components/masked-input.scss
+++ b/ui/app/styles/components/masked-input.scss
@@ -1,13 +1,12 @@
+.masked-font {
+ color: $ui-gray-300;
+}
+
.masked-input {
display: flex;
align-items: center;
}
-.masked-input.masked.display-only,
-.masked-input:not(.masked) {
- align-items: start;
-}
-
.has-label .masked-input {
padding-top: $spacing-s;
}
@@ -91,10 +90,6 @@
color: $grey-light;
}
-.masked-input:not(.masked) .masked-input-toggle {
- color: $blue;
-}
-
.masked-input .input:focus + .masked-input-toggle {
background: rgba($white, 0.95);
}
diff --git a/ui/app/styles/components/text-file.scss b/ui/app/styles/components/text-file.scss
new file mode 100644
index 000000000..f7dd0f8f7
--- /dev/null
+++ b/ui/app/styles/components/text-file.scss
@@ -0,0 +1,14 @@
+.text-file {
+ .has-icon-right {
+ display: flex;
+ width: 97%;
+
+ .textarea {
+ line-height: inherit;
+ }
+ }
+ .button.masked-input-toggle,
+ .button.copy-button {
+ display: flex;
+ }
+}
diff --git a/ui/app/styles/core.scss b/ui/app/styles/core.scss
index 1f658dfdf..3f51210fc 100644
--- a/ui/app/styles/core.scss
+++ b/ui/app/styles/core.scss
@@ -99,6 +99,7 @@
@import './components/splash-page';
@import './components/status-menu';
@import './components/tabs';
+@import './components/text-file';
@import './components/token-expire-warning';
@import './components/toolbar';
@import './components/tool-tip';
diff --git a/ui/app/styles/core/forms.scss b/ui/app/styles/core/forms.scss
index b31b44fe0..7db94e48a 100644
--- a/ui/app/styles/core/forms.scss
+++ b/ui/app/styles/core/forms.scss
@@ -11,6 +11,13 @@ label {
}
}
+.masked-font {
+ font-family: obscure;
+ font-size: $size-medium;
+ letter-spacing: 2px;
+ color: $ui-gray-300;
+}
+
.label {
color: $grey-darker;
text-transform: uppercase;
diff --git a/ui/app/templates/components/config-pki-ca.hbs b/ui/app/templates/components/config-pki-ca.hbs
index 95283b3a9..889946e88 100644
--- a/ui/app/templates/components/config-pki-ca.hbs
+++ b/ui/app/templates/components/config-pki-ca.hbs
@@ -9,7 +9,27 @@
{{#if (or model.certificate model.csr)}}
{{#each model.attrs as |attr|}}
- {{info-table-row data-test-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(get model attr.name)}}
+ {{#if attr.options.masked}}
+
+
+
+ {{else if (eq attr.name "expiration")}}
+ {{info-table-row
+ data-test-table-row
+ label=(capitalize (or attr.options.label (humanize (dasherize attr.name))))
+ value=(date-format (get model attr.name) 'MMM dd, yyyy hh:mm:ss a')
+ }}
+ {{else}}
+ {{info-table-row
+ data-test-table-row
+ label=(capitalize (or attr.options.label (humanize (dasherize attr.name))))
+ value=(get model attr.name)
+ }}
+ {{/if}}
{{/each}}
@@ -70,7 +90,17 @@
data-test-warning
/>
{{#each model.attrs as |attr|}}
- {{info-table-row data-test-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(get model attr.name)}}
+ {{#if attr.options.masked}}
+
+
+
+ {{else}}
+ {{info-table-row data-test-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(get model attr.name)}}
+ {{/if}}
{{/each}}
diff --git a/ui/app/templates/components/generate-credentials.hbs b/ui/app/templates/components/generate-credentials.hbs
index f5344fb43..3d0f7111b 100644
--- a/ui/app/templates/components/generate-credentials.hbs
+++ b/ui/app/templates/components/generate-credentials.hbs
@@ -49,12 +49,14 @@
(eq attr.name "secretKey")
(eq attr.name "securityToken")
(eq attr.name "privateKey")
+ attr.options.masked
)}}
{{else}}
diff --git a/ui/app/templates/components/pki-cert-show.hbs b/ui/app/templates/components/pki-cert-show.hbs
index ef980d1ca..3e19c7d65 100644
--- a/ui/app/templates/components/pki-cert-show.hbs
+++ b/ui/app/templates/components/pki-cert-show.hbs
@@ -15,7 +15,17 @@
{{#if (eq attr.type "object")}}
{{else}}
-
+ {{#if attr.options.masked}}
+
+
+
+ {{else}}
+
+ {{/if}}
{{/if}}
{{/each}}
diff --git a/ui/app/templates/components/text-file.hbs b/ui/app/templates/components/text-file.hbs
index 932b9422b..fab1724a0 100644
--- a/ui/app/templates/components/text-file.hbs
+++ b/ui/app/templates/components/text-file.hbs
@@ -1,13 +1,13 @@
-{{#unless inputOnly}}
+{{#unless this.inputOnly}}
{{/unless}}
-
- {{#if file.enterAsText}}
-
+
+ {{#if @file.enterAsText}}
+
+ >{{@file.value}}
+
- {{textareaHelpText}}
+ {{this.textareaHelpText}}
{{else}}
- {{fileHelpText}}
+ {{this.fileHelpText}}
{{/if}}
diff --git a/ui/app/templates/partials/secret-backend-settings/ssh.hbs b/ui/app/templates/partials/secret-backend-settings/ssh.hbs
index a7fb7a5e9..d2908ab5f 100644
--- a/ui/app/templates/partials/secret-backend-settings/ssh.hbs
+++ b/ui/app/templates/partials/secret-backend-settings/ssh.hbs
@@ -5,13 +5,20 @@
Public key
-
+
-
+
Copy
diff --git a/ui/app/templates/vault/cluster/policies/create.hbs b/ui/app/templates/vault/cluster/policies/create.hbs
index b7ea167da..02a3688a0 100644
--- a/ui/app/templates/vault/cluster/policies/create.hbs
+++ b/ui/app/templates/vault/cluster/policies/create.hbs
@@ -46,7 +46,7 @@
{{#if showFileUpload}}
-
+
{{else}}
*
* @param [value] {String} - The value to display in the input.
- * @param [placeholder=value] {String} - The placeholder to display before the user has entered any input.
* @param [allowCopy=null] {bool} - Whether or not the input should render with a copy button.
* @param [displayOnly=false] {bool} - Whether or not to display the value as a display only `pre` element or as an input.
* @param [onChange=Function.prototype] {Function|action} - A function to call when the value of the input changes.
- *
+ * @param [isCertificate=false] {bool} - If certificate display the label and icons differently.
*
*/
-
export default Component.extend({
layout,
value: null,
- placeholder: 'value',
+ showValue: false,
didInsertElement() {
this._super(...arguments);
autosize(this.element.querySelector('textarea'));
@@ -40,30 +36,12 @@ export default Component.extend({
this._super(...arguments);
autosize.destroy(this.element.querySelector('textarea'));
},
- shouldObscure: computed('isMasked', 'isFocused', 'value', function() {
- if (this.value === '') {
- return false;
- }
- if (this.isFocused === true) {
- return false;
- }
- return this.isMasked;
- }),
- displayValue: computed('shouldObscure', 'value', function() {
- if (this.shouldObscure) {
- return '■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■';
- } else {
- return this.value;
- }
- }),
- isMasked: true,
- isFocused: false,
displayOnly: false,
onKeyDown() {},
onChange() {},
actions: {
toggleMask() {
- this.toggleProperty('isMasked');
+ this.toggleProperty('showValue');
},
updateValue(e) {
let value = e.target.value;
diff --git a/ui/lib/core/addon/helpers/date-format.js b/ui/lib/core/addon/helpers/date-format.js
index 950e51fae..1cfc951ac 100644
--- a/ui/lib/core/addon/helpers/date-format.js
+++ b/ui/lib/core/addon/helpers/date-format.js
@@ -4,6 +4,12 @@ import { format, parseISO } from 'date-fns';
export function dateFormat([date, style]) {
// see format breaking in upgrade to date-fns 2.x https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md#changed-5
let number = typeof date === 'string' ? parseISO(date) : date;
+ if (!number) {
+ return;
+ }
+ if (number.toString().length === 10) {
+ number = new Date(number * 1000);
+ }
return format(number, style);
}
diff --git a/ui/lib/core/addon/templates/components/form-field.hbs b/ui/lib/core/addon/templates/components/form-field.hbs
index 1813d8400..c022209ea 100644
--- a/ui/lib/core/addon/templates/components/form-field.hbs
+++ b/ui/lib/core/addon/templates/components/form-field.hbs
@@ -110,12 +110,9 @@
{{else if (eq attr.options.editType "file")}}
{{!-- File Input --}}
{{text-file
- index=""
- fileHelpText=attr.options.helpText
- textareaHelpText=attr.options.helpText
+ helpText=attr.options.helpText
file=file
onChange=(action "setFile")
- warning=attr.options.warning
label=labelString
}}
{{else if (eq attr.options.editType "ttl")}}
@@ -175,8 +172,11 @@
}}
{{else if (eq attr.options.sensitive true)}}
{{!-- Masked Input --}}
-
+
{{else if (or (eq attr.type "number") (eq attr.type "string"))}}
{{#if (eq attr.options.editType "textarea")}}
diff --git a/ui/lib/core/addon/templates/components/masked-input.hbs b/ui/lib/core/addon/templates/components/masked-input.hbs
index 94ce19a8c..0e6ee3f81 100644
--- a/ui/lib/core/addon/templates/components/masked-input.hbs
+++ b/ui/lib/core/addon/templates/components/masked-input.hbs
@@ -1,35 +1,41 @@
-
\ No newline at end of file
+
diff --git a/ui/lib/kmip/addon/templates/configuration.hbs b/ui/lib/kmip/addon/templates/configuration.hbs
index 3563e55e3..ae3336fbc 100644
--- a/ui/lib/kmip/addon/templates/configuration.hbs
+++ b/ui/lib/kmip/addon/templates/configuration.hbs
@@ -19,10 +19,13 @@
{{#if model}}
-
+
+
+
{{else}}
-
+
+
+
-
+
+
+
{{#each model.caChain as |chain|}}
- {{chain}}
+
{{/each}}
diff --git a/ui/public/fonts/obscure.woff b/ui/public/fonts/obscure.woff
new file mode 100644
index 000000000..9901b3dd4
--- /dev/null
+++ b/ui/public/fonts/obscure.woff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:676f434e3dcd873dca6be5a7aa7a3b9b1991a05b3efbdfcd3e316b5de0171d61
+size 1592
diff --git a/ui/public/fonts/obscure.woff2 b/ui/public/fonts/obscure.woff2
new file mode 100644
index 000000000..8614c59be
--- /dev/null
+++ b/ui/public/fonts/obscure.woff2
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4aa4d15fa8d15d7bd6f896fe201df997bcd0a6051f72cae148e0a408486e22f5
+size 888
diff --git a/ui/tests/acceptance/secrets/backend/pki/cert-test.js b/ui/tests/acceptance/secrets/backend/pki/cert-test.js
index e6ef87898..1c6892c0e 100644
--- a/ui/tests/acceptance/secrets/backend/pki/cert-test.js
+++ b/ui/tests/acceptance/secrets/backend/pki/cert-test.js
@@ -1,10 +1,9 @@
-import { currentRouteName, settled } from '@ember/test-helpers';
+import { currentRouteName, settled, click } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import editPage from 'vault/tests/pages/secrets/backend/pki/edit-role';
import listPage from 'vault/tests/pages/secrets/backend/list';
import generatePage from 'vault/tests/pages/secrets/backend/pki/generate-cert';
-import showPage from 'vault/tests/pages/secrets/backend/pki/show';
import configPage from 'vault/tests/pages/settings/configure-secret-backends/pki/section-cert';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
import authPage from 'vault/tests/pages/auth';
@@ -56,7 +55,11 @@ elRplAzrMF4=
await settled();
await generatePage.issueCert('foo');
await settled();
- assert.ok(generatePage.hasCert, 'displays the cert');
+ let countMaskedFonts = document.querySelectorAll('.masked-font').length;
+ assert.equal(countMaskedFonts, 3); // certificate, issuing ca, and private key
+ let firstUnMaskButton = document.querySelectorAll('.masked-input-toggle')[0];
+ await click(firstUnMaskButton);
+ assert.dom('.masked-value').hasTextContaining('-----BEGIN CERTIFICATE-----');
await settled();
await generatePage.back();
await settled();
@@ -68,7 +71,9 @@ elRplAzrMF4=
await settled();
await generatePage.sign('common', CSR);
await settled();
- assert.ok(generatePage.hasCert, 'displays the cert');
+ let firstUnMaskButton = document.querySelectorAll('.masked-input-toggle')[0];
+ await click(firstUnMaskButton);
+ assert.dom('.masked-value').hasTextContaining('-----BEGIN CERTIFICATE-----');
});
test('it views a cert', async function(assert) {
@@ -82,6 +87,8 @@ elRplAzrMF4=
await listPage.secrets.objectAt(0).click();
await settled();
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.show', 'navigates to the show page');
- assert.ok(showPage.hasCert, 'shows the cert');
+ let firstUnMaskButton = document.querySelectorAll('.masked-input-toggle')[0];
+ await click(firstUnMaskButton);
+ assert.dom('.masked-value').hasTextContaining('-----BEGIN CERTIFICATE-----');
});
});
diff --git a/ui/tests/acceptance/settings/configure-secret-backends/pki/section-cert-test.js b/ui/tests/acceptance/settings/configure-secret-backends/pki/section-cert-test.js
index 530222238..d4c43bc97 100644
--- a/ui/tests/acceptance/settings/configure-secret-backends/pki/section-cert-test.js
+++ b/ui/tests/acceptance/settings/configure-secret-backends/pki/section-cert-test.js
@@ -1,4 +1,4 @@
-import { currentRouteName, settled } from '@ember/test-helpers';
+import { currentRouteName, settled, click } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import page from 'vault/tests/pages/settings/configure-secret-backends/pki/section-cert';
@@ -112,7 +112,8 @@ BXUV2Uwtxf+QCphnlht9muX2fsLIzDJea0JipWj1uf2H8OZsjE8=
await page.form.generateCA('Intermediate CA', 'intermediate');
await settled();
// cache csr
- csrVal = page.form.csr;
+ await click('.masked-input-toggle');
+ csrVal = document.querySelector('.masked-value').innerText;
await page.form.back();
await settled();
await page.visit({ backend: rootPath });
@@ -121,7 +122,8 @@ BXUV2Uwtxf+QCphnlht9muX2fsLIzDJea0JipWj1uf2H8OZsjE8=
await settled();
await page.form.csrField(csrVal).submit();
await settled();
- intermediateCert = page.form.certificate;
+ await click('.masked-input-toggle');
+ intermediateCert = document.querySelector('[data-test-masked-input]').innerText;
await page.form.back();
await settled();
await page.visit({ backend: intermediatePath });
diff --git a/ui/tests/acceptance/wrapped-token-test.js b/ui/tests/acceptance/wrapped-token-test.js
index 565ffd26d..6f3fa390f 100644
--- a/ui/tests/acceptance/wrapped-token-test.js
+++ b/ui/tests/acceptance/wrapped-token-test.js
@@ -28,7 +28,7 @@ const setupWrapping = async () => {
module('Acceptance | wrapped_token query param functionality', function(hooks) {
setupApplicationTest(hooks);
- test('it authenticates you if the query param is present meep', async function(assert) {
+ test('it authenticates you if the query param is present', async function(assert) {
let token = await setupWrapping();
await auth.visit({ wrapped_token: token });
await settled();
diff --git a/ui/tests/integration/components/form-field-test.js b/ui/tests/integration/components/form-field-test.js
index 04948a035..1a720a6f5 100644
--- a/ui/tests/integration/components/form-field-test.js
+++ b/ui/tests/integration/components/form-field-test.js
@@ -1,7 +1,7 @@
import EmberObject from '@ember/object';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
+import { render, click, fillIn } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { create } from 'ember-cli-page-object';
import sinon from 'sinon';
@@ -102,6 +102,11 @@ module('Integration | Component | form field', function(hooks) {
test('it renders: editType file', async function(assert) {
await setup.call(this, createAttr('foo', 'string', { editType: 'file' }));
assert.ok(component.hasTextFile, 'renders the text-file component');
+ await click('[data-test-text-toggle="true"]');
+ await fillIn('[data-test-text-file-textarea="true"]', 'hello world');
+ assert.dom('[data-test-text-file-textarea="true"]').hasClass('masked-font');
+ await click('[data-test-button]');
+ assert.dom('[data-test-text-file-textarea="true"]').doesNotHaveClass('masked-font');
});
test('it renders: editType ttl', async function(assert) {
diff --git a/ui/tests/integration/components/masked-input-test.js b/ui/tests/integration/components/masked-input-test.js
index d2cd29b42..66c815f30 100644
--- a/ui/tests/integration/components/masked-input-test.js
+++ b/ui/tests/integration/components/masked-input-test.js
@@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
+import { render, focus } from '@ember/test-helpers';
import { create } from 'ember-cli-page-object';
import hbs from 'htmlbars-inline-precompile';
import maskedInput from 'vault/tests/pages/components/masked-input';
@@ -10,14 +10,9 @@ const component = create(maskedInput);
module('Integration | Component | masked input', function(hooks) {
setupRenderingTest(hooks);
- const hasClass = (classString = '', classToFind) => {
- return classString.split(' ').includes(classToFind);
- };
-
test('it renders', async function(assert) {
await render(hbs`{{masked-input}}`);
-
- assert.ok(hasClass(component.wrapperClass, 'masked'));
+ assert.dom('[data-test-masked-input]').exists('shows expiration beacon');
});
test('it renders a textarea', async function(assert) {
@@ -26,14 +21,17 @@ module('Integration | Component | masked input', function(hooks) {
assert.ok(component.textareaIsPresent);
});
- test('it renders an input with type password when maskWhileTyping is true', async function(assert) {
- await render(hbs`{{masked-input maskWhileTyping=true}}`);
- assert.ok(component.inputIsPresent);
- assert.equal(
- this.element.querySelector('input').getAttribute('type'),
- 'password',
- 'default type equals password'
- );
+ test('it renders an input with obscure font', async function(assert) {
+ await render(hbs`{{masked-input}}`);
+
+ assert.dom('[data-test-textarea]').hasClass('masked-font', 'loading class with correct font');
+ });
+
+ test('it renders obscure font when displayOnly', async function(assert) {
+ this.set('value', 'value');
+ await render(hbs`{{masked-input displayOnly=true value=value}}`);
+
+ assert.dom('.masked-value').hasClass('masked-font', 'loading class with correct font');
});
test('it does not render a textarea when displayOnly is true', async function(assert) {
@@ -54,36 +52,12 @@ module('Integration | Component | masked input', function(hooks) {
assert.notOk(component.copyButtonIsPresent);
});
- test('it unmasks text on focus', async function(assert) {
- this.set('value', 'value');
- await render(hbs`{{masked-input value=value}}`);
- assert.ok(hasClass(component.wrapperClass, 'masked'));
-
- await component.focusField();
- assert.notOk(hasClass(component.wrapperClass, 'masked'));
- });
-
- test('it remasks text on blur', async function(assert) {
- this.set('value', 'value');
- await render(hbs`{{masked-input value=value}}`);
-
- assert.ok(hasClass(component.wrapperClass, 'masked'));
-
- await component.focusField();
- await component.blurField();
-
- assert.ok(hasClass(component.wrapperClass, 'masked'));
- });
-
test('it unmasks text when button is clicked', async function(assert) {
this.set('value', 'value');
await render(hbs`{{masked-input value=value}}`);
-
- assert.ok(hasClass(component.wrapperClass, 'masked'));
-
await component.toggleMasked();
- assert.notOk(hasClass(component.wrapperClass, 'masked'));
+ assert.dom('.masked-value').doesNotHaveClass('masked-font');
});
test('it remasks text when button is clicked', async function(assert) {
@@ -93,18 +67,25 @@ module('Integration | Component | masked input', function(hooks) {
await component.toggleMasked();
await component.toggleMasked();
- assert.ok(hasClass(component.wrapperClass, 'masked'));
+ assert.dom('.masked-value').hasClass('masked-font');
});
- test('it changes type to text when unmasked button is clicked', async function(assert) {
- this.set('value', 'value');
- await render(hbs`{{masked-input value=value maskWhileTyping=true}}`);
- await component.toggleMasked();
+ test('it shortens long outputs when displayOnly and masked', async function(assert) {
+ this.set('value', '123456789-123456789-123456789');
+ await render(hbs`{{masked-input value=value displayOnly=true}}`);
+ let maskedValue = document.querySelector('.masked-value').innerText;
+ assert.equal(maskedValue.length, 20);
- assert.equal(
- this.element.querySelector('input').getAttribute('type'),
- 'text',
- 'when unmasked type changes to text'
- );
+ await component.toggleMasked();
+ let unMaskedValue = document.querySelector('.masked-value').innerText;
+ assert.equal(unMaskedValue.length, this.value.length);
+ });
+
+ test('it does not unmask text on focus', async function(assert) {
+ this.set('value', '123456789-123456789-123456789');
+ await render(hbs`{{masked-input value=value}}`);
+ assert.dom('.masked-value').hasClass('masked-font');
+ await focus('.masked-value');
+ assert.dom('.masked-value').hasClass('masked-font');
});
});
diff --git a/ui/tests/integration/helpers/date-format-test.js b/ui/tests/integration/helpers/date-format-test.js
index a41db85f9..5349686d6 100644
--- a/ui/tests/integration/helpers/date-format-test.js
+++ b/ui/tests/integration/helpers/date-format-test.js
@@ -34,4 +34,12 @@ module('Integration | Helper | date-format', function(hooks) {
.dom('[data-test-date-format]')
.includesText(todayString, 'it renders the a date if passed in as a string');
});
+
+ test('it supports ten digit dates', async function(assert) {
+ let tenDigitDate = 1621785298;
+ this.set('tenDigitDate', tenDigitDate);
+
+ await render(hbs`Date: {{date-format tenDigitDate "MM/dd/yyyy"}}
`);
+ assert.dom('[data-test-date-format]').includesText('05/23/2021');
+ });
});
diff --git a/ui/tests/pages/components/masked-input.js b/ui/tests/pages/components/masked-input.js
index 260c9cef4..611073269 100644
--- a/ui/tests/pages/components/masked-input.js
+++ b/ui/tests/pages/components/masked-input.js
@@ -1,17 +1,7 @@
-import { attribute, clickable, fillable, isPresent } from 'ember-cli-page-object';
-import { focus, blur } from '@ember/test-helpers';
+import { clickable, isPresent } from 'ember-cli-page-object';
export default {
- wrapperClass: attribute('class', '[data-test-masked-input]'),
- enterText: fillable('[data-test-textarea]'),
textareaIsPresent: isPresent('[data-test-textarea]'),
- inputIsPresent: isPresent('[data-test-input]'),
copyButtonIsPresent: isPresent('[data-test-copy-button]'),
toggleMasked: clickable('[data-test-button]'),
- async focusField() {
- return focus('[data-test-textarea]');
- },
- async blurField() {
- return blur('[data-test-textarea]');
- },
};
diff --git a/vault/ui.go b/vault/ui.go
index 952137da3..c36a247af 100644
--- a/vault/ui.go
+++ b/vault/ui.go
@@ -32,7 +32,7 @@ type UIConfig struct {
// NewUIConfig creates a new UI config
func NewUIConfig(enabled bool, physicalStorage physical.Backend, barrierStorage logical.Storage) *UIConfig {
defaultHeaders := http.Header{}
- defaultHeaders.Set("Content-Security-Policy", "default-src 'none'; connect-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'unsafe-inline' 'self'; form-action 'none'; frame-ancestors 'none'")
+ defaultHeaders.Set("Content-Security-Policy", "default-src 'none'; connect-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'unsafe-inline' 'self'; form-action 'none'; frame-ancestors 'none'; font-src 'self'")
defaultHeaders.Set("Service-Worker-Allowed", "/")
return &UIConfig{