1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-23 07:09:39 +02:00

Multi-factor authentication (#1817)

* Initial pass

* Tests for MFA and locale cleanup

* Brakeman

* Update two-factor authentication status styling

* Update app/models/user.rb

Co-authored-by: Zach Gollwitzer <zach@maybe.co>
Signed-off-by: Josh Pigford <josh@joshpigford.com>

* Refactor MFA verification and session handling in tests

---------

Signed-off-by: Josh Pigford <josh@joshpigford.com>
Co-authored-by: Zach Gollwitzer <zach@maybe.co>
This commit is contained in:
Josh Pigford 2025-02-06 14:16:53 -06:00 committed by GitHub
parent 7ba9063e04
commit 842e37658c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 598 additions and 33 deletions

View file

@ -70,4 +70,72 @@ class UserTest < ActiveSupport::TestCase
assert_equal "Bob", @user.first_name
assert_equal "Dylan", @user.last_name
end
# MFA Tests
test "setup_mfa! generates required fields" do
user = users(:family_member)
user.setup_mfa!
assert user.otp_secret.present?
assert_not user.otp_required?
assert_empty user.otp_backup_codes
end
test "enable_mfa! enables MFA and generates backup codes" do
user = users(:family_member)
user.setup_mfa!
user.enable_mfa!
assert user.otp_required?
assert_equal 8, user.otp_backup_codes.length
assert user.otp_backup_codes.all? { |code| code.length == 8 }
end
test "disable_mfa! removes all MFA data" do
user = users(:family_member)
user.setup_mfa!
user.enable_mfa!
user.disable_mfa!
assert_nil user.otp_secret
assert_not user.otp_required?
assert_empty user.otp_backup_codes
end
test "verify_otp? validates TOTP codes" do
user = users(:family_member)
user.setup_mfa!
totp = ROTP::TOTP.new(user.otp_secret, issuer: "Maybe")
valid_code = totp.now
assert user.verify_otp?(valid_code)
assert_not user.verify_otp?("invalid")
assert_not user.verify_otp?("123456")
end
test "verify_otp? accepts backup codes" do
user = users(:family_member)
user.setup_mfa!
user.enable_mfa!
backup_code = user.otp_backup_codes.first
assert user.verify_otp?(backup_code)
# Backup code should be consumed
assert_not user.otp_backup_codes.include?(backup_code)
assert_equal 7, user.otp_backup_codes.length
# Used backup code should not work again
assert_not user.verify_otp?(backup_code)
end
test "provisioning_uri generates correct URI" do
user = users(:family_member)
user.setup_mfa!
assert_match %r{otpauth://totp/}, user.provisioning_uri
assert_match %r{secret=#{user.otp_secret}}, user.provisioning_uri
assert_match %r{issuer=Maybe}, user.provisioning_uri
end
end