Trying to write REGEX for username validation in Rails - ruby-on-rails

I'm trying to write a regular expression in Ruby (Rails) so that a username's characters only contains numbers and letters (also no spaces).
I have this regex, /^[a-zA-Z0-9]+$/, but it doesn't seem to be working and I get an error in Rails that says "The provided regular expression is using multiline anchors (^ or $), which may present a security risk. Did you mean to use \A and \z, or forgot to add the :multiline => true option?"
My full code for this implementation in my user.rb model is:
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_USERNAME_REGEX = /^[a-zA-Z0-9]+$/
validates :username, presence: true, length: { maximum: 20 },
format: { with: VALID_USERNAME_REGEX },
uniqueness: { case_sensitive: false }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
end
What am I doing wrong and how I can fix this regex so that it only is valid for numbers and letters and no spaces? Thanks.

Short answer: use /\A[a-zA-Z0-9]+\z/ instead (as VALID_EMAIL_REGEX is using).
Long answer: the ^ and $ anchors will match the beginning and end of a line in a string. That means that if your string consists of multiple lines of alphanumeric characters they will match:
/^[a-zA-Z0-9]+$/ =~ "Ana\nBob\nClara\nDaniel" #=> 0 (matches)
The \A and \z on the other hand will match the beginning and end of a string, hence it will prevent a possible attack by a user sending a multiline string (like in the previous example).
/\A[a-zA-Z0-9]+\z/ =~ "Ana\nBob\nClara\nDaniel" #=> nil (doesn't match)
/\A[a-zA-Z0-9]+\z/ =~ "Erika" #=> 0 (matches)

All you have to do is follow the error message. Replace ^ (start of line anchor) with \A (start of string anchor), and $ (end of line anchor) with \z (end of string anchor). Other than that, your regex works as is.
\A[a-zA-Z0-9]+\z
Rails has this security concern because unlike some languages, ^ and $ only match the beginning/end of a single line, rather than the entire string.
This illustrates an example of this possible exploit:
str = "malicious_code()\naValidUsername"
str.match(/^[a-zA-Z0-9]+$/) # => #<MatchData "aValidUsername">

Related

Rails - REGEX - validating length of non whitespace / special characters

I have a few fields with a length validation, which can be bypassed by entering n number of white spaces. I'm trying to write a method that validates the number of alpha numeric characters only (not whitespace or special characters).
I've gotten as far as the following:
validates :title,
presence: true,
length: { minimum: 4, maximum: 140 },
format: { with: /([A-z0-9])/ }
What I can't get is how to validate the length of the title that matches the format. For example I want to allow a title of 'The Beast', but only count 'TheBeast' in the character count. This will allow 'The Beast' and include the space in the length validation
Is there something built into rails that allows me to do this? Or if not what's the best way go about writing a custom method?
Thanks in advance
if you would have aux column like 'filtered_title' you could do:
before_save :filter_title
def filter_title
self.filtered_title = title.gsub(/[^0-9a-zA-Z]/, '') // strip unneeded chars
end
and your validator, but on a the new column
validates :filtered_title,
presence: true,
length: { minimum: 4, maximum: 140 },
format: { with: /([A-z0-9])/ }
To expand on #NeverBe's answer, I went with:
class AlphanumericLengthValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
minimum_length = options.fetch(:length, 100)
stripped_value = value ? value.gsub(/[^0-9a-zA-Z]/, '') : nil
message = "must be at least #{minimum_length} alphanumeric characters in length"
return if stripped_value&.length && stripped_value.length >= minimum_length
record.errors.add(attribute, message) if !stripped_value || stripped_value.length < minimum_length
end
end
Which allowed me to do:
validates :title, alphanumeric_length: { length: 8 }

Using Rails built-in validator to validate hash with dynamic options

I have two models, CharacterSheet and Sheet, both of which have the column properties. Sheet's properties column is used to store key-value pairs of property names and their validation (which are in idiomatically correct Ruby validates syntax).
Sheet.create!(name: 'A Test Sheet',
properties: {
prop1: { numericality: { only_integer: true }, length: { maximum: 3 } },
prop2: { numericality: { only_integer: true }, length: { minimum: 1 } },
prop3: { numericality: { only_integer: true } }
})
CharacterSheet stores key-value pairs of property names and their actual values, validated by the appropriate Sheet key-value pair.
CharacterSheet.create(sheet_id: 1,
name: 'A Test CharacterSheet',
properties: {
prop1: 123,
prop2: 234,
prop3: 345
})
Ideally, I want to use the validation requirements stored in Sheet.properties to use Rails' built-in validation methods, so I don't have to rewrite all of that validation myself. However, I can't find an appropriate method to hook into. self.validates doesn't seem to serve my purposes since it seems like (from what I could tell) it requires a valid column name, which it then uses to pull the value, instead of allowing a value to be passed in directly.
So the first problem I see is that you'll need also to convert what it means, numericality and how to fetch that property, and only_integer and what does that mean.
I would advise you to use something like
prop_name: { type_of_validation: 'regexp', validator: 'Regex-representation' }
prop_name2: { type_of_validation: 'type', validator: 'Hash' }
the regex representation will need to be double-escaped.
because then you can do
loaded_sheet = Sheet.find(csheet.sheet_id).properties.as_json.with_indifferent_access
csheet.properties.as_json.with_indifferent_access.each do |k,v|
if loaded_sheet[k.to_sym][:type_of_validation] == 'regexp'
regex = Regexp.new loaded_sheet[k.to_sym][:validator]
return false unless v =~ regex
elsif loaded_sheet[k.to_sym[:type_of_validation] == 'type'
return false unless v.is_a?(loaded_sheet[k.to_sym][:validator].constantize)
end
end

How to model this complex validation for uniqueness on combined fields

A link has two components: componenta_id and componentb_id. To this end, in the Link model file I have:
belongs_to :componenta, class_name: "Component"
belongs_to :componentb, class_name: "Component"
validates :componenta_id, presence: true
validates :componentb_id, presence: true
validates :componenta_id, uniqueness: { scope: :componentb_id }
validates :componentb_id, uniqueness: { scope: :componenta_id }
And in the migration file:
create_table :links do |t|
t.integer :componenta_id, null: false
t.integer :componentb_id, null: false
...
end
add_index :links, :componenta_id
add_index :links, :componentb_id
add_index :links, [:componenta_id, :componentb_id], unique: true
Question: This all works. Now I want the combination of componanta and componentb to be unique, irrespective their order. So irrespective which component is componenta and which one is componentb (after all that's the same link; a link between the two same components). So the two records below should not be allowed since they represent the same link and thus are not unique:
componenta_id = 1 ; componentb_id = 2
componenta_id = 2 ; componentb_id = 1
How can I create this uniqueness validation? I have model validation working (see below) but wonder whether and how I should also add validation at the migration/db level...?
Model validation
I have model validation working with the code below:
before_save :order_links
validates :componenta_id, uniqueness: { scope: :componentb_id }
private
def order_links
if componenta_id > componentb_id
compb = componentb_id
compa = componenta_id
self.componenta_id = compb
self.componentb_id = compa
end
end
The following test confirms the above works:
1. test "combination of two links should be unique" do
2. assert #link1.valid?
3. assert #link2.valid?
4. #link1.componenta_id = 3 ##link2 already has combination 3-4
5. #link1.componentb_id = 4
6. assert_not #link1.valid?
7. #link1.componenta_id = 4
8. #link1.componentb_id = 3
9. assert_raises ActiveRecord::RecordNotUnique do
10. #link1.save
11. end
12.end
Migration/db validation:
As an extra level of security, is there also a way to incorporate validation for this at the db level? Otherwise it is still possible to write both of the following records to the database: componenta_id = 1 ; componentb_id = 2 as well as componenta_id = 2 ; componentb_id = 1.
Perhaps it is possible to control the creation of the links with:
def create_unique_link( comp_1, comp_2 )
if comp_1.id > comp_2.id
first_component = comp_1
second_component = comp_2
end
link = Link.find_or_create_by( componenta_id: first_comp.id, componentb_id: second_comp.id )
end
If you need the validation, then you can custom validate:
def ensure_uniqueness_of_link
if comp_1.id > comp_2.id
first_component = comp_1
second_component = comp_2
end
if Link.where( componenta_id: first_component.id, componentb_id: second_component ).first
errors.add( :link, 'Links should be unique' )
end
end
validates :componenta_id, uniqueness: { scope: :componentb_id }
validates :componentb_id, uniqueness: { scope: :componenta_id }

How can I allow a regex to be blank?

I'm trying to make a form that accepts only a valid email, or a blank email. I think it will be something along these lines:
EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i OR ""
validates :email, format: { with: EMAIL_REGEX }
or maybe
EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
BLANK_REGEX =
validates :email, format: { with: EMAIL_REGEX OR BLANK_REGEX }
but I can't figure out the proper syntax. Does anyone know the right way to do this?
The approach pointed by #avinash-raj is perfect. However you can use allow_blank: true in your validates. Your code should be like this:
validates :email, format: { with: /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i}, allow_blank: true
Just make your regex optional to make your regex to match blank email also.
EMAIL_REGEX = /\A(?:[\w+\-.]+#[a-z\d\-.]+\.[a-z]+)?\z/i
OR
EMAIL_REGEX = /^(?:[\w+\-.]+#[a-z\d\-.]+\.[a-z]+)?$/i
TO make a regex optional, enclose the whole regex inside a non-capturing group (?:...) and then add a ? next to that group.

Regex : only string can be accepted nothing else ?

I am working on ruby on rails here is the code for regex
string_regex = /[a-z]+\z/i
validates :name , :format => { :with => string_regex , :message => "should not contain special character" }
Attempts:
dbcda-> true
abjdkbcak-> true
jgh1213 -> false
1314134##$ -> false
jbh31$ -> false
,,,,,,, -> false
kjgh,g,, -> TRUE(Which should be false).
You need to use /\A[a-z]+\z/i since you don't want any special characters from the beginning of your string (^) to the end of it (\z)
For help, try http://rubular.com/
[Edit] Changed as mentioned by #2called-chaos

Resources