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
Related
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
When building out a form that handles STI, if I use becomes to transform the object to its parent class, I lose the ability to use nested fields with it.
I have two models
class Login < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :login
accepts_nested_attributes_for :login
end
I also have a few subclasses of User.
class Consumer < User
end
class Admin < User
end
class Agent < User
end
Initially I had problems with the routing, since Rails would assume that I wanted a route specific to the current class rather than the parent class, so I used #user.becomes(User), which is apparently the way to handle that. For the most part it works fine, however this causes #user.login to disappear.
Controller
class Admin::UsersController < AdminController
load_and_authorize_resource
before_filter :authenticate_user!
def index
render 'index'
end
def new
#user = User.new
#user.build_login
render 'new'
end
def create
#user = User.new(user_params)
if #user.save
flash[:notice] = "Account confirmation instructions sent to #{#user.login.email}"
redirect_to new_user_path
else
flash.now[:error] = #user.errors.full_messages.to_sentence
# At this point, I can confirm that #user.login still exists...
render 'new'
end
end
private
def user_params
params.require(:user).permit(
:type,
:dealership_id,
login_attributes: [
:email
])
end
end
Here's the most relevant form view bit
<%= simple_form_for(#user.becomes(User), html: {class: "user-form"}) do |f| %>
<%= f.simple_fields_for :login do |l| %>
<div class="field">
<%= l.label :email %><br />
<%= l.email_field :email %>
</div>
<% end %>
<div class="field">
<%= f.label :type %>
<%= f.select :type, options_for_select(current_user.types_can_create), include_blank: "- Select -", class: "form-control", id: "select_type" %>
</div>
<div class="actions">
<%= f.submit "Register" %>
</div>
<% end %>
The text field for :email doesn't display because #user.login is now nil. Is this expected behavior when using becomes?
Having only used becomes once before, I can only attest to my scant experience -- whenever you use it, it essentially invokes a new instance of the class.
I'm not sure as to the specifics, but the bottom line is that I would surmise that your #user.becomes(User) is overriding #user.build_login...
Returns an instance of the specified klass with the attributes of the current record.
--
In your case, I would set the path explicitly (as you're using User anyway):
<%= simple_form_for #user, url: user_path, method: :post html: {class: "user-form"} do |f| %>
In Rails 5, this can be solved with
<%= form_with scope: :user,
model: #user,
url: #user.id ? user_path(user) : users_path,
local: true do |f| %>
...
...
<% end %>
The previous code instructs the FormBuilder to fill in the fields with the #user attributes, but it also instructs it to submit to the main route (not the inherited ones) and, with scope:, it also instructs it to name the fields using the User class name, not the child class names.
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" %>
i create controller:
class SigninController < ApplicationController
def index
end
def create
#user = User.find_by_email(params[:user][:email])
if #user
....
sign_in(:user, #user)
else
#user = User.new(params...)
#user.save
...
end
end
end
Correctly i call sign_in method?
signin/index.html.erb:
<h2>Sign in</h2>
<%= form_for(User.new, :as => :user, :url => "signin") do |f| %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %><br />
<%= f.password_field :password %></div>
<br>
<div><%= f.submit "Sign in" %></div>
<% end %>
How to call devise sign_in and sign_up methods of devise?
Try this in app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
def after_create_path_for(resource)
sign_in(:user, resource)
end
end
That's just kind of a guess. I don't know if it will work.
You may but in most cases you should not create custom controller to sign in / out user. Devise has sessions_controller which implements this functionality for you. View devise documentation and wiki, it has plenty of documentation. Also you can found out there how to customise views and controller behaviour.
Update
If need to place both forms (login and registrations) on one view you can customise devise view. Form actions may point to different standard controllers. I suggest to use users/registrations/new form for base form and add uses/sessions/new form to it.
My question refers to setting up the view and the controller for updating a user's "profile" by confirming the password from the user before updating the attributes. As you've all probably seen a million times before, the user would go to /users/:id/edit, enter a new email in the text field, enter the current password in the password field and click on the submit button to ultimately update the user's email. If the password entered is incorrect then the edit template is rendered again, otherwise the user record is updated with the new email and redirected to :show (or whatever is appropriate for the app). While in the update action I think it makes sense to stick with using the update_attributes method. However the current password value would end up throwing us off.
What I'm really asking though is if there's anything wrong with my approach. I ended up with including a call to password_field_tag for the :current_password field inside the form_for block in order to call update_attributes with params[:user] without making attr_accessible angry. But then I looked up a couple forms in websites that already do this (hulu and destroyallsoftware for example) and they seem to accept the :current_password value in the user hash (assuming they're built with rails). Looking up twitter's settings page it looks like they retrieve this in a separate hash in param (so params[:current_password] instead of params[:user][:current_password]).
Is it wrong to use password_field_tag within form_for? How are these other sites really doing this? The only thing I can think of is that they're either deleting :current_password from the params hash or assigning each attribute individually.
Here is what I basically ended up with:
# /app/models/user.rb
class User < Activerecord::Base
attr_accessible :email, # ...
# ...
end
# /app/views/users/edit.html.erb
<%= form_for #user do |f| %>
# this is stored in params[:user][:email]
<%= f.label :email, 'Your new email' %>
<%= f.text_field :email, type: :email %>
# this is stored in params[:current_password]
<%= label_tag :current_password, 'Re-enter your password to update your email' %>
<%= password_field_tag :current_password %>
<%= f.submit 'Save changes' %>
<% end %>
# /app/controllers/users_controller.rb
# ...
def update
#user = User.find(params[:id])
if #user.authenticate(params[:current_password])
if #user.update_attributes(params[:user])
sign_in #user
flash[:success] = 'Sweet!'
redirect_to #user
else
render :edit
end
else
flash.now[:error] = 'Incorrect password'
render :edit
end
Otherwise, this is the one other way I thought of:
# /app/views/users/edit.html.erb
<%= form_for #user do |f| %>
# this is stored in params[:user][:email]
<%= f.label :email, 'Your new email' %>
<%= f.text_field :email, type: :email %>
# this is stored in params[:user][:current_password]
<%= f.label :current_password, 'Re-enter your password to update your email' %>
<%= f.password_field :current_password %>
<%= f.submit 'Save changes' %>
<% end %>
# /app/controllers/users_controller.rb
# ...
def update
#user = User.find(params[:id])
if #user.authenticate(params[:user][:current_password])
params[:user].delete(:current_password) # <-- this makes me feel a bit uneasy
if #user.update_attributes(params[:user])
sign_in #user
flash[:success] = 'Sweet!'
redirect_to #user
else
render :edit
end
else
flash.now[:error] = 'Incorrect password'
render :edit
end
Or, should I just do this in the controller?:
def update
#user = User.find(params[:id])
if #user.authenticate(params[:user][:current_password])
#user.email = params[:user][:email]
if #user.save
# ...
Any advice is appreciated.
P.S. - Additionally how would you go about refactoring that update action? I tried out a before_filter to authenticate with :current_password and keep only the #update_attributes part in #update, but it got a bit messy. This post is getting long enough though so maybe I'll post this as a separate question if I can not figure it out by next week.
I've recently done something similar to this, except I used a virtual attribute to handle the current_password. You can then add the :current_password attribute to attr_accessible and keep it happy.