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:
parent
7ba9063e04
commit
842e37658c
29 changed files with 598 additions and 33 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue