ruby 135 lines · 3 tabs

Guard Rails for Dangerous Admin Actions

Shared by codesnips Jan 2026
3 tabs
module DangerousAction
  extend ActiveSupport::Concern

  FRESH_WINDOW = 10.minutes

  class ConfirmationMismatch < StandardError; end
  class StaleAuthentication < StandardError; end

  included do
    rescue_from ConfirmationMismatch, with: :handle_confirmation_mismatch
    rescue_from StaleAuthentication, with: :handle_stale_authentication
  end

  private

  def perform_dangerous_action(action:, target:, expected_phrase:)
    require_fresh_authentication!
    require_confirmation!(expected_phrase)

    event = AuditEvent.record!(
      actor: current_admin,
      action: action,
      target: target,
      metadata: { ip: request.remote_ip, phrase: expected_phrase }
    )

    result = yield
    event.mark_succeeded!
    result
  rescue ConfirmationMismatch, StaleAuthentication
    raise
  rescue StandardError => e
    event&.mark_failed!(e.message)
    raise
  end

  def require_confirmation!(expected_phrase)
    submitted = params[:confirmation].to_s.strip.downcase
    expected = expected_phrase.to_s.strip.downcase
    raise ConfirmationMismatch if submitted.blank? || submitted != expected
  end

  def require_fresh_authentication!
    last = session[:reauthenticated_at]
    raise StaleAuthentication if last.blank?
    raise StaleAuthentication if Time.zone.parse(last) < FRESH_WINDOW.ago
  end

  def handle_confirmation_mismatch
    redirect_back fallback_location: root_path,
                  alert: "The confirmation phrase did not match. Nothing was changed."
  end

  def handle_stale_authentication
    redirect_to reauthenticate_path(return_to: request.fullpath),
                alert: "Please confirm your password to continue."
  end
end
3 files · ruby Explain with highlit

Destructive admin operations — deleting a customer, purging PII, force-refunding an order — are the kind of action that should never happen by accident or by a single misclick. This snippet shows a small, reusable pattern in Rails that forces every dangerous action through the same gate: an explicit typed confirmation phrase, a re-authentication check, and a durable audit record, all before the operation runs.

The DangerousAction concern centralizes the guard so controllers don't each reinvent it. require_confirmation! compares the submitted confirmation param against an expected_phrase (case- and whitespace-normalized) and raises ConfirmationMismatch when it doesn't match; require_fresh_authentication! enforces that the admin re-entered their password within FRESH_WINDOW, which mitigates session-hijack and unattended-terminal risk. The perform_dangerous_action wrapper ties it together: it verifies both guards, records an AuditEvent in a transaction, yields the actual work, and marks the event succeeded or failed. Because the audit row is written before the block runs and updated after, a crash mid-operation still leaves a pending/failed trail rather than silence.

The AuditEvent model is deliberately append-only: readonly? returns true once persisted so existing rows can't be mutated or destroyed through ActiveRecord, and record! captures who did what, against which target, with a JSON metadata blob for context. This is the tamper-resistant part of the pattern — an audit log that can be edited is not really an audit log.

In Admin::CustomersController, the destroy action reads like ordinary Rails code, but the real teardown is nested inside perform_dangerous_action with the target and the expected phrase (the customer's own email). The rescue_from handlers translate ConfirmationMismatch and StaleAuthentication into user-facing flashes rather than 500s.

The trade-off is friction: admins must retype a phrase and their password for high-blast-radius actions, which is intentional for irreversible operations but would be overkill for routine edits. Reserve this gate for actions that are hard or impossible to undo. A subtle pitfall is choosing a good expected_phrase — using a unique identifier like the record's email prevents muscle-memory confirmations from firing against the wrong record.


Related snips

Share this code

Here's the card — post it anywhere.

Guard Rails for Dangerous Admin Actions — share card
Link copied