Module: TwoStep::Models::Authenticatable

Extended by:
ActiveSupport::Concern
Defined in:
lib/two_step/models/authenticatable.rb

Instance Method Summary collapse

Instance Method Details

#consume_backup_code(code) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/two_step/models/authenticatable.rb', line 61

def consume_backup_code(code)
  normalized = BackupCodes.normalize(code)
  return false if normalized.blank?

  # Stored codes are automatically parsed as an Array via `serialize`
  stored = Array(otp_backup_codes)
  return false if stored.empty?

  index = stored.index { |hashed| backup_code_matches?(normalized, hashed) }
  return false unless index

  stored.delete_at(index)
  update_columns(otp_backup_codes: stored.empty? ? nil : stored)
  true
end

#current_otpObject



38
39
40
# File 'lib/two_step/models/authenticatable.rb', line 38

def current_otp
  build_totp.now if otp_secret.present?
end

#disable_otp!Object



77
78
79
80
81
82
83
84
# File 'lib/two_step/models/authenticatable.rb', line 77

def disable_otp!
  update_columns(
    otp_secret: nil,
    otp_required_for_login: false,
    otp_backup_codes: nil,
    last_otp_at: nil
  )
end

#ensure_otp_secret!Object



24
25
26
27
28
29
# File 'lib/two_step/models/authenticatable.rb', line 24

def ensure_otp_secret!
  return otp_secret if otp_secret.present?

  generate_otp_secret!
  otp_secret
end

#generate_backup_codes!Object



53
54
55
56
57
58
59
# File 'lib/two_step/models/authenticatable.rb', line 53

def generate_backup_codes!
  codes = BackupCodes.generate
  hashed_codes = codes.map { |plain| digest_backup_code(plain) }

  update_columns(otp_backup_codes: hashed_codes)
  codes
end

#generate_otp_secret!Object



19
20
21
22
# File 'lib/two_step/models/authenticatable.rb', line 19

def generate_otp_secret!
  # update_columns avoids instantiation overhead and bypasses unrelated model validations
  update_columns(otp_secret: ROTP::Base32.random, last_otp_at: nil)
end

#otp_enabled?Boolean

Returns:

  • (Boolean)


15
16
17
# File 'lib/two_step/models/authenticatable.rb', line 15

def otp_enabled?
  otp_required_for_login? && otp_secret.present?
end

#otp_provisioning_uri(account_label = nil) ⇒ Object

Raises:

  • (ArgumentError)


31
32
33
34
35
36
# File 'lib/two_step/models/authenticatable.rb', line 31

def otp_provisioning_uri( = nil)
  raise ArgumentError, "otp_secret is blank" if otp_secret.blank?

  label = ( || (respond_to?(:email) ? email : id)).to_s
  build_totp.provisioning_uri(label)
end

#verify_otp(code) ⇒ Object



42
43
44
45
46
47
48
49
50
51
# File 'lib/two_step/models/authenticatable.rb', line 42

def verify_otp(code)
  return false if otp_secret.blank? || code.blank?

  timestamp = verified_otp_timestamp(code)
  return false unless timestamp
  return false if last_otp_at.present? && last_otp_at >= timestamp

  update_column(:last_otp_at, timestamp)
  true
end