Skip to content

Commit fecffa5

Browse files
committed
Add additional values for JSON responses when setting up an authenticater
Add the base32 key and the TOTP generated URI. This enables JSON clients to construct a QRcode. closes #1189
1 parent 9770cc2 commit fecffa5

6 files changed

Lines changed: 56 additions & 7 deletions

File tree

docs/openapi.yaml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2486,7 +2486,13 @@ components:
24862486
description: The canonicalized phone number if setting up SMS
24872487
authr_key:
24882488
type: string
2489-
description: TOTP key for setting up authenticator (if chosen_method == 'authenticator')
2489+
description: Pretty TOTP key for setting up authenticator manually (if chosen_method == 'authenticator')
2490+
authr_b32key:
2491+
type: string
2492+
description: Base32 TOTP key for setting up authenticator (useful to create a uri) (if chosen_method == 'authenticator')
2493+
authr_uri:
2494+
type: string
2495+
description: A 'otpauth://' style URI suitable to creating a QRCode (if chosen_method == 'authenticator')
24902496
authr_issuer:
24912497
type: string
24922498
description: Issuer as configured with TOTP_ISSUER (same as used in QRcode) (if chosen_method == 'authenticator')
@@ -2562,7 +2568,13 @@ components:
25622568
example: sms
25632569
tf_authr_key:
25642570
type: string
2565-
description: TOTP key for setting up authenticator (if tf_primary_method == 'authenticator')
2571+
description: Pretty TOTP key for setting up authenticator manually (if tf_primary_method == 'authenticator')
2572+
tf_authr_b32key:
2573+
type: string
2574+
description: Base32 TOTP key for setting up authenticator (useful to create a uri) (if chosen_method == 'authenticator')
2575+
tf_authr_uri:
2576+
type: string
2577+
description: A 'otpauth://' style URI suitable to creating a QRCode (if chosen_method == 'authenticator')
25662578
tf_authr_issuer:
25672579
type: string
25682580
description: Issuer as configured with TOTP_ISSUER (same as used in QRcode) (if tf_primary_method == 'authenticator')

flask_security/totp.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,30 @@ def get_totp_pretty_key(self, totp_secret: str) -> str:
111111
tp = self._totp.from_source(totp_secret)
112112
return tp.pretty_key()
113113

114+
def get_totp_b32_key(self, totp_secret: str) -> str:
115+
"""Generate b32 key for creating a otpauth url
116+
117+
:param totp_secret: a unique shared secret of the user
118+
119+
.. versionadded:: 5.8.0
120+
"""
121+
tp = self._totp.from_source(totp_secret)
122+
return tp.base32_key
123+
114124
def fetch_setup_values(self, totp: str, user: UserMixin) -> dict[str, str]:
115125
"""Generate various values user needs to setup authenticator app.
116126
Returns dict with keys:
117127
'key': totp key
128+
'b32key': totp key in base32
129+
'uri': provisioning url
118130
'image': image as string (useful for <img src=xx>)
119131
'username: qrcode best practice
120132
'issuer': qrcode best practice
121133
122134
.. versionadded:: 4.0.0
135+
136+
.. versionchanged:: 5.8.0
137+
Added keys b32key and uri
123138
"""
124139

125140
r = dict()
@@ -130,8 +145,10 @@ def fetch_setup_values(self, totp: str, user: UserMixin) -> dict[str, str]:
130145
assert self._totp.issuer
131146
r["username"] = username
132147
r["key"] = self.get_totp_pretty_key(totp)
148+
r["b32key"] = self.get_totp_b32_key(totp)
133149
r["issuer"] = self._totp.issuer
134150
r["image"] = self.generate_qrcode(username, totp)
151+
r["uri"] = self.get_totp_uri(username, totp)
135152
return r
136153

137154
def generate_qrcode(self, username: str, totp: str) -> str:

flask_security/unified_signin.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,8 +904,10 @@ def us_setup() -> ResponseValue:
904904

905905
# Add all the values used in qrcode to json response
906906
json_response["authr_key"] = authr_setup_values["key"]
907+
json_response["authr_b32key"] = authr_setup_values["b32key"]
907908
json_response["authr_username"] = authr_setup_values["username"]
908909
json_response["authr_issuer"] = authr_setup_values["issuer"]
910+
json_response["authr_uri"] = authr_setup_values["uri"]
909911

910912
qrcode_values = dict(
911913
authr_qrcode=authr_setup_values["image"],

flask_security/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,8 +854,10 @@ def two_factor_setup():
854854
authr_setup_values = _security.totp_factory.fetch_setup_values(totp, user)
855855
# Add all the values used in qrcode to json response
856856
json_response["tf_authr_key"] = authr_setup_values["key"]
857+
json_response["tf_authr_b32key"] = authr_setup_values["b32key"]
857858
json_response["tf_authr_username"] = authr_setup_values["username"]
858859
json_response["tf_authr_issuer"] = authr_setup_values["issuer"]
860+
json_response["tf_authr_uri"] = authr_setup_values["uri"]
859861

860862
qrcode_values = dict(
861863
authr_qrcode=authr_setup_values["image"],

tests/test_two_factor.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,10 +1265,18 @@ def test_authr_identity(app, client):
12651265

12661266
setup_data = dict(setup="authenticator")
12671267
response = client.post("/tf-setup", json=setup_data, headers=headers)
1268-
assert response.json["response"]["tf_authr_issuer"] == "tests"
1269-
assert response.json["response"]["tf_authr_username"] == "jill"
1270-
assert response.json["response"]["tf_state"] == "validating_profile"
1268+
jr = response.json["response"]
1269+
assert jr["tf_authr_issuer"] == "tests"
1270+
assert jr["tf_authr_username"] == "jill"
1271+
assert jr["tf_state"] == "validating_profile"
1272+
assert len(jr["tf_authr_b32key"]) % 8 == 0 and re.match(
1273+
r"^[A-Z2-7]+=*$", jr["tf_authr_b32key"]
1274+
)
12711275
assert "tf_authr_key" in response.json["response"]
1276+
assert (
1277+
jr["tf_authr_uri"]
1278+
== f"otpauth://totp/tests:jill?secret={jr['tf_authr_b32key']}&issuer=tests"
1279+
)
12721280

12731281

12741282
@pytest.mark.settings(

tests/test_unified_signin.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,9 +1759,17 @@ def test_totp_generation(app, client, get_message):
17591759
"us-setup", json=dict(chosen_method="authenticator"), headers=headers
17601760
)
17611761
assert response.status_code == 200
1762-
assert response.json["response"]["authr_issuer"] == "tests"
1763-
assert response.json["response"]["authr_username"] == "dave@lp.com"
1762+
jr = response.json["response"]
1763+
assert jr["authr_issuer"] == "tests"
1764+
assert jr["authr_username"] == "dave@lp.com"
17641765
assert "authr_key" in response.json["response"]
1766+
assert len(jr["authr_b32key"]) % 8 == 0 and re.match(
1767+
r"^[A-Z2-7]+=*$", jr["authr_b32key"]
1768+
)
1769+
assert (
1770+
jr["authr_uri"]
1771+
== f"otpauth://totp/tests:dave@lp.com?secret={jr['authr_b32key']}&issuer=tests"
1772+
)
17651773

17661774
state = response.json["response"]["state"]
17671775

0 commit comments

Comments
 (0)