#!/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())