Twilio SMS Forgotten Password Rails - ruby-on-rails

When a user clicks on "Forgot My Password" on the login screen, they are redirected to a route '/password-reset'. Right now, I'm trying to understand how to right the form for entering your email to receive and SMS from Twilio with a code.
<div class="form-group", style="width:50%;">
<%= form_for #user, url: password_patch_path(current_user) do |f| %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control" %>
</div>
<%= f.submit "Get Confirmation Code", class: "btn btn-default" %>
<% end %>
</div>
The issue I'm running into is that #user is nil, and I'm uncertain if the url is correct at the beginning of the form for. It makes sense to me that #user is nil because no one is logged in, so I'm not sure what that should be.
My routes are
get '/password-reset', :to => 'passwords#edit', as: :password_reset
post '/password-reset', :to => 'passwords#reset', as: :password_edit
patch '/password-confirmation', :to => 'passwords#update', as: :password_patch
and my passwords controller looks like
class PasswordsController < ApplicationController
before_action :authenticated?, only: [:edit, :update]
def reset
ConfirmationSender.send_confirmation_to(current_user)
redirect_to new_confirmation_path
end
def edit
#user = current_user
end
def update
if passwords_not_empty? && passwords_equal?
current_user.update(password_params)
redirect_to users_dashboard_path(current_user.username), success: "Password Updated"
session[:authenticated] = false
else
redirect_to password_edit_path(current_user.username), warning: "Error, please try again."
end
end
private
def password_params
params.require(:user).permit(:password, :password_confirmation)
end
def passwords_not_empty?
params[:user][:password].length > 0 && params[:user][:password_confirmation].length > 0
end
def passwords_equal?
params[:user][:password] == params[:user][:password_confirmation]
end
def authenticated?
render :file => "#{Rails.root}/public/404.html", :status => 404 unless session[:authenticated]
end
end

You are right that there will be no current_user if a user forgot his/her password. I would redesign as follows:
PasswordsContoller
class PasswordsController < ApplicationController
before_action :authenticated?, only: [:update]
def reset
#user = User.find_by(email: params[:user][:email])
if #user.present?
ConfirmationSender.send_confirmation_to(#user)
redirect_to new_confirmation_path
else
redirect_to password_reset_path, warning: "Email not found."
end
end
def edit
#user = User.new
end
...
end
Form
<div class="form-group", style="width:50%;">
<%= form_for #user, url: password_edit_path do |f| %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control" %>
</div>
<%= f.submit "Get Confirmation Code", class: "btn btn-default" %>
<% end %>
</div>
The new edit method seeds the form with a blank user. The new reset method looks up the user by email and sends the token if the user is found. If not, it displays an email not found flash message and redirects back to the forgotten password form.
This also makes the form use the correct path for requesting a password confirmation.

Related

Not able to login into session (Rails)

I am having issues trying to login into my Rails app. I attempt to login and it redirects my back to my login page. I checked what password I was providing and it's on point. It does not give me an error at all in the browser. I checked my console and I get a 200 code which means its found the page.
Here are my controller actions/routes
get 'login', to: 'sessions#new', as: 'login'
post 'login', to: 'sessions#create'
Here is my Sessions Controller
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(username: params[:username])
if user && user.authenticate(params[:password])
# log_in user
sessions[:user_id] = user.id
redirect_to user_path(user), notice: 'Logged in!'
else
flash.now[:danger] = 'Invalid email/password combination'
render :new
end
end
def destroy
session.delete(:user_id)
redirect_to '/', notice: 'Logged out!'
end
end
and here is my login form.
<h1>Login</h1>
<%= form_tag '/login' do %>
<div>
<%= label_tag :username %><br>
<%= text_field_tag :username %>
</div>
<div>
<%= label_tag :password %><br>
<%= password_field_tag :password %>
</div>
<div>
<%= submit_tag "Log In" %>
</div>
<% end %>
Is any one able to spot why its redirecting back to my login page?
your example has
sessions[:user_id] = user.id
shouldn't it be
session[:user_id] = user.id
session should be singular

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 %>

When resetting password, update function stops working in rails

Here is my code:
password_resets_controller.rb
class PasswordResetsController < ApplicationController
before_action :get_user, only: [:edit, :update]
before_action :valid_user, only: [:edit, :update]
before_action :check_expiration, only: [:edit, :update]
def new
end
def create
# #user = User.find_by(email: params[:password_resets] [:email].downcase)
#user = User.find_by(email: params[:password_reset][:email].downcase)
if #user
#user.create_reset_digest
#user.send_password_reset_email
flash[:info] = "Email sent with password reset instructions"
redirect_to root_url
else
flash.now[:danger] = "Email address not found"
render 'new'
end
end
def edit
end
def update
if params[:member][:password].empty?
#user.errors.add(:password, "Can't be empty")
render 'edit'
elsif #user.update_attributes(user_params)
# log_in #user
flash[:success] = "Password has been reset."
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:member).permit(:password, :password_confirmation)
end
def get_user
#user = User.find_by(email: params[:email])
end
# Confirms a valid user.
def valid_user
unless #user
redirect_to root_url
end
end
# Check expiration of reset token.
def check_expiration
if #user.password_reset_expired?
flash[:danger] = "Password reset has expired."
redirect_to new_password_reset_url
end
end
end
Thr situation is after I enter the path of password reset token, I put the new password and password_confirmation. Then I click update. However, this error page shows. I really don't know how to figure this out.
Here is my edit file:
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#user, url: password_reset_path(params[:id])) do |f| %>
<%= render 'shared/error_messages' %>
<%= hidden_field_tag :email, #user.email %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Update password", class: "btn btn-primary" %>
<% end %>
</div>
Here is the log:
Here is my User table:
Here is the log after change password_reset_path(params[:id]) to password_reset_path(#user)
The problem is because your form sends member key instead of user, as your logs show:
Parameters: {
"utf8"=>"✓",
"authenticity_token"=>"...",
"email"=>"naomi.wuuu#gamil.com",
"member"=>{
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"
},
...
}
So, since there is no user key, params[:user] returns nil and, since it can't respond to [], results in the error:
undefined method `[]' for nil:NilClass
Looking at your controller and view I can't see why member is being used instead of user, but you can force your form to send user key using as: "user", like this:
<%= form_for(#user, url: password_reset_path(params[:id]), as: "user") do |f| %>
With this in place, your parameters will be now:
Parameters: {
"utf8"=>"✓",
"authenticity_token"=>"...",
"email"=>"naomi.wuuu#gamil.com",
"user"=>{
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"
},
...
}

How do I allow logging in from two different locations in my app?

My app allows a user to log in from two different places, the header and the new session page. The new session page logs a user in and redirects them to the correct page, but the home pages just reloads the home page without redirecting the user or logging them in.
This is my sessionscontroller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
session[:user_id] = user.id
redirect_back_or feed_user_path(user)
else
session[:user_id] = nil
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
end
new.html.erb
<%= form_for(:session, url: sessions_path) do |f| %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.submit "Log in", class: "btn btn-large btn-info" %>
<% end %>
Code in my _header.html.erb
<%= form_for :session, :url => {:controller => "sessions", :action => "new"} do |f| %>
<div class="home-login form-group">
<%= f.label :email %>
<%= f.text_field :email, :placeholder => "Email" %>
</div>
<div class="home-login form-group">
<%= f.label :password %>
<%= f.password_field :password, :placeholder => "Password" %>
</div>
<div class="form-group home-login">
<%= f.submit "Log in", class: "btn-info" %>
</div>
<% end %>
This is what shows up in the terminal
--- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: AegIbI8c1TIddIBPVWTt/B2CBoCAgbJxL+NWDe782Cc=
session: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
email: ** # **
password: ****
commit: Log in
controller: pages
action: home
EDIT****
This is my application controller
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
include SessionsHelper
end
Home is a static page in my pages controller
class PagesController < ApplicationController
def home
end
end
EDIT****
This is my SessionsHelper
module SessionsHelper
def signed_in?
!!session[:user_id]
end
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def current_user=(user) # set current user
#current_user = user # session[:user_id] = user.id
end
def current_user?(user) # get current user
user == current_user
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
end
EDIT****
Routes
sessions POST /sessions(.:format) sessions#create
new_session GET /sessions/new(.:format) sessions#new
session DELETE /sessions/:id(.:format) sessions#destroy
try changing your form_for tag to direct it to the create action with a method of post. something like this:
<%= form_for :session, url: session_path, method: 'post', action: 'create' do |f| %>
There's no difference with where do you put your login form unless you refer to any controller-specific data there. I don't see that, so you should be good to go with just copying the exact same form_for call that actually produces working results. At least I don't see anything that prevents doing this.
And the problem is the action. new. Wrong. It's create. "New" is a display page for the form (and uses GET because of that), the actual form's action (data send path) is "create" (that uses POST). HTTP method is picked from the routes, execute rake routes to verify that they are correct.
A form is not bound to a page it's on. Instead, it has its own path, that should accept input data from the form.
I figured it out. The ERB form was wrapped in an HTML form. That was causing the problem.

Creating form_for for atributes User image and description

I have a rails application with devise, and I added to users a profile image, and a description. What I want to do is to create a page (DIFFERENT of the default registration/edit) where the users, after logged in, can set only this two atributes(image and description).
<%= form_for(:user, html: { method: :put, :multipart => true })) do |f| %>
<div class="form-group">
<%= f.label :Profile_Image %>
<%= f.file_field :image, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :Descrição %>
<%= f.text_area :description, class: "form-control", rows: "10" %>
</div>
<% end %>
I have already tried two different controllers and none of them worked:
def edit
end
def edit
#user = User.find(params[:id])
end
My config -> routes are:
get "edit" => "pages#edit"
post "edit" => "pages#edit"
But when i click submit it does nothing! I am new at rails and I am trying to figure this out for hours... How can I create a page to update only the image and the description? Thanks
You need an update method in your controller. Your edit method allows the form to render, but you need something like this:
def update
current_user.update(user_params)
end
Then you would have another method in your controller called user_params, which would look something like this. I was taught to put it under a private heading.
private
def user_params
params.require(:user).permit(:profile_image, :description)
end
I believe there is a shortcut way of including your params with your update method, but this will do.
Use registration controller devise and you should customize it.
You should have one method with the same name in one controller, you have two edit method. Change one edit method to update method ( reference : Allow users to edit their account )
pages_controller.rb
class PagesController < Devise::RegistrationsController
def edit
#user = current_user
end
def update
#user = current_user
successfully_updated = if needs_password?(#user, params)
#user.update_with_password(devise_parameter_sanitizer.for(:account_update))
else
params[:user].delete(:current_password)
#user.update_with_password(devise_parameter_sanitizer.for(:account_update))
end
if successfully_updated
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to after_update_path_for(#user)
else
render "edit"
end
end
private
def needs_password?(user, params)
user.email != params[:user][:email] || params[:user][:password].present?
end
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_filter :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.for(:account_update) do |u|
u.permit(:description, :image, :email, :password, :password_confirmation)
end
end
end
You have wrong http verb (post), you need PUT/PATCH not POST
devise_scope :user do
get "edit" => "pages#edit"
put "update" => "pages#update"
end
On your view looks like (example and not tested)
<%= form_for(#user, :url => update_pages_path, :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :image, "Profile Image" %>
<%= f.file_field :image, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label description, "Descrição" %>
<%= f.text_area :description, class: "form-control", rows: "10" %>
</div>
<% end %>
<%= f.submit "Save Image" %>

Resources