summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-08 15:09:45 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-08 15:09:45 +0000
commit4f0f7d580907e598013ad4b445db60ceacaa4724 (patch)
tree96f8b3224f962ff7011611cfdfa65bdbe079c5cd /app/assets/javascripts
parent148b75b329294f6b6ae409bbf8d70590e63c6bc9 (diff)
downloadgitlab-ce-4f0f7d580907e598013ad4b445db60ceacaa4724.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/authentication/mount_2fa.js10
-rw-r--r--app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue163
-rw-r--r--app/assets/javascripts/authentication/two_factor_auth/constants.js9
-rw-r--r--app/assets/javascripts/authentication/two_factor_auth/index.js46
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/actions.js8
-rw-r--r--app/assets/javascripts/jobs/components/log/line.vue65
-rw-r--r--app/assets/javascripts/pages/profiles/accounts/show/index.js3
-rw-r--r--app/assets/javascripts/pages/profiles/two_factor_auths/index.js5
-rw-r--r--app/assets/javascripts/vue_shared/components/clipboard_button.vue5
9 files changed, 270 insertions, 44 deletions
diff --git a/app/assets/javascripts/authentication/mount_2fa.js b/app/assets/javascripts/authentication/mount_2fa.js
index dd5a42fa5fc..6dead2f03db 100644
--- a/app/assets/javascripts/authentication/mount_2fa.js
+++ b/app/assets/javascripts/authentication/mount_2fa.js
@@ -13,11 +13,17 @@ export const mount2faAuthentication = () => {
};
export const mount2faRegistration = () => {
+ const el = $('#js-register-token-2fa');
+
+ if (!el.length) {
+ return;
+ }
+
if (gon.webauthn) {
- const webauthnRegister = new WebAuthnRegister($('#js-register-token-2fa'), gon.webauthn);
+ const webauthnRegister = new WebAuthnRegister(el, gon.webauthn);
webauthnRegister.start();
} else {
- const u2fRegister = new U2FRegister($('#js-register-token-2fa'), gon.u2f);
+ const u2fRegister = new U2FRegister(el, gon.u2f);
u2fRegister.start();
}
};
diff --git a/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue b/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue
new file mode 100644
index 00000000000..681b501fc98
--- /dev/null
+++ b/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue
@@ -0,0 +1,163 @@
+<script>
+import Mousetrap from 'mousetrap';
+import { GlSprintf, GlButton, GlAlert } from '@gitlab/ui';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { __ } from '~/locale';
+import {
+ COPY_BUTTON_ACTION,
+ DOWNLOAD_BUTTON_ACTION,
+ PRINT_BUTTON_ACTION,
+ RECOVERY_CODE_DOWNLOAD_FILENAME,
+ COPY_KEYBOARD_SHORTCUT,
+} from '../constants';
+
+export const i18n = {
+ pageTitle: __('Two-factor Authentication Recovery codes'),
+ alertTitle: __('Please copy, download, or print your recovery codes before proceeding.'),
+ pageDescription: __(
+ 'Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{boldStart}will%{boldEnd} lose access to your account.',
+ ),
+ copyButton: __('Copy codes'),
+ downloadButton: __('Download codes'),
+ printButton: __('Print codes'),
+ proceedButton: __('Proceed'),
+};
+
+export default {
+ name: 'RecoveryCodes',
+ copyButtonAction: COPY_BUTTON_ACTION,
+ downloadButtonAction: DOWNLOAD_BUTTON_ACTION,
+ printButtonAction: PRINT_BUTTON_ACTION,
+ recoveryCodeDownloadFilename: RECOVERY_CODE_DOWNLOAD_FILENAME,
+ i18n,
+ mousetrap: null,
+ components: { GlSprintf, GlButton, GlAlert, ClipboardButton },
+ props: {
+ codes: {
+ type: Array,
+ required: true,
+ },
+ profileAccountPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ proceedButtonDisabled: true,
+ };
+ },
+ computed: {
+ codesAsString() {
+ return this.codes.join('\n');
+ },
+ codeDownloadUrl() {
+ return `data:text/plain;charset=utf-8,${encodeURIComponent(this.codesAsString)}`;
+ },
+ },
+ created() {
+ this.$options.mousetrap = new Mousetrap();
+
+ this.$options.mousetrap.bind(COPY_KEYBOARD_SHORTCUT, this.handleKeyboardCopy);
+ },
+ beforeDestroy() {
+ if (!this.$options.mousetrap) {
+ return;
+ }
+
+ this.$options.mousetrap.unbind(COPY_KEYBOARD_SHORTCUT);
+ },
+ methods: {
+ handleButtonClick(action) {
+ this.proceedButtonDisabled = false;
+
+ if (action === this.$options.printButtonAction) {
+ window.print();
+ }
+ },
+ handleKeyboardCopy() {
+ if (!window.getSelection) {
+ return;
+ }
+
+ const copiedText = window.getSelection().toString();
+
+ if (copiedText.includes(this.codesAsString)) {
+ this.proceedButtonDisabled = false;
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h3 class="page-title">
+ {{ $options.i18n.pageTitle }}
+ </h3>
+ <hr />
+ <gl-alert variant="info" :dismissible="false">
+ {{ $options.i18n.alertTitle }}
+ </gl-alert>
+ <p class="gl-mt-5">
+ <gl-sprintf :message="$options.i18n.pageDescription">
+ <template #bold="{ content }"
+ ><strong>{{ content }}</strong></template
+ >
+ </gl-sprintf>
+ </p>
+
+ <div
+ class="codes-to-print gl-my-5 gl-p-5 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base"
+ data-testid="recovery-codes"
+ data-qa-selector="codes_content"
+ >
+ <ul class="gl-m-0 gl-pl-5">
+ <li v-for="(code, index) in codes" :key="index">
+ <span class="gl-font-monospace" data-qa-selector="code_content">{{ code }}</span>
+ </li>
+ </ul>
+ </div>
+ <div class="gl-my-n2 gl-mx-n2 gl-display-flex gl-flex-wrap">
+ <div class="gl-p-2">
+ <clipboard-button
+ :title="$options.i18n.copyButton"
+ :text="codesAsString"
+ data-qa-selector="copy_button"
+ @click="handleButtonClick($options.copyButtonAction)"
+ >
+ {{ $options.i18n.copyButton }}
+ </clipboard-button>
+ </div>
+ <div class="gl-p-2">
+ <gl-button
+ :href="codeDownloadUrl"
+ :title="$options.i18n.downloadButton"
+ icon="download"
+ :download="$options.recoveryCodeDownloadFilename"
+ @click="handleButtonClick($options.downloadButtonAction)"
+ >
+ {{ $options.i18n.downloadButton }}
+ </gl-button>
+ </div>
+ <div class="gl-p-2">
+ <gl-button
+ :title="$options.i18n.printButton"
+ @click="handleButtonClick($options.printButtonAction)"
+ >
+ {{ $options.i18n.printButton }}
+ </gl-button>
+ </div>
+ <div class="gl-p-2">
+ <gl-button
+ :href="profileAccountPath"
+ :disabled="proceedButtonDisabled"
+ :title="$options.i18n.proceedButton"
+ variant="success"
+ data-qa-selector="proceed_button"
+ >{{ $options.i18n.proceedButton }}</gl-button
+ >
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/authentication/two_factor_auth/constants.js b/app/assets/javascripts/authentication/two_factor_auth/constants.js
new file mode 100644
index 00000000000..d27454406ef
--- /dev/null
+++ b/app/assets/javascripts/authentication/two_factor_auth/constants.js
@@ -0,0 +1,9 @@
+export const COPY_BUTTON_ACTION = 'copy';
+export const DOWNLOAD_BUTTON_ACTION = 'download';
+export const PRINT_BUTTON_ACTION = 'print';
+
+export const RECOVERY_CODE_DOWNLOAD_FILENAME = 'gitlab-recovery-codes.txt';
+
+export const SUCCESS_QUERY_PARAM = 'two_factor_auth_enabled_successfully';
+
+export const COPY_KEYBOARD_SHORTCUT = 'mod+c';
diff --git a/app/assets/javascripts/authentication/two_factor_auth/index.js b/app/assets/javascripts/authentication/two_factor_auth/index.js
new file mode 100644
index 00000000000..5e59c44e8cd
--- /dev/null
+++ b/app/assets/javascripts/authentication/two_factor_auth/index.js
@@ -0,0 +1,46 @@
+import Vue from 'vue';
+import { updateHistory, removeParams } from '~/lib/utils/url_utility';
+import RecoveryCodes from './components/recovery_codes.vue';
+import { SUCCESS_QUERY_PARAM } from './constants';
+
+export const initRecoveryCodes = () => {
+ const el = document.querySelector('.js-2fa-recovery-codes');
+
+ if (!el) {
+ return false;
+ }
+
+ const { codes = '[]', profileAccountPath = '' } = el.dataset;
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(RecoveryCodes, {
+ props: {
+ codes: JSON.parse(codes),
+ profileAccountPath,
+ },
+ });
+ },
+ });
+};
+
+export const initClose2faSuccessMessage = () => {
+ const closeButton = document.querySelector('.js-close-2fa-enabled-success-alert');
+
+ if (!closeButton) {
+ return;
+ }
+
+ closeButton.addEventListener(
+ 'click',
+ () => {
+ updateHistory({
+ url: removeParams([SUCCESS_QUERY_PARAM]),
+ title: document.title,
+ replace: true,
+ });
+ },
+ { once: true },
+ );
+};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
index f3950a3343a..b182d4dff13 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
@@ -42,7 +42,13 @@ export const createRole = ({ dispatch, state: { createRolePath } }, payload) =>
dispatch('createRoleSuccess', awsData);
})
- .catch(error => dispatch('createRoleError', { error }));
+ .catch(error => {
+ let message = error;
+ if (error?.response?.data?.message) {
+ message = error.response.data.message;
+ }
+ dispatch('createRoleError', { error: message });
+ });
};
export const requestCreateRole = ({ commit }) => {
diff --git a/app/assets/javascripts/jobs/components/log/line.vue b/app/assets/javascripts/jobs/components/log/line.vue
index affaddcdee2..87af387ca91 100644
--- a/app/assets/javascripts/jobs/components/log/line.vue
+++ b/app/assets/javascripts/jobs/components/log/line.vue
@@ -18,46 +18,33 @@ export default {
render(h, { props }) {
const { line, path } = props;
- let chars;
- if (gon?.features?.ciJobLineLinks) {
- chars = line.content.map(content => {
- return h(
- 'span',
- {
- class: ['gl-white-space-pre-wrap', content.style],
- },
- // Simple "tokenization": Split text in chunks of text
- // which alternate between text and urls.
- content.text.split(linkRegex).map(chunk => {
- // Return normal string for non-links
- if (!chunk.match(linkRegex)) {
- return chunk;
- }
- return h(
- 'a',
- {
- attrs: {
- href: chunk,
- class: 'gl-reset-color! gl-text-decoration-underline',
- rel: 'nofollow noopener noreferrer', // eslint-disable-line @gitlab/require-i18n-strings
- },
+ const chars = line.content.map(content => {
+ return h(
+ 'span',
+ {
+ class: ['gl-white-space-pre-wrap', content.style],
+ },
+ // Simple "tokenization": Split text in chunks of text
+ // which alternate between text and urls.
+ content.text.split(linkRegex).map(chunk => {
+ // Return normal string for non-links
+ if (!chunk.match(linkRegex)) {
+ return chunk;
+ }
+ return h(
+ 'a',
+ {
+ attrs: {
+ href: chunk,
+ class: 'gl-reset-color! gl-text-decoration-underline',
+ rel: 'nofollow noopener noreferrer', // eslint-disable-line @gitlab/require-i18n-strings
},
- chunk,
- );
- }),
- );
- });
- } else {
- chars = line.content.map(content => {
- return h(
- 'span',
- {
- class: ['gl-white-space-pre-wrap', content.style],
- },
- content.text,
- );
- });
- }
+ },
+ chunk,
+ );
+ }),
+ );
+ });
return h('div', { class: 'js-line log-line' }, [
h(LineNumber, {
diff --git a/app/assets/javascripts/pages/profiles/accounts/show/index.js b/app/assets/javascripts/pages/profiles/accounts/show/index.js
index 96c3d725780..6c1e953aa83 100644
--- a/app/assets/javascripts/pages/profiles/accounts/show/index.js
+++ b/app/assets/javascripts/pages/profiles/accounts/show/index.js
@@ -1,3 +1,6 @@
import initProfileAccount from '~/profile/account';
+import { initClose2faSuccessMessage } from '~/authentication/two_factor_auth';
document.addEventListener('DOMContentLoaded', initProfileAccount);
+
+initClose2faSuccessMessage();
diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
index 1aeba6669ee..24dbc312dd2 100644
--- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
+++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
@@ -1,9 +1,10 @@
import { parseBoolean } from '~/lib/utils/common_utils';
import { mount2faRegistration } from '~/authentication/mount_2fa';
+import { initRecoveryCodes } from '~/authentication/two_factor_auth';
document.addEventListener('DOMContentLoaded', () => {
const twoFactorNode = document.querySelector('.js-two-factor-auth');
- const skippable = parseBoolean(twoFactorNode.dataset.twoFactorSkippable);
+ const skippable = twoFactorNode ? parseBoolean(twoFactorNode.dataset.twoFactorSkippable) : false;
if (skippable) {
const button = `<a class="btn btn-sm btn-warning float-right" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
@@ -13,3 +14,5 @@ document.addEventListener('DOMContentLoaded', () => {
mount2faRegistration();
});
+
+initRecoveryCodes();
diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
index 960551fae91..bf1361f1a6a 100644
--- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue
+++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
@@ -84,5 +84,8 @@ export default {
:size="size"
icon="copy-to-clipboard"
:aria-label="__('Copy this value')"
- />
+ v-on="$listeners"
+ >
+ <slot></slot>
+ </gl-button>
</template>