Skip to content

Update MFA totp factor enroll flow #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 41 additions & 18 deletions python-django-mfa-example/mfa/static/css/login.css
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ h1 {
align-items: center;
position: relative;
bottom: 10%;
/* background-color: #f9f9fb; */
}

.logged_in_div_left {
Expand Down Expand Up @@ -204,8 +203,7 @@ div.text_box {
background-color: #f9f9fb;
height: 60px;
padding: 15px 30px 15px 30px;

z-index: 1000;
z-index: 998;
}

.logged_in_nav p {
Expand All @@ -216,12 +214,6 @@ div.text_box {

.logged_in_nav img {
height: 50px;
border-radius: 50%;
border: 2px solid #2f2e2e;
}

.logged_in_nav img:hover {
border: 2px solid #555555;
}

.nav-item {
Expand Down Expand Up @@ -274,15 +266,6 @@ pre.prettyprint {
margin-bottom: 20px;
}

.qr_div {
align-self: center;
padding-top: 15px;
}

.qr_code {
width: 7vw;
max-width: 100px;
}

ul {
list-style-type: none;
Expand Down Expand Up @@ -320,4 +303,44 @@ li.even {

.mt-20 {
margin-top: 20px;
}

.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
z-index: 999;
}

.modal {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
padding: 25px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
z-index: 1000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.qr_code_instructions {
width: 300px;
}

.qr_code {
width: 300px;
margin: 25px;
}

button:disabled {
opacity: 0.5;
pointer-events: none;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@
<div class="logged_in_nav">
<div class="flex">
<div>
<img src="../static/images/workos_logo_new.png" alt="workos logo">
</div>
<div class="flex">
<p>WorkOS</p>
<img src="../static/images/workos-logo-with-text.png" alt="workos logo">
</div>

</div>
<div>
<a href="https://workos.com/docs" target="_blank"><button class='button nav-item'>Documentation</button></a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
<div class="logged_in_nav">
<div class="flex">
<div>
<img src="../static/images/workos_logo_new.png" alt="workos logo">
</div>
<div class="flex">
<p>WorkOS</p>
<img src="../static/images/workos-logo-with-text.png" alt="workos logo">
</div>
</div>
<div>
Expand Down Expand Up @@ -47,15 +44,15 @@ <h2 class="home-hero-gradient">Enterprise Ready</h2>
<div class="flex space_between ">
<div class="factor_card">
<h2>Enroll SMS Factor</h2>
<form method="POST" action="{% url 'enroll_factor' %}">
<form method="POST" action="{% url 'enroll_sms_factor' %}">
<div class="flex-column">
<div class="flex mt-20">
<input class="text_input mt-20" type="text" id="phone_number" name="phone_number"
placeholder="Phone Number" required>
placeholder="Phone Number">
</div>
<div class="flex mt-20">
<button type="submit" name="type" value="sms"
class="button button-outline button-sm">Enroll New
<button type="submit" name="type" value="sms" id="sms-factor-submit-btn"
class="button button-outline button-sm" disabled>Enroll New
Factor</button>
</div>
</div>
Expand All @@ -64,31 +61,88 @@ <h2>Enroll SMS Factor</h2>

<div class="factor_card">
<h2>Enroll TOTP Factor</h2>
<form method="POST" action="{% url 'enroll_factor' %}">
{% csrf_token %}
<div>
<div class="flex-column">
<div class="flex mt-20">
<input class="text_input mt-20" type="text" id="totp_issuer" name="totp_issuer"
placeholder="TOTP Issuer" required>
placeholder="TOTP Issuer">
</div>
<div class="flex mt-10">
<input class="text_input text_input_2" type="text" id="totp_user" name="totp_user"
placeholder="User Email" required>
placeholder="User Email">
</div>
<div class="flex mt-20">
<button type="submit" name="type" value="totp"
class="button button-outline button-sm">Enroll New Factor</button>
<button value="totp" id="totp-factor-submit-btn"
class="button button-outline button-sm" disabled>Enroll New Factor</button>
</div>
</div>
</form>
</div>
</div>

</div>
</div>

</div>
</div>

<div class="overlay" id="overlay" style="display: none;">
<div class="modal" id="modal">
<button id="close-modal-btn">I've scanned a QR code</button>
</div>
</div>
</body>

<script>
const phoneNumber = document.getElementById("phone_number")
const totpIssuer = document.getElementById("totp_issuer")
const totpUser = document.getElementById("totp_user")
const smsSubmitButton = document.getElementById("sms-factor-submit-btn")
const totpSubmitButton = document.getElementById("totp-factor-submit-btn")
const closeModalBtn = document.getElementById("close-modal-btn")
const overlay = document.getElementById("overlay")
const modal = document.getElementById("modal")
phoneNumber.addEventListener("input", validateSmsForm)
totpIssuer.addEventListener("input", validateTotpForm)
totpUser.addEventListener("input", validateTotpForm)
function validateSmsForm() {
if (phoneNumber.value.trim() !== "") {
smsSubmitButton.disabled = false
} else {
smsSubmitButton.disabled = true
}
}
function validateTotpForm() {
if (totpIssuer.value.trim() !== "" && totpUser.value.trim() !== "") {
totpSubmitButton.disabled = false
} else {
totpSubmitButton.disabled = true
}
}
totpSubmitButton.addEventListener("click", function() {
fetch('/enroll_totp_factor', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'totp',
issuer: totpIssuer.value,
user: totpUser.value,
})
})
.then(response => response.text())
.then(qr_code => {
overlay.style.display = "block"
modal.innerHTML = `
<h2>Scan the QR code</h2>
<p class="qr_code_instructions">Use the authenticator app to scan the QR code. After you scan the code, click 'Continue'.</p>
<img class="qr_code" src=${qr_code} alt="qr_code">
<a href="/" class="button button-outline">Continue</a>
`
})
})
closeModalBtn.addEventListener("click", function() {
overlay.style.display = "none"
})
</script>

</html>
12 changes: 1 addition & 11 deletions python-django-mfa-example/mfa/templates/mfa/factor_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
<div class="logged_in_nav">
<div class="flex">
<div>
<img src="../static/images/workos_logo_new.png" alt="workos logo">
</div>
<div class="flex">
<p>WorkOS</p>
<img src="../static/images/workos-logo-with-text.png" alt="workos logo">
</div>
</div>
<div>
Expand Down Expand Up @@ -64,13 +61,6 @@ <h2>Factor Details</h2>
<li>Created At: <code>{{factor.created_at}}</code></li>
<li class="even">Updated At: <code>{{factor.updated_at}}</code></li>
</ul>


{% if factor.type == 'totp' %}
<div class="qr_div">
<img class="qr_code" src="{{ qr_code }}" alt="qr_code">
</div>
{% endif %}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<div class="logged_in_nav">
<div class="flex">
<div>
<img src="../static/images/workos_logo_new.png" alt="workos logo">
<img src="../static/images/workos-logo-with-text.png" alt="workos logo">
</div>
</div>
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
<div class="logged_in_nav">
<div class="flex">
<div>
<img src="../static/images/workos_logo_new.png" alt="workos logo">
</div>
<div class="flex">
<p>WorkOS</p>
<img src="../static/images/workos-logo-with-text.png" alt="workos logo">
</div>
</div>
<div>
Expand Down
3 changes: 2 additions & 1 deletion python-django-mfa-example/mfa/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
views.enroll_factor_details,
name="enroll_factor_details",
),
path("enroll_factor", views.enroll_factor, name="enroll_factor"),
path("enroll_sms_factor", views.enroll_sms_factor, name="enroll_sms_factor"),
path("enroll_totp_factor", views.enroll_totp_factor, name="enroll_totp_factor"),
path("factor_detail", views.factor_detail, name="factor_detail"),
path("challenge_factor", views.challenge_factor, name="challenge_factor"),
path("verify_factor", views.verify_factor, name="verify_factor"),
Expand Down
66 changes: 38 additions & 28 deletions python-django-mfa-example/mfa/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from django.conf import settings
from django.shortcuts import redirect, render
from django.views.decorators.csrf import csrf_exempt
import json
from django.http import HttpResponse


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


@csrf_exempt
def enroll_factor(request):
def enroll_sms_factor(request):
factor_type = request.POST["type"]
phone_number = request.POST["phone_number"]

if factor_type == "sms":
factor_type = "sms"
phone_number = request.POST["phone_number"]
new_factor = workos.client.mfa.enroll_factor(
type=factor_type, phone_number=phone_number
)
if request.session.get("factor_list"):
new_factor = workos.client.mfa.enroll_factor(
type=factor_type,
phone_number=phone_number
)

if request.session.get("factor_list"):
request.session["factor_list"].append(new_factor)
else:
request.session["factor_list"] = [new_factor]

if factor_type == "totp":
factor_type = "totp"
totp_issuer = request.POST["totp_issuer"]
totp_user = request.POST["totp_user"]
new_factor = workos.client.mfa.enroll_factor(
type=factor_type, totp_issuer=totp_issuer, totp_user=totp_user
)
if request.session.get("factor_list") != None:
new_session_list = request.session["factor_list"]
new_session_list.append(new_factor)
request.session["factor_list"] = new_session_list
else:
request.session["factor_list"] = [new_factor]
else:
request.session["factor_list"] = [new_factor]

request.session.save()

return redirect("list_factors")


@csrf_exempt
def enroll_totp_factor(request):
data = json.loads(request.body.decode("utf-8"))

type = data["type"]
issuer = data["issuer"]
user = data["user"]

new_factor = workos.client.mfa.enroll_factor(
type=type,
totp_issuer=issuer,
totp_user=user
)

if request.session.get("factor_list"):
request.session["factor_list"].append(new_factor)
else:
request.session["factor_list"] = [new_factor]

request.session.save()

return HttpResponse(new_factor["totp"]["qr_code"])


def factor_detail(request):
factorId = request.GET["id"]
for factor in request.session["factor_list"]:
Expand All @@ -80,9 +94,6 @@ def factor_detail(request):
if factor["type"] == "sms":
phone_number = factor["sms"]["phone_number"]

if factor["type"] == "totp":
request.session["current_factor_qr"] = factor["totp"]["qr_code"]

request.session["current_factor"] = fullFactor["id"]
request.session["current_factor_type"] = fullFactor["type"]

Expand All @@ -92,7 +103,6 @@ def factor_detail(request):
{
"factor": fullFactor,
"phone_number": phone_number,
"qr_code": request.session["current_factor_qr"],
},
)

Expand Down