Skip to content

Instantly share code, notes, and snippets.

@JunilJacob
Forked from Nitrino/totp_2fa.rb
Created May 31, 2019 09:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JunilJacob/62d3d1188dea852a276178289c3b40bc to your computer and use it in GitHub Desktop.
Save JunilJacob/62d3d1188dea852a276178289c3b40bc to your computer and use it in GitHub Desktop.
Rails mixin module for Two Factor Authentication (TOTP)
module OneTimePassword
# Concern containing logic and methods for OTP authentication.
# Is used Time-based One-time Password Algorithm(TOTP)
# https://tools.ietf.org/html/rfc6238
extend ActiveSupport::Concern
OTP_DIGITS = 6
OTP_NUMBER_OF_BACKUP_CODES = 10
OTP_BACKUP_CODE_LENGTH = 12
included do
before_create do
regenerate_otp_secret
end
end
# Generation of a new OTP secret key
# After changing the OTP secret key, the provisioning uri will also change
def regenerate_otp_secret
self.otp_secret_key = ROTP::Base32.random_base32
end
# Generation of a list backup codes
# 1) Invalidates all existing backup codes
# 2) Generates OTP_NUMBER_OF_BACKUP_CODES backup codes
# 3) Stores the hashed backup codes in the otp_backup_codes
# 4) Returns a plaintext array of the generated backup codes
def regenerate_otp_backup_codes
codes = Array.new(OTP_NUMBER_OF_BACKUP_CODES).map { SecureRandom.hex(OTP_BACKUP_CODE_LENGTH / 2) }
hashed_codes = codes.map { |code| Devise::Encryptor.digest(self.class, code) }
self.otp_backup_codes = hashed_codes
codes
end
# Returns true and invalidates the given code
# iff that code is a valid backup code.
def invalidate_otp_backup_code(code)
codes = self.otp_backup_codes || []
codes.each do |backup_code|
next unless Devise::Encryptor.compare(self.class, backup_code, code)
codes.delete(backup_code)
self.otp_backup_codes = codes
self.save!
return true
end
false
end
def otp_is_enabled?
self.otp_is_enabled
end
# Authentication flow is enabled only if otp_is_enabled field is true
def enable_otp
codes = regenerate_otp_backup_codes
self.otp_is_enabled = true
self.save!
codes
end
def disable_otp
self.otp_is_enabled = false
self.save
end
# Shows the current OTP code.
# The code is similar to what will be displayed in the Google authenticator.
# You can use it to authenticate via SMS and for testing.
# @param time [Time] Time for code verification. By default is the current time.
# Explicit changing the test time is convenient for testing
def otp_code(time = Time.current)
ROTP::TOTP.new(self.otp_secret_key, digits: OTP_DIGITS).at(time, true)
end
# Method for verifying OTP code
# @param code [String] OTP code.
# @param drift [Integer] Allows to confirm the OTP code within 15 seconds after the expiration of its time.
# Drift allows you to level out the inaccuracy of time on different devices and server
# By default, the drift is set to 15 seconds.
# @return [Boolean] OTP code verification result
def authenticate_otp(code, drift = 15)
totp = ROTP::TOTP.new(self.otp_secret_key, digits: OTP_DIGITS)
totp.verify_with_drift(code, drift)
end
# Method for combining otp authorization and code recovery
def authenticate_otp_or_invalidate_otp_backup_code(code)
authenticate_otp(code) || invalidate_otp_backup_code(code)
end
# URI compatible with the Google Authenticator App to be scanned with the in-built QR Code scanner.
# @param account [String] account name for which the Provisioning URI is generated. By default user email address
# @param options [Hash] options for generate provisioning uri
# By default is blank hash.
def provisioning_uri(account: nil, options: {})
account ||= self.attributes["email"]
options[:issuer] ||= otp_issuer
ROTP::TOTP.new(self.otp_secret_key, options).provisioning_uri(account)
end
# Exclude the OTP secret key from all serializers
def serializable_hash(options = nil)
options ||= {}
options[:except] = Array(options[:except])
options[:except] << "otp_secret_key"
super(options)
end
def otp_issuer
raise "You must override `otp_issuer` method in #{self.class.name} model"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment