i have a rails app with sorcery
everything work .
the problem is when edit a user like :
http://localhost:3000/users/1/edit
its work fine , but when i change the user id to 2 or 3 ..
i can update all users data
how can i restrict the edit page only if the current user is the one that logged in
here is my controller :
skip_before_action :require_login, only: [:new, :create, :show]
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
auto_login(#user)
flash[:info] = "Welcome."
redirect_to root_url
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
you can also do something like this
before_action :edit_rights?, only: [:update, :edit]
private
def edit_rights?
#user = User.find(params[:id])
redirect_to(root_path) unless current_user == #user
end
you won't need #user = User.find(params[:id]) in both update and edit actions then
There are (at least) two ways to do that. First and straightforward is detailed in another answer, fine-tune your controller.
A less obvious way is to create a singular resource and its own controller. In routes that could look like:
resource :profile, only: [:show, :edit, :update]
# generates:
# /profile (GET, PATCH, PUT)
# /profile/edit (GET)
Then create a controller that is responible solely for user's own profile and operates only on current_user.
Yes, it's okay for one model to have multiple controllers, if your model should behave really differently in different parts of your app.
Why would you do that?
User's own profile could show much more information than is available publicly, you can lay it out in a separate view
No "access denied" errors, as the resource is auto-selected via current_user, all you need is ensure the user is logged in in the entire controller.
Related
In my app, when a user logins he/she is redirected to the users profile page. Say he/she is redirected to http://localhost:3000/users/1
If he/she replaces 1 with any other number I want them to redirect to there
current profile no matter if users exits in the database or not
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
log_in user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
#current_user = nil
reset_session
redirect_to root_path
end
end
User Controller:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:new, :show, :edit, :update]
before_action :correct_user, only: [:new, :show, :edit, :update]
def index
#users = User.all
end
def new
#user = User.new
end
def create
#user = User.new(set_params)
if #user.save
redirect_to new_sessions_path
else
render 'new'
end
end
def show
#user = User.find(params[:id])
#posts = #user.posts
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(update_params)
redirect_to #user
else
render 'edit'
end
end
private
def set_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def update_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
Currenty if user type in search bar localhost:3000/users/5 and user with id 5 does not exists in database it shows error
ActiveRecord::RecordNotFound in UsersController#show
Couldn't find User with 'id'=3
but I want to simply redirect to currently logged in users profile page.
If users type in search bar localhost:3000/users/3 and user with this id exists in db , currenty it show an error that firefox is not able to process this request but i want it redirect to its default page i.e,,user's profile page.
Create another controller call it UserController and don't depend on id. Instead figure out the current user from the session and display that user. So the show method for this controller would look like this:
def show
#user = User.find(session["user_id]")
#posts = #user.posts
end
Also, you might want to protect your UsersController by validating if the current user has access to view / update the user being queried for.
Just change your UsersController#correct_user to catch ActiveRecord NotFound exception:
class UsersController < ApplicationController
...
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
rescue ActiveRecord::RecordNotFound
redirect_to(root_url)
end
end
I would use "where" and ".take" in Users show method. The find method brakes the code when it does not find anything
def show
#user = User.where("id" => params[:id]).take
if #user.present?
#posts = #user.posts
else
redirect_to(root_url)
end
end
Or you can redirect instead of root_url to a more friendly error view that shows User not found
When my user signs up, it originally gets redirected to its blank profile page.
However, I need the user to be redirected to a additional info page in order to retrieve more information
My users controller looks like this
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def additional_info
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
UserMailer.welcome_email(#user).deliver
sign_in #user
redirect_to users: 'additional_info'
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
end
As you can see, additional_info is the other page I'm trying to redirect to. I'm aware that I create 2 users objects, but I'm not sure if its correct. Frankly speaking I'm kind of lost.
my routes for the pages is this:
resources :users
resources :sessions, only: [:new, :create, :destroy]
match '/additionalinfo',to: 'users#additional_info', via: 'get'
Thanks for the help!
in yours routes
match '/additionalinfo',to: 'users#additional_info', via: 'get', as: :additional_info
in controller
redirect_to additional_info_path
Alternatively, you can change the redirect line to:
redirect_to action: 'additional_info'
Assuming you have the action defined in your routes:
get :additional_info, to: 'users#additional_info' # use get rather than match
So I am having some trouble with my rails app and I think I went a little out of my own depth. I am creating a simple alumni application and I want users to be able to join organizations. For some reason in my new join page I get the error "Couldn't find User without an ID". I want to know why the ID isn't passing in, which would imply signed_in? = false. I don't know why everything worked find when I created other additions to my users controller but here it refuses to take on the logged in user id. I feel like I am missing something simple, let me know if updates are necessary!
Here is the relevant information in my Users controller:
class UsersController < ApplicationController
before_action :signed_in_user, only: [:edit, :update, :index, :show, :join]
before_action :correct_user, only: [:edit, :update, :join]
before_action :admin_user, only: :destroy
def join
end
def show
#user = User.find(params[:id])
#organization = #user.organization
end
def create
#user = User.new(user_params)
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
...
private
def user_params
params.require(:user).permit(:name,:email, :password, :password_confirmation,:organization_id)
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
I included the def create method because I tried editing it to redirect users to the join page right after login but then I came across this error and I thought that was the problem so i switched it back. I guess it wasnt.... NOTE: I am basing a lot of this app off of the Hartl tutorial if that is helpful.
You should have a Memberships controller and model with a belongs_to :user (has_many :memberships for User & Organization), instead of defining a join method in the Users controller. The controller should be responsible for adding/deleting organization user-memberships. From that controller you fetch the user info by #user = User.find(:id) and don't forget to properly set the route file for nested resources.
resources :users do
resources :memberships
end
Also note that your join method doesn't create any instance variables for the view (#user). It looks like it properly goes through the signed_in_user action but nothing is instantiated in the join method.
Change line 3 to:
before_action :correct_user, only: [:edit, :update]
If the purpose of "join" is to create a user, then there should not be a user yet. However if your purpose for "join" is to get the current user you should add this to your join method:
#user = current_user
What about if you changed it to this:
def show
#users = User.all
#user = #users.find(params[:id])
#organization = #user.organization
end
I'm working through the Rails Tutorial, by Michael Hartl, and a question popped up, as I was creating an admin user.
I followed the instructions, and created an admin_user, who has access to the :destroy method. It also isn't attr_accessible, so people can't simply put a put request via the browser and change themeselves to admin.
But, I have a two-part question--
1) How would I make a user admin?
I though I would need to write something like this in the console
rails console
user = User.find(params[:101])
user.toggle!(:admin)
When I try that, I get
Undefined Local Variable or Method 'Params' for main:Object
2) Assuming that it is possible to make myself an admin, what's stopping other people from making themselves admin using a command line as well?
Here's a copy of the users_controller, I think Michael addressed this in the tutorial, and I followed his instructions, but I don't get how the below code prevents someone from going to the command line and making themselves admin
class UsersController < ApplicationController
before_filter :signed_in_user,
only: [:edit, :update, :index, :destroy]
before_filter :correct_user, only: [:edit, :update]
before_filter :admin_user, only: :destroy
def destroy
User.find(params[:id]).destroy
flash[:success] = "User destroyed."
redirect_to users_url
end
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
end
def new
unless signed_in?
#user = User.new
else
redirect_to #current_user
end
end
def create
unless signed_in?
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
else
redirect_to #current_user
end
end
def edit
end
def update
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated"
sign_in #user
redirect_to #user
else
render 'edit'
end
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
I would really appreciate your help clearing things up!
User.find(params[:101]) is appropriate only for http browser requests. If you visit http://www.example.com?101=test, then you can use params[:101] with value "test". But in console you can't use params unless you declare it. In your case the wright way will be User.find(101), if 101 is user id.
Other people can't make them admin because you didn't add attr_accessible for admin field. How can they do it via command shell? They have no access to command line. If they are it's a serious security breach.
I have a simple user model with an edit page. Currently you can change your email and your password (with a password confirmation) but I don't currently require you to type your password again before changing any of that information.
I have a before filter that requires you to be logged in as well as a before filter to ensure you can only edit your own profile. However, in the case of public computers, I would like to re-authenticate a user by making them type their password.
I am using sorcery to back my authentication. How would I go about doing this. I don't see any methods for checking the password after being logged in. Below is my current users_controller
class UsersController < ApplicationController
before_filter :require_login, :only => [ :edit, :update ]
before_filter :correct_user, :only => [ :edit, :update ]
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
redirect_to root_url, :notice => "Signed up!"
login_user(#user)
else
render :new
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated."
redirect_to root_url
else
render 'edit'
end
end
private
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user == #user
end
end
I've opened an issue on Sorcery's github page. It sounds like the creator will be opening a method called validate_credentials() in the future.
Source: https://github.com/NoamB/sorcery/issues/34#issuecomment-2108845