Files
totp-generator/totp-code
2025-06-24 19:11:09 +02:00

103 lines
2.3 KiB
Elixir
Executable File

#!/usr/bin/env -S ERL_FLAGS=+B elixir
Mix.install([{:nimble_totp, ">= 1.0.0"},{:yaml_elixir, "~> 2.9"},{:ymlr, "~> 5.0"}])
if System.get_env("DEPS_ONLY") == "true" do
System.halt(0)
Process.sleep(:infinity)
end
defmodule TOTP do
@moduledoc """
## Usage
$ totp-code get-code identifier
$ totp-code list-identifiers
$ totp-code add-secret identifier secret
$ totp-code remove-secret identifier
"""
@conffile ".totp-code.conf"
def main([]), do: cmd("help", [])
def main(argv) do
[command | args] = argv
cmd(command, args)
end
defp cmd("get-code", [identifier]) do
get_secret(identifier)
|> Base.decode32!()
|> NimbleTOTP.verification_code()
|> IO.write()
end
defp cmd("list-identifiers", _) do
read_config()
|> Map.get("secrets")
|> Map.keys()
|> Enum.join("\n")
|> IO.puts()
end
defp cmd("add-secret", [identifier, secret]) do
secret = validize_secret(secret)
config = read_config()
secrets = config["secrets"] |> Map.put(identifier, secret)
config = Map.replace(config, "secrets", secrets)
|> Ymlr.document!()
conf_path()
|> File.write!(config)
end
defp cmd("add-secret", _), do: IO.puts("add-secret expects exactly 2 arguments: identifier and secret")
defp cmd("remove-secret", [identifier]) do
config = read_config()
secrets = config["secrets"] |> Map.delete(identifier)
config = Map.replace(config, "secrets", secrets)
|> Ymlr.document!()
conf_path()
|> File.write!(config)
end
defp cmd("remove-secret", _), do: IO.puts("remove-secret expects exactly 1 argument: identifier")
defp cmd("help", _), do: IO.puts(@moduledoc)
defp cmd(command, _), do: IO.puts("no such command: '#{command}'")
defp read_config() do
if File.exists?(conf_path()) do
conf_path()
|> YamlElixir.read_from_file!()
else
%{"secrets" => %{}}
end
end
defp get_secret(identifier) do
read_config()
|> Map.get("secrets")
|> Map.get(identifier)
end
defp validize_secret(secret) do
secret = String.replace(secret, ~r/\s/,"")
if Base.decode32(secret) == :error do
IO.puts("invalid secret: '#{secret}'")
System.halt(1)
end
secret
end
defp conf_path() do
System.user_home()
|> Path.join(@conffile)
end
end
TOTP.main(System.argv())