Skip to content

Consolidate auth callbacks and fix Slack controller issues#71

Open
JuanVqz wants to merge 2 commits intomainfrom
refactor-auth-callbacks
Open

Consolidate auth callbacks and fix Slack controller issues#71
JuanVqz wants to merge 2 commits intomainfrom
refactor-auth-callbacks

Conversation

@JuanVqz
Copy link
Member

@JuanVqz JuanVqz commented Mar 6, 2026

Supersedes #66, #67, #68, #70.

Context

A code review identified four related bugs across the auth and Slack controller layers. Rather than merging four separate fixes, this PR addresses all of them with a more robust design.

Problems

#66 — Double render in ApplicationController
Two separate before_actions (check_session_expiry, check_user_token) both called render without returning. When a session expired, both fired and raised AbstractController::DoubleRenderError.

#67 — Slack auth filter never halted
valid_slack_request? returned a boolean but never called render or head, so invalid/unauthenticated Slack requests silently passed through to the action.

#68 — Double render in Slack::PuzzlesController
send_message called head :unprocessable_entity on Slack API errors, but execution returned to create which then called render json: — another double render. It also incorrectly told Slack the request failed even when the puzzle had already been saved.

#70SessionsController missing skip_before_action
check_user_token was skipped but check_session_expiry was not, so users with an expired session were shown the login page instead of completing the OAuth callback.

Solution

ApplicationController — consolidate into one authenticate! callback:

before_action :authenticate!

def authenticate!
  if session[:expires_at].present? && Time.current > session[:expires_at]
    reset_session
    render "puzzles/login" and return
  end
  session[:expires_at] = 1.hour.from_now
  render "puzzles/login" unless session[:user_token]
end

One callback means one render path — the double-render is impossible by design.

SessionsController — one skip covers everything:

skip_before_action :authenticate!

Slack::ApplicationController — three fixes:

skip_before_action :authenticate!  # was :check_user_token

before_action :verify_slack_request!

def verify_slack_request!
  head :unauthorized unless verify_slack_signature  # now actually halts
end

def send_message(message, channel_id:)
  SlackClient::Client.instance.chat_postMessage(...)
rescue Slack::Web::Api::Errors::SlackError => e
  Rails.logger.error "Failed to send Slack message: #{e.message}"
  Sentry.capture_exception(e)  # log + track, never render
end

Tests

12 tests covering all fixed scenarios:

  • Unauthenticated request renders login
  • Expired session renders login
  • OAuth completes even with expired session
  • Invalid/expired Slack signature → 401
  • Slack notification failure → still returns 200 to Slack

Replaces #66, #67, #68, #70 with a single cohesive fix:

- Merge check_session_expiry + check_user_token into one authenticate!
  callback, eliminating the double-render risk by design
- SessionsController and Slack::ApplicationController now each skip a
  single authenticate! instead of managing multiple callbacks
- Rename valid_slack_request? to verify_slack_request! and make it halt
  with head :unauthorized when signature verification fails
- Replace head :unprocessable_entity in send_message with logging and
  Sentry.capture_exception so notification failures never affect the
  HTTP response back to Slack
@JuanVqz JuanVqz requested review from mateusdeap and torresga March 6, 2026 00:52
@JuanVqz JuanVqz self-assigned this Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant