WIP: Add Two Factor Authentication #7
4 changed files with 48 additions and 28 deletions
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
margin-bottom: 30px;
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
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:
|
|
||||||
<br/>
|
|
||||||
<i class="monospace">{{ .Key }}</i>
|
|
||||||
<br/><br/>
|
|
||||||
Afterwards please enter the passcode shown in the TOTP application here for confirmation:
|
|
||||||
<br/>
|
|
||||||
<form method="post" action="javascript:HandleMFASetupTokenSubmit();" style="width: fit-content;" autocomplete="off">
|
|
||||||
<div style="display: flex;">
|
|
||||||
<input type="text" name="token" id="token" inputmode="numeric" pattern="[0-9]{6}" autocomplete="one-time-code">
|
|
||||||
|
|
||||||
<input type="submit" value="Submit" class="contrast" style="width: fit-content;">
|
Or enter the secret manually into your TOTP application:
|
||||||
|
<br />
|
||||||
|
<i class="monospace">{{ .Key }}</i>
|
||||||
|
<br /><br />
|
||||||
|
Afterwards please enter the passcode shown in the TOTP application here for confirmation:
|
||||||
|
<br />
|
||||||
|
<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;">
|
||||||
|
<input type="text" name="token" id="token" inputmode="numeric" autocomplete="one-time-code">
|
||||||
|
|
||||||
|
<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;">
|
||||||
|
|
19
web/main.js
19
web/main.js
|
@ -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', '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue