Skip to content

Commit 894f845

Browse files
New version of otpauth_uri/4 with mandatory issuer
This helps build a correct URI following the recommendations to have the issuer both as a prefix to the account and as an "issuer" query parameter. Also, this checks that no colons are used either in the account or the issuer. Co-authored-by: José Valim <jose.valim@dashbit.co>
1 parent 5ec3d7d commit 894f845

2 files changed

Lines changed: 60 additions & 6 deletions

File tree

lib/nimble_totp.ex

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ defmodule NimbleTOTP do
5151
configured the authentication app in a compatible device. The most common
5252
way to do that is to generate a QR Code that can be read by the app.
5353
54-
You can use `NimbleTOTP.otpauth_uri/3` along with
54+
You can use `NimbleTOTP.otpauth_uri/4` along with
5555
[eqrcode](https://github.com/SiliconJungles/eqrcode) to generate the QR
5656
code as **SVG**.
5757
5858
Example:
5959
60-
uri = NimbleTOTP.otpauth_uri("Acme:alice", secret, issuer: "Acme")
60+
uri = NimbleTOTP.otpauth_uri("Acme", "alice", secret)
6161
#=> "otpauth://totp/Acme:alice?secret=MFRGGZA&issuer=Acme"
6262
uri |> EQRCode.encode() |> EQRCode.svg()
6363
#=> "<?xml version=\\"1.0\\" standalone=\\"yes\\"?>\\n<svg version=\\"1.1\\" ...
@@ -143,20 +143,50 @@ defmodule NimbleTOTP do
143143
@doc """
144144
Generate the URI to be encoded in the QR code.
145145
146+
## Examples
147+
148+
iex> NimbleTOTP.otpauth_uri("Acme", "alice", "abcd")
149+
"otpauth://totp/Acme:alice?secret=MFRGGZA&issuer=Acme"
150+
151+
iex> NimbleTOTP.otpauth_uri("Acme", "alice", "abcd", extra: "some_value")
152+
"otpauth://totp/Acme:alice?secret=MFRGGZA&issuer=Acme&extra=some_value"
153+
"""
154+
@spec otpauth_uri(String.t(), String.t(), <<>>, keyword()) :: String.t()
155+
def otpauth_uri(issuer, account, secret, uri_params)
156+
when is_binary(issuer) and is_binary(account) and is_binary(secret) and is_list(uri_params) do
157+
issuer =~ ":" && raise ArgumentError, "issuer cannot have :"
158+
account =~ ":" && raise ArgumentError, "account cannot have :"
159+
key = Base.encode32(secret, padding: false)
160+
params = uri_params |> Keyword.put(:issuer, issuer) |> Keyword.put(:secret, key)
161+
query = URI.encode_query(params, :rfc3986)
162+
"otpauth://totp/#{URI.encode(issuer)}:#{URI.encode(account)}?#{query}"
163+
end
164+
165+
@doc """
166+
Generate the URI to be encoded in the QR code.
167+
146168
## Examples
147169
148170
iex> NimbleTOTP.otpauth_uri("Acme:alice", "abcd", issuer: "Acme")
149171
"otpauth://totp/Acme:alice?secret=MFRGGZA&issuer=Acme"
150172
151173
"""
152-
@spec otpauth_uri(String.t(), String.t(), keyword()) :: String.t()
153-
def otpauth_uri(label, secret, uri_params \\ []) when is_binary(label) and is_binary(secret) do
174+
@spec otpauth_uri(String.t(), String.t(), keyword() | <<>>) :: String.t()
175+
def otpauth_uri(label, secret, uri_params \\ [])
176+
177+
def otpauth_uri(label, secret, uri_params)
178+
when is_binary(label) and is_binary(secret) and is_list(uri_params) do
154179
key = Base.encode32(secret, padding: false)
155180
params = [{:secret, key} | uri_params]
156181
query = URI.encode_query(params, :rfc3986)
157182
"otpauth://totp/#{URI.encode(label)}?#{query}"
158183
end
159184

185+
def otpauth_uri(issuer, account, secret)
186+
when is_binary(issuer) and is_binary(account) and is_binary(secret) do
187+
otpauth_uri(issuer, account, secret, [])
188+
end
189+
160190
@doc """
161191
Generate a binary composed of random bytes.
162192

test/nimble_totp_test.exs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ defmodule NimbleTOTPTest do
33
doctest NimbleTOTP
44

55
describe "otpauth_uri" do
6-
test "Generate the QR Code uri without params" do
6+
test "Generate the QR Code uri without params (no issuer)" do
77
secret = Base.decode32!("PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA")
88

99
assert NimbleTOTP.otpauth_uri("bytepack", secret) ==
1010
"otpauth://totp/bytepack?secret=PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA"
1111
end
1212

13-
test "Generate the uri with extra params" do
13+
test "Generate the uri with extra params (issuer:account)" do
1414
secret = Base.decode32!("PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA")
1515
app = "Bytepack App"
1616

@@ -20,6 +20,30 @@ defmodule NimbleTOTPTest do
2020
issuer=Bytepack%20App\
2121
"""
2222
end
23+
24+
test "Generate the QR Code uri without params" do
25+
secret = Base.decode32!("PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA")
26+
app = "Bytepack App"
27+
28+
assert NimbleTOTP.otpauth_uri(app, "bytepack", secret) == """
29+
otpauth://totp/Bytepack%20App:bytepack?\
30+
secret=PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA&\
31+
issuer=Bytepack%20App\
32+
"""
33+
end
34+
35+
test "Generate the uri with extra params" do
36+
secret = Base.decode32!("PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA")
37+
app = "Bytepack App"
38+
extra = "extra value"
39+
40+
assert NimbleTOTP.otpauth_uri(app, "user@test.com", secret, extra: extra) == """
41+
otpauth://totp/Bytepack%20App:user@test.com?\
42+
secret=PTEPUGZ7DUWTBGMW4WLKB6U63MGKKMCA&\
43+
issuer=Bytepack%20App&\
44+
extra=extra%20value\
45+
"""
46+
end
2347
end
2448

2549
describe "secret" do

0 commit comments

Comments
 (0)