Devise 3.5.2, Rails 4.2.3
While logging in, I'm trying to pass a hidden role_id along with the email/password combination. I am allowing the same email to register again, on a different subdomain, which causes a different role_id to be passed. The email+role_id is the unique index for the user.
I can create a user, but cannot log in. When I submit the log in form, I am faced with the following error:
undefined method 'email' for #<ActionDispatch::Request:0x007fa21628bda0>
EDIT:
If anyone can explain the process of changing the email uniqueness validation to email+role_id (not either/or, but and), that's all I need to accomplish. Following that process properly may avoid this error.
Debugging info:
The POST parameters are as follows:
{"utf8"=>"✓",
"authenticity_token"=>"[FILTERED]",
"member"=>{"role_id"=>"1",
"email"=>"some.user#email.com",
"password"=>"[FILTERED]",
"remember_me"=>"0"},
"commit"=>"Log in"}
Here is my Member model:
class Member < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable, :omniauthable
belongs_to :role
def self.find_for_authentication(warden_conditions)
where(:email => warden_conditions[:email], :role_id => warden_conditions[:role_id]).first
end
end
In config/initializers/devise.rb, the following is set:
config.authentication_keys = [:email, :role_id]
config.request_keys = [:email, :role_id]
My views/devise/sessions/new.html.erb includes:
<%= f.hidden_field :role_id, :value => Role.find_by_name(current_subdomain).id %>
I adjusted vendor/bundle/ruby/1.9.1/gems/devise-3.5.2/lib/devise/models/validatable.rb by changing this line:
validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
to:
validates_uniqueness_of :email, :scope => :role_id, allow_blank: true, if: :email_changed? #using subdomains for validation
The relevant database migrations for the member are found here:
...devise_create_members.rb
class DeviseCreateMembers < ActiveRecord::Migration
def change
create_table(:members) do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
t.string :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
t.timestamps null: false
end
add_index :members, :email, unique: true
add_index :members, :reset_password_token, unique: true
add_index :members, :confirmation_token, unique: true
add_index :members, :unlock_token, unique: true
end
...add_columns_to_member.rb
class AddColumnsToMember < ActiveRecord::Migration
def change
add_reference :members, :contact, index: true
add_reference :members, :role, index: true
add_reference :members, :ownership, index: true
add_column :members, :account_status, :string
end
end
...reindex_members_email_and_role.rb
class ReindexMembersEmailAndRole < ActiveRecord::Migration
def change
add_index :members, [:email, :role_id], :unique => true
end
end
The last item on the trace is:
vendor/bundle/ruby/1.9.1/gems/devise-3.5.2/lib/devise/strategies/authenticatable.rb:152:in `block in request_values'
keys = request_keys.respond_to?(:keys) ? request_keys.keys : request_keys
values = keys.map { |k| self.request.send(k) } <--ERROR THIS LINE
Hash[keys.zip(values)]
end
What am I missing?
To fix this, I changed my config/initializers/devise.rb to reflect the following:
config.request_keys = { role_id: false }
This fixed the issue, but still prevented the same email from signing up with a different role ID. To fix this, I removed :validatable from my User model and added:
validates_uniqueness_of :email, :case_sensitive => false, :scope => :role_id, :allow_blank => true, :if => :email_changed?
validates_format_of :email, :with => Devise.email_regexp, :allow_blank => true, :if => :email_changed?
validates_presence_of :password, :on=>:create
validates_confirmation_of :password, :on=>:create
validates_length_of :password, :within => Devise.password_length, :allow_blank => true
This allows the same email address to sign up with a different role_id.
I also changed the following in authenticatable.rb:
def request_values
keys = request_keys.respond_to?(:keys) ? request_keys.keys : request_keys
values = keys.map { |k| self.request[self.scope][k] }
# values = keys.map { |k| self.request.send(k) }
Hash[keys.zip(values)]
end
UPDATE
I got tired of always having to re-hack the devise library, especially after I updated gems or transferred the app. I found this page that offered a better work-around (still follow the step regarding validations the User model listed above):
(From https://github.com/plataformatec/devise/pull/3965)
Comment out the following line we edited above:
# config.request_keys = { role_id: false }
Edit the config.authentication_keys line as follows:
config.authentication_keys = { email: true, role_id: false }
The issue is that request_keys honors only predefined keys such as :subdomain.
That should work now for creating a combination of custom keys to authenticate with.
Related
I created an only API rails project and added devise user model
now I am trying to sign-up using Postman but it does not save the new user on the
database
however, it does not return an error as well.
Postman post request and parameters passed
My app controller with the additional permitted attributes
`class ApplicationController < ActionController::API
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
attributes = :name, { roles: [] }
devise_parameter_sanitizer.permit(:sign_up, keys: attributes)
devise_parameter_sanitizer.permit(:account_update, keys: attributes)
end
end`
Devise User model with validations
`class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates :name, presence: true
validates :password, presence: true
validates :email, presence: true
validates :roles, presence: true
has_many :reservations, dependent: :destroy
has_many :vechiles, through: :group_entities
end`
The migration file for the devise user table
class DeviseCreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name, null: false
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
# t.integer :sign_in_count, default: 0, null: false
# t.datetime :current_sign_in_at
# t.datetime :last_sign_in_at
# t.string :current_sign_in_ip
# t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
end
Roles col. added to user table
class AddRolesToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :roles, :text, array: true, default: []
end
end
My Routes.rb
# frozen_string_literal: true
Rails.application.routes.draw do
devise_for :users
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
resources :users, only: [:index]
# Defines the root path route ("/")
# root "articles#index"
end
I am using devise 4.8.1 with rails 7.0.3 and postgresql, I just started using devise, and I generated the views using rails g devise:views and then applied the migration using "rails db:migrate"
This is my user model:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
has_many :posts, foreign_key: 'author_id'
has_many :comments, foreign_key: 'author_id'
has_many :likes, foreign_key: 'author_id'
attr_accessor :password, :password_confirmation
validates :name, presence: true
validates :PostsCounter, presence: true, numericality: { greater_than_or_equal_to: 0 }
def recent_posts
posts.order(created_at: :desc).limit(3)
end
end
This is the migration that I added:
# frozen_string_literal: true
class AddDeviseToUsers < ActiveRecord::Migration[7.0]
def self.up
change_table :users do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
# t.integer :sign_in_count, default: 0, null: false
# t.datetime :current_sign_in_at
# t.datetime :last_sign_in_at
# t.string :current_sign_in_ip
# t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
# Uncomment below if timestamps were not included in your original model.
# t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
def self.down
# By default, we don't want to make any assumption about how to roll back a migration when your
# model already existed. Please edit below which fields you would like to remove in this migration.
raise ActiveRecord::IrreversibleMigration
end
end
This morning devise was working fine, when I sign up the password gets encrypted and then saved now when I sign up and then try to login it says invalid email/password, when I checked my PostgreSQL database I found out that nothing is getting saved in the encrypted_password column as shown in this picture, I tried searching online but couldn't find any solution, I also tried to reinstall the gems and nothing worked, I am not sure how to fix this issue, please assist me with this.
If you need any more information about my code please let me know.
Edit: I am not sure if it's okay to share this but this is my GitHub repository and branch that has the issue (the issue is only presented in that branch feature/devise), I tried to share some pieces of the code I have but it's better to see the project structure in my opinion.
Did you try creating user from the rails console? If it gets saved with encrypted password, there is nothing wrong in the code. If it does not, it will throw the error and you can debug from there?
I used devide in gem and now I made Sign up&Log in pages.
When I wrote my id and email address in my app,blowser told me 1 error prohibited this user from being saved: Id can't be blank.Of course,I surely wrote id,so I don't know why.
Is this blowser error?
I wrote in home_controller,
class HomeController < ApplicationController
before_filter :find_user, only: [:index]
def index
# #id = params[:id]
# #email = params[:email]
if id == #user.id && email == #user.email
render :text => "sucsess"
else
render :text => "fail"
end
end
def create
id = params[:id]
email = params[:email]
#user = UserData.new(user_params)
#user.save
unless userData.save
#error_message = errors.full_messages.compact
end
end
private
def find_user
user = User.find(params[:id])
if current_user.id != user.id
redirect_to root_path
end
end
end
in users.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
validates :id, presence: true
validates :email, presence: true, uniqueness: true
end
in migration file
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
end
in routes.rb
Rails.application.routes.draw do
get 'notes/new'
devise_for :users
root to: "home#index"
get 'home/index'
get 'home/create'
namespace :home, default: {format: :json} do
resources :index, only: :create
end
end
Validation runs before creation. So the id will have no chance of being created before you save.
Remove this
validates :id, presence: true
and don't set the id manually on the create action.
Remove below line from model, as ID is auto generated field.
validates :id, presence: true
Update create action in controller file as below,
def create
#user = User.new(user_params)
#user.save
unless #user.save
#error_message = errors.full_messages.compact
end
end
Replace "UserData" with "User" which is the model name as per your code.
Comment or remove below line
validates :id, presence: true
as id is create automatically by rails and no need to validate it.
And
In your controller, change private method code:
def find_user
#user = User.find(params[:id]) #replace user to #user
if current_user.id != user.id
redirect_to root_path
end
end
I am using Rails 4 and Devise.
I am trying to display a user's first name in a status posted on my App.
I have done everything I can think of correctly (migrated database after adding user model) and the database is still not saving the three new fields listed in my title, Devise saves the things it came with (email + password) but not the new fields.
Here is my user.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible attributes for your model
attr_accessible :role_ids, :as => :admin
attr_accessible :email, :password, :password_confirmation, :remember_me,
:first_name, :last_name, :profile_name
end
My db migration:
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
t.string: :first_name;
t.string: :last_name;
t.string: :profile_name;
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
end
Having done some research, I don't think attr_accessible is compatible with Rails 4 and Devise, is this correct?
If so, how do I pass this new information into my Devise db?
Does anyone know a good guide to do this?
Let me know if there is any more information that needs to be provided!
Thanks in advance
Cheers for your help guys, here's how I got it to work:
I updated my Application Controller to this:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:first_name,:last_name,:profile_name,:email, :password) }
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:first_name,:last_name,:profile_name,:email, :password) }
end
end
And made sure to add user_id to the bottom of your status controller:
params.require(:status).permit(:user_id, :name, :content)
This will save the new fields in your db and allow it to be displayed in statuses.
You need to place the following in your ApplicationController.
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |params|
params.permit(
:email, :password, :password_confirmation, :first_name,
:last_name, :profile_name
)
}
devise_parameter_sanitizer.for(:account_update) { |params|
params.permit(
:email, :password, :password_confirmation, :first_name,
:last_name, :profile_name
)
}
end
Take a look at the answer of this question:
User model won't update
If the ruby-on-rails-4 Tag is correct, you should keep in mind that
With rails 4 attr_accessible is no longer used, instead we now have
strong params.
1- as noted above you need to use strong parameters
2- I am using Rails '4.0.4' and devise. A couple of weeks ago tried to update to Rails 4.1.0. I got some errors from use of devise so I wnet back to rails version 4.0.4
Pierre
I am really new to rails and have been trying to work on building an application. I recently have installed devise and omniauth for facebook with great success after some time. When I was reading into devise, I noticed that Devise has a "forgot password" module built into it.
I have scoured the internet and for the life of me haven't figured out how to set it up. Is there any guide?I have been working for hours, but I haven't really had any results. How do I set this up? I am using rails 4.0 and the newest version of devise.
Thanks,
Routes
Omrails::Application.routes.draw do
resources :boards
resources :pins
get 'about' => "pages#about"
root :to => 'pins#index'
resources :tests, :birthdays
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
end
Devise Migration:
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0, :null => false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
# add_index :users, :confirmation_token, :unique => true
# add_index :users, :unlock_token, :unique => true
end
end
User.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable,
:registerable,
:rememberable,
:trackable,
:recoverable,
:validatable,
:omniauthable,
:omniauth_providers => [:facebook]
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :name, :birthday, :sex, :address, :mobile, :provider, :uid
has_many :pins, :dependent => :destroy
has_many :boards, :dependent => :destroy
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
user = User.create(name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20])
end
user
end
end
Devise consists of 10 modules and the one you're looking for is recoverable. In your devise model, you need to add :recoverable attribute for devise.