two_factor_authentication_concern.rb 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. # frozen_string_literal: true
  2. module TwoFactorAuthenticationConcern
  3. extend ActiveSupport::Concern
  4. included do
  5. prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
  6. helper_method :webauthn_enabled?
  7. end
  8. def two_factor_enabled?
  9. find_user&.two_factor_enabled?
  10. end
  11. def valid_webauthn_credential?(user, webauthn_credential)
  12. user_credential = user.webauthn_credentials.find_by!(external_id: webauthn_credential.id)
  13. begin
  14. webauthn_credential.verify(
  15. session[:webauthn_challenge],
  16. public_key: user_credential.public_key,
  17. sign_count: user_credential.sign_count
  18. )
  19. user_credential.update!(sign_count: webauthn_credential.sign_count)
  20. rescue WebAuthn::Error
  21. false
  22. end
  23. end
  24. def valid_otp_attempt?(user)
  25. user.validate_and_consume_otp!(user_params[:otp_attempt]) ||
  26. user.invalidate_otp_backup_code!(user_params[:otp_attempt])
  27. rescue OpenSSL::Cipher::CipherError
  28. false
  29. end
  30. def authenticate_with_two_factor
  31. if user_params[:email].present?
  32. user = self.resource = find_user_from_params
  33. prompt_for_two_factor(user) if user&.external_or_valid_password?(user_params[:password])
  34. elsif session[:attempt_user_id]
  35. user = self.resource = User.find_by(id: session[:attempt_user_id])
  36. return if user.nil?
  37. if session[:attempt_user_updated_at] != user.updated_at.to_s
  38. restart_session
  39. elsif user.webauthn_enabled? && user_params.key?(:credential)
  40. authenticate_with_two_factor_via_webauthn(user)
  41. elsif user_params.key?(:otp_attempt)
  42. authenticate_with_two_factor_via_otp(user)
  43. end
  44. end
  45. end
  46. def authenticate_with_two_factor_via_webauthn(user)
  47. webauthn_credential = WebAuthn::Credential.from_get(user_params[:credential])
  48. if valid_webauthn_credential?(user, webauthn_credential)
  49. on_authentication_success(user, :webauthn)
  50. render json: { redirect_path: after_sign_in_path_for(user) }, status: 200
  51. else
  52. on_authentication_failure(user, :webauthn, :invalid_credential)
  53. render json: { error: t('webauthn_credentials.invalid_credential') }, status: 422
  54. end
  55. end
  56. def authenticate_with_two_factor_via_otp(user)
  57. if valid_otp_attempt?(user)
  58. on_authentication_success(user, :otp)
  59. else
  60. on_authentication_failure(user, :otp, :invalid_otp_token)
  61. flash.now[:alert] = I18n.t('users.invalid_otp_token')
  62. prompt_for_two_factor(user)
  63. end
  64. end
  65. def prompt_for_two_factor(user)
  66. register_attempt_in_session(user)
  67. @body_classes = 'lighter'
  68. @webauthn_enabled = user.webauthn_enabled?
  69. @scheme_type = if user.webauthn_enabled? && user_params[:otp_attempt].blank?
  70. 'webauthn'
  71. else
  72. 'totp'
  73. end
  74. set_locale { render :two_factor }
  75. end
  76. protected
  77. def webauthn_enabled?
  78. @webauthn_enabled
  79. end
  80. end