diff --git a/lib/nimble_totp.ex b/lib/nimble_totp.ex index fc7a33b..69a18f9 100644 --- a/lib/nimble_totp.ex +++ b/lib/nimble_totp.ex @@ -51,13 +51,13 @@ defmodule NimbleTOTP do configured the authentication app in a compatible device. The most common way to do that is to generate a QR Code that can be read by the app. - You can use `NimbleTOTP.otpauth_uri/3` along with + You can use `NimbleTOTP.otpauth_uri/4` along with [eqrcode](https://github.com/SiliconJungles/eqrcode) to generate the QR code as **SVG**. Example: - uri = NimbleTOTP.otpauth_uri("Acme:alice", secret, issuer: "Acme") + uri = NimbleTOTP.otpauth_uri("Acme", "alice", secret) #=> "otpauth://totp/Acme:alice?secret=MFRGGZA&issuer=Acme" uri |> EQRCode.encode() |> EQRCode.svg() #=> "\\n NimbleTOTP.otpauth_uri("Acme", "alice", "abcd") + "otpauth://totp/Acme:alice?secret=MFRGGZA&issuer=Acme" + + iex> NimbleTOTP.otpauth_uri("Acme", "alice", "abcd", extra: "some_value") + "otpauth://totp/Acme:alice?secret=MFRGGZA&issuer=Acme&extra=some_value" + """ + @spec otpauth_uri(String.t(), String.t(), <<>>, keyword()) :: String.t() + def otpauth_uri(issuer, account, secret, uri_params) + when is_binary(issuer) and is_binary(account) and is_binary(secret) and is_list(uri_params) do + issuer =~ ":" && raise ArgumentError, "issuer cannot have :" + account =~ ":" && raise ArgumentError, "account cannot have :" + key = Base.encode32(secret, padding: false) + params = uri_params |> Keyword.put(:issuer, issuer) |> Keyword.put(:secret, key) + query = URI.encode_query(params, :rfc3986) + "otpauth://totp/#{URI.encode(issuer)}:#{URI.encode(account)}?#{query}" + end + + @doc """ + Generate the URI to be encoded in the QR code. + This function is deprecated, use `otpauth_uri/4` which has a safer API. + ## Examples iex> NimbleTOTP.otpauth_uri("Acme:alice", "abcd", issuer: "Acme") "otpauth://totp/Acme:alice?secret=MFRGGZA&issuer=Acme" """ - @spec otpauth_uri(String.t(), String.t(), keyword()) :: String.t() - def otpauth_uri(label, secret, uri_params \\ []) when is_binary(label) and is_binary(secret) do + @spec otpauth_uri(String.t(), String.t(), keyword() | <<>>) :: String.t() + def otpauth_uri(label, secret, uri_params \\ []) + + def otpauth_uri(label, secret, uri_params) + when is_binary(label) and is_binary(secret) and is_list(uri_params) do key = Base.encode32(secret, padding: false) params = [{:secret, key} | uri_params] query = URI.encode_query(params, :rfc3986) "otpauth://totp/#{URI.encode(label)}?#{query}" end + def otpauth_uri(issuer, account, secret) + when is_binary(issuer) and is_binary(account) and is_binary(secret) do + otpauth_uri(issuer, account, secret, []) + end + @doc """ Generate a binary composed of random bytes. diff --git a/test/nimble_totp_test.exs b/test/nimble_totp_test.exs index 1b03ea5..4479f1a 100644 --- a/test/nimble_totp_test.exs +++ b/test/nimble_totp_test.exs @@ -3,14 +3,14 @@ defmodule NimbleTOTPTest do doctest NimbleTOTP describe "otpauth_uri" do - test "Generate the QR Code uri without params" do + test "Generate the QR Code uri without params (no issuer)" do secret = Base.decode32!("PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA") assert NimbleTOTP.otpauth_uri("bytepack", secret) == "otpauth://totp/bytepack?secret=PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA" end - test "Generate the uri with extra params" do + test "Generate the uri with extra params (issuer:account)" do secret = Base.decode32!("PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA") app = "Bytepack App" @@ -20,6 +20,30 @@ defmodule NimbleTOTPTest do issuer=Bytepack%20App\ """ end + + test "Generate the QR Code uri without params" do + secret = Base.decode32!("PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA") + app = "Bytepack App" + + assert NimbleTOTP.otpauth_uri(app, "bytepack", secret) == """ + otpauth://totp/Bytepack%20App:bytepack?\ + secret=PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA&\ + issuer=Bytepack%20App\ + """ + end + + test "Generate the uri with extra params" do + secret = Base.decode32!("PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA") + app = "Bytepack App" + extra = "extra value" + + assert NimbleTOTP.otpauth_uri(app, "user@test.com", secret, extra: extra) == """ + otpauth://totp/Bytepack%20App:user@test.com?\ + secret=PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA&\ + issuer=Bytepack%20App&\ + extra=extra%20value\ + """ + end end describe "secret" do