about summary refs log tree commit diff
path: root/app/controllers/concerns/challengable_concern.rb
diff options
context:
space:
mode:
authorReverite <github@reverite.sh>2019-09-30 16:38:09 -0700
committerReverite <github@reverite.sh>2019-09-30 16:38:09 -0700
commit01001ba118d7adcd4966c893cf95db32fe76c273 (patch)
treefc8b580c8f0a3c1e14a8ca20e3bea5bd7acb4ff1 /app/controllers/concerns/challengable_concern.rb
parent1e75b3d874973dd71ea74b158ebeb501d9ac68a3 (diff)
parent65db9df011a3e13efa79f4e56343aa69bdad7716 (diff)
Merge branch 'glitch' into production
Diffstat (limited to 'app/controllers/concerns/challengable_concern.rb')
-rw-r--r--app/controllers/concerns/challengable_concern.rb65
1 files changed, 65 insertions, 0 deletions
diff --git a/app/controllers/concerns/challengable_concern.rb b/app/controllers/concerns/challengable_concern.rb
new file mode 100644
index 000000000..b29d90b3c
--- /dev/null
+++ b/app/controllers/concerns/challengable_concern.rb
@@ -0,0 +1,65 @@
+# 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
+        return
+      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