Commit ff288a43 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch '225214-require-username-namespace-to-be-at-least-2-characters-long' into 'master'

Require namespace path (and username) to be at least 2 chars long

Closes #225214

See merge request gitlab-org/gitlab!35649
parents 42ea5023 3d3fa624
...@@ -114,7 +114,7 @@ export default class GlFieldError { ...@@ -114,7 +114,7 @@ export default class GlFieldError {
this.state.empty = currentValue === ''; this.state.empty = currentValue === '';
this.state.submitted = true; this.state.submitted = true;
this.renderValidity(); this.renderValidity();
this.form.focusOnFirstInvalid.apply(this.form); this.form.focusInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup // For UX, wait til after first invalid submission to check each keyup
this.inputElement this.inputElement
......
...@@ -52,10 +52,23 @@ export default class GlFieldErrors { ...@@ -52,10 +52,23 @@ export default class GlFieldErrors {
}); });
} }
focusOnFirstInvalid() { get invalidInputs() {
const firstInvalid = this.state.inputs.filter( return this.state.inputs.filter(
input => !input.inputDomElement.validity.valid, ({
)[0]; inputDomElement: {
firstInvalid.inputElement.focus(); validity: { valid },
},
}) => !valid,
);
}
get focusedInvalidInput() {
return this.invalidInputs.find(({ inputElement }) => inputElement.is(':focus'));
}
focusInvalid() {
if (this.focusedInvalidInput) return;
this.invalidInputs[0].inputElement.focus();
} }
} }
...@@ -21,11 +21,24 @@ export default class LengthValidator extends InputValidator { ...@@ -21,11 +21,24 @@ export default class LengthValidator extends InputValidator {
); );
const { value } = this.inputDomElement; const { value } = this.inputDomElement;
const { maxLengthMessage, maxLength } = this.inputDomElement.dataset; const {
minLength,
this.errorMessage = maxLengthMessage; minLengthMessage,
maxLengthMessage,
this.invalidInput = value.length > parseInt(maxLength, 10); maxLength,
} = this.inputDomElement.dataset;
this.invalidInput = false;
if (value.length > parseInt(maxLength, 10)) {
this.invalidInput = true;
this.errorMessage = maxLengthMessage;
}
if (value.length < parseInt(minLength, 10)) {
this.invalidInput = true;
this.errorMessage = minLengthMessage;
}
this.setValidationStateAndMessage(); this.setValidationStateAndMessage();
} }
......
...@@ -39,7 +39,7 @@ export default class UsernameValidator extends InputValidator { ...@@ -39,7 +39,7 @@ export default class UsernameValidator extends InputValidator {
static validateUsernameInput(inputDomElement) { static validateUsernameInput(inputDomElement) {
const username = inputDomElement.value; const username = inputDomElement.value;
if (inputDomElement.checkValidity() && username.length > 0) { if (inputDomElement.checkValidity() && username.length > 1) {
UsernameValidator.setMessageVisibility(inputDomElement, pendingMessageSelector); UsernameValidator.setMessageVisibility(inputDomElement, pendingMessageSelector);
UsernameValidator.fetchUsernameAvailability(username) UsernameValidator.fetchUsernameAvailability(username)
.then(usernameTaken => { .then(usernameTaken => {
......
...@@ -48,6 +48,13 @@ class Namespace < ApplicationRecord ...@@ -48,6 +48,13 @@ class Namespace < ApplicationRecord
length: { maximum: 255 }, length: { maximum: 255 },
namespace_path: true namespace_path: true
# Introduce minimal path length of 2 characters.
# Allow change of other attributes without forcing users to
# rename their user or group. At the same time prevent changing
# the path without complying with new 2 chars requirement.
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/225214
validates :path, length: { minimum: 2 }, if: :path_changed?
validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true } validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
validate :nesting_level_allowed validate :nesting_level_allowed
......
- max_first_name_length = max_last_name_length = 127 - max_first_name_length = max_last_name_length = 127
- max_username_length = 255 - max_username_length = 255
- min_username_length = 2
.signup-box.p-3.mb-2 .signup-box.p-3.mb-2
.signup-body .signup-body
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f| = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
...@@ -16,7 +17,7 @@ ...@@ -16,7 +17,7 @@
= f.text_field :last_name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_last_name_length, :max_length_message => _("Last Name is too long (maximum is %{max_length} characters).") % { max_length: max_last_name_length }, :qa_selector => 'new_user_lastname_field' }, required: true, title: _("This field is required.") = f.text_field :last_name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_last_name_length, :max_length_message => _("Last Name is too long (maximum is %{max_length} characters).") % { max_length: max_last_name_length }, :qa_selector => 'new_user_lastname_field' }, required: true, title: _("This field is required.")
.username.form-group .username.form-group
= f.label :username, class: 'label-bold' = f.label :username, class: 'label-bold'
= f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => _("Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.") = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :min_length => min_username_length, :min_length_message => s_("SignUp|Username is too short (minimum is %{min_length} characters).") % { min_length: min_username_length }, :max_length => max_username_length, :max_length_message => _("Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
%p.validation-error.gl-field-error-ignore.field-validation.mt-1.hide.cred= _('Username is already taken.') %p.validation-error.gl-field-error-ignore.field-validation.mt-1.hide.cred= _('Username is already taken.')
%p.validation-success.gl-field-error-ignore.field-validation.mt-1.hide.cgreen= _('Username is available.') %p.validation-success.gl-field-error-ignore.field-validation.mt-1.hide.cgreen= _('Username is available.')
%p.validation-pending.gl-field-error-ignore.field-validation.mt-1.hide= _('Checking username availability...') %p.validation-pending.gl-field-error-ignore.field-validation.mt-1.hide= _('Checking username availability...')
......
- max_name_length = 255 - max_name_length = 255
- max_username_length = 255 - max_username_length = 255
- min_username_length = 2
#register-pane.tab-pane.login-box{ role: 'tabpanel' } #register-pane.tab-pane.login-box{ role: 'tabpanel' }
.login-body .login-body
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f| = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
...@@ -12,7 +13,7 @@ ...@@ -12,7 +13,7 @@
= f.text_field :name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length }, :qa_selector => 'new_user_name_field' }, required: true, title: _("This field is required.") = f.text_field :name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length }, :qa_selector => 'new_user_name_field' }, required: true, title: _("This field is required.")
.username.form-group .username.form-group
= f.label :username, class: 'label-bold' = f.label :username, class: 'label-bold'
= f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.") = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :min_length => min_username_length, :min_length_message => s_("SignUp|Username is too short (minimum is %{min_length} characters).") % { min_length: min_username_length }, :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
%p.validation-error.gl-field-error-ignore.field-validation.hide= _('Username is already taken.') %p.validation-error.gl-field-error-ignore.field-validation.hide= _('Username is already taken.')
%p.validation-success.gl-field-error-ignore.field-validation.hide= _('Username is available.') %p.validation-success.gl-field-error-ignore.field-validation.hide= _('Username is available.')
%p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking username availability...') %p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking username availability...')
......
---
title: Require namespace path (and username) to be at least 2 chars long
merge_request: 35649
author:
type: changed
...@@ -999,8 +999,8 @@ RSpec.describe User do ...@@ -999,8 +999,8 @@ RSpec.describe User do
describe '#managed_free_namespaces' do describe '#managed_free_namespaces' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:licensed_group) { create(:group, gitlab_subscription: create(:gitlab_subscription, :bronze)) } let_it_be(:licensed_group) { create(:group, gitlab_subscription: create(:gitlab_subscription, :bronze)) }
let_it_be(:free_group_z) { create(:group, name: 'Z', gitlab_subscription: create(:gitlab_subscription, :free)) } let_it_be(:free_group_z) { create(:group, name: 'AZ', gitlab_subscription: create(:gitlab_subscription, :free)) }
let_it_be(:free_group_a) { create(:group, name: 'A', gitlab_subscription: create(:gitlab_subscription, :free)) } let_it_be(:free_group_a) { create(:group, name: 'AA', gitlab_subscription: create(:gitlab_subscription, :free)) }
subject { user.managed_free_namespaces } subject { user.managed_free_namespaces }
......
...@@ -21138,6 +21138,9 @@ msgstr "" ...@@ -21138,6 +21138,9 @@ msgstr ""
msgid "SignUp|Username is too long (maximum is %{max_length} characters)." msgid "SignUp|Username is too long (maximum is %{max_length} characters)."
msgstr "" msgstr ""
msgid "SignUp|Username is too short (minimum is %{min_length} characters)."
msgstr ""
msgid "Signed in" msgid "Signed in"
msgstr "" msgstr ""
......
...@@ -70,6 +70,13 @@ RSpec.shared_examples 'Signup' do ...@@ -70,6 +70,13 @@ RSpec.shared_examples 'Signup' do
expect(page).to have_content("Username is too long (maximum is 255 characters).") expect(page).to have_content("Username is too long (maximum is 255 characters).")
end end
it 'shows an error message if the username is less than 2 characters' do
fill_in 'new_user_username', with: 'u'
wait_for_requests
expect(page).to have_content("Username is too short (minimum is 2 characters).")
end
it 'shows an error message on submit if the username contains special characters' do it 'shows an error message on submit if the username contains special characters' do
fill_in 'new_user_username', with: 'new$user!username' fill_in 'new_user_username', with: 'new$user!username'
wait_for_requests wait_for_requests
......
...@@ -65,6 +65,36 @@ RSpec.describe Namespace do ...@@ -65,6 +65,36 @@ RSpec.describe Namespace do
it { expect(group).to be_valid } it { expect(group).to be_valid }
end end
end end
describe '1 char path length' do
it 'does not allow to create one' do
namespace = build(:namespace, path: 'j')
expect(namespace).not_to be_valid
expect(namespace.errors[:path].first).to eq('is too short (minimum is 2 characters)')
end
it 'does not allow to update one' do
namespace = create(:namespace)
namespace.update(path: 'j')
expect(namespace).not_to be_valid
expect(namespace.errors[:path].first).to eq('is too short (minimum is 2 characters)')
end
it 'allows updating other attributes for existing record' do
namespace = build(:namespace, path: 'j')
namespace.save(validate: false)
namespace.reload
expect(namespace.path).to eq('j')
namespace.update(name: 'something new')
expect(namespace).to be_valid
expect(namespace.name).to eq('something new')
end
end
end end
describe 'delegate' do describe 'delegate' do
......
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