1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-24 15:49: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

@ -110,6 +110,41 @@ class User < ApplicationRecord
end
end
# MFA
def setup_mfa!
update!(
otp_secret: ROTP::Base32.random(32),
otp_required: false,
otp_backup_codes: []
)
end
def enable_mfa!
update!(
otp_required: true,
otp_backup_codes: generate_backup_codes
)
end
def disable_mfa!
update!(
otp_secret: nil,
otp_required: false,
otp_backup_codes: []
)
end
def verify_otp?(code)
return false if otp_secret.blank?
return true if verify_backup_code?(code)
totp.verify(code, drift_behind: 15)
end
def provisioning_uri
return nil unless otp_secret.present?
totp.provisioning_uri(email)
end
private
def ensure_valid_profile_image
return unless profile_image.attached?
@ -133,4 +168,26 @@ class User < ApplicationRecord
errors.add(:profile_image, :invalid_file_size, max_megabytes: 10)
end
end
def totp
ROTP::TOTP.new(otp_secret, issuer: "Maybe Finance")
end
def verify_backup_code?(code)
return false if otp_backup_codes.blank?
# Find and remove the used backup code
if (index = otp_backup_codes.index(code))
remaining_codes = otp_backup_codes.dup
remaining_codes.delete_at(index)
update_column(:otp_backup_codes, remaining_codes)
true
else
false
end
end
def generate_backup_codes
8.times.map { SecureRandom.hex(4) }
end
end