two_factor_authentication_concern.rb 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  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. user = self.resource = find_user
  31. if user.webauthn_enabled? && user_params[:credential].present? && session[:attempt_user_id]
  32. authenticate_with_two_factor_via_webauthn(user)
  33. elsif user_params[:otp_attempt].present? && session[:attempt_user_id]
  34. authenticate_with_two_factor_via_otp(user)
  35. elsif user.present? && user.external_or_valid_password?(user_params[:password])
  36. prompt_for_two_factor(user)
  37. end
  38. end
  39. def authenticate_with_two_factor_via_webauthn(user)
  40. webauthn_credential = WebAuthn::Credential.from_get(user_params[:credential])
  41. if valid_webauthn_credential?(user, webauthn_credential)
  42. session.delete(:attempt_user_id)
  43. remember_me(user)
  44. sign_in(user)
  45. render json: { redirect_path: root_path }, status: :ok
  46. else
  47. render json: { error: t('webauthn_credentials.invalid_credential') }, status: :unprocessable_entity
  48. end
  49. end
  50. def authenticate_with_two_factor_via_otp(user)
  51. if valid_otp_attempt?(user)
  52. session.delete(:attempt_user_id)
  53. remember_me(user)
  54. sign_in(user)
  55. else
  56. flash.now[:alert] = I18n.t('users.invalid_otp_token')
  57. prompt_for_two_factor(user)
  58. end
  59. end
  60. def prompt_for_two_factor(user)
  61. set_locale do
  62. session[:attempt_user_id] = user.id
  63. @body_classes = 'lighter'
  64. @webauthn_enabled = user.webauthn_enabled?
  65. @scheme_type = if user.webauthn_enabled? && user_params[:otp_attempt].blank?
  66. 'webauthn'
  67. else
  68. 'totp'
  69. end
  70. render :two_factor
  71. end
  72. end
  73. end