(Part 2) Resolve "Recognise when a user is trying to validate a private SSH key"
This commit is contained in:
		
							parent
							
								
									3033e01924
								
							
						
					
					
						commit
						02ad729308
					
				| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
import AddSshKeyValidation from '~/profile/add_ssh_key_validation';
 | 
			
		||||
 | 
			
		||||
document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
  const input = document.querySelector('.js-add-ssh-key-validation-input');
 | 
			
		||||
  const warning = document.querySelector('.js-add-ssh-key-validation-warning');
 | 
			
		||||
  const originalSubmit = input.form.querySelector('.js-add-ssh-key-validation-original-submit');
 | 
			
		||||
  const confirmSubmit = warning.querySelector('.js-add-ssh-key-validation-confirm-submit');
 | 
			
		||||
 | 
			
		||||
  const addSshKeyValidation = new AddSshKeyValidation(
 | 
			
		||||
    input,
 | 
			
		||||
    warning,
 | 
			
		||||
    originalSubmit,
 | 
			
		||||
    confirmSubmit,
 | 
			
		||||
  );
 | 
			
		||||
  addSshKeyValidation.register();
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
export default class AddSshKeyValidation {
 | 
			
		||||
  constructor(inputElement, warningElement, originalSubmitElement, confirmSubmitElement) {
 | 
			
		||||
    this.inputElement = inputElement;
 | 
			
		||||
    this.form = inputElement.form;
 | 
			
		||||
 | 
			
		||||
    this.warningElement = warningElement;
 | 
			
		||||
 | 
			
		||||
    this.originalSubmitElement = originalSubmitElement;
 | 
			
		||||
    this.confirmSubmitElement = confirmSubmitElement;
 | 
			
		||||
 | 
			
		||||
    this.isValid = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  register() {
 | 
			
		||||
    this.form.addEventListener('submit', event => this.submit(event));
 | 
			
		||||
 | 
			
		||||
    this.confirmSubmitElement.addEventListener('click', () => {
 | 
			
		||||
      this.isValid = true;
 | 
			
		||||
      this.form.submit();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.inputElement.addEventListener('input', () => this.toggleWarning(false));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  submit(event) {
 | 
			
		||||
    this.isValid = AddSshKeyValidation.isPublicKey(this.inputElement.value);
 | 
			
		||||
 | 
			
		||||
    if (this.isValid) return true;
 | 
			
		||||
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    this.toggleWarning(true);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleWarning(isVisible) {
 | 
			
		||||
    this.warningElement.classList.toggle('hide', !isVisible);
 | 
			
		||||
    this.originalSubmitElement.classList.toggle('hide', isVisible);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static isPublicKey(value) {
 | 
			
		||||
    return /^(ssh|ecdsa-sha2)-/.test(value);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -255,3 +255,8 @@ label {
 | 
			
		|||
    color: $theme-gray-600;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.input-lg {
 | 
			
		||||
  max-width: 320px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,11 +5,18 @@
 | 
			
		|||
    .form-group
 | 
			
		||||
      = f.label :key, class: 'label-light'
 | 
			
		||||
      %p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key.")
 | 
			
		||||
      = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: 'Typically starts with "ssh-rsa …"'
 | 
			
		||||
      = f.text_area :key, class: "form-control js-add-ssh-key-validation-input", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-rsa …"')
 | 
			
		||||
    .form-group
 | 
			
		||||
      = f.label :title, class: 'label-light'
 | 
			
		||||
      = f.text_field :title, class: "form-control", required: true, placeholder: 'e.g. My MacBook key'
 | 
			
		||||
      = f.text_field :title, class: "form-control input-lg", required: true, placeholder: s_('Profiles|e.g. My MacBook key')
 | 
			
		||||
      %p.form-text.text-muted= _('Name your individual key via a title')
 | 
			
		||||
 | 
			
		||||
    .js-add-ssh-key-validation-warning.hide
 | 
			
		||||
      .bs-callout.bs-callout-warning{ role: 'alert', aria_live: 'assertive' }
 | 
			
		||||
        %strong= _('Oops, are you sure?')
 | 
			
		||||
        %p= s_("Profiles|This doesn't look like a public SSH key, are you sure you want to add it?")
 | 
			
		||||
 | 
			
		||||
      %button.btn.btn-create.js-add-ssh-key-validation-confirm-submit= _("Yes, add it")
 | 
			
		||||
 | 
			
		||||
    .prepend-top-default
 | 
			
		||||
      = f.submit 'Add key', class: "btn btn-create"
 | 
			
		||||
      = f.submit s_('Profiles|Add key'), class: "btn btn-create js-add-ssh-key-validation-original-submit"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
---
 | 
			
		||||
title: Update new SSH key page to improve key input validation
 | 
			
		||||
merge_request: 19997
 | 
			
		||||
author:
 | 
			
		||||
type: other
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +30,20 @@ describe 'Profile > SSH Keys' do
 | 
			
		|||
      expect(find('.breadcrumbs-sub-title')).to have_link(attrs[:title])
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'shows a confirmable warning if the key does not start with ssh-' do
 | 
			
		||||
      attrs = attributes_for(:key)
 | 
			
		||||
 | 
			
		||||
      fill_in('Key', with: 'invalid-key')
 | 
			
		||||
      fill_in('Title', with: attrs[:title])
 | 
			
		||||
      click_button('Add key')
 | 
			
		||||
 | 
			
		||||
      expect(page).to have_selector('.js-add-ssh-key-validation-warning')
 | 
			
		||||
 | 
			
		||||
      find('.js-add-ssh-key-validation-confirm-submit').click
 | 
			
		||||
 | 
			
		||||
      expect(page).to have_content('Key is invalid')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when only DSA and ECDSA keys are allowed' do
 | 
			
		||||
      before do
 | 
			
		||||
        forbidden = ApplicationSetting::FORBIDDEN_KEY_VALUE
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,69 @@
 | 
			
		|||
import AddSshKeyValidation from '../../../app/assets/javascripts/profile/add_ssh_key_validation';
 | 
			
		||||
 | 
			
		||||
describe('AddSshKeyValidation', () => {
 | 
			
		||||
  describe('submit', () => {
 | 
			
		||||
    it('returns true if isValid is true', () => {
 | 
			
		||||
      const addSshKeyValidation = new AddSshKeyValidation({});
 | 
			
		||||
      spyOn(AddSshKeyValidation, 'isPublicKey').and.returnValue(true);
 | 
			
		||||
 | 
			
		||||
      expect(addSshKeyValidation.submit()).toBeTruthy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('calls preventDefault and toggleWarning if isValid is false', () => {
 | 
			
		||||
      const addSshKeyValidation = new AddSshKeyValidation({});
 | 
			
		||||
      const event = jasmine.createSpyObj('event', ['preventDefault']);
 | 
			
		||||
      spyOn(AddSshKeyValidation, 'isPublicKey').and.returnValue(false);
 | 
			
		||||
      spyOn(addSshKeyValidation, 'toggleWarning');
 | 
			
		||||
 | 
			
		||||
      addSshKeyValidation.submit(event);
 | 
			
		||||
 | 
			
		||||
      expect(event.preventDefault).toHaveBeenCalled();
 | 
			
		||||
      expect(addSshKeyValidation.toggleWarning).toHaveBeenCalledWith(true);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('toggleWarning', () => {
 | 
			
		||||
    it('shows warningElement and hides originalSubmitElement if isVisible is true', () => {
 | 
			
		||||
      const warningElement = document.createElement('div');
 | 
			
		||||
      const originalSubmitElement = document.createElement('div');
 | 
			
		||||
      warningElement.classList.add('hide');
 | 
			
		||||
 | 
			
		||||
      const addSshKeyValidation = new AddSshKeyValidation(
 | 
			
		||||
        {},
 | 
			
		||||
        warningElement,
 | 
			
		||||
        originalSubmitElement,
 | 
			
		||||
      );
 | 
			
		||||
      addSshKeyValidation.toggleWarning(true);
 | 
			
		||||
 | 
			
		||||
      expect(warningElement.classList.contains('hide')).toBeFalsy();
 | 
			
		||||
      expect(originalSubmitElement.classList.contains('hide')).toBeTruthy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('hides warningElement and shows originalSubmitElement if isVisible is false', () => {
 | 
			
		||||
      const warningElement = document.createElement('div');
 | 
			
		||||
      const originalSubmitElement = document.createElement('div');
 | 
			
		||||
      originalSubmitElement.classList.add('hide');
 | 
			
		||||
 | 
			
		||||
      const addSshKeyValidation = new AddSshKeyValidation(
 | 
			
		||||
        {},
 | 
			
		||||
        warningElement,
 | 
			
		||||
        originalSubmitElement,
 | 
			
		||||
      );
 | 
			
		||||
      addSshKeyValidation.toggleWarning(false);
 | 
			
		||||
 | 
			
		||||
      expect(warningElement.classList.contains('hide')).toBeTruthy();
 | 
			
		||||
      expect(originalSubmitElement.classList.contains('hide')).toBeFalsy();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('isPublicKey', () => {
 | 
			
		||||
    it('returns false if probably invalid public ssh key', () => {
 | 
			
		||||
      expect(AddSshKeyValidation.isPublicKey('nope')).toBeFalsy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('returns true if probably valid public ssh key', () => {
 | 
			
		||||
      expect(AddSshKeyValidation.isPublicKey('ssh-')).toBeTruthy();
 | 
			
		||||
      expect(AddSshKeyValidation.isPublicKey('ecdsa-sha2-')).toBeTruthy();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue