diff --git a/changelog/23702.txt b/changelog/23702.txt
new file mode 100644
index 0000000000..3fee98a1e3
--- /dev/null
+++ b/changelog/23702.txt
@@ -0,0 +1,3 @@
+```release-note:improvement
+ui: Adds a warning when whitespace is detected in a key of a KV secret
+```
\ No newline at end of file
diff --git a/ui/lib/core/addon/components/kv-object-editor.hbs b/ui/lib/core/addon/components/kv-object-editor.hbs
index 9c803b8c9c..30e8b5e562 100644
--- a/ui/lib/core/addon/components/kv-object-editor.hbs
+++ b/ui/lib/core/addon/components/kv-object-editor.hbs
@@ -25,6 +25,7 @@
@value={{row.name}}
placeholder={{this.placeholders.key}}
{{on "change" (fn this.updateRow row index)}}
+ {{on "input" (fn this.validateKey index)}}
class="input"
/>
@@ -70,6 +71,15 @@
{{/if}}
+ {{#if (includes index this.whitespaceWarningRows)}}
+
+ {{/if}}
{{/each}}
{{#if this.hasDuplicateKeys}}
diff --git a/ui/lib/core/addon/components/kv-object-editor.js b/ui/lib/core/addon/components/kv-object-editor.js
index 69cdc2d455..40d0f3974f 100644
--- a/ui/lib/core/addon/components/kv-object-editor.js
+++ b/ui/lib/core/addon/components/kv-object-editor.js
@@ -9,6 +9,7 @@ import { isNone } from '@ember/utils';
import { assert } from '@ember/debug';
import { action } from '@ember/object';
import { guidFor } from '@ember/object/internals';
+import { A } from '@ember/array';
import KVObject from 'vault/lib/kv-object';
/**
@@ -38,6 +39,7 @@ import KVObject from 'vault/lib/kv-object';
export default class KvObjectEditor extends Component {
@tracked kvData;
+ whitespaceWarningRows = A();
get placeholders() {
return {
@@ -73,6 +75,7 @@ export default class KvObjectEditor extends Component {
const oldObj = this.kvData.objectAt(index);
assert('object guids match', guidFor(oldObj) === guidFor(object));
this.kvData.removeAt(index);
+ this.whitespaceWarningRows.removeObject(index);
this.args.onChange(this.kvData.toJSON());
}
@action
@@ -81,4 +84,16 @@ export default class KvObjectEditor extends Component {
this.args.onKeyUp(event.target.value);
}
}
+ @action
+ validateKey(rowIndex, event) {
+ const { value } = event.target;
+ const keyHasWhitespace = new RegExp('\\s', 'g').test(value);
+ const rows = [...this.whitespaceWarningRows];
+ const rowHasWarning = rows.includes(rowIndex);
+ if (!keyHasWhitespace && rowHasWarning) {
+ this.whitespaceWarningRows.removeObject(rowIndex);
+ } else if (keyHasWhitespace && !rowHasWarning) {
+ this.whitespaceWarningRows.addObject(rowIndex);
+ }
+ }
}
diff --git a/ui/tests/integration/components/kv-object-editor-test.js b/ui/tests/integration/components/kv-object-editor-test.js
index b6642ffa64..8f7eeccdbb 100644
--- a/ui/tests/integration/components/kv-object-editor-test.js
+++ b/ui/tests/integration/components/kv-object-editor-test.js
@@ -5,7 +5,7 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
+import { render, fillIn, click } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { create } from 'ember-cli-page-object';
@@ -22,14 +22,14 @@ module('Integration | Component | kv-object-editor', function (hooks) {
});
test('it renders with no initial value', async function (assert) {
- await render(hbs`{{kv-object-editor onChange=this.spy}}`);
+ await render(hbs``);
assert.strictEqual(component.rows.length, 1, 'renders a single row');
await component.addRow();
assert.strictEqual(component.rows.length, 1, 'will only render row with a blank key');
});
test('it calls onChange when the val changes', async function (assert) {
- await render(hbs`{{kv-object-editor onChange=this.spy}}`);
+ await render(hbs``);
await component.rows.objectAt(0).kvKey('foo').kvVal('bar');
assert.strictEqual(this.spy.callCount, 2, 'calls onChange each time change is triggered');
assert.deepEqual(
@@ -50,7 +50,7 @@ module('Integration | Component | kv-object-editor', function (hooks) {
test('it renders passed data', async function (assert) {
const metadata = { foo: 'bar', baz: 'bop' };
this.set('value', metadata);
- await render(hbs`{{kv-object-editor value=this.value}}`);
+ await render(hbs``);
assert.strictEqual(
component.rows.length,
Object.keys(metadata).length + 1,
@@ -59,7 +59,7 @@ module('Integration | Component | kv-object-editor', function (hooks) {
});
test('it deletes a row', async function (assert) {
- await render(hbs`{{kv-object-editor onChange=this.spy}}`);
+ await render(hbs``);
await component.rows.objectAt(0).kvKey('foo').kvVal('bar');
await component.addRow();
assert.strictEqual(component.rows.length, 2);
@@ -74,7 +74,7 @@ module('Integration | Component | kv-object-editor', function (hooks) {
test('it shows a warning if there are duplicate keys', async function (assert) {
const metadata = { foo: 'bar', baz: 'bop' };
this.set('value', metadata);
- await render(hbs`{{kv-object-editor value=this.value onChange=this.spy}}`);
+ await render(hbs``);
await component.rows.objectAt(0).kvKey('foo');
assert.ok(component.showsDuplicateError, 'duplicate keys are allowed but an error message is shown');
@@ -97,4 +97,17 @@ module('Integration | Component | kv-object-editor', function (hooks) {
assert.dom('textarea').doesNotExist('Value input hidden when block is provided');
assert.dom('[data-test-yield]').exists('Component yields block');
});
+
+ test('it should display whitespace warning for keys', async function (assert) {
+ await render(hbs``);
+ await fillIn('[data-test-kv-key="0"]', 'test ');
+ assert.dom('[data-test-kv-whitespace-warning="0"]').exists();
+ await fillIn('[data-test-kv-key="0"]', 'test');
+ assert.dom('[data-test-kv-whitespace-warning="0"]').doesNotExist();
+ await fillIn('[data-test-kv-key="0"]', 'test ');
+ await click('[data-test-kv-add-row="0"]');
+ assert.dom('[data-test-kv-whitespace-warning="0"]').exists();
+ await click('[data-test-kv-delete-row="0"]');
+ assert.dom('[data-test-kv-whitespace-warning="0"]').doesNotExist();
+ });
});