Class: RuboCop::Cop::DevDoc::Auth::CurrentUserBranching
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::DevDoc::Auth::CurrentUserBranching
- Defined in:
- lib/rubocop/cop/dev_doc/auth/current_user_branching.rb
Overview
Forbid branching on authentication state in page-specific code.
Rationale
In a Rails app using Pundit + Devise, current_user is guaranteed
non-nil inside any controller action or view that requires auth —
the policy has already denied anonymous visitors. Branching on
if current_user or if user_signed_in? inside that code is
therefore either dead code (the branch for nil can never fire) or
a signal that the developer was unsure whether the page requires auth.
If the page genuinely serves both anonymous and signed-in visitors, the branching should be explicit and kept in shared/layout code, not sprinkled through action bodies and page views.
❌ Authenticated page branching on auth state (branch is dead code)
# app/views/posts/show.json.jbuilder
if current_user
json.actions [:edit, :delete]
end
✔️ Shared layout — branching here is the right place
# app/views/layouts/_nav_bar.html.erb
<% if user_signed_in? %>
<%= render 'profile_menu' %>
<% else %>
<%= link_to 'Log in', new_user_session_path %>
<% end %>
✔️ Genuinely dual-state page — suppress the cop with a reason comment
def new
# rubocop:disable DevDoc/Auth/CurrentUserBranching
# Reason: contact form is intentionally dual-state; pre-fills for signed-in users.
if current_user
@form.name = current_user.full_name
end
# rubocop:enable DevDoc/Auth/CurrentUserBranching
end
Patterns flagged
if current_user/unless current_user(block or modifier) where the body is non-empty and is not a barereturn(bare returns are the nil-guard pattern covered by LoadResourceCurrentUserGuard).if user_signed_in?/unless user_signed_in?(any form).if current_user&.fooand other safe-nav uses ofcurrent_useras a condition. The&.is itself a confession that the dev expectscurrent_userto be nil sometimes — i.e., it's the same anti-pattern asif current_user, just in disguise: whencurrent_useris nil the csend returns nil → branch is dead for anonymous visitors, exactly what the cop prevents.- Ternaries:
current_user ? a : b,user_signed_in? ? a : b. - Hash/argument values:
authenticated: user_signed_in?.
Allowed paths (Exclude:)
By default the cop is silent in:
app/policies/**/*.rb ← auth-dependent checks belong here
app/helpers/**/*.rb
app/controllers/concerns/**/*.rb
app/views/layouts/**/* ← display branching (nav bars, etc.)
Override via Exclude: in your .rubocop.yml. Note: literal file
paths (e.g. app/controllers/application_controller.rb) in
.rubocop.yml Exclude: lists are flagged by the
DevDoc::Test::Lints::NoFileExcludes lint — they hide the
suppression from readers of that file. If ApplicationController
needs an exception, use an inline # rubocop:disable at the
specific line with a reason.
Before disabling inline, consider the Policy
The Policy exclusion is not accidental — it's the canonical
Rails home for auth-dependent branching. If the flagged line
is an authorization check (return if @record.accessible_by?(current_user),
if current_user.admin?, etc.), the right move is almost
always to push that check into a Pundit policy method, not
disable inline. The controller becomes
return unless policy(@record).access? — same behaviour,
no current_user reference in the controller, and the auth
logic is reusable + testable in isolation.
Even better: declare glib_authorize_resource at the
controller level. glib-web runs the appropriate policy
method before each action automatically, so the per-action
verify_access / authorize @record line is usually not
needed at all — the controller body no longer references
current_user because the auth check has moved out of the
action entirely. See best_practices/backend/en/05_controller.md
item #7 for the canonical pattern.
❌ Auth check in the controller — flagged
def verify_access
return if @support_question.accessible_by?(current_user)
# ... token fallback ...
end
✔️ Same check in the Policy — silently allowed
# app/policies/support_question_policy.rb
def access?
user.present? && record.accessible_by?(user)
end
# in the controller:
def verify_access
return if policy(@support_question).access?
# ... token fallback ...
end
NOTE: The cop does not autocorrect — there is no mechanical fix. The right response depends on developer intent: drop the branch (if auth is required), restructure into a shared layout (if dual-state), or add an inline disable with a reason.
Constant Summary collapse
- MSG =
'Avoid branching on auth state in page code. ' \ 'If this page serves both anonymous and signed-in users, ' \ 'add an inline disable with a reason.'.freeze
- AUTH_METHODS =
%i[current_user user_signed_in?].freeze
Instance Method Summary collapse
- #on_if(node) ⇒ Object
-
#on_send(node) ⇒ Object
value: user_signed_in?— condition passed as a value.
Instance Method Details
#on_if(node) ⇒ Object
148 149 150 151 152 153 |
# File 'lib/rubocop/cop/dev_doc/auth/current_user_branching.rb', line 148 def on_if(node) return unless auth_branch?(node) return if (node) add_offense(if_offense_location(node)) end |
#on_send(node) ⇒ Object
value: user_signed_in? — condition passed as a value.
156 157 158 159 160 161 |
# File 'lib/rubocop/cop/dev_doc/auth/current_user_branching.rb', line 156 def on_send(node) return unless auth_method_call?(node) return unless used_as_value?(node) add_offense(node.loc.selector) end |