Pundit keeps asking me to be logged in - ruby-on-rails

I am building a simple website where people can upload their poems and writing new ones.
I am trying to use Pundit so that:
Everyone can see all the poems/poetries (in index)
Only logged in user can create a poetry
Only the user who create the poetry can updated OR delete it
I followed the official following documentations but I still need to login to perform any action in my controller.
I am not sure what I am doing wrong since I am following the docs and it seems a copy/paste type of work.
My code:
poetries_controller.rb
class PoetriesController < ApplicationController
before_action :set_poetry, only: [:show, :edit, :update, :destroy]
def index
#poetries = policy_scope(Poetry).order("RANDOM()").limit(30)
end
def show
end
def new
#poetry = Poetry.new
authorize #poetry
end
def create
Poetry.create(poetry_params)
authorize #poetry
redirect_to poetries_path
end
def edit
end
def update
#poetry.save
redirect_to poetry_path(#poetry)
end
def destroy
#poetry.destroy
redirect_to poetries_path
end
private
def poetry_params
params.require(:poetry).permit(:title, :author, :body)
end
def set_poetry
#poetry = Poetry.find(params[:id])
authorize #poetry
end
end
application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery with: :exception
before_action :authenticate_user!
after_action :verify_authorized, :except => :index, unless: :devise_controller?
after_action :verify_policy_scoped, :only => :index, unless: :devise_controller?
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = "Non sei autorizzato a eseguire questa azione"
redirect_to(root_path)
end
end
poetry_policy.rb
class PoetryPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
def show?
true # Anyone can view a poetry
end
def create?
true # Anyone can create a poetry
end
def update?
record.user == user # Only poetry creator can update it
end
def destroy?
record.user == user # Only poetry creator can update it
end
end
application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope.all
end
end
end
index.html.erb
<div class="container">
<div class="row">
<% #poetries.each do | poetry| %>
<div class="col-xs-12 col-sm-4">
<% if policy(poetry).show? %>
<%= link_to poetry_path(poetry) do %>
<div class="card">
<div class="card-description">
<h2> <%=poetry.title=%> </h2>
<% a = sanitize(poetry.body.truncate(170), tags: %w(br)) %></p>
<p> <%= a %></p>
<p><i><%=poetry.author=%><i></p>
</div>
</div>
<% end %>
<% end %>
</div>
<% end %>
<!-- </div> -->
</div>
</div>

You call before_action :authenticate_user! in ApplicationController, that's why Devise doesn't allow you see poetries#index. It's not a Pundit problem at all.
Move this callback from ApplicationController to needed controllers, where you really want to check authentication. And restrict it with particular actions.
class PoetriesController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
end

Related

Rails - routing to a parent show page with a child_id

I would like to route to the parent (journal) show page after deleting the child (habit) using the child_id.
In my app, a User can create journals, which then have multiple habits. I would like to be able to delete (and edit) a habit and then return to the journal show page, which displays all of the habits.
Getting the following and similar errors:
ActiveRecord::RecordNotFound in HabitsController#destroy
journal.rb
class Journal < ApplicationRecord
has_many :entries
has_many :habits
belongs_to :user
end
habit.rb
class Habit < ApplicationRecord
belongs_to :journal
has_many :completed_dates
end
show.html.erb
<h3>Habits</h3>
<% #journal.habits.each do |habit| %>
<div iv class="habit-list">
<div class="habit-name"><%= habit.name %></div>
<div class="habit-status">
<%= simple_form_for [#journal, #habit] do |f| %>
<%= f.input :status, label: false, :inline_label => true %>
<% end %>
</div>
<div>
<%= link_to habit_path(habit), method: :delete do %>
<i class="fas fa-trash btn-edit"></i>
<% end %>
</div>
</div>
<% end %>
habits_controller.rb
class HabitsController < ApplicationController
before_action :set_journal, only: [:new, :create, :edit, :update, :destroy]
before_action :set_habit, only: [:show, :edit, :update, :destroy]
def index
#habits = Habit.all.sort_by &:name
end
def new
#habit = Habit.new
end
def show
end
def create
#habit = #journal.habits.new(habit_params)
if #habit.save
redirect_to journal_path(#journal)
else
render :new
end
end
def edit
end
def update
if #journal.habits.update(habit_params)
redirect_to journals_path(#journal)
else
render :edit
end
end
def destroy
#habit.destroy
redirect_to journal_path(#habit, #journal)
end
private
def habit_params
params.require(:habit).permit(:name, :status, :user_id, :journal_id)
end
def set_journal
#journal = Journal.find(params[:journal_id])
end
def set_habit
#habit = Habit.find(params[:id])
end
end
Your trying to create URL to a deleted resource (#habit).
Change
def destroy
#habit.destroy
redirect_to journal_path(#habit, #journal)
end
to
def destroy
#habit.destroy
redirect_to journal_path(#journal)
end

Can't seem to login

I've been spending a few hours trying to understand sessions and I can't seem to get logged in. I hope someone can tell me what I'm doing wrong.
Application Controller
protect_from_forgery with: :exception
helper_method :current_user
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def authorize
redirect_to '/' unless current_user
end
Session Controller
class SessionsController < ApplicationController
def new
end
def create
#user = User.where(email: params[:email]).first
if #user && #user.authenticate(params[:session][:password])
session[:user_id] = user.id
redirect_to '/'
else
redirect_to '/sessions/new'
end
end
def destroy
session[:user_id] = nil
redirect_to '/'
end
end
User Controller
class UsersController < ApplicationController
before_action :find_user, only: [:show, :edit, :update, :destroy]
def index
#user = User.all
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save!
redirect_to '/'
else
render 'new'
end
end
def show
end
def edit
end
def update
if #user.update
redirect_to '/'
else
render 'edit'
end
end
def destroy
#user.destroy
redirect_to '/'
end
private
def find_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:email, :username, :password, :about_me)
end
end
Sessions/new
<%= form_tag('/sessions', method: "POST") do %>
Email: <%= email_field_tag :email %>
Password: <%= password_field_tag :password %>
<%= submit_tag 'Submit' %>
<% end %>
Routes
root 'users#index'
resources :sessions, only: [:new, :create, :destroy]
resources :users
If there are additional code that would be required please let me know. Also if you know a better convention in order to add login for my user, I'm open to learn different ways. Thank you for everyone help in understanding this problem.

Rails: How do I resolve: Pundit::NotDefinedError in PostsController#show?

whenever I run the below program and try to view my posts (in my show view) as any user, I am introduced to this error page:
Pundit::NotDefinedError in PostsController#show
unable to find policy of nil
Within that error page:
def show
#post = Post.find(params[:id])
authorize #posts # <- The error highlights this line
end
I'm not sure how to get around this dilemma as I'm just learning about Pundit Policy rules and am new to Rails and Ruby. Any help would be much appreciated. Below are my policy pages and related pages:
User.rb Model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_many :posts
def admin?
role == 'admin'
end
def moderator?
role == 'moderator'
end
def member?
role == 'member'
end
def guest?
role == 'guest'
end
end
Application Controller
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
include Pundit
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
rescue_from Pundit::NotAuthorizedError do |exception|
redirect_to root_url, alert: exception.message
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :name
end
end
Posts Controller
class PostsController < ApplicationController
# before_action :flash_attack
# protected
# def flash_attack
# flash[:notice] = "Create/Edit/Comment on a post!"
# end
def index
#posts = Post.all
authorize #posts
end
def show
#post = Post.find(params[:id])
authorize #posts
end
def new
#post = Post.new
authorize #post
end
def create
#post = current_user.posts.build(params.require(:post).permit(:title, :body))
authorize #post
if #post.save
flash[:notice] = "Post was saved."
redirect_to #post
else
flash[:error] = "There was an error saving the post. Please try again."
render :new
end
end
def edit
#post = Post.find(params[:id])
authorize #post
end
def update
#post = Post.find(params[:id])
authorize #post
if #post.update_attributes(params.require(:post).permit(:title, :body))
flash[:notice] = "Post was updated."
redirect_to #post
else
flash[:error] = "There was an error saving the post. Please try again."
render :edit
end
end
end
Application Policy
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
user.present?
end
def new?
create?
end
def update?
user.present? && (record.user == user || user.admin?)
end
def edit?
update?
end
def destroy?
update?
end
def scope
record.class
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
Posts Policy
class PostPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
scope.all
else
scope.where(:published => true)
end
end
def index?
true
end
def show?
true
end
def update?
user.admin? or not post.published?
end
end
end
Index View
<h1>All Posts</h1>
<% if policy(Post.new).create? %>
<%= link_to "New Post", new_post_path, class: 'btn btn-success' %>
<% end %>
<% #posts.each do |post| %>
<div class="media">
<div class="media-body">
<h4 class="media-heading">
<%= link_to post.title, post %>
</h4>
<small>
submitted <%= time_ago_in_words(post.created_at) %> ago by <%= post.user.name unless post.user.nil? %><br>
<%= post.comments.count %> Comments
</small>
</div>
</div>
<% end %>
Show View
<h1> <%= #post.title %> </h1>
<% if policy(#post).edit? %>
<%= link_to "Edit", edit_post_path(#post), class: 'btn btn-success' %>
<% end %>
<p> <%= #post.body %> </p>
Thanks in advance everyone. Let me know if any more information would be great.
#posts is nil in show action, you should use #post as such:
authorize #post
I had this issue when working on a Rails 6 API only application with the Pundit gem.
I was running into the error below when I test my Pundit authorization for my controller actions:
Pundit::NotDefinedError - unable to find policy of nil
Here's how I solved:
The instance variables called by the authorize method in your controller must correspond to the instance variable of the controller action being called.
So for the index action it should be #posts:
authorize #posts
For the show action it should be #post:
authorize #post
and for the create action it should be #post
authorize #post
and so on.
That's all.
I hope this helps

Authenticate user and admin separately

class ApplicationController < ActionController::Base
protect_from_forgery
skip_before_filter :authenticate_user! , :only => ["welcome#index"]
# before_filter :authenticate_user! :except => ["welocme#index"]
def after_sign_in_path_for(user)
# user_dashboard_index_path
user_dashboard_index_path
end
def after_sign_out_path_for(user)
welcome_index_path
end
after_filter :authenticate_admin!
def after_sign_in_path_for(admin)
admin_dashboard_index_path
end
def after_sign_out_path_for(admin)
welcome_index_path
end
end
Admin should not access the users dashboard and similarly user should not access the admin dashboard.
How can I achieve this?
i have done in my project:
protect_from_forgery with: :exception
def after_sign_in_path_for(resource)
if user_signed_in?
user_dashboard_index_path
elsif admin_signed_in?
admin_dashboard_index_path
else
xyz_path
end
end
Same for sign-out:
def after_sign_out_path_for(resource)
if user_signed_in?
welcome_index_path
elsif admin_signed_in?
welcome_index_path
else
xyz_path
end
end
for authentication:
in (welcome/index)
<% if user_signed_in? %>
contant_of user
<% else %>
you are not authenticated #admin can not authenticate this page
<% end %>
Hope it would be helpfull

Rails DRY problem: Need same code in controller and view

I have the following login check in my page:
class LoungeController < ApplicationController
before_filter :confirm_logged_in
def index
end
end
while confirm_logged_in defined here:
class ApplicationController < ActionController::Base
protect_from_forgery
protected
def confirm_logged_in
return true if current_user
redirect_to(:controller => 'access', :action => 'login')
return false # halts the before_filter
end
def current_user
return false unless session[:user_id]
user = User.find(session[:user_id])
return false unless user
(user.display_name == session[:user_display_name]) ? user : nil
end
end
Now, I want to use confirm_logged_in also in app/views/layouts/application.html.erb:
<% if confirm_logged_in %>
<div id="logged_in_as">You are logged in as <%= session[:user_display_name] %></div>
<div id="logout"><%= link_to("Logout", {:controller => "access", :action => "logout"}, :id => "logout_link") %></div>
<% end %>
How would you suggest to solve this problem ? Where should I define confirm_logged_in ?
You can use helper_method
controller.rb
helper_method :confirm_logged_in, :current_user
protected
def confirm_logged_in
# code...
end
def current_user
# code...
end

Resources