lib/rotp/totp.rb in rotp-3.3.1 vs lib/rotp/totp.rb in rotp-4.0.0

- old
+ new

@@ -12,68 +12,50 @@ super end # Accepts either a Unix timestamp integer or a Time object. # Time objects will be adjusted to UTC automatically - # @param [Time/Integer] time the time to generate an OTP for - # @option [Boolean] padding (true) Issue the number as a 0 padded string - def at(time, padding=true) - unless time.class == Time - time = Time.at(time.to_i) - end - - generate_otp(timecode(time), padding) + # @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(padding=true) - generate_otp(timecode(Time.now), padding) + def now() + generate_otp(timecode(Time.now)) end # Verifies the OTP passed in against the current time OTP - # @param [String/Integer] otp the OTP to check against - def verify(otp, time = Time.now) - super(otp, self.at(time)) - end - - # Verifies the OTP passed in against the current time OTP - # and adjacent intervals up to +drift+. - # @param [String] otp the OTP to check against - # @param [Integer] drift the number of seconds that the client - # and server are allowed to drift apart - def verify_with_drift(otp, drift, time = Time.now) - time = time.to_i - times = (time-drift..time+drift).step(interval).to_a - times << time + drift if times.last < time + drift - times.any? { |ti| verify(otp, ti) } - end - - # Verifies the OTP passed in against the current time OTP # and adjacent intervals up to +drift+. Excludes OTPs - # from prior_time and earlier. Returns time value of + # from `after` and earlier. Returns time value of # matching OTP code for use in subsequent call. - # @param [String] otp the OTP to check against - # @param [Integer] drift the number of seconds that the client - # and server are allowed to drift apart - # @param [Integer] time value of previous match - def verify_with_drift_and_prior(otp, drift, prior_time = nil, time = Time.now) - # calculate normalized bin start times based on drift - first_bin = (time - drift).to_i / interval * interval - last_bin = (time + drift).to_i / interval * interval + # @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) - # if prior_time was supplied, adjust first bin if necessary to exclude it - if prior_time - prior_bin = prior_time.to_i / interval * interval - first_bin = prior_bin + interval if prior_bin >= first_bin - # fail if we've already used the last available OTP code - return if first_bin > last_bin + if after + timecodes = timecodes.select { |t| t > timecode(after) } end - times = (first_bin..last_bin).step(interval).to_a - times.find { |ti| verify(otp, ti) } + + result = nil + timecodes.each { |t| + if (super(otp, self.generate_otp(t))) + result = t * interval + end + } + return 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 @@ -93,11 +75,27 @@ encode_params("otpauth://totp/#{issuer_string}#{URI.encode(name)}", params) 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) + return (timecode_start..timecode_end).step(1).to_a + end + + # Ensure UTC int + def timeint(time) + unless time.class == Time + return time.to_i + end + return time.utc.to_i + end + def timecode(time) - time.utc.to_i / interval + return timeint(time) / interval end end end