Devise super do with current record in user controller for sending mails in Ruby on Rails - devise

I'm trying to set up a registration confirmation through email for the users. I'm using devise for authentication. But I could'nt access the saved user resource from the devise controller, though I tried a few tinkers in futile. If some Could lend a helping head, that's great!!!
I'm trying to send a confirmation link to the registered user after the user got saved. But I could not grab the new user records as any usual instance variable in the devise controller.
Now my user registrations controller looks like this:
class Users::RegistrationsController < Devise::RegistrationsController
before_action :select_plan, only: :new
# Extend the default Devise gem behaviour so that
# the users signing up with a Pro account(plan_id 2) should be
# saved with a Stripe subscription function
# Otherwise, save the sign up as usual.
def create
super do |resource|
# #user = User.new(configure_permitted_parameters)
if params[:plan]
resource.plan_id = params[:plan]
if resource.plan_id == 2
resource.save_with_subscription
else
resource.save
end
//These do not works and returns null
//#user = User.find_by_email(params[:email])
//#user = User.find(params[:id]
//#user = resource
UserMailer.registration_confirmation(params[:email]).deliver
flash[:success] = "Please confirm your email address to continue"
redirect_to root_url
end
end
end
private
def select_plan
unless (params[:plan] == '1' || params[:plan] == '2')
flash[:notice] = "Please select a membership plan to sign up."
redirect_to root_url
end
end
end
User model:
class User < ApplicationRecord
before_create :confirmation_token
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :plan
has_one :profile
attr_accessor :stripe_card_token
# If Pro user passes the validation for email, password etc. then call Stripe
# and tell Stripe to add a subscription by charging customer's card
# Stripe then returns a customer token in response
# Store the token id as customer id and save the user
def save_with_subscription
if valid?
customer = Stripe::Customer.create(description: email, plan: plan_id, card: stripe_card_token)
self.stripe_customer_token = customer.id
save!
end
end
private
def confirmation_token
if self.confirm_token.blank?
self.confirm_token = SecureRandom.urlsafe_base64.to_s
end
end
def email_activate
self.email_confirmed = true
self.confirm_token = nil
save!(:validate => false)
end
end
Anyone has any idea of sending the email confirmation email from devise controller? Thanks in advance!!!

While using devise we can solve the super do requirements in many ways, like
adding an after_create action in the User model to send a confirmation email.
But I think this solution is handy is you really need to work in with the super do your action with the devise registration controller.
Add the codes after the resource saves.
class Users::RegistrationsController < Devise::RegistrationsController
def create
super do |resource|
# Your super codes here
resource.save
yield resource if block_given?
if resource.persisted?
# We know that the user has been persisted to the database, so now we can send our mail to the resource(user) now!
UserMailer.registration_confirmation(resource).deliver
# ....
end
# ....
end
end
# .....
end
Make sure no double rendering or multi redirect is implied with the devise

Related

Bcrypt with two different user models on rails 5.2

I have two migration tables: parents and teachers. And I use Bcrypt for registration. I can't figure out what I should do with log in and sessions_controller (sessions helper). I can register new user and when user registers he can only see Sign Out link in navbar. However, I can not sign out user, I am not sure how I define the methods in sessions controller and session helper. Maybe somebody can help me with this?
I can not find information about bcrypt with different user models - is this thing not popular or is this so easy and I am just stupid and do not understand a thing??
class SessionsController < ApplicationController
include SessionsHelper
def new
end
def create
teacher = Teacher.find_by(email: params[:session][:email])
parent = Parent.find_by(email: params[:session][:email])
if teacher && teacher.authenticate(params[:session][:password])
log_in teacher
redirect_to documents_path
flash[:notice] = "Welcome!"
elsif parent && parent.authenticate(params[:session][:password])
log_in parent
redirect_to root_path
flash[:notice] = "Welcome!"
else
flash[:alert] = "Please log in again!"
render 'new'
end
end
def destroy
if log_out parent
redirect_to root_path
elsif log_out teacher
redirect_to root_path
end
end
end
And here is my sessions helper:
module SessionsHelper
# Logs in the given user.
def log_in(parent)
session[:parent_id] = parent.id
end
def log_in(teacher)
session[:teacher_id] = teacher.id
end
# Returns the current logged-in user (if any).
def current_teacher
#current_teacher ||= Teacher.find_by(id: session[:teacher_id])
end
def current_parent
#current_parent ||= Parent.find_by(id: session[:parent_id])
end
# Returns true if the user is logged in, false otherwise.
def logged_in?(teacher)
!current_teacher.nil?
end
def logged_in?(parent)
!current_parent.nil?
end
def log_out(teacher)
session.delete(:teacher_id)
#current_teacher = nil
end
def log_out(parent)
session.delete(:parent_id)
#current_parent = nil
end
end
I don't know the details of the your application, but I can explain the general case.
First of all, controller which has log in function is not have to be named sessions_controller, name is anything OK.
And Bcrypt is basically only a library for encrypting passwords. Main process is checking the password entered by user is valid or not without decrypting. There is no clear answer how to implement controller logic.
Apparently, user is divided into two types, Teacher and Parent, and probably each has different functions. So essentially, I want to divide the two login processes into separate controllers or actions. Because each login process is not the same one.
But Teacher and Parent will log in with the same URL if user have to login from the same page due to the UI restriction. If you are in such circumstances, implementing in the same controller & action will be appropriate.
After all, it depends on how to design your application. So your code is not always wrong.
However, look at your code, Teacher or Parent is judged only by e-mail, it is doubtful whether this is a proper approach. I have not seen many websites where users with different privileges log in from the same page.
I think that it is basically divide the login page depending on Teacher or Parent. If you divide the login page, example is as follows.
class TeachersController < ApplicationController
include SessionsHelper
def login
end
def login_action
teacher = Teacher.find_by(email: params[:teacher][:email])
if teacher && teacher.authenticate(params[:teacher][:password])
log_in teacher
flash[:notice] = 'Welcome!'
redirect_to root_path
else
flash[:notice] = 'Invalid log in information!'
redirect_to action: :login
end
end
def logout
teacher = Teacher.find(session[:teacher_id])
log_out teacher
redirect_to root_path
end
end
class ParentsController < ApplicationController
include SessionsHelper
def login
end
def login_action
parent = Parent.find_by(email: params[:parent][:email])
if parent && parent.authenticate(params[:parent][:password])
log_in parent
flash[:notice] = 'Welcome!'
redirect_to root_path
else
flash[:notice] = 'Invalid log in information!'
redirect_to action: :login
end
end
def logout
parent = Parent.find(session[:parent_id])
log_out parent
redirect_to root_path
end
end
Although this is not the main issue, did you write sessions_helper in the helpers directory?
Usually, helper is used to implement for view logic, so if you want to share method in controller, use ActiveSupport::Concern in the concerns directory.

Administration of a Multi-Tenant Rails App

We have a multi-tentant rails app that very closely resembels the "from scratch" approach that Ryan Bates explains/teaches.
Authentication from scratch.
Multi-tenancy from scratch.
We have an account model that has a sub-domain attribute and uses scoping to separate the data.
#ApplicationController
around_filter :scope_current_account
private
def current_account
if request.subdomain.present? && request.subdomain != 'www' && request.subdomain != 'ndt-staging'
#account ||= Account.find_by_subdomain!(request.subdomain)
end
end
helper_method :current_account
def scope_current_account
if request.subdomain.present? && request.subdomain != 'www' && request.subdomain != 'ndt-staging'
Account.current_id = current_account.id
end
yield
ensure
Account.current_id = nil
end
The models:
#Account.rb
has_many :users, :inverse_of => :account, :dependent => :destroy
#User.rb
belongs_to :account, :inverse_of => :users
default_scope { where(account_id: Account.current_id) }
My questino is: What is the best way to manage users application wide.. meaning User.scoped and User.unscoped?
The first thing that comes to mind is to add an admin_password attribute to the User model. Set the password with an environment variable, and at User/Account creation add the admin password value into a hidden field.
(the account new action also builds a user and creates a user account)
#AccountsController
def new
#account = Account.new
#account.users.build(params[:user])
end
The biggest problem I see with this approach is the authentication. I would need to re-write things so that if the admin_password is correct, the normal password attribute will not be checked. If the admin_password is incorrect the password attribute will be used.
As a side note, I've looked at plugins like acts_as_tenant and devise, but would rather build these parts myself.
I could be going down the wrong path here, that is why I am asking for recommended solutions/ideas. Thank you in advance for those :)
#SessionsController
def create
user = User.find_by_email(params[:email].downcase)
if user && user.authenticate(params[:password])
sign_in user
redirect_to welcome_path
else
flash.now[:error] = 'Invalid email/password combination' # Not quite right!
render 'new'
end
end
I solved this using the cancan gem, and creating an admin user in the account create action.

Integrating ActiveAdmin and adauth

I have a running Rails application, using ActiveAdmin and its models to autenticate users. Now I'm interested in moving to an ActiveDirectory authentication, so my users can validate wiht the domain's users.
I've been trying adauth and it looks like a great gem, but I'm a little bit lost when trying to "mix" this gem with my ActiveAdmin authentication. I'm pretty sure I'm not the first one in doing it, so any help would be appreciated.
Thanks!
I finally was able to manage to integrate AD in ActiveAdmin.
Here's what I did, in case someone is interested:
Include gem 'adauth' in your gems
Execute bundle install
Execute rails g adauth:config
Configure the config/initializers/adauth.rb for your AD connection. For example, if your domain is example.com, you must include:
c.domain = "example.com"
c.server = "IP address of your domain controller"
c.base = "dc=example, dc=com"
Execute rails g adauth:sessions
Modify your application_controller.rb. Mine was:
class ApplicationController< ActionController::Base
protect_from_forgery
helper_method :current_user
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def authenticate_user!
if current_user.nil?
redirect_to '/sessions/new', :error => "Invalid Login"
end
end
end
Execute rails g adauth:user_model user install_adauth.
This creates the migration install_adauth, but for some reason it was empty. I had to fill it myself with:
class InstallAdauth < ActiveRecord::Migration
def up
create_table :users do |u|
u.string 'login'
u.text 'group_strings'
u.string 'name'
u.string 'ou_strings'
end
end
def down
drop_table :users
end
end
Execute rake db:migrate
Modify your sessions_controller.rb. Mine was:
class SessionsController < ApplicationController
def new
redirect_to '/admin' if current_user
end
def create
ldap_user = Adauth.authenticate(params[:username], params[:password])
if ldap_user
user = User.return_and_create_with_adauth(ldap_user)
session[:user_id] = user.id
redirect_to '/admin'
else
redirect_to '/sessions/new', :error => "Invalid Login"
end
end
def destroy
session[:user_id] = nil
redirect_to '/sessions/new'
end
end
So far the validation through ActiveAdmin still works. To switch to ActiveDirectory we must change the file initializers/active_admin.rb
# config.authentication_method = :authenticate_admin_user!
config.authentication_method = :authenticate_user!
#config.current_user_method = :current_admin_user
config.current_user_method = :current_user
In my case, I needed to restart Apache too.
If anytime we want to switch back to ActiveAdmin, we just need to undo the last change

Rails remember_token session not working

I am new to Ruby on Rails. I am learning it through Michel Hartl's reference doc available on the internet. However i ran into a problem during sign-in of users, I suspect the before_save is not calling the create_remember_token method. I am trying to debug this issue from a very long time. My code is as follows:
user.rb file:
# == Schema Information
#
# Table name: users
#
# id :integer(4) not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# password_digest :string(255)
# username :string(255)
# remember_token :string(255)
#
class User < ActiveRecord::Base
def to_param
username
end
attr_accessible :name, :email, :password, :password_confirmation, :username
has_secure_password
before_save { |user| user.email = email.downcase }
before_save :create_remember_token
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
session_helper.rb
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
def signed_in?
!current_user.nil?
end
private
def user_from_remember_token
remember_token = cookies[:remember_token]
User.find_by_remember_token(remember_token) unless remember_token.nil?
end
end
sessions_controller.rb:
class SessionsController < ApplicationController
def new
end
def create
user=User.find_by_username(params[:session][:username])
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_to "/#/#{params[:session][:username]}"
else
flash.now[:error] = "Login failed! Please try again!"
render 'new'
end
end
def destroy
end
end
Please help me and let me know as to where the problem exists.
Cheers :)
What exactly is the problem that you are having during sign in?
The reason I ask is that the create_remember_token method is not supposed to be called during the sign-in process. It gets called when the user is saved (sign-up).
The sign-in process just fetches the token from the users table and copies it into the permanent cookie. The sign-in process looks roughly like this:
sign_in user --> session_helper.rb --> def sign_in(user) --> cookies.permanent[:remember_token] = user.remember_token --> back to session_controller.rb --> redirect_to ...
The before_save callbacks should only be called when the user is created (ie. when they sign up, not when they sign in). You don't show the whole sign-up/user creation flow in your code sample, but i assume it exists.
You can test whether create_remember_token is called (when the user is created) by running this command from the Rails console:
User._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:create_remember_token)
That should return true if and only if the create_remember_token function is being run on save. More details in the 'Debugging callbacks' section of the Callbacks API Documentation.
If the callback is called during the signup flow, the remember_token should be saved to the DB then. You can verify that by inspecting the user with the Rails console (User.last, after creating your test user).
What does your sign-up/user creation flow look like? What repro steps produce the problem you're describing?
You might have not used csrf meta tag in your layout. Try removing ' protect from forgery ' in application_controller.rb in controllers folder. Then try running the app again, if it works then you didnt add tags to your layout.
I had posted this question a year ago.
Now I am using the Devise gem for authentication. Everything is working as expected.
Thanks

Devise: Create User without Password then Require Password to Use Account?

I'm trying to layout a Rails app using Devise for authentication. I'd like to have an initial landing page where people could enter then email address. I'd like to create an account as soon as I get the email address and then let the user finalize the authentication process later. Is there some documentation that would would show how to do this?
Thanks!
I ended up solving this by using the "devise_invitable" gem. The only thing that I had to extend to get this working was to make sure that a user didn't need to be authenticated to send an invitation. Over-ride the invitations contoller and it's working great:
class InvitationsController < Devise::InvitationsController
include Devise::Controllers::InternalHelpers
skip_filter :authenticate_inviter!
skip_filter :authenticate!
def current_inviter
#current_inviter ||= User.new(params[:user])
end
end
I'm not sure if there is good documentation for how to do this, but it would not be hard. Just don't require authentication on your landing page, or on the post from the login form on that page. Collect an email address in the login form. Send the user a mail to the address they log in with. In the email, include the 'forgot password' link (renamed to 'click here to sign in' ... or whatever) to force the user to login and choose a password. Does that work for you, or did i miss something?
I realize this is late, but this might be helpful to others. I had a similar requirement as you. Basically, I wanted to have a user enter an email address, and persist the user as a guest. Once the user is promoted to a 'regular' user, I re-enable password authentication. First, I created a warden strategy based on the devise-gullible gem. I made a modification to the authenticate method:
class Guest < Authenticatable
def authenticate!
resource = mapping.to.find_for_database_authentication(authentication_hash)
if resource
if resource.respond_to?(:guest?) and resource.guest?
success!(resource)
else
fail(:regular_user)
end
else
fail(:invalid)
end
end
end
I define a guest user in my user model as follows:
def guest?
self.roles.length == 0
end
I'm using CanCan with devise and a HABTM to handle the roles. Currently, I have a 'regular' user and an 'admin' user. A user is a guest if he has no assigned roles yet.
Then, you need to override the devise registrations controller:
class Users::RegistrationsController < Devise::RegistrationsController
prepend_before_filter :allow_params_authentication!, :only => :create
def create
resource = warden.authenticate(:guest,
{ scope: resource_name,
recall: "#{controller_path}#new" })
if resource
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
elsif warden.winning_strategy.message == :regular_user
set_flash_message :notice, :regular_user if is_navigational_format?
redirect_to new_session_path(resource_name)
else
super
end
end
end
Note that the idea is that we attempt to authenticate a user running the guest strategy only. If he is already registered as a guest, just sign him in normally. If the guest strategy fails because he is registered, but is now a regular user, redirect to the normal sign in page.
Now it is possible to persist some limited information I may wish to collect from a guest, and not require him to have to do a full sign up. I use a normal User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :omniauthable,
:token_authenticatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
has_and_belongs_to_many :roles
def self.new_with_session(params, session)
super.tap do |user|
if user.password.blank?
user.password = Devise.friendly_token[0,31]
# Also, we don't need to do email confirmation in this case.
# The user will have the guest role, and we'll do a confirmation
# when we promote him to a 'regular' user.
user.skip_confirmation!
end
end
end
def has_role?(role)
!!self.roles.find_by_name(role.to_s.camelize)
end
end
Note that I auto-generate a password in build_with_session. You can send a auth token at the time of your choosing, and then require the user to set a new password at that time. You'll also want to then change his role so that he becomes a regular user (or do whatever it is you want to note he is no longer a guest).
Here's my partial that appears on the front page:
<%= simple_form_for(resource,
as: resource_name,
url: user_registration_path,
defaults: { required: false },
html: { class: 'well' },
wrapper: :bootstrap,
validate: true) do |f| %>
<%= f.error_notification %>
<fieldset>
<legend>New here? Let's get started!</legend>
<%= f.input :email, placeholder: 'user#example.com', validate: { uniqueness: false } %>
<%= f.button :submit, "Get my quote!", class: 'btn-primary' %>
</fieldset>
<% end %>
So, this form functions as both a guest registration and login form. The only think I don't really care for so far is the the registrations controller is handling an authentication, but I didn't see an immediate way around this.
I implemented this strategy in my local lib directory. Be sure to set your load path appropriately, and add require 'devise_guest' to config/initializers/devise.rb.