Email attribute downcased when accessed via binding.pry - ruby-on-rails

I have a method adjusts an auto-generated email for a user as part of a before_validation callback:
def update_device_email
binding.pry
self.email = username.gsub(/\s/, '_') + FAKE_EMAIL_SUFFIX
self.email_confirmation = email
end
For some reason though when I hit the binding.pry call, it shows me a downcased version of the email, which then returns to normal after I call gsub:
Does anyone know why this may be happening?

You should be checking what the username returns prior to the changes being made to self.email

Related

Rails - Capitalized Email Addresses cause errors no matter what I try

I have a relatively simple Rails app with a form using the Devise gem. The form does not require username but does require email address. Everything works fine and the page directs the user to the next page when their email address is all lowercase. However, when the user enters an email address with an uppercase character in the string, I receive the following error from Rails:
NoMethodError at /users
undefined method `uuid' for nil:NilClass
RegistrationsController#create
app/controllers/registrations_controller.rb, line 81
On line 81 (email: new_user.email,):
account = Account.create(user: resource)
new_user = User.find_by_email(params[:user][:email])
recurly_account = Recurly::Account.create(
account_code: new_user.uuid,
email: new_user.email,
first_name: new_user.first_name,
last_name: new_user.last_name
)
sign_in new_user
Request Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"ySBGjHGNwiyeBqGh9nv0N5a8fIJO51bi1nIByev6a21Zh+ncMx5d99cTGKbKWpPvdYmGiBfLX9Gya0qmglrBWg==",
"plan"=>"10day-fmf",
"disc"=>"firstmonthfree10day",
"user"=>{"first_name"=>"asdf", "last_name"=>"asdfsadf", "email"=>"Fjsdlkjs#FLFklsd.com", "password"=>"lsdkjlfkjlskjdf"},
"commit"=>"TRY RISK FREE!",
"controller"=>"registrations",
"action"=>"create"}
--note the email address with uppercase letters
I have researched this problem extensively and found that it has something to do with the email address not being downcased properly. Thus, based on my research I added the following code to various files:
# Devise.rb
config.case_insensitive_keys = [:email]
# User.rb
def downcase_email
self.email.downcase!
end
#registrations_controller.rb
def check_email
email = params[:email].downcase
check = User.where(email: email).first
begin
check_recurly = Recurly::Account.find(email)
if check_recurly
recurly_email = "taken"
end
rescue Recurly::Resource::NotFound => e
recurly_email = "available"
end
However, despite all my attempts at downcasing the email addresses inputted by the user, uppercase letters still cause errors! Any help anyone could give me would be much appreciated!
Thanks
Try to add .downcase here:
new_user = User.find_by_email(params[:user][:email].downcase)
I suppose that in database you have stored lowercase email value, but in controller in params[:user][:email] you have "Fjsdlkjs#FLFklsd.com". Thats why User.find_by_email(params[:user][:email]) returns nil, and you cant call uuid method on a nil object.
Although, I already marked the correct answer above, I wanted to mention that a little javascript onchange event also fixed the problem as well!
<%= f.input_field :email, placeholder: "Email", id:"email", onchange: "this.value=this.value.toLowerCase();" %>
Why are you checking manually if the email already exists? Devise normally do that for you. (did you specify the good user table name?
To lower the case in your controller to lower the params you can do
before_action :downcase_email, if: -> { params[:user][:email].present? }
def downcase_email
params[:user][:email].downcase!
end
Btw you could refacto this method check_email into:
def check_email
return unless params[:email].present?
user = User.find_by_email(params[:email].downcase)
return 'taken' if Recurly::Account.find(user)
'available'
end
Something like that. You don't need to use begin/rescue

Rails API - Devise: Update password on User update

I'm having a very weird issue with devise when trying to edit a user's password - here is a simplified version of what I'm doing at the moment:
def update
user = User.find(params[:id])
password = params.delete(:password)
password_confirmation = params.delete(:password_confirmation)
if password.present? and password != "" and password == password_confirmation
user.password = password
end
user.update(params)
user.save
render json: user
end
What's really weird is that if I set password to an arbitrary string, by putting
password = "testpassword"
above
user.password = password
it works properly, the password is set to "testpassword", and i can log in using it just fine. But if I try and use the param like in the code above, I cannot log in using the password set in params[:password]. I have tried forcing the encoding of the string, and to use user.update_without_password, but to no avail.
Would anybody have any idea about this? it's driving me bonkers!
here is a dump of the params hash:
{"username"=>"testytest",
"first_name"=>"Test",
"last_name"=>"testy",
"password"=>"password",
"password_confirmation"=>"password"}
To be a bit more precise, trying to login once this has been fired results in me not being able to login using the old password, or the new one. if there was a way to see which password gets saved in that function, I should be able to debug it!
Well, after 3 days of struggle I finally came up with a solution, however it's pretty ugly: what I did was this (I've added a method to the user model to simplify):
def update_password(new_password)
password_forced = ""
new_password.each_byte do |byte|
password_forced << byte.chr
end
self.password = password_forced
self.save
end
Again, inspecting the string coming from the params didn't point to anything that might be different from another ruby string... but anyway, this works!

Updating password with BCrypt

When I login with a username and password by BCrypt checks no problem, everything is fine.
But when I go through the process of recovering password and try to login with the new password the BCrypt never returns true.
The code I have is as follows:
before_save :encrypt_password
before_update :encrypt_password
def authenticate
player = Player.find_by(mail: self.mail)
unless player.nil?
current_password = BCrypt::Password.new(player.password)
if current_password == self.password
player
else
nil
end
end
end
private
def encrypt_password
unless self.password.nil?
self.password = BCrypt::Password.create(self.password)
end
I'm using rails 4
You don't need the before_update callback.
When creating a new record (user in this case), only before_save is triggered. So you get the right behavior.
But when updating a record, both before_update and before_save are triggered, which means your password column is encrypted twice. That's why you get unexpected behavior.
Check this page for more information about callbacks.
What's more, I think it's a bad idea to make password a real column in database. All you need is a column called encrypted_password in database and making password a virtual attribute.
So you can write encrypt_password method like this:
def encrypt_password
unless self.password.nil?
self.encrypt_password = BCrypt::Password.create(self.password)
end
Which gave you no chance to make a mistake like you just made.

Devise models run before_save multiple times?

My client wants all user data encrypted, so I've created a before_save and after_find call back that will encrypt certain properties using Gibberish:
# user.rb
before_save UserEncryptor.new
after_find UserEncryptor.new
# user_encryptor.rb
class UserEncryptor
def initialize
#cipher = Gibberish::AES.new("password")
end
def before_save(user)
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.email = encrypt(user.email) unless not user.confirmed? or user.unconfirmed_email
end
def after_find(user)
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.email = decrypt(user.email) unless not user.confirmed? or user.unconfirmed_email
end
private
def encrypt(value)
#cipher.enc(value)
end
def decrypt(value)
#cipher.dec(value)
end
end
Well, when the user first signs up using Devise, the model looks about like it should. But then once the user confirms, if I inspect the user, the first_name and last_name properties look to have been encrypted multiple times. So I put a breakpoint in the before_save method and click the confirmation link, and I see that it's getting executed three times in a row. The result is that the encrypted value gets encrypted again, and then again, so next time we retrieve the record, and every time thereafter, we get a twice encrypted value.
Now, why the heck is this happening? It's not occurring for other non-devise models that are executing the same logic. Does Devise have the current_user cached in a few different places, and it saves the user in each location? How else could a before_save callback be called 3 times before the next before_find is executed?
And, more importantly, how can I successfully encrypt my user data when I'm using Devise? I've also had problems with attr_encrypted and devise_aes_encryptable so if I get a lot of those suggestions then I guess I have some more questions to post :-)
I solved my problem with the help of a coworker.
For encrypting the first and last name, it was sufficient to add a flag to the model indicating whether or not it's been encrypted. That way, if multiple saves occur, the model knows it's already encrypted and can skip that step:
def before_update(user)
unless user.encrypted
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.encrypted = true
end
end
def after_find(user)
if user.encrypted
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.encrypted = false
end
end
For the email address, this was not sufficient. Devise was doing some really weird stuff with resetting cached values, so the email address was still getting double encrypted. So instead of hooking into the callbacks to encrypt the email address, we overrode some methods on the user model:
def email_before_type_cast
super.present? ? AES.decrypt(super, KEY) : ""
end
def email
return "" unless self[:email]
#email ||= AES.decrypt(self[:email], KEY)
end
def email=(provided_email)
self[:email] = encrypted_email(provided_email)
#email = provided_email
end
def self.find_for_authentication(conditions={})
conditions[:email] = encrypted_email(conditions[:email])
super
end
def self.find_or_initialize_with_errors(required_attributes, attributes, error=:invalid)
attributes[:email] = encrypted_email(attributes[:email]) if attributes[:email]
super
end
def self.encrypted_email decrypted_email
AES.encrypt(decrypted_email, KEY, {:iv => IV})
end
This got us most of the way there. However, my Devise models are reconfirmable, so when I changed a user's email address and tried to save, the reconfirmable module encountered something funky, the record got saved like a hundred times or so, and then I got a stack overflow and a rollback. We found that we needed to override one more method on the user model to do the trick:
def email_was
super.present? ? AES.decrypt(super, KEY) : ""
end
Now all of our personally identifiable information is encrypted! Yay!

Rails validation "failing when succeeding"

I have this in my user.rb:
attr_accessor :old_password
def validate
unless password.nil?
errors.add_to_base("Old password entered incorrect") unless self.valid_password? old_password
end
end
I have old_password as a a virtual attribute that has to be validated as matching with the current before updating to a new password. My problem is that upon correct entering ( password == password confirmation and self.valid_password? old_password ) an error will yield and pass me back to the form. The strange part is that the data will actually be updated in the database, and it will not on wrong input; although it will yield the very same error ("Old password entered incorrect"). What on earth am I doing wrong?
Alright, found the problem.
I used a custom update attributes, which used to look like:
def custom_update_attributes(userparams, updater)
unless updater.may_change_user_role_name? self
userparams.delete(:role_name)
userparams.delete(:active)
end
self.update_attributes(userparams)
save
end
The obvious problem here is save, even though I changed my def validate to def validate_on_update this validation has been running on the save action too, and since no old_password is supplied, error will be yielded.

Resources