In our application we have normal users. However, we want to be able to make invitations, to invite certain people. Note that an invitation is directly coupled to a user, as we want to be able to set certain settings for these users already. (We are mitigating clients from our old software to the new).
So:
An admin should be able to create a new user and change its settings.
When someone follows a link with their invitation_token, they should see a form where they can set a password for their account.
What I am having trouble with, is how to enable the admin to create an user account, bypassing the normal password validation. It would be a horrible solution if a default password would need to be set, as this would create a severe security flaw.
How to create a new User in Devise without providing a password?
There are at least two ways to do what you want:
Method 1:
Overload Devise's password_required? method
class User < ActiveRecord::Base
attr_accessor :skip_password_validation # virtual attribute to skip password validation while saving
protected
def password_required?
return false if skip_password_validation
super
end
end
Usage:
#user.skip_password_validation = true
#user.save
Method 2:
Disable validation with validate: false option:
user.save(validate: false)
This will skip validation of all fields (not only password). In this case you should make sure that all other fields are valid.
...
But I advise you to not create users without password in your particular case. I would create some additional table (for example, invitations) and store all required information including the fields that you want to be assigned to a user after confirmation.
TL;DR:
user.define_singleton_method(:password_required?) { false }
Fiddle:
class MockDeviseUser
protected
def password_required?
true
end
end
class User < MockDeviseUser
def is_password_required?
puts password_required?
end
end
unrequired_password_user = User.new
unrequired_password_user.define_singleton_method(:password_required?) { false }
unrequired_password_user.is_password_required?
regular_user = User.new
regular_user.is_password_required?
#false
#true
You can now use the DeviseInvitable gem for this.
It allows you to do exactly what you're asking.
If you need to completely overwrite the password requirement just define the following on your model:
def password_required?
false
end
What's the best way to enable users to log in with their email address OR their username? I am using warden + devise for authentication. I think it probably won't be too hard to do it but i guess i need some advice here on where to put all the stuff that is needed. Perhaps devise already provides this feature? Like in the config/initializers/devise.rb you would write:
config.authentication_keys = [ :email, :username ]
To require both username AND email for signing in. But i really want to have only one field for both username and email and require only one of them. I'll just visualize that with some ASCII art, it should look something like this in the view:
Username or Email:
[____________________]
Password:
[____________________]
[Sign In]
I have found a solution for the problem. I'm not quite satisfied with it (I'd rather have a way to specify this in the initializer), but it works for now. In the user model I added the following method:
def self.find_for_database_authentication(conditions={})
find_by(username: conditions[:email]) || find_by(email: conditions[:email])
end
As #sguha and #Chetan have pointed out, another great resource is available on the official devise wiki.
From their Wiki — How To: Allow users to sign in using their username or email address.
def self.find_for_authentication(conditions)
conditions = ["username = ? or email = ?", conditions[authentication_keys.first], conditions[authentication_keys.first]]
# raise StandardError, conditions.inspect
super
end
Use their example!
Make sure you already added username field and add username to attr_accessible.
Create a login virtual attribute in Users
1) Add login as an attr_accessor
# Virtual attribute for authenticating by either username or email
# This is in addition to a real persisted field like 'username'
attr_accessor :login
2) Add login to attr_accessible
attr_accessible :login
Tell Devise to use :login in the authentication_keys
Modify config/initializers/devise.rb to have:
config.authentication_keys = [ :login ]
Overwrite Devise’s find_for_database_authentication method in Users
# Overrides the devise method find_for_authentication
# Allow users to Sign In using their username or email address
def self.find_for_authentication(conditions)
login = conditions.delete(:login)
where(conditions).where(["username = :value OR email = :value", { :value => login }]).first
end
Update your views
Make sure you have the Devise views in your project so that you can customize them
remove <%= f.label :email %>
remove <%= f.email_field :email %>
add <%= f.label :login %>
add <%= f.text_field :login %>
https://gist.github.com/867932 : One solution for everything. Sign in, forgot password, confirmation, unlock instructions.
Platforma Tec (devise author) has posted a solution to their github wiki which uses an underlying Warden authentication strategy rather than plugging into the Controller:
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address
(An earlier answer had a broken link, which I believe was intended to link to this resource.)
If you are using MongoDB (with MongoId), you need to query differently:
def self.find_for_database_authentication(conditions={})
self.any_of({name: conditions[:email]},{email: conditions[:email]}).limit(1).first
end
just so it will be somewhere online.
With squeel gem you can do:
def self.find_for_authentication(conditions={})
self.where{(email == conditions[:email]) | (username == conditions[:email])}.first
end
I wrote like this and it works out. Don't know if it's "ugly fix", but if I'll come up with a a better solution I'll let you know...
def self.authenticate(email, password)
user = find_by_email(email) ||
username = find_by_username(email)
if user && user.password_hash = BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
I use a quick hack for this, to avoid changing any devise specific code and use it for my specific scenario (I particularly use it for an API where mobile apps can create users on the server).
I have added a before_filter to all the devise controllers where if username is being passed, I generate an email from the username ("#{params[:user][:username]}#mycustomdomain.com") and save the user. For all other calls as well, I generate the email based on same logic. My before_filter looks like this:
def generate_email_for_username
return if(!params[:user][:email].blank? || params[:user][:username].blank?)
params[:user][:email] = "#{params[:user][:username]}#mycustomdomain.com"
end
I am also saving username in the users table, so I know that users with email ending in #mycustomdomain.com were created using username.
Here's a Rails solution which refactors #padde's answer. It uses ActiveRecord's find_by to simplify the calls, ensures there's only one call based on the regex, and also supports numeric IDs if you want to allow that (useful for scripts/APIs). The regex for email is as simple as it needs to be in this context; just checking for the presence of an # as I assume your username validtor doesn't allow # characters.
def self.find_for_database_authentication(conditions={})
email = conditions[:email]
if email =~ /#/
self.find_by_email(email)
elsif email.to_s =~ /\A[0-9]+\z/
self.find(Integer(email))
else
self.find_by_username(email])
end
end
Like the wiki and #aku's answer, I'd also recommend making a new :login parameter using attr_accessible and authentication_keys instead of using :email here. (I kept it as :email in the example to show the quick fix.)
What are you using to validate users' email addresses, and why?
I had been using validates_email_veracity_of which actually queries the MX servers. But that is full of fail for various reasons, mostly related to network traffic and reliability.
I looked around and I couldn't find anything obvious that a lot of people are using to perform a sanity check on an email address. Is there a maintained, reasonably accurate plugin or gem for this?
P.S.: Please don't tell me to send an email with a link to see if the email works. I'm developing a "send to a friend" feature, so this isn't practical.
Don't make this harder than it needs to be. Your feature is non-critical; validation's just a basic sanity step to catch typos. I would do it with a simple regex, and not waste the CPU cycles on anything too complicated:
/\A[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]+\z/
That was adapted from http://www.regular-expressions.info/email.html -- which you should read if you really want to know all the tradeoffs. If you want a more correct and much more complicated fully RFC822-compliant regex, that's on that page too. But the thing is this: you don't have to get it totally right.
If the address passes validation, you're going to send an email. If the email fails, you're going to get an error message. At which point you can tell the user "Sorry, your friend didn't receive that, would you like to try again?" or flag it for manual review, or just ignore it, or whatever.
These are the same options you'd have to deal with if the address did pass validation. Because even if your validation is perfect and you acquire absolute proof that the address exists, sending could still fail.
The cost of a false positive on validation is low. The benefit of better validation is also low. Validate generously, and worry about errors when they happen.
With Rails 3.0 you can use a email validation without regexp using the Mail gem.
Here is my implementation (packaged as a gem).
I created a gem for email validation in Rails 3. I'm kinda surprised that Rails doesn't include something like this by default.
http://github.com/balexand/email_validator
This project seems to have the most watchers on github at the moment (for email validation in rails):
https://github.com/alexdunae/validates_email_format_of
From the Rails 4 docs:
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || "is not an email")
end
end
end
class Person < ActiveRecord::Base
validates :email, presence: true, email: true
end
In Rails 4 simply add validates :email, email:true (assuming your field is called email) to your model and then write a simple (or complex†) EmailValidator to suit your needs.
eg: - your model:
class TestUser
include Mongoid::Document
field :email, type: String
validates :email, email: true
end
Your validator (goes in app/validators/email_validator.rb)
class EmailValidator < ActiveModel::EachValidator
EMAIL_ADDRESS_QTEXT = Regexp.new '[^\\x0d\\x22\\x5c\\x80-\\xff]', nil, 'n'
EMAIL_ADDRESS_DTEXT = Regexp.new '[^\\x0d\\x5b-\\x5d\\x80-\\xff]', nil, 'n'
EMAIL_ADDRESS_ATOM = Regexp.new '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+', nil, 'n'
EMAIL_ADDRESS_QUOTED_PAIR = Regexp.new '\\x5c[\\x00-\\x7f]', nil, 'n'
EMAIL_ADDRESS_DOMAIN_LITERAL = Regexp.new "\\x5b(?:#{EMAIL_ADDRESS_DTEXT}|#{EMAIL_ADDRESS_QUOTED_PAIR})*\\x5d", nil, 'n'
EMAIL_ADDRESS_QUOTED_STRING = Regexp.new "\\x22(?:#{EMAIL_ADDRESS_QTEXT}|#{EMAIL_ADDRESS_QUOTED_PAIR})*\\x22", nil, 'n'
EMAIL_ADDRESS_DOMAIN_REF = EMAIL_ADDRESS_ATOM
EMAIL_ADDRESS_SUB_DOMAIN = "(?:#{EMAIL_ADDRESS_DOMAIN_REF}|#{EMAIL_ADDRESS_DOMAIN_LITERAL})"
EMAIL_ADDRESS_WORD = "(?:#{EMAIL_ADDRESS_ATOM}|#{EMAIL_ADDRESS_QUOTED_STRING})"
EMAIL_ADDRESS_DOMAIN = "#{EMAIL_ADDRESS_SUB_DOMAIN}(?:\\x2e#{EMAIL_ADDRESS_SUB_DOMAIN})*"
EMAIL_ADDRESS_LOCAL_PART = "#{EMAIL_ADDRESS_WORD}(?:\\x2e#{EMAIL_ADDRESS_WORD})*"
EMAIL_ADDRESS_SPEC = "#{EMAIL_ADDRESS_LOCAL_PART}\\x40#{EMAIL_ADDRESS_DOMAIN}"
EMAIL_ADDRESS_PATTERN = Regexp.new "#{EMAIL_ADDRESS_SPEC}", nil, 'n'
EMAIL_ADDRESS_EXACT_PATTERN = Regexp.new "\\A#{EMAIL_ADDRESS_SPEC}\\z", nil, 'n'
def validate_each(record, attribute, value)
unless value =~ EMAIL_ADDRESS_EXACT_PATTERN
record.errors[attribute] << (options[:message] || 'is not a valid email')
end
end
end
This will allow all sorts of valid emails, including tagged emails like "test+no_really#test.tes" and so on.
To test this with rspec in your spec/validators/email_validator_spec.rb
require 'spec_helper'
describe "EmailValidator" do
let(:validator) { EmailValidator.new({attributes: [:email]}) }
let(:model) { double('model') }
before :each do
model.stub("errors").and_return([])
model.errors.stub('[]').and_return({})
model.errors[].stub('<<')
end
context "given an invalid email address" do
let(:invalid_email) { 'test test tes' }
it "is rejected as invalid" do
model.errors[].should_receive('<<')
validator.validate_each(model, "email", invalid_email)
end
end
context "given a simple valid address" do
let(:valid_simple_email) { 'test#test.tes' }
it "is accepted as valid" do
model.errors[].should_not_receive('<<')
validator.validate_each(model, "email", valid_simple_email)
end
end
context "given a valid tagged address" do
let(:valid_tagged_email) { 'test+thingo#test.tes' }
it "is accepted as valid" do
model.errors[].should_not_receive('<<')
validator.validate_each(model, "email", valid_tagged_email)
end
end
end
This is how I've done it anyway. YMMV
†Regular expressions are like violence; if they don't work you are not using enough of them.
As Hallelujah suggests I think using the Mail gem is a good approach. However, I dislike some of the hoops there.
I use:
def self.is_valid?(email)
parser = Mail::RFC2822Parser.new
parser.root = :addr_spec
result = parser.parse(email)
# Don't allow for a TLD by itself list (sam#localhost)
# The Grammar is: (local_part "#" domain) / local_part ... discard latter
result &&
result.respond_to?(:domain) &&
result.domain.dot_atom_text.elements.size > 1
end
You could be stricter by demanding that the TLDs (top level domains) are in this list, however you would be forced to update that list as new TLDs pop up (like the 2012 addition .mobi and .tel)
The advantage of hooking the parser direct is that the rules in Mail grammar are fairly wide for the portions the Mail gem uses, it is designed to allow it to parse an address like user<user#example.com> which is common for SMTP. By consuming it from the Mail::Address you are forced to do a bunch of extra checks.
Another note regarding the Mail gem, even though the class is called RFC2822, the grammar has some elements of RFC5322, for example this test.
In Rails 3 it's possible to write a reusable validator, as this great post explains:
http://archives.ryandaigle.com/articles/2009/8/11/what-s-new-in-edge-rails-independent-model-validators
class EmailValidator < ActiveRecord::Validator
def validate()
record.errors[:email] << "is not valid" unless
record.email =~ /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
end
end
and use it with validates_with:
class User < ActiveRecord::Base
validates_with EmailValidator
end
Noting the other answers, the question still remains - why bother being clever about it?
The actual volume of edge cases that many regex may deny or miss seems problematic.
I think the question is 'what am I trying to acheive?', even if you 'validate' the email address, you're not actually validating that it is a working email address.
If you go for regexp, just check for the presence of # on the client side.
As for the incorrect email scenario, have a 'message failed to send' branch to your code.
There are basically 3 most common options:
Regexp (there is no works-for-all e-mail address regexp, so roll your own)
MX query (that is what you use)
Generating an activation token and mailing it (restful_authentication way)
If you don't want to use both validates_email_veracity_of and token generation, I'd go with old school regexp checking.
The Mail gem has a built in address parser.
begin
Mail::Address.new(email)
#valid
rescue Mail::Field::ParseError => e
#invalid
end
This solution is based on answers by #SFEley and #Alessandro DS, with a refactor, and usage clarification.
You can use this validator class in your model like so:
class MyModel < ActiveRecord::Base
# ...
validates :colum, :email => { :allow_nil => true, :message => 'O hai Mark!' }
# ...
end
Given you have the following in your app/validators folder (Rails 3):
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return options[:allow_nil] == true if value.nil?
unless matches?(value)
record.errors[attribute] << (options[:message] || 'must be a valid email address')
end
end
def matches?(value)
return false unless value
if /\A[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value).nil?
false
else
true
end
end
end
For Mailing Lists Validation. (I use Rails 4.1.6)
I got my regexp from here. It seems to be a very complete one, and it's been tested against a great number of combinations. You can see the results on that page.
I slightly changed it to a Ruby regexp, and put it in my lib/validators/email_list_validator.rb
Here's the code:
require 'mail'
class EmailListValidator < ActiveModel::EachValidator
# Regexp source: https://fightingforalostcause.net/content/misc/2006/compare-email-regex.php
EMAIL_VALIDATION_REGEXP = Regexp.new('\A(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}#)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*#(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))\z', true)
def validate_each(record, attribute, value)
begin
invalid_emails = Mail::AddressList.new(value).addresses.map do |mail_address|
# check if domain is present and if it passes validation through the regex
(mail_address.domain.present? && mail_address.address =~ EMAIL_VALIDATION_REGEXP) ? nil : mail_address.address
end
invalid_emails.uniq!
invalid_emails.compact!
record.errors.add(attribute, :invalid_emails, :emails => invalid_emails.to_sentence) if invalid_emails.present?
rescue Mail::Field::ParseError => e
# Parse error on email field.
# exception attributes are:
# e.element : Kind of element that was wrong (in case of invalid addres it is Mail::AddressListParser)
# e.value: mail adresses passed to parser (string)
# e.reason: Description of the problem. A message that is not very user friendly
if e.reason.include?('Expected one of')
record.errors.add(attribute, :invalid_email_list_characters)
else
record.errors.add(attribute, :invalid_emails_generic)
end
end
end
end
And I use it like this in the model:
validates :emails, :presence => true, :email_list => true
It will validate mailing lists like this one, with different separators and synthax:
mail_list = 'John Doe <john#doe.com>, chuck#schuld.dea.th; David G. <david#pink.floyd.division.bell>'
Before using this regexp, I used Devise.email_regexp, but that is a very simple regexp and didn't get all the cases I needed. Some emails bumped.
I tried other regexps from the web, but this one's got the best results till now. Hope it helps in your case.
It will also validate against mails_lists such as:
mail_list = 'John Doe <john#doe.com>, chuck#schuld.dea.th; Nedda G. <nedda-current-gold#tiwwmaawnc.co.uk>'
class EmailListValidator < ActiveModel::EachValidator
EMAIL_VALIDATION_REGEXP = Regexp.new('\A(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}#)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*#(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))\z', true)
def validate_each(record, attribute, value)
begin
invalid_emails = Mail::AddressList.new(value).addresses.map do |mail_address|
(mail_address.domain.present? && mail_address.address =~ EMAIL_VALIDATION_REGEXP) ? nil : mail_address.address
end
invalid_emails.uniq!
invalid_emails.compact!
record.errors.add(attribute, :invalid_emails, :emails => invalid_emails.to_sentence) if invalid_emails.present?
rescue Mail::Field::ParseError => e
if e.reason.include?('Expected one of')
record.errors.add(attribute, :invalid_email_list_characters)
else
record.errors.add(attribute, :invalid_emails_generic)
end
end
end
end```