Skip to content

Commit

Permalink
hashicorp#6435 UI: Switch Mask secrets while they are entered
Browse files Browse the repository at this point in the history
  • Loading branch information
hmalphettes committed Jun 22, 2019
1 parent 84919f4 commit 7776565
Show file tree
Hide file tree
Showing 16 changed files with 104 additions and 33 deletions.
18 changes: 14 additions & 4 deletions ui/app/components/secret-edit.js
@@ -1,14 +1,14 @@
import { isBlank, isNone } from '@ember/utils';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { computed, set } from '@ember/object';
import { alias, or } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import { isBlank, isNone } from '@ember/utils';
import { task, waitForEvent } from 'ember-concurrency';
import FocusOnInsertMixin from 'vault/mixins/focus-on-insert';
import WithNavToNearestAncestor from 'vault/mixins/with-nav-to-nearest-ancestor';
import keys from 'vault/lib/keycodes';
import KVObject from 'vault/lib/kv-object';
import { maybeQueryRecord } from 'vault/macros/maybe-query-record';
import FocusOnInsertMixin from 'vault/mixins/focus-on-insert';
import WithNavToNearestAncestor from 'vault/mixins/with-nav-to-nearest-ancestor';

const LIST_ROUTE = 'vault.cluster.secrets.backend.list';
const LIST_ROOT_ROUTE = 'vault.cluster.secrets.backend.list-root';
Expand All @@ -19,6 +19,7 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
router: service(),
store: service(),
flashMessages: service(),
classNameBindings: ['showObfuscatedInputMode:obfuscated-input'],

// a key model
key: null,
Expand All @@ -41,13 +42,16 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
onDataChange() {},
onRefresh() {},
onToggleAdvancedEdit() {},
onToggleObfuscatedInput() {},

// did user request advanced mode
preferAdvancedEdit: false,
preferObfuscatedInput: false,

// use a named action here so we don't have to pass one in
// this will bubble to the route
toggleAdvancedEdit: 'toggleAdvancedEdit',
toggleObfuscatedInput: 'toggleObfuscatedInput',
error: null,

codemirrorString: null,
Expand Down Expand Up @@ -174,6 +178,9 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
return false;
}
),
showObfuscatedInputMode: computed('preferObfuscatedInput', 'lastChange', function() {
return this.preferObfuscatedInput;
}),

transitionToRoute() {
return this.router.transitionTo(...arguments);
Expand Down Expand Up @@ -350,6 +357,9 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
toggleAdvanced(bool) {
this.onToggleAdvancedEdit(bool);
},
toggleObfuscated(bool) {
this.onToggleObfuscatedInput(bool);
},

codemirrorUpdated(val, codemirror) {
this.set('error', null);
Expand Down
4 changes: 4 additions & 0 deletions ui/app/controllers/vault/cluster/secrets/backend/create.js
Expand Up @@ -15,5 +15,9 @@ export default Controller.extend(BackendCrumbMixin, {
this.set('preferAdvancedEdit', bool);
this.get('backendController').set('preferAdvancedEdit', bool);
},
toggleObfuscatedInput(bool) {
this.set('preferObfuscatedInput', bool);
this.get('backendController').set('preferObfuscatedInput', bool);
},
},
});
4 changes: 4 additions & 0 deletions ui/app/controllers/vault/cluster/secrets/backend/edit.js
Expand Up @@ -19,5 +19,9 @@ export default Controller.extend(BackendCrumbMixin, {
this.set('preferAdvancedEdit', bool);
this.get('backendController').set('preferAdvancedEdit', bool);
},
toggleObfuscatedInput(bool) {
this.set('preferObfuscatedInput', bool);
this.get('backendController').set('preferObfuscatedInput', bool);
},
},
});
4 changes: 4 additions & 0 deletions ui/app/controllers/vault/cluster/secrets/backend/show.js
Expand Up @@ -21,5 +21,9 @@ export default Controller.extend(BackendCrumbMixin, {
this.set('preferAdvancedEdit', bool);
this.get('backendController').set('preferAdvancedEdit', bool);
},
toggleObfuscatedInput(bool) {
this.set('preferObfuscated', bool);
this.get('backendController').set('preferObfuscated', bool);
},
},
});
3 changes: 3 additions & 0 deletions ui/app/routes/vault/cluster/secrets/backend/secret-edit.js
Expand Up @@ -221,6 +221,8 @@ export default Route.extend(UnloadModelRoute, {
let backend = this.enginePathParam();
const preferAdvancedEdit =
this.controllerFor('vault.cluster.secrets.backend').get('preferAdvancedEdit') || false;
const preferObfuscatedInput =
this.controllerFor('vault.cluster.secrets.backend').get('preferObfuscatedInput') || false;
const backendType = this.backendType();
model.secret.setProperties({ backend });
controller.setProperties({
Expand All @@ -234,6 +236,7 @@ export default Route.extend(UnloadModelRoute, {
.replace('-root', ''),
backend,
preferAdvancedEdit,
preferObfuscatedInput,
backendType,
});
},
Expand Down
10 changes: 10 additions & 0 deletions ui/app/styles/app.scss
@@ -1,3 +1,13 @@
@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('ObfuscatedPasswordFont');
6 changes: 6 additions & 0 deletions ui/app/styles/components/masked-input.scss
Expand Up @@ -24,6 +24,12 @@
line-height: 2.5;
}

.obfuscated-input .masked-input .input {
font-size: 9px;
font-family: ObfuscatedPasswordFont;
line-height: 2.5;
}

.masked-input.display-only .masked-value {
order: 1;
}
Expand Down
36 changes: 25 additions & 11 deletions ui/app/templates/components/secret-edit.hbs
Expand Up @@ -26,17 +26,31 @@
<Toolbar>
{{#unless (and (eq mode 'show') isWriteWithoutRead)}}
<ToolbarFilters>
<input
data-test-secret-json-toggle=true
id="json"
type="checkbox"
name="json"
class="switch is-rounded is-success is-small"
checked={{showAdvancedMode}}
onchange={{action "toggleAdvanced" value="target.checked"}}
disabled={{and (eq mode 'show') secretDataIsAdvanced}}
/>
<label for="json" class="has-text-grey">JSON</label>
<div class="control">
<input
data-test-secret-obfuscated-input-toggle=true
id="obfuscated"
type="checkbox"
name="obfuscated"
class="switch is-rounded is-success is-small"
checked={{showObfuscatedInputMode}}
onchange={{action "toggleObfuscated" value="target.checked"}}
/>
<label for="obfuscated" class="has-text-grey">Obfuscated Input</label>
</div>
<div class="control">
<input
data-test-secret-json-toggle=true
id="json"
type="checkbox"
name="json"
class="switch is-rounded is-success is-small"
checked={{showAdvancedMode}}
onchange={{action "toggleAdvanced" value="target.checked"}}
disabled={{and (eq mode 'show') secretDataIsAdvanced}}
/>
<label for="json" class="has-text-grey">JSON</label>
</div>
</ToolbarFilters>
{{/unless}}
<ToolbarActions>
Expand Down
5 changes: 3 additions & 2 deletions ui/app/templates/partials/secret-form-create.hbs
@@ -1,10 +1,10 @@
<form class="{{if showAdvancedMode 'advanced-edit' 'simple-edit'}}" onsubmit={{action "createOrUpdateKey" "create"}}>
<form class="{{if showAdvancedMode 'advanced-edit' 'simple-edit'}} {{if showObfuscatedInput 'obfuscated-input'}}" onsubmit={{action "createOrUpdateKey" "create"}}>
<div class="field box is-fullwidth is-sideless is-marginless">
<NamespaceReminder @mode="create" @noun="secret" />
<MessageError @model={{model}} @errorMessage={{error}} />
<label class="is-label" for="kv-key">Path for this secret</label>
<p class="control is-expanded">
{{input
{{input
autocomplete="off"
spellcheck="false"
data-test-secret-path="true"
Expand All @@ -21,6 +21,7 @@
</div>
<SecretEditDisplay
@showAdvancedMode={{showAdvancedMode}}
@showObfuscatedInputMode={{showObfuscatedInputMode}}
@codemirrorString={{codemirrorString}}
@secretData={{secretData}}
@isV2={{isV2}}
Expand Down
1 change: 1 addition & 0 deletions ui/app/templates/partials/secret-form-edit.hbs
Expand Up @@ -13,6 +13,7 @@
{{/if}}
<SecretEditDisplay
@showAdvancedMode={{showAdvancedMode}}
@showObfuscatedInputMode={{showObfuscatedInputMode}}
@codemirrorString={{codemirrorString}}
@secretData={{secretData}}
@isV2={{isV2}}
Expand Down
Expand Up @@ -7,7 +7,9 @@
capabilities=capabilities
onRefresh=(action "refresh")
onToggleAdvancedEdit=(action "toggleAdvancedEdit")
onToggleObfuscatedInput=(action "toggleObfuscatedInput")
initialKey=initialKey
baseKey=baseKey
preferAdvancedEdit=preferAdvancedEdit
preferObfuscatedInput=preferObfuscatedInput
}}
1 change: 1 addition & 0 deletions ui/config/environment.js
Expand Up @@ -55,6 +55,7 @@ module.exports = function(environment) {
ENV.contentSecurityPolicyHeader = 'Content-Security-Policy';
ENV.contentSecurityPolicyMeta = true;
ENV.contentSecurityPolicy = {
'font-src': ["'self'"],
'connect-src': ["'self'"],
'img-src': ["'self'", 'data:'],
'form-action': ["'none'"],
Expand Down
41 changes: 26 additions & 15 deletions ui/lib/core/addon/templates/components/masked-input.hbs
@@ -1,21 +1,32 @@
<div class="masked-input {{if shouldObscure "masked"}} {{if displayOnly "display-only"}} {{if allowCopy "allow-copy"}}"
data-test-masked-input data-test-field>
{{#if displayOnly}}
<pre class="masked-value display-only is-word-break">{{displayValue}}</pre>
<div class="masked-input {{if shouldObscure "masked"}} {{if displayOnly "display-only"}} {{if allowCopy "allow-copy"}}" data-test-masked-input data-test-field>
{{#if displayOnly}}
<pre class="masked-value display-only is-word-break">{{displayValue}}</pre>
{{else}}
<textarea class="input masked-value" rows=1 wrap="off" placeholder={{placeholder}}
onfocus={{action (mut isFocused) true}} onblur={{action (mut isFocused) false}} onkeydown={{action onKeyDown}}
onchange={{action "updateValue"}} value={{readonly displayValue}} data-test-textarea />
{{/if}}
<textarea
class="input masked-value"
rows=1
wrap="off"
spellcheck="false"
placeholder={{placeholder}}
onfocus={{action (mut isFocused) true}}
onblur={{action (mut isFocused) false}}
onkeydown={{action onKeyDown}}
onchange={{action "updateValue"}}
value={{readonly displayValue}}
data-test-textarea
/>
{{/if}}
{{#if allowCopy}}
<CopyButton @clipboardText={{value}} @success={{success}} class="copy-button button {{if displayOnly "is-compact"}}"
data-test-copy-button>
<CopyButton
@clipboardText={{value}}
@success={{success}}
class="copy-button button {{if displayOnly "is-compact"}}"
data-test-copy-button
>
<Icon @glyph="copy-action" aria-hidden="Copy value" />
</CopyButton>
{{/if}}
<button {{action "toggleMask"}}
class="{{if (eq value "") "has-text-grey"}} masked-input-toggle button {{if displayOnly "is-compact"}}"
data-test-button>
<button {{action "toggleMask"}} class="{{if (eq value "") "has-text-grey"}} masked-input-toggle button {{if displayOnly "is-compact"}}" data-test-button>
<Icon @glyph={{if shouldObscure "visibility-hide" "visibility-show"}} aria-hidden="true" />
</button>
</div>
</button>
</div>
Binary file added ui/public/fonts/ObfuscatedPasswordFont.woff
Binary file not shown.
Binary file added ui/public/fonts/ObfuscatedPasswordFont.woff2
Binary file not shown.
2 changes: 1 addition & 1 deletion vault/ui.go
Expand Up @@ -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'")

return &UIConfig{
physicalStorage: physicalStorage,
Expand Down

0 comments on commit 7776565

Please sign in to comment.