feat: add main source file
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
import argv
|
||||||
|
import gleam/bit_array
|
||||||
|
import gleam/crypto
|
||||||
|
import gleam/int
|
||||||
|
import gleam/io
|
||||||
|
import gleam/result
|
||||||
|
import gleam/string
|
||||||
|
import gleam/time/timestamp
|
||||||
|
import thirtytwo
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
case argv.load().arguments {
|
||||||
|
[secret, ..] ->
|
||||||
|
case totp_string(secret) {
|
||||||
|
Ok(code) -> io.println(code)
|
||||||
|
Error(_) -> io.println("Error: invalid secret")
|
||||||
|
}
|
||||||
|
[] -> io.println("Usage: teotipi <base32-secret>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn totp_string(secret_b32: String) -> Result(String, Nil) {
|
||||||
|
use code <- result.map(totp(secret_b32))
|
||||||
|
code |> int.to_string |> string.pad_start(6, "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn totp(secret_b32: String) -> Result(Int, Nil) {
|
||||||
|
use secret <- result.try(thirtytwo.decode(secret_b32))
|
||||||
|
let now = timestamp.system_time()
|
||||||
|
let #(seconds, _nanoseconds) = timestamp.to_unix_seconds_and_nanoseconds(now)
|
||||||
|
let counter = seconds / 30
|
||||||
|
|
||||||
|
let counter_bits = <<counter:big-int-size(64)>>
|
||||||
|
let mac = crypto.hmac(counter_bits, crypto.Sha1, secret)
|
||||||
|
|
||||||
|
Ok(truncate(mac))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate(mac: BitArray) -> Int {
|
||||||
|
let assert <<_:bytes-size(19), last:int>> = mac
|
||||||
|
let offset = int.bitwise_and(last, 0x0f)
|
||||||
|
let assert Ok(slice) = bit_array.slice(mac, offset, 4)
|
||||||
|
let assert <<b0:int, b1:int, b2:int, b3:int>> = slice
|
||||||
|
let code =
|
||||||
|
int.bitwise_and(b0, 0x7f)
|
||||||
|
|> int.bitwise_shift_left(24)
|
||||||
|
|> int.bitwise_or(int.bitwise_shift_left(b1, 16))
|
||||||
|
|> int.bitwise_or(int.bitwise_shift_left(b2, 8))
|
||||||
|
|> int.bitwise_or(b3)
|
||||||
|
code % 1_000_000
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user