about summary refs log tree commit diff
path: root/app/controllers/concerns/challengable_concern.rb
blob: 2995a25e096296218adf23d244cf09d265119371 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# frozen_string_literal: true

# This concern is inspired by "sudo mode" on GitHub. It
# is a way to re-authenticate a user before allowing them
# to see or perform an action.
#
# Add `before_action :require_challenge!` to actions you
# want to protect.
#
# The user will be shown a page to enter the challenge (which
# is either the password, or just the username when no
# password exists). Upon passing, there is a grace period
# during which no challenge will be asked from the user.
#
# Accessing challenge-protected resources during the grace
# period will refresh the grace period.
module ChallengableConcern
  extend ActiveSupport::Concern

  CHALLENGE_TIMEOUT = 1.hour.freeze

  def require_challenge!
    return if skip_challenge?

    if challenge_passed_recently?
      session[:challenge_passed_at] = Time.now.utc
      return
    end

    @challenge = Form::Challenge.new(return_to: request.url)

    if params.key?(:form_challenge)
      if challenge_passed?
        session[:challenge_passed_at] = Time.now.utc
      else
        flash.now[:alert] = I18n.t('challenge.invalid_password')
        render_challenge
      end
    else
      render_challenge
    end
  end

  def render_challenge
    @body_classes = 'lighter'
    render template: 'auth/challenges/new', layout: 'auth'
  end

  def challenge_passed?
    current_user.valid_password?(challenge_params[:current_password])
  end

  def skip_challenge?
    current_user.encrypted_password.blank?
  end

  def challenge_passed_recently?
    session[:challenge_passed_at].present? && session[:challenge_passed_at] >= CHALLENGE_TIMEOUT.ago
  end

  def challenge_params
    params.require(:form_challenge).permit(:current_password, :return_to)
  end
end