1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
|
module ROTP
DEFAULT_INTERVAL = 30
class TOTP < OTP
attr_reader :interval, :issuer
# @option options [Integer] interval (30) the time interval in seconds for OTP
# This defaults to 30 which is standard.
def initialize(s, options = {})
@interval = options[:interval] || DEFAULT_INTERVAL
@issuer = options[:issuer]
super
end
# Accepts either a Unix timestamp integer or a Time object.
# Time objects will be adjusted to UTC automatically
# @param time [Time/Integer] the time to generate an OTP for, integer unix timestamp or Time object
def at(time)
generate_otp(timecode(time))
end
# Generate the current time OTP
# @return [Integer] the OTP as an integer
def now
generate_otp(timecode(Time.now))
end
# Verifies the OTP passed in against the current time OTP
# and adjacent intervals up to +drift+. Excludes OTPs
# from `after` and earlier. Returns time value of
# matching OTP code for use in subsequent call.
# @param otp [String] the one time password to verify
# @param drift_behind [Integer] how many seconds to look back
# @param drift_ahead [Integer] how many seconds to look ahead
# @param after [Integer] prevent token reuse, last login timestamp
# @param at [Time] time at which to generate and verify a particular
# otp. default Time.now
# @return [Integer, nil] the last successful timestamp
# interval
def verify(otp, drift_ahead: 0, drift_behind: 0, after: nil, at: Time.now)
timecodes = get_timecodes(at, drift_behind, drift_ahead)
timecodes = timecodes.select { |t| t > timecode(after) } if after
result = nil
timecodes.each do |t|
result = t * interval if super(otp, generate_otp(t))
end
result
end
# Returns the provisioning URI for the OTP
# This can then be encoded in a QR Code and used
# to provision the Google Authenticator app
# @param [String] name of the account
# @return [String] provisioning URI
def provisioning_uri(name)
OTP::URI.new(self, account_name: name).to_s
end
private
# Get back an array of timecodes for a period
def get_timecodes(at, drift_behind, drift_ahead)
now = timeint(at)
timecode_start = timecode(now - drift_behind)
timecode_end = timecode(now + drift_ahead)
(timecode_start..timecode_end).step(1).to_a
end
# Ensure UTC int
def timeint(time)
return time.to_i unless time.class == Time
time.utc.to_i
end
def timecode(time)
timeint(time) / interval
end
end
end
|