Ruby on Rails Authentication - ruby-on-rails

I am trying to create a separate view for changing user password i don't how to do that.When I start I realize that it need different methods and may be some validations in model.
I need help how can I do this. I have no idea.What I need to include in controller , model and view.
I am also implementing "Enter old password to create new password".

I would recommend you to follow the RESTful principles.
Create a PasswordsController in your project with the actions edit and update.
Then create the edit.html.erb view with the form for the password change.
The validations in your model depend on your requirements.
Here is an example of this above:
Controller:
class PasswordsController < ApplicationController
before_action :set_user
before_action :check_current_password, only: :update
def edit
end
def update
if #user.update password_params
flash[:success] = 'Password changed'
redirect_to user_path # Your user's profile for example
else
flash[:danger] = 'Error'
render :edit
end
end
private
def password_params
params.require(:user).permit(:password, :password_confirmation)
end
def set_user
#user = current_user # Your current user
end
def check_current_password
unless #user.authenticate(params[:current_password])
raise # I would recommend you to work with exceptions here because you interrupt the process.
# You can make it also a bit more specific if you define an exception class and just catch them.
end
rescue
flash[:danger] = 'Current password incorrect!'
redirect_to password_path(current_user) # Redirect back to the change page
end
end
View:
<!-- HTML skeleton is in application.html.erb -->
<%= form_for #user, url: password_path, method: :patch do |f| %>
<%= text_field_tag :current_password %>
<%= f.text_field :password %>
<%= f.text_field :password_confirmation %>
<%= f.submit 'Change password' %>
<% end %>
Assuming you have installed bcrypt gem and your user model has a field called password_digest, your model should like this.
Model:
class User < ApplicationRecord
has_secure_password
end
This is a very simple implementation of a password change. I haven't tested it but it's just here to give you an idea how it works.
For more, see https://gist.github.com/thebucknerlife/10090014#steps

Related

ActiveModel::ForbiddenAttributesError in UserStepsController#update

I used Wicked gem to create a multistep form. First step is sign up with email name and password, second step would be address for now containing only the street. Here is my address.html.erb
<%= form_for #user, url: wizard_path do |f| %>
<div class="field">
<%= f.label :street %>
<%= f.text_area :street %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I permitted street and other params in the UsersController:
class UsersController < ApplicationController
def index
#users = User.all
end
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
session[:user_id] = #user.id
redirect_to user_steps_path
else
render :new
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :street, :house_number, :city, :zip_code)
end
end
I am getting the error mentioned in the title. And these are the params. It basically gets the street, but somhow assignes id to address?
{"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"ZOkBaqFUdFj47iI8vB0D4PI26ZsgEKasqbzvVM2ry4Z3e+AsYMh0yRSuUoZF5zbJ3SzAkPShI0sjaZOgh0yXRw==",
"user"=>{"street"=>"jef b"},
"commit"=>"Update User",
"id"=>"address"}
What is happening and how to correct it? Here is UserSteprController:
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :address
def show
#user = current_user
render_wizard
end
def update
#user = current_user
#user.attributes = params[:user]
render_wizard #user
end
private
def redirect_to_finish_wizard
new_user_profile_path(current_user.id)
end
end
Second line in the update action is wrong: #user.attributes = params[:user]
Thank you!
The reason you are getting a ActiveModel::ForbiddenAttributesError is that you are passing an unfiltered hash from the params to your model.
#user.attributes = params[:user]
Is pretty much a textbook example of a mass assignment vulnerability which allows a malicious user to assign any attribute they want like for example admin: true. Fortunately Rails has had built in mass-assignment protection since Rails 4 which stopped you from inflicting the vulnerability on your app.
You want to use update or update_attributes instead of the setter and pass it your filtered parameters instead.
#user.update_attributes(user_params)

Getting my Ruby on Rails page to save the user's email without using devise

I am building a one page website where visitors will simply be able to submit their email address. The only goal in the database is to get an email (no name, etc). There is only one page visible at first, which is the homepage. If the user submits an email already in use, it sends the user to an error page. If the email is not in use, it sends the user to a success page.
I have asked a question about this previously, and after a lot of comments and trial and error, it appeared that it worked and then it stopped working. When I do Rails C, there is only one user in the system and that user doesnt have an email...
Here is what my user migration looks like :
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :email
t.timestamps
end
add_index :users, :email, unique: true
end
end
Here is what my user model looks like:
class User < ApplicationRecord
end
Here is what users/new.html.erb looks like:
<%= form_for #user, as: :post, url: users_path do |f| %>
<div class="wrapper">
<%= f.email_field :email , id: "search", class:"search input" %> <br />
<%= f.submit "yep", class: "submit input" %>
</div>
<% end %>
Here is my user controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:email])
if #user.save
redirect_to '/users/success'
else
redirect_to '/users/error'
end
end
def show
end
end
Here are my routes:
Rails.application.routes.draw do
root "users#new"
resources :users
end
When i run the code, it renders the homepage but when i click on submit, it sends me on a page called show.html.erb with http://localhost:3000/users/error on my brownser. No users are being saved in the console.
EDIT:
My model is
class User < ApplicationRecord
validates_uniqueness_of :email
end
It is still not working....
change new.html.erb as
<%= form_with(model: #user, local: true) do |f| %>
<div class="wrapper">
<%= f.email_field :email , id: "search", class:"search input" %> <br />
<%= f.submit "yep", class: "submit input" %>
</div>
your controller will be
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to user_path(#user), notice: "yeh!!!!"
else
redirect_to new_user_path, notice: "email already registered"
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:email)
end
end
add
<p id="notice"><%= notice %></p> to your application.html.erb in layouts
rest as your question
There are a couple things wrong here.
You're so close, but you're misusing the as: attribute of form_for. Perhaps you think that will send as a POST request, but instead that is actually wrapping your form params in an object called "post". I saw this in the comments on another thread.
Remove the as: attribute and the helper will again wrap your params in the user object. While we're at it, you should also be able to remove the url: attribute as well since Rails form helpers are smart enough to infer that this is a new resourceful record and output the create URL as well as the POST action accordingly.
You need your controller to expect a whole "user" object instead of just checking for the email param. ALSO, assuming you're on Rails 4 or higher, you need to permit the email attribute to be mass-assigned on your User object. See the code.
def create
#user = User.new(params.require(:user).permit(:email)) # Not params[:email]
if #user.save
redirect_to '/users/success'
else
redirect_to '/users/error'
end
end
Also be careful about duplicate emails with different cases. The default in Rails is case-sensitive validation which means "JIM#gmail.com" would not trigger a validation error against "jim#gmail.com". You can fix this with.
class User < ApplicationRecord
validates :email, uniqueness: { case_sensitive: false }
end
BONUS!
Nowadays, it's better to move over to form_with (instead of form_for). It's on its way to becoming the new Rails standard and also makes a few of these things easier. The one point you'll want to keep in mind is that with form_with (and general Rails assumptions), forms are remote by default. So if you want to trigger a full page submit/refresh, add local: true to your form_with helper.
<%= form_with model: #user, local: true do |f| %>
<div class="wrapper">
<%= f.email_field :email , id: "search", class:"search input" %> <br />
<%= f.submit "yep", class: "submit input" %>
</div>
<% end %>
As you are using resources in routes so def show is called when route is /users/:id. That's why its calling show.html.erb file.
When you try to validate an email, then in model write the validation for it
class User < ApplicationRecord
validates_uniqueness_of :email
end
Hope this helps.
Try to add validates_uniqueness_of in your model
class User < ApplicationRecord::Base
attr_accessor :email
validates_uniqueness_of :email
end
And
def show
#user = User.find(email: params[:email])
end
And if you wanna check all
def show
#user = User.all
end
Please try this.
I hope that helpful

Twilio - phone number verification on Rails 5

I have done a phone number verification via Twilio, but I can't find a way how to implement a feature that sends pin code again (if user didn't received it) but also does it not more that 3 times (so users couldn't keep sending codes over and over again). Also, my code looks a bit anti-pattern, so feel free to suggest a better implementation.
When Devise User registers itself, I send him to create a Profile that belongs_to User. Profile holds all user info (and phone number). Here is the form:
<%= form_for #profile, remote: true do |f| %>
<%= f.label 'Your name' %><br />
<%= f.text_field :first_name, autofocus: true, class: 'form-control' %>
<%= f.label 'Phone number' %><br />
<%= f.text_field :phone, class: 'form-control' %>
</br>
<div id="hideAfterSubmit">
<%= f.submit 'Save', class: 'btn btn-lg btn-primary btn-block' %>
</div>
<% end %>
<div id="verify-pin">
<h3>Enter your PIN</h3>
<%= form_tag profiles_verify_path, remote: true do |f| %>
<div class="form-group">
<%= text_field_tag :pin %>
</div>
<%= submit_tag "Verify PIN", class: "btn btn-primary" %>
<% end %>
</div>
<div id="status-box" class="alert alert-success">
<p id="status-message">Status: Haven’t done anything yet</p>
</div>
#verify-pin and #status-box are display: none. I unhide them with responding create.js.erb.
Create action:
def create
if user_signed_in? && current_user.profile
redirect_to profile_path(current_user), notice: 'Jūs jau sukūrėte paskyrą'
else
#profile = Profile.new(profile_params)
#phone_number = params[:profile][:phone]
#profile.user_id = current_user.id
SmsTool.generate_pin
SmsTool.send_pin(phone_number: #phone_number)
if #profile.save
respond_to do |format|
format.js
end
else
render :new
end
end
end
So at this point profile been created, saved and pin code generated and sent to phone number that user just added.
SmsTool:
def self.generate_pin
##pin = rand(0000..9999).to_s.rjust(4, "0")
puts "#{##pin}, Generated"
end
def self.send_pin(phone_number:)
#client.messages.create(
from: ENV['TWILIO_PHONE_NUMBER'],
to: "+370#{phone_number}",
body: "Your pin is #{##pin}"
)
end
def self.verify(entered_pin)
puts "#{##pin}, pin #{entered_pin} entered"
if ##pin == entered_pin
Current.user.profile.update(verified: true)
else
return
end
end
And Profiles#verify :
def verify
SmsTool.verify(params[:pin])
#profile = current_user.profile
respond_to do |format|
format.js
end
if #profile.verified
redirect_to root_path, notice: 'Account created'
end
end
So what I dont like is SmsTool - as you see I use class variable - couldn't find another way. Also I created a separate Current module just to access Devise current_user object.. :
module Current
thread_mattr_accessor :user
end
ApplicationController:
around_action :set_current_user
def set_current_user
Current.user = current_user
yield
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.user = nil
end
And as I mentioned above - I can't find a way how to implement a feature that sends pin code again (if user didn't received it).
And please - feel free to suggest elegant implementations.
p.s. this is my longest post yet. Sorry for that, but I think all info was needed to show you.
UPDATE:
So to resend pin was easy, I just added:
<div id="hiddenUnlessWrongPin">
<%= button_to "Re-send pin", action: "send_pin_again" %>
</div>
and action:
def send_pin_again
#phone_number = current_user.profile.phone
SmsTool.generate_pin
SmsTool.send_pin(phone_number: #phone_number)
end
But I still don't know how to stop sending pin if user already sent three of them. Only way I see is to make new row in db with integer value and increment it every time user sends pin. Is it the only way?
A good starting point would be to look at the Devise::Confirmable module which handles email confirmation. What I really like about it is that it models confirmations as a plain old resource.
I would try something similar but with a seperate model as it makes it really easy to add a time based limit.
class User < ApplicationRecord
has_one :profile
has_many :activations, through: :profiles
end
class Profile < ApplicationRecord
belongs_to :user
has_many :activations
end
# columns:
# - pin [int or string]
# - profile_id [int] - foreign_key
# - confirmed_at [datetime]
class Activation < ApplicationRecord
belongs_to :profile
has_one :user, through: :profile
delegate :phone_number, to: :profile
authenticate :resend_limit, if: :new_record?
authenticate :valid_pin, unless: :new_record?
attr_accessor :response_pin
after_initialize :set_random_pin!, if: :new_record?
def set_random_pin!
self.pin = rand(0000..9999).to_s.rjust(4, "0")
end
def resend_limit
if self.profile.activations.where(created_at: (1.day.ago..Time.now)).count >= 3
errors.add(:base, 'You have reached the maximum allow number of reminders!')
end
end
def valid_pin
unless response_pin.present? && response_pin == pin
errors.add(:response_pin, 'Incorrect pin number')
end
end
def send_sms!
// #todo add logic to send sms
end
end
Feel free to come up with a better name. Additionally this allows you to use plain old rails validations to handle the logic.
You can then CRUD it like any other resource:
devise_scope :user do
resources :activations, only: [:new, :create, :edit, :update]
end
class ActivationsController < ApplicationController
before_action :authenticate_user!
before_action :set_profile
before_action :set_activation, only: [:edit, :update]
# Form to resend a pin notification.
# GET /users/activations/new
def new
#activation = #profile.phone_authentication.new
end
# POST /users/activations/new
def create
#activation = #profile.phone_authentication.new
if #activation.save
#activation.send_sms!
redirect_to edit_user_phone_activations_path(#activation)
else
render :new
end
end
# Renders form where user enters the activation code
# GET /users/activations/:id/edit
def edit
end
# confirms the users entered the correct pin number.
# PATCH /users/activations/:id
def update
if #activation.update(update_params)
# cleans up
#profile.activations.where.not(id: #activation.id).destroy_all
redirect_to profile_path(#profile), success: 'Your account was activated'
else
render :edit
end
end
private
def update_params
params.require(:activation)
.permit(:response_pin)
.merge(confirmed_at: Time.now)
end
def set_profile
#profile = current_user.profile
end
def set_activation
#profile.activations.find(params[:id])
end
end
app/views/activations/new.html.erb:
<%= form_for(#activation) do |f| %>
<%= f.submit("Send activation to #{#activation.phone_number}") %>
<% end %>
No activation SMS? <%= link_ to "Resend", new_user_activation_path %>
app/views/activations/edit.html.erb:
<%= form_for(#activation) do |f| %>
<%= f.text_field :response_pin %>
<%= f.submit("Confirm") %>
<% end %>

Is it safe to edit user based on devise current_user helper?

I'm using Devise, Omniauth-twitter and Omniauth-facebook for my rails app authentication and i had to make my own controller for editing user parameters without needing a password for users with providers like facebook and twitter.
And instead of routing the user to his profile by his user id, I used the devise helper current_user to show and edit the current_user parameters
My question is.. is it safe to do that ?
I'm a beginner.. so when something is done that easy i worry about security vulnerabilities. Here's my code.
profile_controller.rb
class ProfileController < ApplicationController
before_action :authenticate_user!
def show
#user = current_user
end
def edit
#profile = current_user
end
def update
#profile = current_user
if #profile.update(profile_params)
redirect_to profile_path(#profile)
else
render 'edit'
end
end
private
def profile_params
params.require(:user).permit(:username,:first_name,:last_name,:gender)
end
end
routes.rb
get'profile' => 'profile#show'
get'profile/edit' => 'profile#edit'
patch'profile/edit' => 'profile#update'
edit.html.erb
<%= form_for #profile, url: {action: "edit"} do |f| %>
<%= f.text_field :username, autofocus: true %>
<%= f.text_field :first_name, autofocus: true %>
<%= f.text_field :last_name, autofocus: true %>
<%= f.text_field :gender, autofocus: true %>
<%= f.submit "Sign up" %>
<% end %>
Well if you are using Devise you could make user of their existing views rather than, you trying to implement them on your own. But, I don't see any security threats with your current approach it's just that it is a waste of time.
Take a look at the devise documentation and check the Configuring Views section,
https://github.com/plataformatec/devise

devise: update user on another controller without password

how can I update my user without the required password, password confirmation and current password?
I'm trying to update the user outside devise controller, my form is working with this helper:
module ContentHelper
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
end
and my form for editing:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, multipart: true }) do |f| %>
<%= devise_error_messages! %>
<%= f.file_field :personal_file, placeholder: "Upload file" %>
<%= f.submit %>
<% end %>
With this its appearing that I can't have my password blank and redirecting to user edit page:
Password is too short (minimum is 8 characters)
Password can't be blank
Current password can't be blank
You can just write up your own controller that updates User as you wish. Something like
class UserController < ApplicationController
before_filter :authenticate_user!
def update
current_user.update(user_params)
end
private
def user_params
params.require(:user).permit(:personal_file)
end
end
would do the trick. You don't need to think of the current_user as some magic devise thing but instead use it as any other model you have.
If you anyway wish to use Devise controller to do this, you should see this answer.

Resources