diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-29 12:53:15 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-29 12:53:43 +0000 |
commit | 8a2a8c40a84b97bd1df668b3458cf61cadce1c2a (patch) | |
tree | 838787352e579632098ddc791afe20b5ed856c12 /app | |
parent | 86842c660b55c74269649851bb694e40367e8bef (diff) | |
download | gitlab-ce-8a2a8c40a84b97bd1df668b3458cf61cadce1c2a.tar.gz |
Add latest changes from gitlab-org/security/gitlab@14-3-stable-ee
Diffstat (limited to 'app')
5 files changed, 148 insertions, 8 deletions
diff --git a/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue b/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue new file mode 100644 index 00000000000..280c222c380 --- /dev/null +++ b/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue @@ -0,0 +1,98 @@ +<script> +import { GlFormInput, GlFormGroup, GlButton, GlForm } from '@gitlab/ui'; +import csrf from '~/lib/utils/csrf'; +import { __ } from '~/locale'; + +export const i18n = { + currentPassword: __('Current password'), + confirmWebAuthn: __( + 'Are you sure? This will invalidate your registered applications and U2F / WebAuthn devices.', + ), + confirm: __('Are you sure? This will invalidate your registered applications and U2F devices.'), + disableTwoFactor: __('Disable two-factor authentication'), + regenerateRecoveryCodes: __('Regenerate recovery codes'), +}; + +export default { + name: 'ManageTwoFactorForm', + i18n, + components: { + GlForm, + GlFormInput, + GlFormGroup, + GlButton, + }, + inject: [ + 'webauthnEnabled', + 'profileTwoFactorAuthPath', + 'profileTwoFactorAuthMethod', + 'codesProfileTwoFactorAuthPath', + 'codesProfileTwoFactorAuthMethod', + ], + data() { + return { + method: '', + action: '#', + }; + }, + computed: { + confirmText() { + if (this.webauthnEnabled) { + return i18n.confirmWebAuthn; + } + + return i18n.confirm; + }, + }, + methods: { + handleFormSubmit(event) { + this.method = event.submitter.dataset.formMethod; + this.action = event.submitter.dataset.formAction; + }, + }, + csrf, +}; +</script> + +<template> + <gl-form + class="gl-display-inline-block" + method="post" + :action="action" + @submit="handleFormSubmit($event)" + > + <input type="hidden" name="_method" data-testid="test-2fa-method-field" :value="method" /> + <input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> + + <gl-form-group :label="$options.i18n.currentPassword" label-for="current-password"> + <gl-form-input + id="current-password" + type="password" + name="current_password" + required + data-qa-selector="current_password_field" + /> + </gl-form-group> + + <gl-button + type="submit" + class="btn-danger gl-mr-3 gl-display-inline-block" + data-testid="test-2fa-disable-button" + variant="danger" + :data-confirm="confirmText" + :data-form-action="profileTwoFactorAuthPath" + :data-form-method="profileTwoFactorAuthMethod" + > + {{ $options.i18n.disableTwoFactor }} + </gl-button> + <gl-button + type="submit" + class="gl-display-inline-block" + data-testid="test-2fa-regenerate-codes-button" + :data-form-action="codesProfileTwoFactorAuthPath" + :data-form-method="codesProfileTwoFactorAuthMethod" + > + {{ $options.i18n.regenerateRecoveryCodes }} + </gl-button> + </gl-form> +</template> diff --git a/app/assets/javascripts/authentication/two_factor_auth/index.js b/app/assets/javascripts/authentication/two_factor_auth/index.js index 5e59c44e8cd..f663c0705e6 100644 --- a/app/assets/javascripts/authentication/two_factor_auth/index.js +++ b/app/assets/javascripts/authentication/two_factor_auth/index.js @@ -1,8 +1,39 @@ import Vue from 'vue'; import { updateHistory, removeParams } from '~/lib/utils/url_utility'; +import ManageTwoFactorForm from './components/manage_two_factor_form.vue'; import RecoveryCodes from './components/recovery_codes.vue'; import { SUCCESS_QUERY_PARAM } from './constants'; +export const initManageTwoFactorForm = () => { + const el = document.querySelector('.js-manage-two-factor-form'); + + if (!el) { + return false; + } + + const { + webauthnEnabled = false, + profileTwoFactorAuthPath = '', + profileTwoFactorAuthMethod = '', + codesProfileTwoFactorAuthPath = '', + codesProfileTwoFactorAuthMethod = '', + } = el.dataset; + + return new Vue({ + el, + provide: { + webauthnEnabled, + profileTwoFactorAuthPath, + profileTwoFactorAuthMethod, + codesProfileTwoFactorAuthPath, + codesProfileTwoFactorAuthMethod, + }, + render(createElement) { + return createElement(ManageTwoFactorForm); + }, + }); +}; + export const initRecoveryCodes = () => { const el = document.querySelector('.js-2fa-recovery-codes'); 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 50835333a54..f6f136f2402 100644 --- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -1,5 +1,5 @@ import { mount2faRegistration } from '~/authentication/mount_2fa'; -import { initRecoveryCodes } from '~/authentication/two_factor_auth'; +import { initRecoveryCodes, initManageTwoFactorForm } from '~/authentication/two_factor_auth'; import { parseBoolean } from '~/lib/utils/common_utils'; const twoFactorNode = document.querySelector('.js-two-factor-auth'); @@ -14,3 +14,5 @@ if (skippable) { mount2faRegistration(); initRecoveryCodes(); + +initManageTwoFactorForm(); diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index 5eb46421583..d1b9485f06d 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -3,6 +3,8 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController skip_before_action :check_two_factor_requirement before_action :ensure_verified_primary_email, only: [:show, :create] + before_action :validate_current_password, only: [:create, :codes, :destroy] + before_action do push_frontend_feature_flag(:webauthn) end @@ -134,6 +136,14 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController private + def validate_current_password + return if current_user.valid_password?(params[:current_password]) + + current_user.increment_failed_attempts! + + redirect_to profile_two_factor_auth_path, alert: _('You must provide a valid current password') + end + def build_qr_code uri = current_user.otp_provisioning_uri(account_string, issuer: issuer_host) RQRCode.render_qrcode(uri, :svg, level: :m, unit: 3) diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 927b6d4edef..d1d6b6301b8 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -17,13 +17,7 @@ = _("You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.") %p = _('If you lose your recovery codes you can generate new ones, invalidating all previous codes.') - %div - = link_to _('Disable two-factor authentication'), profile_two_factor_auth_path, - method: :delete, - data: { confirm: webauthn_enabled ? _('Are you sure? This will invalidate your registered applications and U2F / WebAuthn devices.') : _('Are you sure? This will invalidate your registered applications and U2F devices.') }, - class: 'gl-button btn btn-danger gl-mr-3' - = form_tag codes_profile_two_factor_auth_path, {style: 'display: inline-block', method: :post} do |f| - = submit_tag _('Regenerate recovery codes'), class: 'gl-button btn btn-default' + .js-manage-two-factor-form{ data: { webauthn_enabled: webauthn_enabled, profile_two_factor_auth_path: profile_two_factor_auth_path, profile_two_factor_auth_method: 'delete', codes_profile_two_factor_auth_path: codes_profile_two_factor_auth_path, codes_profile_two_factor_auth_method: 'post' } } - else %p @@ -53,6 +47,11 @@ .form-group = label_tag :pin_code, _('Pin code'), class: "label-bold" = text_field_tag :pin_code, nil, class: "form-control gl-form-input", required: true, data: { qa_selector: 'pin_code_field' } + .form-group + = label_tag :current_password, _('Current password'), class: 'label-bold' + = password_field_tag :current_password, nil, required: true, class: 'form-control gl-form-input', data: { qa_selector: 'current_password_field' } + %p.form-text.text-muted + = _('Your current password is required to register a two-factor authenticator app.') .gl-mt-3 = submit_tag _('Register with two-factor app'), class: 'gl-button btn btn-confirm', data: { qa_selector: 'register_2fa_app_button' } |