hcb
hcb copied to clipboard
[Login] Create back up codes
Similar to what github has. hash them when storing in the DB
When a user wants to restore their account.
- Successful complete email login code
- Enter a valid back up code
just going to create a new branch - was trying to resolve the merge conflicts but realized it's probably easier to just start over and steal some code from my old branch
- A user can request backup codes and view them once.
- When backup codes are requested, we prompt them (almost force them) to note them down somewhere.
- If a user doesn't confirm they've saved the backup codes anywhere, the process is incomplete, and we basically reset (as if they didn't request them in the first place).
- We'll provide a copy button to make it easy to paste it into their password manager
- We will NOT provide a
.txtfile of the backup codes. - We will provide 10 backup codes.
- Each backup code can only be used once
- Hash backup codes
- Prompt users to generate backup codes after turning on 2FA
- When backup codes are rolled, the old ones are immediately invalidated.
- Users should be able to disable back up codes.
- Send a mailer once backup codes have been full setup, and anytime backup codes are rolled.
- Also send a mailer when a backup code is used.
- When the user has two or less unused backup codes, we will prompt them to regenerate (basically roll) their backup codes.
- Ensure backup codes are unique (across all)
- Add a database unique index
- Add a rails validation
- The
generate_backup_codesshould retry if it accidentally generates a used code.
Implementation
Login::BackupCode
- belongs_to user
- aasm_state
- unsaved # user has generated codes, but hasn't confirmed they saved them.
- unused
- used
- invalidated # when rolled or backup codes disabled
- hash
User
- has_many :backup_codes
- has_many :unused_backup_codes, -> { where(aasm_state: :unused) }, class_name: "Login::BackupCodes"
- def backup_codes_enabled? = unused_backup_codes.any?
-
def generate_backup_codes backup_codes.each &:mark_invalidated! @codes = 10.times.map do code = SecureRandom.hex(10) backup_codes.create(hash: some_hash_function(code), aasm_state: :unsaved) code end end
In the controller, we call the User#generate_backup_codes and render the codes. When the user click the "I have saved them" button, we'll make an API call to activate those backup codes (current_user.backup_codes.each(&:mark_unused))