I followed these instructions to check whether a user has been soft-deleted or not when logging in. In the example below I can check for a boolean value:
Class User < ActiveRecord::Base
def self.find_for_authentication(conditions)
super(conditions.merge(:deleted_flag => false))
end
I would prefer a timestamp (created_at) field. How can I check whether such a field is empty? The database throws errors for the following checks:
super(conditions.merge(:deleted_at => false)) # also tried nil, "NULL" and "IS NULL"
You can't using this solution, without modifying devise of course. Devise will send your conditions directly to the database, so no way of calling a method or using a library like squeel (that will allow something like where{created_at == nil}.
You could use the solution provided in How to "soft delete" user with Devise, but the error message will be: "You have to confirm your account before continuing."
Add this to your resource model:
def inactive_message
!!deleted_at ? :deleted : super
end
And add a message to your locales:
en:
devise:
failure:
deleted: "Your account was deleted."
I hope it helps!
Related
I have a User model with devise and have a active column which indicates if the user is active or not. After the trial period is over the account will be inactive unless the user sign up for one of the plans.
I am currently showing custom error message to the users when email already exists using devise like below
en:
activerecord:
errors:
models:
user:
attributes:
email:
taken: 'This email address has been used already. Please contact support if you would like more access'
However sometimes a user with active account in trial period tries to sign up again, in that case i want to display a different error message something like 'Please sign in __sign__in__link'
What would be proper way to display error message conditionally when email already exists and account is active?
Thanks.
The way to handle this isn't really through a validation since validations don't have the context of where you are in the application flow.
And just displaying a little error on the form is a lot less helpful to the user then displaying a flash message.
Devise makes this fairly easy as Devise::RegistrationsController#create yields to let subclasses "tap into" the flow:
class MyRegistrationsController < Devise::RegistrationsController
def create
super do
if has_duplicate_email? && user_is_active?
flash.now("You seem to already have an account, do you want to #{ link_to(new_session_path, "log in") } instead?")
end
end
end
private
def has_duplicate_email?
return false unless resource.errors.has_key?(:email)
resource.errors.details[:email].any? do |hash|
hash[:error] == :taken
end
end
def user_is_active?
User.find_by(email: resource.email)&.active?
end
end
# routes.rb
devise_for :users, controllers: {
registrations: :my_registrations
}
To me it seems that ActiveAdmin should check the create authorization mainly in 2 cases:
The UI needs to show the "create new Ticket" button: here, it is useful to check wether the current_user has permission to create a generic Ticket.
Cancancan syntax looks like the following:
user.can?(:create, Ticket)
ActiveAdmin needs to understand if the resource can actually be stored in the db after a form submission: here it is useful to check wether the current user can store that ticket with the values just "typed in" using the ticket form.
Cancancan syntax looks like the following:
user.can?(:create, Ticket.new({author_id: user.id, some: "x", other: "y", values: "z"}))
That's it! So why would ActiveAdmin check the following right before showing the generated "create form" for the user?
user.can?(:create, Ticket.new({author_id: nil, some: nil, other: nil, values: nil}))
What if the current user has only permission to create tickets where author_id = own_user_id?
The authorization would fail even before seeing the form.
I can't explain why ActiveAdmin was written that way, but I can show you how I've solved a similar problem.
First, you will need to grant your user the ability to create the desired record under all conditions:
# app/models/ability.rb
...
can :create, Ticket
...
This will get your past ActiveAdmin's can? check and allow the user to see the form. But we need to make sure the author_id belongs to the current user. To do this, you can use the before_create callback to set the proper author_id before saving:
# app/admin/ticket.rb
ActiveAdmin.register Ticket do
...
before_create do |ticket|
ticket.author_id = own_user_id
end
...
end
The above assumes you have a helper method or a variable called own_user_id that is available to the ActiveAdmin module and returns the proper user id. If you were using Devise, you might substitute current_user.id for own_user_id.
I'll admit, this is a not the cleanest solution, but it works. I have implemented something similar in my own projects.
I did override the Data Access class as follows, in order to have it working.
I am:
disabling authorization that i feel is done in the wrong time
forced validation before the authorization before saving a resource
ActiveAdmin::ResourceController::DataAccess.module_eval do
def build_resource
get_resource_ivar || begin
resource = build_new_resource
resource = apply_decorations(resource)
run_build_callbacks resource
# this authorization check is the one we don't need anymore
# authorize_resource! resource
set_resource_ivar resource
end
end
end
ActiveAdmin::ResourceController::DataAccess.module_eval do
def save_resource(object)
run_save_callbacks object do
return false unless object.validate # added it
authorize_resource! resource # added it
object.save(validate: false) # disabled validation since i do it 2 lines up
end
end
end
So, I am somewhat new to rails and devise, so I apologize in advance if this is a basic question. I couldn't find any information on this anywhere, and I searched thoroughly. This also makes me wonder if Devise is the right tool for this, but here we go:
I have an app where devise user authentication works great, I got it, implemented it correctly and it works.
In my app, users can belong to a group, and this group has a password that a user must enter to 'join' the group.
I successfully added devise :database_authenticatable to my model, and when I create it an encrypted password is created.
My problem, is that I cannot authenticate this! I have a form where the user joins the group, searching for their group, then entering the password for it.
This is what I tried:
def join
#home = Home.find_for_authentication(params[:_id]) # method i found that devise uses
if #home.valid_password?(params[:password]);
render :json => {success: true}
else
render :json => {success: false, message: "Invalid password"}
end
end
This gives me the error: can't dup NilClass
on this line: #home = Home.find_for_authentication(params[:_id])
What is the problem?
The problem will be here:
Home.find_for_authentication(params[:_id])
I've never used database_authenticatable before (will research it, thanks!), so I checked the Devise docs for you
The method they recommend:
User.find(1).valid_password?('password123') # returns true/false
--
Object?
The method you've used has a doc:
Find first record based on conditions given (ie by the sign in form).
This method is always called during an authentication process but it
may be wrapped as well. For instance, database authenticatable
provides a find_for_database_authentication that wraps a call to
this method. This allows you to customize both database
authenticatable or the whole authenticate stack by customize
find_for_authentication.
Overwrite to add customized conditions, create a join, or maybe use a
namedscope to filter records while authenticating
The actual code looks like this:
def self.find_for_authentication(tainted_conditions)
find_first_by_auth_conditions(tainted_conditions)
end
Looking at this code, it seems to me passing a single param is not going to cut it. You'll either need an object (hence User.find([id])), or you'll need to send a series of params to the method
I then found this:
class User
def self.authenticate(username, password)
user = User.find_for_authentication(:username => username)
user.valid_password?(password) ? user : nil
end
end
I would recommend doing this:
#home = Home.find_for_authentication(id: params[:_id])
...
I need to write a custom validator to check if a record exists in the database or not. Sort of like the opposite of validate uniqueness, however I couldn't find something that could achieve what I wanted in the built in validators.
What I'm attempting to do is check if the referrer exists or not in the Users table. If the referrer's username is "testuser", I want to check in the Users table whether "testuser" actually exists.
I have created a custom validator:
class ReferrerExistsValidator < ActiveModel::EachValidator
However I'm unsure how to proceed to fetch details from there database, any pointers?
Write the following validation class
class ReferrerExistsValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
unless User.find_by_username(value)
object.errors[attribute] << (options[:message] || "referrer does not exist")
end
end
end
Add the following to the relevant model
validates :referrer_exists => true
I'm new to rails/coding, and I may misunderstand you, but couldnt you just do something like:
#user.each do |user|
unless #user.username == current_user.referral_name
:alert "no user found"
else whatever
end
end
EDIT: that wouldn't work at all - ignore that, let me think about it for a sec ;)
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.