Skip to content

Commit b809290

Browse files
edignotAdam Wolfman
andauthored
Update MFA totp factor enroll flow (#37)
* update styles * remove qr from factor details * update styles * qr code modal for totp * update factor endpoints * Update html to center buttons on enroll_factor --------- Co-authored-by: Adam Wolfman <[email protected]>
1 parent 31a2891 commit b809290

File tree

4 files changed

+141
-55
lines changed

4 files changed

+141
-55
lines changed

python-flask-mfa-example/app.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
from flask import Flask, session, redirect, render_template, request, url_for
2+
from flask import Flask, session, redirect, render_template, request, url_for, jsonify
33
import json
44
import workos
55

@@ -36,31 +36,39 @@ def enroll_factor_details():
3636
return render_template("enroll_factor.html")
3737

3838

39-
@app.route("/enroll_factor", methods=["POST"])
40-
def enroll_factor():
39+
@app.route("/enroll_sms_factor", methods=["POST"])
40+
def enroll_sms_factor():
4141
factor_type = request.form.get("type")
42-
totp_issuer = request.form.get("totp_issuer")
43-
totp_user = request.form.get("totp_user")
4442
phone_number = request.form.get("phone_number")
4543

46-
if factor_type == "sms":
47-
factor_type = "sms"
48-
new_factor = workos.client.mfa.enroll_factor(
49-
type=factor_type, phone_number=phone_number
50-
)
44+
new_factor = workos.client.mfa.enroll_factor(
45+
type=factor_type,
46+
phone_number=phone_number
47+
)
5148

52-
if factor_type == "totp":
53-
factor_type = "totp"
54-
new_factor = workos.client.mfa.enroll_factor(
55-
type=factor_type, totp_issuer=totp_issuer, totp_user=totp_user
56-
)
57-
print(new_factor)
5849
session["factor_list"].append(new_factor)
59-
print(session["factor_list"])
6050
session.modified = True
6151
return redirect("/")
6252

6353

54+
@app.route('/enroll_totp_factor', methods=['POST'])
55+
def enroll_totp_factor():
56+
data = request.get_json()
57+
type = data['type']
58+
issuer = data['issuer']
59+
user = data['user']
60+
61+
new_factor = workos.client.mfa.enroll_factor(
62+
type=type,
63+
totp_issuer=issuer,
64+
totp_user=user
65+
)
66+
67+
session['factor_list'].append(new_factor)
68+
session.modified = True
69+
return jsonify(new_factor['totp']['qr_code'])
70+
71+
6472
@app.route("/factor_detail")
6573
def factor_detail():
6674
factorId = request.args.get("id")
@@ -72,17 +80,13 @@ def factor_detail():
7280
if factor["type"] == "sms":
7381
phone_number = factor["sms"]["phone_number"]
7482

75-
if factor["type"] == "totp":
76-
session["current_factor_qr"] = factor["totp"]["qr_code"]
77-
7883
session["current_factor"] = fullFactor["id"]
7984
session["current_factor_type"] = fullFactor["type"]
8085
session.modified = True
8186
return render_template(
8287
"factor_detail.html",
8388
factor=fullFactor,
8489
phone_number=phone_number,
85-
qr_code=session["current_factor_qr"],
8690
)
8791

8892

python-flask-mfa-example/static/login.css

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
body {
22
font-family: Inter, sans-serif;
33
background-color: #f9f9fb;
4-
54
}
65

76
.container_login {
@@ -126,7 +125,6 @@ h1 {
126125
align-items: center;
127126
position: relative;
128127
bottom: 10%;
129-
/* background-color: #f9f9fb; */
130128
}
131129

132130
.logged_in_div_left {
@@ -208,8 +206,7 @@ div.text_box {
208206
background-color: #f9f9fb;
209207
height: 60px;
210208
padding: 15px 30px 15px 30px;
211-
212-
z-index: 1000;
209+
z-index: 998;
213210
}
214211

215212
.logged_in_nav p {
@@ -273,20 +270,50 @@ pre.prettyprint {
273270
margin-bottom: 20px;
274271
}
275272

276-
.qr_div {
277-
align-self: center;
278-
padding-top: 15px;
279-
}
280-
281-
.qr_code {
282-
width: 7vw;
283-
max-width: 100px;
284-
}
285-
286273
.code-input {
287274
width: 75px;
288275
height: 100px;
289276
margin: 0px 5px 30px 5px;
290277
font-size: 60px;
291278
color: darkslategray;
279+
}
280+
281+
.overlay {
282+
position: fixed;
283+
top: 0;
284+
left: 0;
285+
right: 0;
286+
bottom: 0;
287+
background-color: rgba(0,0,0,0.5);
288+
z-index: 999;
289+
}
290+
291+
.modal {
292+
position: absolute;
293+
top: 50%;
294+
left: 50%;
295+
transform: translate(-50%, -50%);
296+
background-color: #fff;
297+
padding: 25px;
298+
border-radius: 10px;
299+
box-shadow: 0 0 10px rgba(0,0,0,0.5);
300+
z-index: 1000;
301+
display: flex;
302+
flex-direction: column;
303+
justify-content: center;
304+
align-items: center;
305+
}
306+
307+
.qr_code_instructions {
308+
width: 300px;
309+
}
310+
311+
.qr_code {
312+
width: 300px;
313+
margin: 25px;
314+
}
315+
316+
button:disabled {
317+
opacity: 0.5;
318+
pointer-events: none;
292319
}

python-flask-mfa-example/templates/enroll_factor.html

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,47 +42,108 @@ <h2 class="home-hero-gradient">Enterprise Ready</h2>
4242
<div class="flex space_between ">
4343
<div class="factor_card">
4444
<h2>Enroll SMS Factor</h2>
45-
<form method="POST" action="{{ url_for('enroll_factor') }}">
45+
<form method="POST" action="{{ url_for('enroll_sms_factor') }}">
4646
<div class="flex">
4747
<div class="flex">
4848
<input class="text_input" type="text" id="phone_number" name="phone_number"
49-
placeholder="Phone Number" required>
49+
placeholder="Phone Number">
5050
</div>
5151
</div>
52-
<div>
53-
<button type="submit" name="type" value="sms"
54-
class="button button-outline button-sm">Enroll New
52+
<div class="flex">
53+
<div>
54+
<button type="submit" name="type" value="sms" id="sms-factor-submit-btn"
55+
class="button button-outline button-sm" disabled>Enroll New
5556
Factor</button>
57+
</div>
5658
</div>
5759
</form>
5860
</div>
5961

6062
<div class="factor_card">
6163
<h2>Enroll TOTP Factor</h2>
62-
<form method="POST" action="{{ url_for('enroll_factor') }}">
64+
<div>
6365
<div class="flex-column">
6466
<div class="flex">
6567
<input class="text_input" type="text" id="totp_issuer" name="totp_issuer"
66-
placeholder="TOTP Issuer" required>
68+
placeholder="TOTP Issuer">
6769
</div>
6870
<div class="flex">
6971
<input class="text_input" type="text" id="totp_user" name="totp_user"
70-
placeholder="User Email" required>
72+
placeholder="User Email">
7173
</div>
7274
</div>
73-
<div>
74-
<button type="submit" name="type" value="totp"
75-
class="button button-outline button-sm">Enroll New Factor</button>
76-
</div>
77-
</form>
75+
<div class="flex">
76+
<div>
77+
<button value="totp" id="totp-factor-submit-btn" class="button button-outline button-sm" disabled>Enroll New Factor</button>
78+
</div>
79+
</div>
80+
</div>
7881
</div>
7982

8083
</div>
8184
</div>
8285

8386
</div>
8487
</div>
85-
88+
<div class="overlay" id="overlay" style="display: none;">
89+
<div class="modal" id="modal">
90+
<button id="close-modal-btn">I've scanned a QR code</button>
91+
</div>
92+
</div>
8693
</body>
8794

95+
<script>
96+
const phoneNumber = document.getElementById("phone_number")
97+
const totpIssuer = document.getElementById("totp_issuer")
98+
const totpUser = document.getElementById("totp_user")
99+
const smsSubmitButton = document.getElementById("sms-factor-submit-btn")
100+
const totpSubmitButton = document.getElementById("totp-factor-submit-btn")
101+
const closeModalBtn = document.getElementById("close-modal-btn")
102+
const overlay = document.getElementById("overlay")
103+
const modal = document.getElementById("modal")
104+
phoneNumber.addEventListener("input", validateSmsForm)
105+
totpIssuer.addEventListener("input", validateTotpForm)
106+
totpUser.addEventListener("input", validateTotpForm)
107+
function validateSmsForm() {
108+
if (phoneNumber.value.trim() !== "") {
109+
smsSubmitButton.disabled = false
110+
} else {
111+
smsSubmitButton.disabled = true
112+
}
113+
}
114+
function validateTotpForm() {
115+
if (totpIssuer.value.trim() !== "" && totpUser.value.trim() !== "") {
116+
totpSubmitButton.disabled = false
117+
} else {
118+
totpSubmitButton.disabled = true
119+
}
120+
}
121+
totpSubmitButton.addEventListener("click", function() {
122+
fetch('/enroll_totp_factor', {
123+
method: 'POST',
124+
headers: {
125+
'Content-Type': 'application/json'
126+
},
127+
body: JSON.stringify({
128+
type: 'totp',
129+
issuer: totpIssuer.value,
130+
user: totpUser.value,
131+
})
132+
})
133+
.then(response => response.json())
134+
.then(qr_code => {
135+
overlay.style.display = "block"
136+
modal.innerHTML = `
137+
<h2>Scan the QR code</h2>
138+
<p class="qr_code_instructions">Use the authenticator app to scan the QR code. After you scan the code, click 'Continue'.</p>
139+
<img class="qr_code" src=${qr_code} alt="qr_code">
140+
<a href="/" class="button button-outline">Continue</a>
141+
`
142+
})
143+
})
144+
closeModalBtn.addEventListener("click", function() {
145+
overlay.style.display = "none"
146+
})
147+
</script>
148+
88149
</html>

python-flask-mfa-example/templates/factor_detail.html

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,6 @@ <h2>ID: {{factor['id']}}</h2>
5252
<p>Updated At: <code>{{factor['updated_at']}}</code></p>
5353
</div>
5454
</div>
55-
{% if factor['type'] == 'totp' %}
56-
<div class="qr_div">
57-
<img class="qr_code" src="{{qr_code}}" alt="qr_code">
58-
</div>
59-
60-
{% endif %}
6155
</div>
6256

6357
<div class="flex-column">

0 commit comments

Comments
 (0)