Log in user after accept invitation using rails with devise_invitable - ruby-on-rails

I use devise_invitable with Rails and need some help. I want make user logged in after accept invitation. Here is my InvitationsController
class InvitationsController < Devise::InvitationsController
def update
if User.accept_invitation!(user_params)
# log in user here
redirect_to dashboard_show_path, notice: t('invitaion.accepted')
else
redirect_to root_path, error: t('invitation.not_accepted')
end
end
private
def user_params
params.require(:user).permit(:invitation_token, :password, :password_confirmation)
end
end
You can see comment in code
# log in user here
here I want log in the user who has accept the invitation.
Thanks.

The method your looking for is sign_in, try this:
def update
if User.accept_invitation!(user_params)
sign_in(params[:user])
redirect_to dashboard_show_path, notice: t('invitaion.accepted')
else
redirect_to root_path, error: t('invitation.not_accepted')
end
end
However I should note that devise_invitable, by default, signs in users after they have accepted an invitation. See the default update action here, if you wish to use the default functionality simply call the super method or don't implement the update action at all.

Here is a minor update for the Noz version:
# invitations_controller.rb
def update
user = User.accept_invitation!(update_resource_params)
if user
sign_in(user)
redirect_to root_path, notice: t('devise.layout.edit_invitation.accepted')
else
redirect_to root_path
end
end
private
def update_resource_params
params.require(:user).permit(:password, :first_name, :last_name, :invitation_token)
end
This definition update won't throw Could not find a valid mapping for {...} and instead login the user after invitation acceptance.

Related

Devise let me to change new password as empty

This is my code for changing a password:
class RegistrationsController < Devise::RegistrationsController
protect_from_forgery
def update_password
if current_user.update_with_password(devise_parameter_sanitizer.sanitize(:account_update))
sign_in(current_user, bypass: true)
redirect_to settings_path, notice: "updated"
else
redirect_to settings_path, alert: current_user.errors.full_messages
end
end
protected
def update_resource(resource, params)
resource.update_without_password(params)
end
def after_sign_up_path_for(_resource)
end
def after_update_path_for(_resource)
settings_path
end
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:account_update, keys: [:first_name, :last_name])
end
private
def user_params
# NOTE: Using `strong_parameters` gem
params.require(:user).permit(:current_password, :password, :password_confirmation)
end
end
The code works if I enter my current password and set a new one, but it doesn't when I enter a correct current password and as a new password + confirmation I leave empty fields (empty string).
The password will not get changed as "no password", but I get a flash message "updated". How do prevent that? I can think of this:
if current_user.update_with_password(devise_parameter_sanitizer.sanitize(:account_update))
if params[:user][:password].blank?
redirect_to settings_path, alert: "blank pswd"
return
end
sign_in(current_user, bypass: true)
redirect_to settings_path, notice: "Your password has been updated!"
else
redirect_to settings_path, alert: current_user.errors.full_messages
end
However, this solution is a bit... ugly. Is there a more elegant way to handle this situation?
Thank you in advance
Reading your question I was a bit surprised about this behavior. But looking at the source code I could confirm that this behavior is expected and describe in a comment in the code:
# This method also rejects the password field if it is blank (allowing
# users to change relevant information like the e-mail without changing
# their password). In case the password field is rejected, the confirmation
# is also rejected as long as it is also blank.
I have mixed feelings about how to handle it. I would probably not show an error message because when the user didn't enter a new password they probably would not expect the password to change.
But another way to handle the error message could be to not handle that case in the method at all but to use a before_action:
before_action :ensure_new_password_provided, only: :update_password
private
def ensure_new_password_provided
return if params[:user][:password].present?
redirect_to settings_path, alert: "blank pswd"
end

Updating current_user attribute to true from password update controller action

My Rails 5 App only permits an admin or support user to create a user, when the user is created a password is generated and emailed to the user, on the first user login the app forces them to change the password.
I have a password_updated field in my schema that I want to be filled to true when the password is updated, however I am hitting a wall here, not sure if its coder eye and I just cant see what where im going wrong.
my application controller:
# Force User To Change Password On First Login
def after_sign_in_path_for(resource)
if current_user.password_updated == "false"
edit_passwords_path
else
authenticated_root_path
end
end
I have it set up so that if the user tries to skip or jump past the password change they are redirected to the password change.
my passwords controller:
class PasswordsController < ApplicationController
def edit
#user = current_user
end
def update
if current_user.update_with_password(user_params)
current_user.password_updated = "true"
flash[:notice] = 'Your Password Has Been Sucessfully Updated.'
redirect_to authenticated_root_path
else
flash[:error] = 'Oh No! Something Went Wrong, Please Try Again.'
render :edit
end
end
private
def user_params
params.require(:user).permit(:password_updated, :current_password, :password, :password_confirmation)
end
end
Originally I had the application controller looking at the sign in count, however if the user closed out and waited long enough they could log back in and not have to change the password. I felt this was more of a risk.
Any assistance here would be greatly appreciated.
I think the problem is that you don't save current_user after setting password_updated to true.
So the code should something like
def update
update_params = user_params.merge(password_updated: true)
if current_user.update_with_password(update_params)
flash[:notice] = 'Your Password Has Been Successfully Updated.'
redirect_to authenticated_root_path
else
flash[:error] = 'Oh No! Something Went Wrong, Please Try Again.'
render :edit
end
end
This way you would save current_user just once.
I suppose that password_updated is boolean field in DB.
Then in your application_controller.rb you can check it like current_user.password_updated?.
I would suggest to allow admin create a user without a password. You will have to override the password_required method from devise.
def password_required?
new_record? ? false : true
end
For a new record, when admin creates it, password is not required, but when the user signs up, it will prompt to add a password.
Or you can even keep condition like when the user is an admin, return false, else return true.
I would choose a different approach using invitations.
Invited users are created with a token (a crypographically random string) which is used to identify the user. This removes the need to communicate the temporary password in clear-text and you can for example add expiry times to the invitation token for security.
So the app flow is the following:
An admin visits /invitiations/new
He fills in the form with the new users email and POSTs to /invitations
An email is sent to the new user containing a link with an access token.
The new user clicks the link and is sent to /invitations/edit?invitation_token=ABCD12
The user fills in the form with a password, and sends a PATCH to /invitations with an invitation token in the request body.
The user should then be prompted to sign with their new password.
A minimal example is:
class AddInvitationTokenToUser < ActiveRecord::Migration[5.0]
def change
add_column :users, :invitation_token, :string
# this may not work on DBs that do not allow NULL on unique columns
add_index :users, :invitation_token, unique: true
end
end
Then we need to setup the User model to create random invitation tokens.
require 'securerandom'
class User < ApplicationRecord
# #todo skip password validation if user has invitation_token
def set_invitation_token!
self.invitation_token = generate_invitation_token
end
private
def generate_invitation_token
# this ensures that the token is unique
begin
token = SecureRandom.urlsafe_base64
end while User.where(invitation_token: token).any?
token
end
end
Setup a controller for invitations:
class InvitationsController < ApplicationController
before_action :authenticate!, only: [:new, :create]
before_action :authorize!, only: [:new, :create]
prepend_before_action :authenticate_user_from_token!, only: [:edit, :update]
skip_before_action :authenticate!, :authorize!, only: [:edit, :update]
def new
#user = User.new
end
def create
#user = User.new(create_params) do |u|
u.set_invitation_token!
end
if #user.save
# #todo email user invitation email
redirect_to '/somewhere'
else
render :new
end
end
def edit
end
def update
if #user.update(update_params)
#user.update_attibute(:invitation_token, nil)
redirect_to new_session_path, notice: 'Please sign in.'
else
render :edit
end
end
private
def authenticate_user_from_token!
unless params[:invitation_token].present?
raise ActiveRecord::RecordNotFound and return
end
#user = User.find_by!(invitation_token: params[:invitation_token])
end
def create_params
require(:user).permit(:email)
end
def update_params
require(:user).permit(:password, :password_confirmation)
end
end
There are several steps omitted here for brevity like skipping the password validation.
I would encourage you to check out DeviseInvitable for a more complete example of this pattern.

Rails authentication from scratch, skip current password validation

I have an auth system from scratch, and when a user clicks on 'edit profile' it has to input the current password no matter the field he wants to edit.
def update
if params[:user][:password].present?
authenticated = #user.authenticate(params[:user][:current_password])
if authenticated && #user.update(user_params)
redirect_to root_url
flash[:notice] = "Your profile was successfully updated!"
else
#user.errors.add(:current_password, 'is invalid') unless authenticated
render :edit
end
elsif #user.update(user_params)
redirect_to root_url
flash[:notice] = "Your profile was successfully updated!"
else
render :edit
end
end
How can I call authenticate or use some context model validation only for the scenario when the user wants to change his password?
I wouldn't recommend mixing this logic into the model because you end up with complexity that is hard to follow as your application grows over time.
Try taking a look into form objects:
Form-backing objects for fun and profit
Railscast #416 Form Objects [paid subscription required]
I'd implement something like this:
class UserUpdateForm
include ActiveModel::Model
# Attributes
attr_accessor :user, :new_password, :new_password_confirmation
# Validations
validates :current_password, if: :new_password
validate :authenticate, if: :current_password
validates :new_password, confirmation: true, allow_blank: true
def initialize(user)
self.user = user
end
def submit(params)
self.new_password = params[:new_password]
self.new_password_confirmation = params[:new_password_confirmation]
if self.valid?
# Set other attributes as needed, then set new password below.
self.user.password = self.new_password if self.new_password.present?
self.user.save
else
false
end
end
private
def authenticate
unless self.authenticate(self.current_password)
self.errors.add(:current_password, 'is invalid')
end
end
end
Then you can call it from your controller like so:
def update
#user_update_form = UserUpdateForm.new(#user)
if #user_update_form.submit(params)
flash[:notice] = "Your profile was successfully updated!"
redirect_to root_url
else
render :edit
end
end
See the links above for how to handle the view and such. This is just to get you started.
You may create a nested if-else in this action statement that will check for existence of new_password and new_password_confirmation (or whatever the new password and confirmation fields are called) in the params[:user] object. If they are present - you may redirect to some king of page with request to enter existent password.
Another way is to use ajax to show asynchronously the dialog box with the same request (like respond_with self-invoking javascript function that handles that). Then handle submit button in of the dialog in the other action of the controller.
Update (considering use of validators):
Considering validation you may write your own validator (for password) and condition to check when the new password field come with some data from the client.
I think it could look like this:
validate :password_update?
def password_update?
if new_password.present?
if current_password !== self.password
errors.add(:current_password, "must be supplied!")
else
# update data and password
end
else
# do your regular update
end
end

redirecting from another page taking info

So after the user signs up, i redirect them to my additional info page where i collect some more information. However, something is wrong with my design/implementation as rails is saying im missing users/create template
this is my users controller
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def additional_info
#user = User.new(user_addinfo)
if #user.save
redirect_to show_path
end
end
def create
#user = User.new(user_params)
if #user.save
# UserMailer.welcome_email(#user).deliver
sign_in #user
redirect_to additional_info_path
flash[:success] = "Welcome to InYourShoes!"
#return #user
else
render'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def user_addinfo
params.permit(:year)
end
end
def show is the user profile page i want to show after redirecting to the additional_info page
def additional_info is just take additional info from the private method def user_addinfo
def create is the sign up process.
After entering the basic user info, it gets redirected to additional which is fine. but after the additional, it says im missing the users/create template, but my code i attempted to redirect to show_path and #usersshow, still doesnt work
any suggestions? sorry if this seems intuitive but Im new to rails.
I think your problem is in the additional_info method, as i said in the comment. What you're doing is:
creating a user
creating a session for the user (sign_in #user) - storing somewhere the user_id in the session
redirecting to your additional_info page
And here comes the problem. As the user is already signed in you don't have any need to create a new user with additional params. You should have some helper to retrieve the current signed in user (like current_user) and in additional_info method, just update it.
So your additional_info method would become something like:
def additional_info
user = User.find session[:user_id]
user.update params[:user]
redirect_to user_path #show action
end

How to build an update email function in Ruby on Rails?

I am trying to refactor the update action in my Rails action, so that users can change their own email address only after confirming it by clicking on a link that I send to them.
class UsersController < ApplicationController
before_filter :authorized_user
def update
current_email = #user.email
new_email = params[:user][:email].downcase.to_s
if #user.update_attributes(params[:user])
if new_email != current_email
#user.change_email(current_email, new_email)
flash[:success] = "Please click on the link that we've sent you."
else
flash[:success] = "User updated."
end
redirect_to edit_user_path(#user)
else
render :edit
end
end
def confirm_email
#user = User.find_by_email_token!(params[:id])
#user.email = #user.new_email
#user.save
end
private
def authorized_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
end
This function saves the new email to a database field new_email. email will be replaced only after the user has confirmed his new_email through a URL:
class User < ActiveRecord::Base
def change_email(old_email, new_email)
self.email = old_email
self.new_email = new_email.downcase
self.send_email_confirmation_link
end
end
The code partially works, but I wonder if there's a more elegant way to do this, maybe by using an after_save callback or at least moving more code to the model.
What would be the best way to do this?
Thanks for any help!
P.S. Please don't suggest to use Devise for this. I really want to build my own authentication system here :-)
I would advise you against using ActiveRecord callbacks to perform business logic: ActiveRecord models should only be a thin wrapper around the database persistence layer.
Look at how the controller code can be changed:
def update
if UpdatesUserCheckingEmail.new(#user, params[:user], flash).execute!
redirect_to edit_user_path(#user)
else
render :edit
end
end
All the business logic is performed by an external object, which encapsulates all your business logic (which you can put in app/services/updates_user_checking_email.rb)
class UpdatesUserCheckingEmail
attr_reader :user, :user_params, :flash
def initialize(user, user_params, options = {})
#user = user
#user_params = user_params
#flash = options[:flash]
end
def execute!
if user.update_attributes(user_params)
if new_email != current_email
user.change_email(current_email, new_email)
flash[:success] = "Please click on the link that we've sent you."
else
flash[:success] = "User updated."
end
end
end
private
def current_email
user.email
end
def new_email
user_params[:email].downcase.to_s
end
end
I'd also advise you to move the logic which sends the email out of the ActiveRecord model and inside a dedicated service object. This will make your app much more easier to change (and to test) in the future!
You can find a lot more about these concepts here: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
I think you should not check current_email and new_email after update in database because it should before the database update. Another one is you are sending link to user after updating email to the database. So, that could not meet your goal i.e. "email will be replaced only after the user has confirmed his new_email through a URL." You should create new action for updating user email or you should write logic for updating user email when user get email of "reset email" in update action of UserController. Following is something simple approach to resolve your problem:
class UsersController < ApplicationController
def send_email_rest
#user.change_email(#user.email, params[:new_email]) if params[:new_email].present?
end
def update
if #user.update_attributes(params[:user])
#stuff you want to do
end
end
end
Hope that helps!!!

Resources