Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use BCrypt to encrypt backup codes #111

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

guilleiguaran
Copy link
Member

No description provided.

return false unless backup_codes.present?

valid_code = backup_codes.find do |backup_code|
backup_code = BCrypt::Password.new(backup_code) if self.class.otp_backup_codes_encrypted

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [98/80]

self.plain_backup_codes = backup_codes

backup_codes = backup_codes.map do |code|
cost = ActiveModel::OneTimePassword.min_bcrypt_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [112/80]


include InstanceMethodsOnActivation

before_create(**options.slice(:if, :unless)) do
self.otp_regenerate_secret if !otp_column
self.otp_regenerate_counter if otp_counter_based && !otp_counter
otp_regenerate_backup_codes if backup_codes_enabled?
self.otp_regenerate_backup_codes if backup_codes_enabled?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/RedundantSelf: Redundant self detected.

@@ -27,13 +33,14 @@ def has_one_time_password(options = {})
self.otp_backup_codes_column_name = (options[:backup_codes_column_name] || OTP_DEFAULT_BACKUP_CODES_COLUMN_NAME).to_s
self.otp_backup_codes_count = options[:backup_codes_count] || OTP_DEFAULT_BACKUP_CODES_COUNT
self.otp_one_time_backup_codes = options[:one_time_backup_codes] || OTP_BACKUP_CODES_ENABLED_BY_DEFAULT
self.otp_backup_codes_encrypted = options.fetch(:backup_codes_encrypted, OTP_BACKUP_CODES_ENCRYPTED_BY_DEFAULT)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [119/80]

@@ -1,5 +1,6 @@
require "active_model"
require "active_support/core_ext/module/attribute_accessors"
require "bcrypt"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.

@@ -7,7 +7,7 @@ class User
define_model_callbacks :create
attr_accessor :otp_secret_key, :otp_backup_codes, :email

has_one_time_password one_time_backup_codes: true
has_one_time_password one_time_backup_codes: true, backup_codes_encrypted: false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [82/80]

@@ -0,0 +1,11 @@
class UserWithEncryptedCodes

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/FrozenStringLiteralComment: Missing magic comment # frozen_string_literal: true.

@admsev
Copy link

admsev commented May 26, 2024

@gooroodev can you review this PR please?

@gooroodev
Copy link

Hey @admsev , thanks for summoning me!

Summary of Changes

  1. BCrypt Integration:

    • Added bcrypt as a dependency.
    • Introduced min_bcrypt_cost to control the BCrypt cost factor.
    • Added an option to encrypt backup codes with BCrypt.
  2. Class and Instance Methods:

    • Added class-level attribute for BCrypt cost control.
    • Modified has_one_time_password to accept a backup_codes_encrypted option.
    • Modified otp_regenerate_backup_codes to encrypt backup codes if backup_codes_encrypted is true.
    • Modified authenticate_backup_code to handle encrypted backup codes.
  3. Test Enhancements:

    • Added a new test model UserWithEncryptedCodes.
    • Added tests for encrypted backup codes.

Issues, Bugs, and Typos

  1. Bug in Test:

    • In test_authenticate_with_encrypted_backup_code, the last assertion uses @user instead of @user_with_encrypted_code.

    Improved Code:

    def test_authenticate_with_encrypted_backup_code
      backup_code = @user_with_encrypted_code.plain_backup_codes.first
      assert_equal true, @user_with_encrypted_code.authenticate_otp(backup_code)
    
      backup_code = @user_with_encrypted_code.plain_backup_codes.last
      @user_with_encrypted_code.otp_regenerate_backup_codes
      assert_equal false, @user_with_encrypted_code.authenticate_otp(backup_code)
    end
  2. Potential Issue with otp_regenerate_backup_codes:

    • The variable backup_codes is reassigned, which might cause confusion. It's better to use a different variable name for the encrypted codes.

    Improved Code:

    def otp_regenerate_backup_codes
      backup_codes = Array.new(self.class.otp_backup_codes_count) do
        otp.generate_otp((SecureRandom.random_number(9e5) + 1e5).to_i)
      end
    
      if self.class.otp_backup_codes_encrypted
        self.plain_backup_codes = backup_codes
    
        encrypted_backup_codes = backup_codes.map do |code|
          cost = ActiveModel::OneTimePassword.min_bcrypt_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
          BCrypt::Password.create(code, cost: cost)
        end
        backup_codes = encrypted_backup_codes
      end
    
      public_send("#{self.class.otp_backup_codes_column_name}=", backup_codes)
    end

General Review of Code Quality and Style

  1. Code Quality:

    • The code is well-structured and follows good practices.
    • The use of class_attribute and module is appropriate for defining class-level and instance-level attributes.
  2. Code Style:

    • The code adheres to Ruby conventions, making it readable and maintainable.
    • The use of self.class and public_send is appropriate for dynamic method calls and accessing class attributes.
  3. Tests:

    • The tests are comprehensive and cover both encrypted and non-encrypted backup codes.
    • The addition of a new test model for encrypted codes is a good practice to isolate test cases.

Overall

The pull request introduces a valuable feature by adding encryption for backup codes using BCrypt. The implementation is clean, and the tests ensure that the feature works as expected. The minor issues identified can be easily fixed to improve the robustness of the code.

Yours, Gooroo.dev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants