I get the following error:
undefined method `can_read?' for nil:NilClass
..when trying to access a product page with a logged-out user. At the moment I have
class ProductAuthorizer < ApplicationAuthorizer
def self.readable_by?(user)
true
end
end
I'd like to allow even non-logged in users to see the page. Is this possible?
I tried changing the default user method to:
config.user_method = :current_user ||= User.new
However, this causes problems, and my server won't even start.
Ok I found this at https://github.com/nathanl/authority/pull/32:
OK! For the sake of anyone else reading this issue, Chris and I
chatted and agreed about the best way to proceed. Here's the gist of
it.
Authority won't specially handle nil users or give a specific option
to do so. We want to limit Authority to authorization and keep
authentication totally separate. If there's no user signed in, that's
an authentication concern; Authority can't meaningfully answer the
question "can this user do X?" if it isn't given a user or something
that quacks like one.
Besides the philosophical point, having authentication handle this is
a better user experience. If an admin has forgotten to sign in and
attempts some admin-only action, it would be confusing to them to say
"access denied". It would be much more helpful to say "please sign
in".
What developers using Authority can do is:
Have something like Devise's before_filter :authenticate_user! running
prior to any Authority checks on the request (since any action that
requires authorization clearly requires authentication). Have their
user method return a NullUser object that quacks like a user, then
have their authorizers know what to do with those What Authority can
do is improve the error it gives you if you pass nil or anything else
that doesn't quack like a user. Chris is going to implement this.
Hi I've just put this
class ApplicationController < ActionController::Base
def current_or_null_user
if current_user == nil
User.new
else
current_user
end
end
end
...
Authority.configure do |config|
config.user_method = :current_or_null_user
end
Related
I have functionality of inactive account in my application for handling this i override active_for_authentication? method as below
def active_for_authentication?
super && activated?
end
But In my application super admin can also directly login in to other user account, whether it is active or not active
bypass_sign_in(User.find(resource.id))
I used above method for by pass sign in, it allows me to directly sign in only for activated user, when i login for non activated user it goes in infinite loop .
Any solutions to over come this issue or don't run active_for_authentication? callback when bypass_sign_in?
When admin logs in to another user account you can store some additional data in session, that makes it clear that this is the super admin mode.
def login_as(another_user)
return unless current_user.super_admin?
session[:super_admin_mode] = true
bypass_sign_in(another_user)
end
Unfortunately, you can't access session in Rails models, but you can store needed session information in some per-request global variable that is available in models. The solution might be like this:
module SessionInfo
def self.super_user_mode?
!!Thread.current[:super_user_mode]
end
def self.super_user_mode=(value)
Thread.current[:super_user_mode] = value
end
end
In the ApplicationController:
class ApplicationController < ActionController::Base
before_filter :store_session_info
private
def store_session_info
SessionInfo.super_user_mode = session[:super_admin_mode]
end
end
In the model:
def active_for_authentication?
super && (activated? || SessionInfo.super_user_mode?)
end
Also, you should make sure that the :super_admin_mode flag is removed from session when the super user logs out. Maybe it happens automatically, I am not sure. Maybe you will need to do it manually overriding Devise::SessionsController#destroy method (see the example below)
def destroy
session[:super_admin_mode] = nil
super
end
Also read this for better understanding of how devise handles session Stop Devise from clearing session
I recently came across a similar issue where I needed to allow an Admin to sign in as regular Users who were not active in Devise. I came up with the following solution that doesn't involve using Thread.current (which after looking into further online it seems like using Thread.current could be a precarious solution to this problem).
You can create a subclass of User called ProxyUser that has the active_for_authentication? return true. Something like this:
class ProxyUser < User
# If you have a type column on User then uncomment this line below
# as you dont want to expect ProxyUser to have type 'ProxyUser'
#
# self.inheritance_column = :_type_disabled
devise :database_authenticatable
def active_for_authentication?
true
end
end
Then in the controller you want something like this:
proxy_user = ProxyUser.find(params[:user_id])
sign_in :proxy_user, proxy_user
Also in your routes you will need devise to expect ProxyUser so include:
devise_for :proxy_users
And finally when you sign this user out (assuming you can sign the user out in your controller code) make sure to tell devise the scope of the sign out, so you would do
sign_out :proxy_user
And then finally note that in your app you may be expecting current_user in different places (such as if you use CanCanCan for authorization) and now when you sign in as a proxy_user your app will return current_user as nil. Your app will instead have an object called current_proxy_user that will be your signed-in ProxyUser object. There are many ways to handle the issues resulting from your current_user returning nil in this case (including overwriting current_user in your application controller).
So I have this app that I'm making where users have profiles after they signup and input their information.
At the moment I'm trying to add a feature that allows for new unregistered users to go to a profile to see what the app is like before they need to sign up (I'm planning on putting a "try it for free" button on the home_controller#index action. For authentication, I'm using the Devise gem.
Currently, I've watched the Railscast (393) on this, but I can't figure out (after several hours of trying) how to implement guest users and log them in using Devise.
I've also read about 4 different solutions on SO, and have decided to stick to this one (how to create a guest user in Rails 3 + Devise):
class ApplicationController < ActionController::Base
def current_user
super || guest_user
end
private
def guest_user
User.find(session[:guest_user_id].nil? ? session[:guest_user_id] = create_guest_user.id : session[:guest_user_id])
end
def create_guest_user
u = User.create(:name => "guest", :email => "guest_#{Time.now.to_i}#{rand(99)}#example.com")
u.save(:validate => false)
u
end
...
end
I have this in my application_controller.rb and don't understand how I would use these functions in the home_controller#index to create a guest user and log into the profile when the "Try it" button is clicked.
I've tried manually creating a user, saving it without authentication and then using Devise's sign_in method on link like so: Try it! and also tried
Try it!
I tried this, but the profile throws some validation messages saying I need to log in to view it. I've also tried removing before_filter authenticate! on the profiles_controller but I can't seem to get this to work at all.
Would anyone know how to create a user on the button click, and auto sign them into a guest profile? Thanks a lot for any help, I'm completely lost here.
I think you have a misunderstanding on what a guest user is. You say that you want guest users to auto sign in, and that is wrong. Guest users can't sign in, because... Well, because they are guests. You want to create them, but not sign them in.
This is why you want these helper methods in your ApplicationController, because when you try to get the current_user, if that doesn't exist (nil), you will have a fallback (that is why you use the || operator), that will assign a guest_user as a current_user.
So, forget about using sign_in links for guest users and you should be fine.
If a user requests 2 password reset links in a row, then only the most recent one is valid.
If a user clicks on the older one, they are not immediately informed of the token being invalid. They are asked to type in the new password twice. Only upon hitting submit are they informed that the token is not valid.
Is there a standard way to change this behavior, so the user is immediately informed that they used the wrong link?
I was a bit surprised to find out that this is standard behavior - thanks for bringing it up! I would probably use a custom controller that inherits from Devise::PasswordsController and simply override :edit. The trouble is we're working with an abstract user (since no user is actually authenticated at this point), so we can't check it against a particular token in the database, and we have to check whether the given token exists.
class PasswordsController < Devise::PasswordsController
before_filter :validate_reset_password_token, only: :edit
private
def validate_reset_password_token
recoverable = resource_class.find_by_reset_password_token(params[:reset_password_token])
redirect_to root_path unless (recoverable && recoverable.reset_password_period_valid?)
end
end
This will redirect unless a user exists with the token passed. You can handle the redirection/error display however you wish.
Finally, in your routes file, change the controller used by devise for passwords:
devise_for :users, :controllers => { passwords: "passwords" }
Caveats:
I have not tested this (but I might look into it tomorrow).
There may be some additional security issues to consider - could someone realistically gain access to a random account? (probably not; Devise uses a similar method of locating a model instance using the reset token in reset_password_by_token)
By the way, there is no way to tell them that they used the wrong link and they should click the other one, as there is no way to positively identify them in the first place, and therefore no way to know that they have already generated another valid token.
There is another safety solution for this question;
class User::PasswordsController < Devise::PasswordsController
def update
super do |resource|
# Jump to super code
next unless resource.errors.any?
token_errors = resource.errors.details[:reset_password_token]
expired_error = token_errors.select { |detail| detail[:error] == :expired }
# Jump to super code
next unless expired_error.present?
message = resource.errors.full_messages_for(:reset_password_token).join(',')
return redirect_to new_user_password_path, alert: message
end
end
end
I am building a daily deal app on Rails to train myself to Ruby on Rails.
I have installed authentication with devise/cancan/rolify.
I'd like to create in cancan two type of users
users who confirmed
users who did not confirmed yet
How can I achieve that ? how can I access on devise users who have and those who have not confirmed their account(i.e clicked on the activation link sent to them by email).
There is no need to add roles for confirmed and unconfirmed. You can use user.confirmed? in your ability.rb file to control authorization:
# models/ability.rb
if user.confirmed?
can :manage, Model
end
if !user.confirmed?
can :view, Model
end
Note: you can use an if/else construct, but I prefer to keep my rules nicely separated.
In regards to your comments, you're reimplementing what's already been done. With cancan you can use load_and_authorize_resource (see: here).
class ProductsController < ActionController::Base
load_and_authorize_resource
end
That's it. The user will receive an "unauthorized" response if they try to access without the required permissions.
I highly recommend you read through the documentation for rolify and cancan.
I would like to set a boolean flag upon user confirmation via Devise. Essentially I want to set the user as 'active'. However, Devise simply confirms the account and logs them in.
How can I create a callback of some sorts to update my user record to set that 'active' column to true upon confirmation?
Any help very appreciated!
Presuming that your authentication model is called User, you can do this:
class User < ActiveRecord::Base
def active?
super and (not self.confirmed_at.nil?)
end
end
With this, Devise will not login the user but will wait until the user confirms (the confirmed_at field will be non-NULL if a user has confirmed)
For your particular question, you're better off implementing your active? attribute as confirmed_at being nil, as suggested by Zabba.
But here is how to do what you're asking, since it may be helpful to people trying to set other values on the user after confirmation.
class Users::ConfirmationsController < Devise::ConfirmationsController
def show
# let Devise actually confirm the user
super
# if they're confirmed, it will also log them in
if current_user then
# and you can do whatever you want with their record
current_user.active = true
end
end
end
This is basically a comment on Turadg's Answer below. If you follow that suggestion (which I did) you will have a small problem when users attempt to use an invalid confirmation_token. You will get a "Missing template users/confirmations/new". What the Devise::ConfirmationsController is doing here is sending you to devise/confirmations/new to inform you the token is invalid and allow you to send another one.
Since I had already customized the Devise views, what I ended up doing to get around this minor issue is moving the devise/confirmations/new.html.haml file into the now expected location under user/confirmations/new.html.haml.