Packages

Setaria is OATH One Time Passwords Library for Elixir.

Current section

Files

Jump to
setaria lib setaria.ex
Raw

lib/setaria.ex

defmodule Setaria do
@moduledoc """
## Setaria
Setaria is OATH One Time Passwords Library for Elixir.
This is wrapper of [POT](https://hex.pm/packages/pot).
NOTE: Some parameters are fixed now. The timestep is 30, digits is 6, and digest method is sha.
```elixir
# hotp creation
hotp_with_encoded_secret = Setaria.hotp(encoded_secret, counter)
hotp_with_raw_secret = Setaria.hotp(secret, counter, [encoded_secret: false])
# hotp validation
# with encoded secret
if Setaria.valid_hotp?(token, encoded_secret, counter) do
# ...
end
# with raw secret
if Setaria.valid_hotp?(token, secret, counter, [encoded_secret: false]) do
# ...
end
# totp creation
totp_with_encoded_secret = Setaria.totp(encoded_secret)
totp_with_raw_secret = Setaria.totp(secret, [encoded_secret: false])
totp_with_raw_secret_and_manual_timestamp = Setaria.totp(secret, [timestamp: timestamp + 30, encoded_secret: false])
# totp validation
if Setaria.valid_totp?(token, encoded_secret) do
# ...
end
if Setaria.valid_totp?(token, secret, [encoded_secret: false]) do
# ...
end
if Setaria.valid_totp?(token, secret, [timestamp: timestamp + 30, encoded_secret: false]) do
# ...
end
```
"""
@default_timestep 30
# hotp creation
@doc """
Create HOTP with secret and counter
* opts
* `encoded_secret: false` : secret is not base32 encoded.
"""
@spec hotp(secret :: String.t(), counter :: Integer.t(), opts :: Keyword.t()) :: String.t()
def hotp(secret, counter, opts \\ []) do
encoded_secret = get_encoded_secret(secret, opts)
:pot.hotp(encoded_secret, counter)
end
# hotp validation
@doc """
Validate HOTP with secret and counter.
* opts
* `encoded_secret: false` : secret is not base32 encoded.
"""
@spec valid_hotp?(
token :: String.t(),
secret :: String.t(),
counter :: Integer.t(),
opts :: Keyword.t()
) :: boolean
def valid_hotp?(token, secret, counter, opts \\ []) do
encoded_secret = get_encoded_secret(secret, opts)
:pot.valid_hotp(token, encoded_secret, [{:last, counter - 1}])
end
# totp creation
@doc """
Create TOTP with secret.
* opts
* `encoded_secret: false` : secret is not base32 encoded.
* `timestamp` : timestamp
"""
@spec totp(secret :: String.t(), opts :: Keyword.t()) :: String.t()
def totp(secret, opts \\ []) do
encoded_secret = get_encoded_secret(secret, opts)
timestamp = get_timestamp(opts)
counter = get_counter_from_timestamp(timestamp)
hotp(encoded_secret, counter)
end
# totp validation
@doc """
Validate TOTP with secret.
* opts
* `encoded_secret: false` : secret is not base32 encoded.
* `timestamp` : timestamp, default is current timestamp
"""
@spec valid_totp?(token :: String.t(), secret :: String.t(), opts :: Keyword.t()) :: boolean
def valid_totp?(token, secret, opts \\ []) do
encoded_secret = get_encoded_secret(secret, opts)
timestamp = get_timestamp(opts)
counter = get_counter_from_timestamp(timestamp)
valid_hotp?(token, encoded_secret, counter)
end
defp get_encoded_secret(secret, opts) do
case opts |> Keyword.get(:encoded_secret) do
false ->
Base.encode32(secret, padding: false)
_ ->
secret
end
end
defp get_timestamp(opts) do
if opts |> Keyword.get(:timestamp) do
opts |> Keyword.get(:timestamp)
else
System.system_time(:second)
end
end
defp get_counter_from_timestamp(timestamp) do
trunc(timestamp / @default_timestep)
end
end