/// TOTP: time-based one-time password, RFC 6238.
///|
/// Compute a TOTP code from a Unix timestamp in seconds.
///
/// - `unix_time`: number of seconds since 1970-01-01.
/// - `digits`: output digits (RFC 6238 uses 6 or 8).
/// - `period`: time step in seconds (default 30).
pub fn totp(
algorithm : HashAlgorithm,
key : Bytes,
unix_time : UInt64,
digits? : Int = 6,
period? : Int = 30,
) -> String raise OtpError {
if period <= 0 {
raise InvalidPeriod(period)
}
let counter = unix_time / period.to_uint64()
hotp(algorithm, key, counter, digits~)
}
///|
/// Compute the TOTP code for the current system time.
pub fn totp_now(
algorithm : HashAlgorithm,
key : Bytes,
digits? : Int = 6,
period? : Int = 30,
) -> String raise OtpError {
// @env.now() returns milliseconds.
let unix_time = @env.now() / 1000UL
totp(algorithm, key, unix_time, digits~, period~)
}
///|
/// Verify a submitted TOTP code, accepting `window` time steps of clock
/// drift in each direction (default 1 step).
pub fn totp_verify(
algorithm : HashAlgorithm,
key : Bytes,
code : String,
window? : Int = 1,
digits? : Int = 6,
period? : Int = 30,
) -> Bool raise OtpError {
if window < 0 {
raise InvalidUri("window must be non-negative")
}
// @env.now() returns milliseconds.
let counter = @env.now() / 1000UL / period.to_uint64()
let mut offset = 0
while offset <= window {
if hotp(algorithm, key, counter + offset.to_uint64(), digits~) == code {
return true
}
if offset != 0 &&
hotp(algorithm, key, counter - offset.to_uint64(), digits~) == code {
return true
}
offset += 1
}
false
}