two_factor_authentication_concern.rb 2.8 KB

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