Skip to content

Commit 79012d0

Browse files
edignotAdam Wolfman
and
Adam Wolfman
authored
Update MFA totp factor enroll flow (#76)
* enroll sms factor endpoint created * enroll totp factor endpoint created * enroll totp factor * totp factor detaqils updated * enroll factor url removed * styles update * remove logo border * save session * Update logo on templates --------- Co-authored-by: Adam Wolfman <[email protected]>
1 parent 70c7427 commit 79012d0

File tree

8 files changed

+156
-83
lines changed

8 files changed

+156
-83
lines changed

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

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ h1 {
122122
align-items: center;
123123
position: relative;
124124
bottom: 10%;
125-
/* background-color: #f9f9fb; */
126125
}
127126

128127
.logged_in_div_left {
@@ -204,8 +203,7 @@ div.text_box {
204203
background-color: #f9f9fb;
205204
height: 60px;
206205
padding: 15px 30px 15px 30px;
207-
208-
z-index: 1000;
206+
z-index: 998;
209207
}
210208

211209
.logged_in_nav p {
@@ -216,12 +214,6 @@ div.text_box {
216214

217215
.logged_in_nav img {
218216
height: 50px;
219-
border-radius: 50%;
220-
border: 2px solid #2f2e2e;
221-
}
222-
223-
.logged_in_nav img:hover {
224-
border: 2px solid #555555;
225217
}
226218

227219
.nav-item {
@@ -274,15 +266,6 @@ pre.prettyprint {
274266
margin-bottom: 20px;
275267
}
276268

277-
.qr_div {
278-
align-self: center;
279-
padding-top: 15px;
280-
}
281-
282-
.qr_code {
283-
width: 7vw;
284-
max-width: 100px;
285-
}
286269

287270
ul {
288271
list-style-type: none;
@@ -320,4 +303,44 @@ li.even {
320303

321304
.mt-20 {
322305
margin-top: 20px;
306+
}
307+
308+
.overlay {
309+
position: fixed;
310+
top: 0;
311+
left: 0;
312+
right: 0;
313+
bottom: 0;
314+
background-color: rgba(0,0,0,0.5);
315+
z-index: 999;
316+
}
317+
318+
.modal {
319+
position: absolute;
320+
top: 50%;
321+
left: 50%;
322+
transform: translate(-50%, -50%);
323+
background-color: #fff;
324+
padding: 25px;
325+
border-radius: 10px;
326+
box-shadow: 0 0 10px rgba(0,0,0,0.5);
327+
z-index: 1000;
328+
display: flex;
329+
flex-direction: column;
330+
justify-content: center;
331+
align-items: center;
332+
}
333+
334+
.qr_code_instructions {
335+
width: 300px;
336+
}
337+
338+
.qr_code {
339+
width: 300px;
340+
margin: 25px;
341+
}
342+
343+
button:disabled {
344+
opacity: 0.5;
345+
pointer-events: none;
323346
}

python-django-mfa-example/mfa/templates/mfa/challenge_factor.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,9 @@
1010
<div class="logged_in_nav">
1111
<div class="flex">
1212
<div>
13-
<img src="../static/images/workos_logo_new.png" alt="workos logo">
14-
</div>
15-
<div class="flex">
16-
<p>WorkOS</p>
13+
<img src="../static/images/workos-logo-with-text.png" alt="workos logo">
1714
</div>
15+
1816
</div>
1917
<div>
2018
<a href="https://workos.com/docs" target="_blank"><button class='button nav-item'>Documentation</button></a>

python-django-mfa-example/mfa/templates/mfa/enroll_factor_details.html

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
<div class="logged_in_nav">
1111
<div class="flex">
1212
<div>
13-
<img src="../static/images/workos_logo_new.png" alt="workos logo">
14-
</div>
15-
<div class="flex">
16-
<p>WorkOS</p>
13+
<img src="../static/images/workos-logo-with-text.png" alt="workos logo">
1714
</div>
1815
</div>
1916
<div>
@@ -47,15 +44,15 @@ <h2 class="home-hero-gradient">Enterprise Ready</h2>
4744
<div class="flex space_between ">
4845
<div class="factor_card">
4946
<h2>Enroll SMS Factor</h2>
50-
<form method="POST" action="{% url 'enroll_factor' %}">
47+
<form method="POST" action="{% url 'enroll_sms_factor' %}">
5148
<div class="flex-column">
5249
<div class="flex mt-20">
5350
<input class="text_input mt-20" type="text" id="phone_number" name="phone_number"
54-
placeholder="Phone Number" required>
51+
placeholder="Phone Number">
5552
</div>
5653
<div class="flex mt-20">
57-
<button type="submit" name="type" value="sms"
58-
class="button button-outline button-sm">Enroll New
54+
<button type="submit" name="type" value="sms" id="sms-factor-submit-btn"
55+
class="button button-outline button-sm" disabled>Enroll New
5956
Factor</button>
6057
</div>
6158
</div>
@@ -64,31 +61,88 @@ <h2>Enroll SMS Factor</h2>
6461

6562
<div class="factor_card">
6663
<h2>Enroll TOTP Factor</h2>
67-
<form method="POST" action="{% url 'enroll_factor' %}">
68-
{% csrf_token %}
64+
<div>
6965
<div class="flex-column">
7066
<div class="flex mt-20">
7167
<input class="text_input mt-20" type="text" id="totp_issuer" name="totp_issuer"
72-
placeholder="TOTP Issuer" required>
68+
placeholder="TOTP Issuer">
7369
</div>
7470
<div class="flex mt-10">
7571
<input class="text_input text_input_2" type="text" id="totp_user" name="totp_user"
76-
placeholder="User Email" required>
72+
placeholder="User Email">
7773
</div>
7874
<div class="flex mt-20">
79-
<button type="submit" name="type" value="totp"
80-
class="button button-outline button-sm">Enroll New Factor</button>
75+
<button value="totp" id="totp-factor-submit-btn"
76+
class="button button-outline button-sm" disabled>Enroll New Factor</button>
8177
</div>
8278
</div>
83-
</form>
79+
</div>
8480
</div>
8581

8682
</div>
8783
</div>
8884

8985
</div>
9086
</div>
91-
87+
<div class="overlay" id="overlay" style="display: none;">
88+
<div class="modal" id="modal">
89+
<button id="close-modal-btn">I've scanned a QR code</button>
90+
</div>
91+
</div>
9292
</body>
9393

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

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@
1111
<div class="logged_in_nav">
1212
<div class="flex">
1313
<div>
14-
<img src="../static/images/workos_logo_new.png" alt="workos logo">
15-
</div>
16-
<div class="flex">
17-
<p>WorkOS</p>
14+
<img src="../static/images/workos-logo-with-text.png" alt="workos logo">
1815
</div>
1916
</div>
2017
<div>
@@ -64,13 +61,6 @@ <h2>Factor Details</h2>
6461
<li>Created At: <code>{{factor.created_at}}</code></li>
6562
<li class="even">Updated At: <code>{{factor.updated_at}}</code></li>
6663
</ul>
67-
68-
69-
{% if factor.type == 'totp' %}
70-
<div class="qr_div">
71-
<img class="qr_code" src="{{ qr_code }}" alt="qr_code">
72-
</div>
73-
{% endif %}
7464
</div>
7565
</div>
7666
</div>

python-django-mfa-example/mfa/templates/mfa/list_factors.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<div class="logged_in_nav">
1212
<div class="flex">
1313
<div>
14-
<img src="../static/images/workos_logo_new.png" alt="workos logo">
14+
<img src="../static/images/workos-logo-with-text.png" alt="workos logo">
1515
</div>
1616
</div>
1717
<div>

python-django-mfa-example/mfa/templates/mfa/verify_factor.html

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
<div class="logged_in_nav">
1111
<div class="flex">
1212
<div>
13-
<img src="../static/images/workos_logo_new.png" alt="workos logo">
14-
</div>
15-
<div class="flex">
16-
<p>WorkOS</p>
13+
<img src="../static/images/workos-logo-with-text.png" alt="workos logo">
1714
</div>
1815
</div>
1916
<div>

python-django-mfa-example/mfa/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
views.enroll_factor_details,
1010
name="enroll_factor_details",
1111
),
12-
path("enroll_factor", views.enroll_factor, name="enroll_factor"),
12+
path("enroll_sms_factor", views.enroll_sms_factor, name="enroll_sms_factor"),
13+
path("enroll_totp_factor", views.enroll_totp_factor, name="enroll_totp_factor"),
1314
path("factor_detail", views.factor_detail, name="factor_detail"),
1415
path("challenge_factor", views.challenge_factor, name="challenge_factor"),
1516
path("verify_factor", views.verify_factor, name="verify_factor"),

python-django-mfa-example/mfa/views.py

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from django.conf import settings
44
from django.shortcuts import redirect, render
55
from django.views.decorators.csrf import csrf_exempt
6+
import json
7+
from django.http import HttpResponse
68

79

810
workos.api_key = os.getenv("WORKOS_API_KEY")
@@ -39,37 +41,49 @@ def enroll_factor_details(request):
3941

4042

4143
@csrf_exempt
42-
def enroll_factor(request):
44+
def enroll_sms_factor(request):
4345
factor_type = request.POST["type"]
46+
phone_number = request.POST["phone_number"]
4447

45-
if factor_type == "sms":
46-
factor_type = "sms"
47-
phone_number = request.POST["phone_number"]
48-
new_factor = workos.client.mfa.enroll_factor(
49-
type=factor_type, phone_number=phone_number
50-
)
51-
if request.session.get("factor_list"):
48+
new_factor = workos.client.mfa.enroll_factor(
49+
type=factor_type,
50+
phone_number=phone_number
51+
)
52+
53+
if request.session.get("factor_list"):
5254
request.session["factor_list"].append(new_factor)
53-
else:
54-
request.session["factor_list"] = [new_factor]
55-
56-
if factor_type == "totp":
57-
factor_type = "totp"
58-
totp_issuer = request.POST["totp_issuer"]
59-
totp_user = request.POST["totp_user"]
60-
new_factor = workos.client.mfa.enroll_factor(
61-
type=factor_type, totp_issuer=totp_issuer, totp_user=totp_user
62-
)
63-
if request.session.get("factor_list") != None:
64-
new_session_list = request.session["factor_list"]
65-
new_session_list.append(new_factor)
66-
request.session["factor_list"] = new_session_list
67-
else:
68-
request.session["factor_list"] = [new_factor]
55+
else:
56+
request.session["factor_list"] = [new_factor]
57+
58+
request.session.save()
6959

7060
return redirect("list_factors")
7161

7262

63+
@csrf_exempt
64+
def enroll_totp_factor(request):
65+
data = json.loads(request.body.decode("utf-8"))
66+
67+
type = data["type"]
68+
issuer = data["issuer"]
69+
user = data["user"]
70+
71+
new_factor = workos.client.mfa.enroll_factor(
72+
type=type,
73+
totp_issuer=issuer,
74+
totp_user=user
75+
)
76+
77+
if request.session.get("factor_list"):
78+
request.session["factor_list"].append(new_factor)
79+
else:
80+
request.session["factor_list"] = [new_factor]
81+
82+
request.session.save()
83+
84+
return HttpResponse(new_factor["totp"]["qr_code"])
85+
86+
7387
def factor_detail(request):
7488
factorId = request.GET["id"]
7589
for factor in request.session["factor_list"]:
@@ -80,9 +94,6 @@ def factor_detail(request):
8094
if factor["type"] == "sms":
8195
phone_number = factor["sms"]["phone_number"]
8296

83-
if factor["type"] == "totp":
84-
request.session["current_factor_qr"] = factor["totp"]["qr_code"]
85-
8697
request.session["current_factor"] = fullFactor["id"]
8798
request.session["current_factor_type"] = fullFactor["type"]
8899

@@ -92,7 +103,6 @@ def factor_detail(request):
92103
{
93104
"factor": fullFactor,
94105
"phone_number": phone_number,
95-
"qr_code": request.session["current_factor_qr"],
96106
},
97107
)
98108

0 commit comments

Comments
 (0)