I'm trying to set up my application to use Sorcery. When I add authenticates_with_sorcery! to my user model, my specs start to run really slow (about one per second). Is there some kind of configuration or set up that could cause this with Sorcery?
Here's my user model:
# This model represents a user of the application, disregarding that person's use of the system. For
# instance, a user could be a job hunter, an employer, an administrator, or some other stakeholder.
class User < ActiveRecord::Base
authenticates_with_sorcery!
attr_accessible :email, :password, :password_confirmation
# validations
validates :email,
:presence => true,
:uniqueness => true,
:format => /[^#]+#[^#]+\.[^#]+/
validates :password, :presence => true, :confirmation => true
validates :password_confirmation, :presence => true
# before filters
before_save :sanitize_email
private
# Strips and removes HTML tags from the email parameter.
def sanitize_email
self.email = email.strip
# remove anything that looks like an email
self.email = email.gsub(/<[^<>]+>/, "")
end
end
and my user factory:
require 'factory_girl'
require 'ffaker'
FactoryGirl.define do
sequence :email do |n|
"email#{n}#example.com"
end
factory :user do |f|
email
password "password"
password_confirmation "password"
end
end
My first guess is slow password encryption. For instance in devise we have config.stretches configuration variable which in the test env could be set to a small number.
check What does the "stretches" of database_authenticatable of devise mean?
Related
I have researched a lot on this topic but due to some reason I am unable to perform password complexity implementation on my Ruby on Rails Web Application. I have installed the devise gem and followed Best flexible rails password security implementation and How to validate password strength with Devise in Ruby on Rails?.
My regex seems to be working when I check it online
/\A(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[[:^alnum:]])/x
but once I implement it inside my user.rb it just does not work.
My user.rb file:
#Active Record for Users
class User < ActiveRecord::Base
belongs_to :entity
has_and_belongs_to_many :groups, :join_table => "users_groups"
has_many :surveys, inverse_of: :user
has_many :results, inverse_of: :user
validates :password, :firstName, :email, :salt, :role, :timezone, presence: true
validates :email, :uniqueness => {:scope => :entity_id}
validates_format_of :email, :with => /.+#.+\..+/i
devise :database_authenticatable, :validatable
validate :password_complexity
#User Authentication
def self.authenticate(email="", lpassword="")
users = User.where(email: email)
results = []
users.each do |user|
if user && user.match_password(lpassword)
results.push(user)
end
end
if(results.length == 0)
return false
else
return results
end
end
#Password Check
def match_password(lpassword="")
return (BCrypt::Password.new(password).is_password?(lpassword+salt))
end
#Password Authentication
def password_complexity
if password.present? and not password.match(/\A(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[[:^alnum:]])/x)
errors.add :password, "must include at least one lowercase letter, one uppercase letter, and one digit"
end
end
end
What you did should work, but why not do it simply like this
validates :password, format: { with: /\A(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[[:^alnum:]])/, message: "must include at least one lowercase letter, one uppercase letter, and one digit" }
I'm trying to mix a custom User authentication mechanism based on SecurePassword with Facebook integration through omniauth-facebook gem.
my app uses Ruby 2.0.0 and Rails 4.0.0.
i tried to follow this guide omniauth and some other articles to came up with something like this for the User and Authentication Models
User model:
class User < ActiveRecord::Base
has_one :user_playlist
has_one :user_info
has_many :band_likes
has_many :song_likes
has_many :band_comments
has_many :song_comments
has_many :authorizations
#many to many relation between User and Band
#todo: make a bands_users migration
has_and_belongs_to_many :bands
has_secure_password
validates :username, presence: true, uniqueness: {case_sensitive: false}, length: {in: 8..64}, format: {with: /\A[a-zA-Z ]+\Z/, message: 'Debe poseer solo letras y espacios.'}
validates :email, presence: true, uniqueness: {case_sensitive: false}, format: {with: /#/, message: 'Dirección de correo inváilda.'}
validates :password, length: {in: 8..24}
validates :password_confirmation, length: {in: 8..24}
def self.create_from_hash!(hash)
create(:email => hash['info']['email'], :username => hash['info']['name'], :password => hash['uid'], :password_confirmation => hash['uid'] )
end
end
Authorization Model:
class Authorization < ActiveRecord::Base
belongs_to :user
validates_presence_of :user_id, :uid, :provider
validates_uniqueness_of :uid, :scope => :provider
def self.find_from_hash(hash)
find_by_provider_and_uid(hash['provider'], hash['uid'])
end
def self.create_from_hash(hash, user = nil)
user ||= User.create_from_hash!(hash)
Authorization.create(:user => user, :uid => hash['uid'], :provider => hash['provider'])
end
end
SessionsController
class SessionsController < ApplicationController
def create
auth = request.env['omniauth.auth']
unless #auth = Authorization.find_from_hash(auth)
# Create a new user or add an auth to existing user, depending on
# whether there is already a user signed in.
#auth = Authorization.create_from_hash(auth, current_user)
end
# Log the authorizing user in.
self.current_user = #auth.user
render :text => "Welcome, #{current_user.username}. <br />User saved = #{current_user.save} .<br/>User valid = #{current_user.valid?}.<br />errors= #{current_user.errors.full_messages}"
end
end
The last render was written to check about the fact that my password does not gets validated, it doesn't matter if i use hash['uid'], hash['info']['name'], or whatever.
The reason why i use this values is just because, i will figure out later how to build a random password for the oauth-ed user, but i don't want blank ones nor disable the validations.
but, no matter what value i use, always get only my name and email:
*Welcome, "My Real Name Here.
User saved = false.
User valid = false.
errors= ["Password is too short (minimum is 8 characters)", "Password confirmation is too short (minimum is 8 characters)"]*
When creating users in Rails Console got no problem, just when OAuth tries to create a User with create_from_hash.
also, if i try to assign a non existing value from hash to password fields, it adds the message that can be blank. so, it isn't blank.
and rendering hash['uid'] in controller shows that it's longer than 8.
I Must warn that i'm new to rails, so if you can, explain me with apples xD
Thanks in advance!
finally i came up with this on User model:
def self.create_from_hash!(hash)
self.where(:email => hash.info.email.to_s).first_or_create do |user|
user.email = hash.info.email
user.username = hash.info.name
user.password = hash.uid
user.password_confirmation = hash.uid
end
end
I don't know why the later doesn't work but at least this one works!
Greetings!
I'm trying to use has_secure_password on my user model, but have found that while it works in the application it breaks all my tests. I have a simple user model:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email
validates :email, :presence => true,
:format => { :with => /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true
end
My first test (rspec) simply confirms that I can create a new user with valid attributes:
describe User do
before(:each) do
#attr = { :forename => "Captain",
:surname => "Hook",
:email => "email#test.com",
:password => "password",
:password_confirmation => "password" }
end
it "should create a new instance given valid attributes" do
User.create!(#attr)
end
end
This doesn't work, however when I do
user = User.new(#attr);
user.password = "password";
user.save
it works fine. I believe this is because has_secure_password adds a new method, password, which deals with the generation of the password_digest, so calling it directly like this generates the fields that I need. Is there any way I can use User.create but still call this method?
Turns out the problem was really simple. Since I hadn't added :password to attr_accessible it wasn't populating the field when I called User.create or User.new. The only modification I needed to make to the code was
attr_accessible :email, :password
I have this code in my user model:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates :email, :presence => true,
:uniqueness => { :case_sensitive => false },
:format => { :with => /\A[^#]+#[^#]+\z/ },
:length => 7..128
validates :password, :presence => true,
:confirmation => true,
:length => 6..128
private
def encrypt_password
return unless password
self.encrypted_password = BCrypt::Password.create(password)
end
end
Now in my controller when I'm updating some user fields with
#user.update_attributes(params[:user])
the password field is always validated, even when it is not set in the params hash. I figured that this is happening because of the attr_accesor :password which always sets password = "" on update_attributes.
Now I could simply skip the validation of password if it is an empty string:
validates :password, :presence => true,
:confirmation => true,
:length => 6..128,
:if => "password.present?"
But this doesn't work because it allows a user to set an empty password.
Using update_attribute on the field I'd like to change is not a solution because i need validation on that attribute.
If I pass in the exact parameter with
#user.update_attributes(params[:user][:fieldname])
it doesn't solve the problem because it also triggers password validation.
Isn't there a way to prevent attr_accesor :password from always setting password = "" on update?
New answer
This works for me:
validates :password, :presence => true,
:confirmation => true,
:length => { :minimum => 6 },
:if => :password # only validate if password changed!
If I remember correctly it also took me some time to get this right (a lot of trial and error). I never had the time to find out exactly why this works (in contrast to :if => "password.present?").
Old answer - not really useful for your purpose (see comments)
I get around this problem by using a completely different action for password update (user#update_password). Now it is sufficient to only validate the password field
:on => [:create, :update_password]
(and also only make it accessible to those actions).
Here some more details:
in your routes:
resources :users do
member do
GET :edit_password # for the user#edit_password action
PUT :update_password # for the user#update_passwor action
end
end
in your UsersController:
def edit_password
# could be same content as #edit action, e.g.
#user = User.find(params[:id])
end
def update_password
# code to update password (and only password) here
end
In your edit_password view, you now have a form for only updating the password, very similar to your form in the edit view, but with :method => :put and :url => edit_password_user_path(#user)
The solution I have started using to get round this problem is this:
Start using ActiveModel's built in has_secure_password method.
At console
rails g migration add_password_digest_to_users password_digest:string
rake db:migrate
In your model:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :login_name, :password, :password_confirmation
# secure_password.rb already checks for presence of :password_digest
# so we can assume that a password is present if that validation passes
# and thus, we don't need to explicitly check for presence of password
validates :password,
:length => { :minimum => 6 }, :if => :password_digest_changed?
# secure_password.rb also checks for confirmation of :password
# but we also have to check for presence of :password_confirmation
validates :password_confirmation,
:presence=>true, :if => :password_digest_changed?
end
And finally,
# In `config/locales/en.yml` make sure that errors on
# the password_digest field refer to "Password" as it's more human friendly
en:
hello: "Hello world"
activerecord:
attributes:
user:
password_digest: "Password"
Oh, one more thing: watch the railscast
I set up Devise to log in with a username instead of email address because a significant number of our users share the same email address with their spouse. It is a unique market that we serve. But each spouse needs separate accounts to record personality instrument results.
The database no longer requires a unique email address so it will accept the same email addy for two accounts. However, Devise is still requiring a unique email address for each account. Is there a setting or a work around that i can use to change this?
= User Model
def email_required?
false
end
def email_changed?
false
end
# For ActiveRecord 5.1+
def will_save_change_to_email?
false
end
= Migration
rails g migration update_index_on_users
def up
sql = 'DROP INDEX index_users_on_email'
sql << ' ON users' if Rails.env == 'production' # Heroku pg
ActiveRecord::Base.connection.execute(sql)
end
Look in the config/initializers/devise.rb. You can change the default authentication key, which by default is :email to be anything you want, for example:
config.authentication_keys = [ :username ]
Please find the instructions here
If you only want to remove uniqueness validation given by the devise and keep the others, refer this (for rails >= 4.1.6).
So, this is really old... but I thought I'd respond.
I'm using an old version of devise (1.4.9), but, through some help in #rubyonrails on freenode, I found that you have to edit the lib / devise / models / validatable.rb:
module Devise
module Models
module Validatable
# All validations used by this module.
VALIDATIONS = [ :validates_presence_of, :validates_uniqueness_of, :validates_format_of,
:validates_confirmation_of, :validates_length_of ].freeze
def self.included(base)
base.extend ClassMethods
assert_validations_api!(base)
base.class_eval do
validates_presence_of :email, :if => :email_required?
# commented out so that we don't check for unique emails
#validates_uniqueness_of :email, :case_sensitive => (case_insensitive_keys != false), :allow_blank => true, :if => :email_changed?
validates_format_of :email, :with => email_regexp, :allow_blank => true, :if => :email_changed?
validates_presence_of :password, :if => :password_required?
validates_confirmation_of :password, :if => :password_required?
validates_length_of :password, :within => password_length, :allow_blank => true
end
end
end
end
end
This also required going into the database and changing the index on the email column, which is set to unique out of the box.