Commit ddc541bb authored by Miguel Rincon's avatar Miguel Rincon

Mask runner registration

This change prevents users from accidentally revealing the runner
registration token by adding a "****" style mask.

Users can click on the corresponding icon to reveal the token if needed.

Changelog: changed
parent b05c6c7f
<script>
import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlButton,
},
props: {
value: {
type: String,
required: false,
default: '',
},
},
data() {
return {
isMasked: true,
};
},
computed: {
label() {
if (this.isMasked) {
return __('Click to reveal');
}
return __('Click to hide');
},
icon() {
if (this.isMasked) {
return 'eye';
}
return 'eye-slash';
},
displayedValue() {
if (this.isMasked && this.value?.length) {
return '*'.repeat(this.value.length);
}
return this.value;
},
},
methods: {
toggleMasked() {
this.isMasked = !this.isMasked;
},
},
};
</script>
<template>
<span
>{{ displayedValue }}
<gl-button
:aria-label="label"
:icon="icon"
class="gl-text-body!"
data-testid="toggle-masked"
variant="link"
@click="toggleMasked"
/>
</span>
</template>
<script>
import { GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import MaskedValue from '~/runner/components/helpers/masked_value.vue';
import RunnerRegistrationTokenReset from '~/runner/components/runner_registration_token_reset.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
......@@ -11,6 +12,7 @@ export default {
GlLink,
GlSprintf,
ClipboardButton,
MaskedValue,
RunnerInstructions,
RunnerRegistrationTokenReset,
},
......@@ -92,7 +94,9 @@ export default {
{{ __('And this registration token:') }}
<br />
<code data-testid="registration-token">{{ currentRegistrationToken }}</code>
<code data-testid="registration-token"
><masked-value :value="currentRegistrationToken"
/></code>
<clipboard-button :title="__('Copy token')" :text="currentRegistrationToken" />
</li>
</ol>
......
......@@ -6834,6 +6834,12 @@ msgstr ""
msgid "Click to expand text"
msgstr ""
msgid "Click to hide"
msgstr ""
msgid "Click to reveal"
msgstr ""
msgid "Client authentication certificate"
msgstr ""
......
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MaskedValue from '~/runner/components/helpers/masked_value.vue';
const mockSecret = '01234567890';
const mockMasked = '***********';
describe('MaskedValue', () => {
let wrapper;
const findButton = () => wrapper.findComponent(GlButton);
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(MaskedValue, {
propsData: {
value: mockSecret,
...props,
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('Displays masked value by default', () => {
expect(wrapper.text()).toBe(mockMasked);
});
describe('When the icon is clicked', () => {
beforeEach(() => {
findButton().vm.$emit('click');
});
it('Displays the actual value', () => {
expect(wrapper.text()).toBe(mockSecret);
expect(wrapper.text()).not.toBe(mockMasked);
});
it('When user clicks again, displays masked value', async () => {
await findButton().vm.$emit('click');
expect(wrapper.text()).toBe(mockMasked);
expect(wrapper.text()).not.toBe(mockSecret);
});
});
});
......@@ -3,6 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import MaskedValue from '~/runner/components/helpers/masked_value.vue';
import RunnerManualSetupHelp from '~/runner/components/runner_manual_setup_help.vue';
import RunnerRegistrationTokenReset from '~/runner/components/runner_registration_token_reset.vue';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
......@@ -37,6 +38,7 @@ describe('RunnerManualSetupHelp', () => {
...props,
},
stubs: {
MaskedValue,
GlSprintf,
},
}),
......@@ -93,7 +95,11 @@ describe('RunnerManualSetupHelp', () => {
expect(findRunnerInstructions().exists()).toBe(true);
});
it('Displays the registration token', () => {
it('Displays the registration token', async () => {
findRegistrationToken().find('[data-testid="toggle-masked"]').vm.$emit('click');
await nextTick();
expect(findRegistrationToken().text()).toBe(mockRegistrationToken);
expect(findClipboardButtons().at(1).props('text')).toBe(mockRegistrationToken);
});
......@@ -105,6 +111,7 @@ describe('RunnerManualSetupHelp', () => {
it('Replaces the runner reset button', async () => {
const mockNewRegistrationToken = 'NEW_MOCK_REGISTRATION_TOKEN';
findRegistrationToken().find('[data-testid="toggle-masked"]').vm.$emit('click');
findRunnerRegistrationTokenReset().vm.$emit('tokenReset', mockNewRegistrationToken);
await nextTick();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment