WIP: Add Two Factor Authentication #7

Draft
adoralaura wants to merge 11 commits from feature-2fa into main
4 changed files with 48 additions and 28 deletions
Showing only changes of commit 4c42d23cf4 - Show all commits

View file

@ -10,8 +10,8 @@ func addWebRoutes(f *fiber.App) {
f.Get("/admin/", web.HandleAdminLinkIndexGet) f.Get("/admin/", web.HandleAdminLinkIndexGet)
f.Get("/admin/account/", web.HandleAdminAccountGet) f.Get("/admin/account/", web.HandleAdminAccountGet)
f.Get("/admin/account/mfasetup", web.HandleAdminAccountMFASetupGet) f.Get("/admin/account/setup-multifactor", web.HandleAdminAccountMFASetupGet)
f.Post("/admin/account/mfasetup", web.HandleAdminAccountMFASetupPost) f.Post("/admin/account/mfa/confirm", web.HandleAdminAccountMFASetupPost)
f.Delete("/admin/account/mfa", web.HandleAdminAccountMFARemove) f.Delete("/admin/account/mfa", web.HandleAdminAccountMFARemove)
f.Get("/admin/login", web.HandleAdminLoginGet) f.Get("/admin/login", web.HandleAdminLoginGet)

View file

@ -60,7 +60,7 @@
{{if .MFAEnabled}} {{if .MFAEnabled}}
<a href="#" onclick="HandleMFARemovalRequest()" role="button">disable Two-Factor Authentication</a> <a href="#" onclick="HandleMFARemovalRequest()" role="button">disable Two-Factor Authentication</a>
{{else}} {{else}}
<a href="/admin/account/mfasetup" role="button">Setup Two-Factor Authentication</a> <a href="/admin/account/setup-multifactor" role="button">Setup Two-Factor Authentication</a>
{{end}} {{end}}
<dialog id="mfa-disable-dialog" style="flex-direction: column;"> <dialog id="mfa-disable-dialog" style="flex-direction: column;">
<h2 id="dialog-heading">⚠️ Are you sure you want to disable two-factor authentication?</h2> <h2 id="dialog-heading">⚠️ Are you sure you want to disable two-factor authentication?</h2>

View file

@ -4,30 +4,43 @@
<head> <head>
<meta charset='utf-8'> <meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'> <meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Setup MFA - go-urlsh</title> <title>Set up Two-Factor authentication - go-urlsh</title>
<meta name='viewport' content='width=device-width, initial-scale=1'> <meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel="stylesheet" href="/admin/pico.min.css"> <link rel="stylesheet" href="/admin/pico.min.css">
<link rel="stylesheet" href="/admin/pico.colors.min.css"> <link rel="stylesheet" href="/admin/pico.colors.min.css">
<link rel="stylesheet" href="/admin/custom.css"> <link rel="stylesheet" href="/admin/custom.css">
<script src="/admin/main.js" defer></script> <script src="/admin/main.js" defer></script>
<style> <style>
h1, h2, h3, h4, h5, h6 { h1,
h2,
h3,
h4,
h5,
h6 {
margin-bottom: 30px; margin-bottom: 30px;
} }
i { i {
font-style: normal; font-style: normal;
margin: 15px; margin: 15px;
} }
input { input {
margin-top: 15px; margin-top: 15px;
} }
img { img {
margin: 15px; margin: 15px;
} }
#token { #token {
width: 15em; width: 15em;
margin-right: 15px; margin-right: 15px;
} }
.hidden {
display: none;
}
</style> </style>
</head> </head>
@ -47,7 +60,7 @@
<a href="/admin/apikeys/">API Keys</a> <a href="/admin/apikeys/">API Keys</a>
</li> </li>
<li> <li>
<a href="javascript: void(0)" >Users (coming soon)</a> <a href="javascript: void(0)">Users (coming soon)</a>
</li> </li>
<li> <li>
<a href="javascript: Logout()" style="color: red;">Logout</a> <a href="javascript: Logout()" style="color: red;">Logout</a>
@ -55,21 +68,23 @@
</ul> </ul>
</nav> </nav>
<main class="container" style="padding: 0"> <main class="container" style="padding: 0">
<h1>Setup Multifactor Authentication</h1> <h1>Setup Two-Factor authentication</h1>
Scan this image with your authentication application:<br/> In order to setup Two-Factor authentication, please scan this image with your authentication application:<br />
<img src="data:image/png;base64,{{ .Image }}" alt="TOTP QR-Code" width="200" height="200"><br/> <img src="data:image/png;base64,{{ .Image }}" alt="TOTP QR-Code" width="200" height="200"><br />
Or enter the secret manually into your TOTP application: Or enter the secret manually into your TOTP application:
<br/> <br />
<i class="monospace">{{ .Key }}</i> <i class="monospace">{{ .Key }}</i>
<br/><br/> <br /><br />
Afterwards please enter the passcode shown in the TOTP application here for confirmation: Afterwards please enter the passcode shown in the TOTP application here for confirmation:
<br/> <br />
<form method="post" action="javascript:HandleMFASetupTokenSubmit();" style="width: fit-content;" autocomplete="off"> <form action="javascript:HandleMFASetupTokenSubmit();" style="width: fit-content;" autocomplete="off">
<p id="error-message" class="hidden" style="color: var(--pico-color-red-500)">
The TOTP code you provided is not correct. Please try again</p>
<div style="display: flex;"> <div style="display: flex;">
<input type="text" name="token" id="token" inputmode="numeric" pattern="[0-9]{6}" autocomplete="one-time-code"> <input type="text" name="token" id="token" inputmode="numeric" autocomplete="one-time-code">
<input type="submit" value="Submit" class="contrast" style="width: fit-content;"> <input id="button-submit" type="submit" value="Submit" class="contrast" style="width: fit-content;">
</div> </div>
</form> </form>
<dialog id="user-dialog" style="flex-direction: column;"> <dialog id="user-dialog" style="flex-direction: column;">

View file

@ -118,12 +118,16 @@ async function HandleApiKeyNewSubmit() {
async function HandleMFASetupTokenSubmit() { async function HandleMFASetupTokenSubmit() {
let token = document.getElementById("token").value let token = document.getElementById("token").value
let button = document.getElementById("button-submit")
button.disabled = true
button.setAttribute('aria-busy', 'true')
let body = { let body = {
"token": token "token": token
} }
let response = await fetch("/admin/account/mfasetup", { let response = await fetch("/admin/account/mfa/confirm", {
credentials: "include", credentials: "include",
body: JSON.stringify(body), body: JSON.stringify(body),
headers: { "Content-Type": "application/json", }, headers: { "Content-Type": "application/json", },
@ -135,13 +139,14 @@ async function HandleMFASetupTokenSubmit() {
let data = await response.json() let data = await response.json()
document.getElementById("dialog-tokens").textContent = data["recovery_tokens"].join(" ") document.getElementById("dialog-tokens").textContent = data["recovery_tokens"].join(" ")
document.getElementById('dialog-success').showModal() document.getElementById('user-dialog').showModal()
} else { } else if (response.status == 400) {
document.cookie = 'gourlsh_mfa_setup=; Max-Age=-1; path=/; domain=' + location.hostname; // document.cookie = 'gourlsh_mfa_setup=; Max-Age=-1; path=/; domain=' + location.hostname;
document.getElementById("dialog-heading").textContent = "Error!" document.getElementById('token').setAttribute('aria-invalid', 'true')
document.getElementById("dialog-text").textContent = "Something didnt work!" button.setAttribute('aria-busy', 'false')
document.getElementById('dialog-error').showModal() button.disabled = false
document.getElementById('error-message').setAttribute('class', '')
} }
} }