From 4650d5bdbfd8619979db413759de873d55b8a3c7 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 27 Sep 2016 12:46:09 +0200 Subject: [PATCH 1/2] Hide private token by default Only show the private token on the profile page after the user enters their current password. Works nicer with JS, but also works without. --- CHANGELOG | 1 + app/assets/javascripts/dispatcher.js | 3 ++ .../javascripts/private_token_viewer.js.es6 | 46 +++++++++++++++++++ app/assets/stylesheets/behaviors.scss | 4 ++ .../profiles/accounts_controller.rb | 31 ++++++++++++- app/views/profiles/accounts/show.html.haml | 28 +++++++---- config/routes.rb | 1 + spec/features/profiles/private_token_spec.rb | 46 +++++++++++++++++++ 8 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/private_token_viewer.js.es6 create mode 100644 spec/features/profiles/private_token_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 28c5d23d604c..542b76028093 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Speed-up group milestones show page - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) + - Hide API token by default on profile page - Revoke button in Applications Settings underlines on hover. - Add organization field to user profile diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ddf11ecf34c7..58b4e9c258dd 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -193,6 +193,9 @@ case 'projects:cycle_analytics:show': new gl.CycleAnalytics(); break; + case 'profiles:accounts:show': + new gl.PrivateTokenViewer(); + break; } switch (path.first()) { case 'admin': diff --git a/app/assets/javascripts/private_token_viewer.js.es6 b/app/assets/javascripts/private_token_viewer.js.es6 new file mode 100644 index 000000000000..6c7cd436e37b --- /dev/null +++ b/app/assets/javascripts/private_token_viewer.js.es6 @@ -0,0 +1,46 @@ +((global) => { + gl.PrivateTokenViewer = class PrivateTokenViewer { + constructor() { + this.$show = $('#private-token-show'); + this.$error = $('#private-token-error'); + this.$request = $('#private-token-request'); + this.$requestForm = this.$request.find('form'); + + this.initialSetup(); + } + + initialSetup() { + this.$show.addClass('hidden'); + this.$error.addClass('hidden'); + + this.$requestForm.on('submit', this.submitPassword.bind(this)); + } + + submitPassword(event) { + $.ajax({ + url: this.$requestForm.attr('action'), + method: 'POST', + dataType: 'json', + contentType: 'application/json', + data: JSON.stringify({ + current_password: this.$requestForm.find('#current_password').val() + }) + }).done((data) => { + this.$show.find('#token').val(data.private_token); + + this.$show.removeClass('hidden'); + this.$error.addClass('hidden'); + this.$request.addClass('hidden'); + }).error((request) => { + this.$error.text(request.responseJSON.message); + + this.$show.addClass('hidden'); + this.$error.removeClass('hidden'); + this.$request.removeClass('hidden'); + this.$requestForm.find('[type="submit"]').enable(); + }); + + event.preventDefault(); + } + }; +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss index 897bc49e7df0..f76396b0a02f 100644 --- a/app/assets/stylesheets/behaviors.scss +++ b/app/assets/stylesheets/behaviors.scss @@ -21,6 +21,10 @@ } } +.hidden { + display: none; +} + // Hide element if Vue is still working on rendering it fully. [v-cloak="true"] { display: none !important; diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb index 69959fe3687c..99a06fbe7024 100644 --- a/app/controllers/profiles/accounts_controller.rb +++ b/app/controllers/profiles/accounts_controller.rb @@ -1,6 +1,29 @@ class Profiles::AccountsController < Profiles::ApplicationController + before_action :set_user, only: [:show, :private_token] + def show - @user = current_user + end + + def private_token + if params[:current_password] + if @user.valid_password?(params[:current_password]) + @private_token = @user.private_token + else + @private_token_error = 'The password you entered is incorrect. Please try again.' + end + end + + respond_to do |format| + format.html { render 'show' } + + format.json do + if @private_token + render json: { private_token: @private_token } + else + render status: :bad_request, json: { message: @private_token_error } + end + end + end end def unlink @@ -8,4 +31,10 @@ def unlink current_user.identities.find_by(provider: provider).destroy unless provider.to_s == 'saml' redirect_to profile_account_path end + + private + + def set_user + @user = current_user + end end diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index c80f22457b4e..cf1ec9a21ced 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -12,16 +12,26 @@ %p Your private token is used to access application resources without authentication. .col-lg-9 - = form_for @user, url: reset_private_token_profile_path, method: :put, html: { class: "private-token" } do |f| - %p.cgray - - if current_user.private_token + %p.cgray + - if current_user.private_token + #private-token-show{class: @private_token.blank? && 'hidden'} = label_tag "token", "Private token", class: "label-light" - = text_field_tag "token", current_user.private_token, class: "form-control" - - else - %span You don`t have one yet. Click generate to fix it. - %p.help-block - It can be used for atom feeds or the API. Keep it secret! - .prepend-top-default + = text_field_tag "token", @private_token, class: "form-control" + #private-token-request{class: @private_token.present? && 'hidden'} + = form_tag private_token_profile_account_path, method: 'POST' do + .form-group + = label_tag :current_password, 'To view your private token, enter your current password', class: 'label-light' + = password_field_tag :current_password, nil, required: true, class: 'form-control' + #private-token-error.prepend-top-default.error-message{class: @private_token_error.blank? && 'hidden'} + = @private_token_error + .prepend-top-default + = submit_tag 'Show private token', class: 'btn btn-default' + - else + %span You don't have a private token yet. Click generate to create one. + %p.help-block + This is used for Atom feeds and the API, and gives full access to your account. Keep it secret! + .prepend-top-default + = form_for @user, url: reset_private_token_profile_path, method: :put, html: { class: "private-token" } do |f| - if current_user.private_token = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default" - else diff --git a/config/routes.rb b/config/routes.rb index 4d6ec699cbde..3edd77f6ee17 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -351,6 +351,7 @@ resource :account, only: [:show] do member do delete :unlink + post :private_token end end resource :notifications, only: [:show, :update] diff --git a/spec/features/profiles/private_token_spec.rb b/spec/features/profiles/private_token_spec.rb new file mode 100644 index 000000000000..eb16f47065a8 --- /dev/null +++ b/spec/features/profiles/private_token_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +describe 'Profile > Private tokens', feature: true do + let(:user) { create(:user) } + + shared_examples 'private token viewer' do + context 'when entering an invalid password' do + before do + login_as(user) + visit profile_account_path + fill_in 'current_password', with: user.password.succ + click_on 'Show private token' + end + + it 'shows an error and the form' do + expect(find('#private-token-error')[:class]).not_to include('hidden') + expect(find('#private-token-request')[:class]).not_to include('hidden') + + expect(find('#private-token-show', visible: false)[:class]).to include('hidden') + end + + context 'when entering a valid password' do + before do + fill_in 'current_password', with: user.password + click_on 'Show private token' + end + + it 'shows only the private token' do + expect(find('#private-token-show')[:class]).not_to include('hidden') + expect(find('#token').value).to eq(user.private_token) + + expect(find('#private-token-error', visible: false)[:class]).to include('hidden') + expect(find('#private-token-request', visible: false)[:class]).to include('hidden') + end + end + end + end + + context 'with JavaScript enabled', js: true do + it_behaves_like 'private token viewer' + end + + context 'with JavaScript disabled' do + it_behaves_like 'private token viewer' + end +end -- GitLab From 42d3c5b66df03c1cc4969ebae448e611321e06c3 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 28 Sep 2016 14:50:25 +0100 Subject: [PATCH 2/2] fixup! Hide private token by default --- .../javascripts/private_token_viewer.js.es6 | 19 +++++++++---------- app/assets/stylesheets/behaviors.scss | 4 ---- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/private_token_viewer.js.es6 b/app/assets/javascripts/private_token_viewer.js.es6 index 6c7cd436e37b..47f3ebff50ad 100644 --- a/app/assets/javascripts/private_token_viewer.js.es6 +++ b/app/assets/javascripts/private_token_viewer.js.es6 @@ -6,17 +6,12 @@ this.$request = $('#private-token-request'); this.$requestForm = this.$request.find('form'); - this.initialSetup(); - } - - initialSetup() { - this.$show.addClass('hidden'); - this.$error.addClass('hidden'); - this.$requestForm.on('submit', this.submitPassword.bind(this)); } - submitPassword(event) { + submitPassword(e) { + e.preventDefault(); + $.ajax({ url: this.$requestForm.attr('action'), method: 'POST', @@ -32,6 +27,12 @@ this.$error.addClass('hidden'); this.$request.addClass('hidden'); }).error((request) => { + var message = request.responseJSON && request.responseJSON.message; + + if (!message) { + message = 'There was an error checking your password. Please try again.'; + } + this.$error.text(request.responseJSON.message); this.$show.addClass('hidden'); @@ -39,8 +40,6 @@ this.$request.removeClass('hidden'); this.$requestForm.find('[type="submit"]').enable(); }); - - event.preventDefault(); } }; })(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss index f76396b0a02f..897bc49e7df0 100644 --- a/app/assets/stylesheets/behaviors.scss +++ b/app/assets/stylesheets/behaviors.scss @@ -21,10 +21,6 @@ } } -.hidden { - display: none; -} - // Hide element if Vue is still working on rendering it fully. [v-cloak="true"] { display: none !important; -- GitLab