Rails Authlogic: Login using Database ID - ruby-on-rails

I'm brand new to rails (3.2) and I'm looking for a way to allow users to login using their Database ID and password. Reading the documentation, Authlogic seems to allow login using a login name or email. Is it possible to somehow extend the gem such to allow a user to authenticate by providing their database ID and password?
Here is my current user model
class User < ActiveRecord::Base
attr_accessible :password, :password_confirmation, :current_login_ip, :email, :failed_login_count, :first_name, :last_login_ip, :last_name, :last_request_at, :login_count, :password_salt,
:perishable_token, :persistence_token, :single_access_token
acts_as_authentic
end
And my current User Session model
class UserSessionsController < ApplicationController
def new
#user_session = UserSession.new
end
def create
#user_session = UserSession.new(params[:user_session])
if #user_session.save
flash[:notice] = "Successfully logged in."
redirect_to root_url
else
render :action => 'new'
end
end
def destroy
if UserSession.find
UserSession.find.destroy
flash[:notice] = "Successfully logged out."
end
redirect_to root_url
end
end
My current setup uses email address authentication.
Thanks in advance!

Authlogic allows you to choose the column to use as the login field. See the Login::Config module (login_field option):
acts_as_authentic do |c|
c.login_field = :database_id_or_whatever_fits_your_needs
end

Related

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

How to create new User with an Organization association

In my webapp my User Signup page has an Organization Name field. I have an Organization model that has_many :users, and my User model belongs_to :organization. When a new user is created, I'd like for the Organization Name value to be used to create a new organization record, and associate it with the user, such that user.organization_id = the new organization id.
This is my users_controller.rb code:
class UsersController < ApplicationController
def new
#user = User.new
end
def show
#user = User.find(params[:id])
#organization = Organization.find(#user.organization_id)
end
def create
#organization = Organization.new(organization_params)
#user = User.new(user_params)
if #user.save && #organization.save
sign_in #user
redirect_to #user
flash[:success] = "Welcome to the App!"
else
flash.now[:danger] = "Uh oh, there's been an error"
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
def organization_params
params.require(:organization).permit(:name)
end
end
Right now both the user record and organization record are being created when a user submits the signup form, but the association is not saved. The user.organization_id value is nil.
Can you comment on what's wrong, and if there's a good way to do what I'm going for -- maybe with .build?
Thanks!
Brennan
Yes! .build will work, but because its a single association back, you will be using the association name in your build command, ie build_organization
def create
#user = User.new(user_params)
#user.build_organization(organization_params)
if #user.save
blah blah blah
You only need to save the user (not #organization) if done this way because the association is taken care of.
At user_params permit :organization_id ;)
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation, :organization_id)
end
EDIT: You also need to set organization_id somewhere for user - if you're not doing it in form (like with select or some else field) you can use .build method.

Log in user after accept invitation using rails with devise_invitable

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.

Rails User Sign Up Mail Confirmation

I'm trying to create a mailer that sends out an email whenever a user signs up. Pretty simple but I'm new to rails.
I have a site that already creates the user. I have a login and sign up page that works correctly, but need some help creating a mailer that sends out an email confirmation link and possibly an option to send out these emails without the user signing up like make a separate page for user invitations.
I've generated a model invitation.rb
class Invitation < ActiveRecord::Base
belongs_to :sender, :class_name => 'User'
has_one :recipient, :class_name => 'User'
validates_presence_of :recipient_email
validate :recipient_is_not_registered
validate :sender_has_invitations, :if => :sender
before_create :generate_token
before_create :decrement_sender_count, :if => :sender
private
def recipient_is_not_registered
errors.add :recipient_email, 'is already registered' if User.find_by_email(recipient_email)
end
def sender_has_invitations
unless sender.invitation_limit > 0
errors.add_to_base 'You have reached your limit of invitations to send.'
end
end
def generate_token
self.token = Digest::SHA1.hexdigest([Time.now, rand].join)
end
def decrement_sender_count
sender.decrement! :invitation_limit
end
#attr_accessible :sender_id, :recipient_email, :token, :sent_at
end
and my invitiation_controller.rb
class InvitationsController < ApplicationController
def new
#invitation = Invitation.new
end
def create
#invitation = Invitation.new(params[:invitation])
#invitation.sender = current_user
if #invitation.save
if logged_in?
Mailer.deliver_invitation(#invitation, signup_url(#invitation.token))
flash[:notice] = "Thank you, invitation sent."
redirect_to projects_url
else
flash[:notice] = "Thank you, we will notify when we are ready."
redirect_to root_url
end
else
render :action => 'new'
end
end
end
What else do I need to edit? how do I hook this up to an already existing user signup and login that is working fine?
You should already have a UsersController or something like that for registration purposes, which you currently access through the signup_url named route. Suppose that this route is now something like:
http://localhost:3000/register/code_here
All you have to do now is check for the invitation in the controller action and process it accordingly like so:
def new
invite = Invite.find_by_token(params[:id]
if invite.nil?
redirect_to root_path, :notice => "Sorry, you need an invite to register"
end
#user = User.new(:email => invite.recipient_email)
end
def create
invite = Invite.find_by_token(params[:token]
if invite.nil?
redirect_to root_path, :notice => "Sorry, you need an invite to register"
end
begin
invite.nil.transaction do
invite.nil.destroy!
#user = User.create(params[:user)
end
redirect_to my_dashboard_path, :notice => "Yay!"
rescue ActiveRecord::RecordInvalid => invalid
render :new, :alert => "Validation errors"
end
end
Without the invite code, you will simply redirect to root page. You may want to DRY that check though. When someone uses the invite code, you may want to delete it from the database. I wrapped it up in a transaction, but this is up to you (creating the user may be more important).
If you want to create a page that allows users to create invitations without signing up, then simply don't add authentication to InvitationsController and update this snippet:
def create
#invitation = Invitation.new(params[:invitation])
#invitation.sender = current_user if logged_in?
if #invitation.save
Mailer.deliver_invitation(#invitation, signup_url(#invitation.token))
flash[:notice] = "Thank you, invitation sent."
if logged_in?
redirect_to projects_url
else
redirect_to root_url
end
else
render :action => 'new'
end
end
I'm not sure if I covered all the bases, but I think this should point you in the right direction at least.
I can not see where Mailer.deliver_invitation comes from, do you use a gem? would it help if you would create mailer.rb, do you have any error mgs/ stack trace?
Have a look here there are some guides, 5 Action Mailer Configuration
http://guides.rubyonrails.org/action_mailer_basics.html
Consider using devise for user authentication, https://github.com/plataformatec/devise
It is complex, but well documented and easy to configure to jump start.
I assume you are using Rails 3.1 (works also in earlier versions, just find the right guide to your Rails version, to be sure)

Resources