two_factor_authentication_concern.rb 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. # frozen_string_literal: true
  2. module Auth::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 check_second_factor_rate_limits(user)
  58. flash.now[:alert] = I18n.t('users.rate_limited')
  59. return prompt_for_two_factor(user)
  60. end
  61. if valid_otp_attempt?(user)
  62. on_authentication_success(user, :otp)
  63. else
  64. on_authentication_failure(user, :otp, :invalid_otp_token)
  65. flash.now[:alert] = I18n.t('users.invalid_otp_token')
  66. prompt_for_two_factor(user)
  67. end
  68. end
  69. def prompt_for_two_factor(user)
  70. register_attempt_in_session(user)
  71. @body_classes = 'lighter'
  72. @webauthn_enabled = user.webauthn_enabled?
  73. @scheme_type = if user.webauthn_enabled? && user_params[:otp_attempt].blank?
  74. 'webauthn'
  75. else
  76. 'totp'
  77. end
  78. set_locale { render :two_factor }
  79. end
  80. protected
  81. def webauthn_enabled?
  82. @webauthn_enabled
  83. end
  84. end