WIP: Add Two Factor Authentication #7
5 changed files with 70 additions and 24 deletions
|
@ -1,7 +1,10 @@
|
||||||
package misc
|
package misc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"code.lila.network/adoralaura/go-urlsh/models"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,6 +33,11 @@ func New400Error() error {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request")
|
return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func New400WithMessageError(msg string) error {
|
||||||
|
body, _ := json.Marshal(models.HttpErrorBody{Message: msg})
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, string(body[:]))
|
||||||
|
}
|
||||||
|
|
||||||
func New401Error() error {
|
func New401Error() error {
|
||||||
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
|
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,13 @@ func HandleAdminLoginMFAPost(c *fiber.Ctx) error {
|
||||||
return misc.New400Error()
|
return misc.New400Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
var token models.TokenRequest
|
token := new(models.TokenRequest)
|
||||||
var istotp bool
|
var istotp bool
|
||||||
|
|
||||||
if err := c.BodyParser(&token); err != nil {
|
if err := c.BodyParser(token); err != nil {
|
||||||
// TODO: Debug logging
|
// TODO: Debug logging
|
||||||
return misc.New400Error()
|
return misc.New400Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(token.Token) == 6 {
|
if len(token.Token) == 6 {
|
||||||
istotp = true
|
istotp = true
|
||||||
} else if len(token.Token) == 8 {
|
} else if len(token.Token) == 8 {
|
||||||
|
@ -42,7 +41,7 @@ func HandleAdminLoginMFAPost(c *fiber.Ctx) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
// TODO: Debug logging
|
// TODO: Debug logging
|
||||||
return misc.New500Error()
|
return misc.New400Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// check token/scratch validity
|
// check token/scratch validity
|
||||||
|
@ -57,11 +56,10 @@ func HandleAdminLoginMFAPost(c *fiber.Ctx) error {
|
||||||
err = misc.SetLoginCookie(c, user, constants.LoginCookieExpiryDuration)
|
err = misc.SetLoginCookie(c, user, constants.LoginCookieExpiryDuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[HandleAdminLoginPost] Error setting cookie: %q\n", err)
|
log.Printf("[HandleAdminLoginPost] Error setting cookie: %q\n", err)
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
return misc.New500Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Status(fiber.StatusSeeOther)
|
c.Status(fiber.StatusOK)
|
||||||
c.Location("/admin/")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
models/error.go
Normal file
5
models/error.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type HttpErrorBody struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
|
@ -7,8 +7,11 @@
|
||||||
<title>Multi Factor Authentication - go-urlsh</title>
|
<title>Multi 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">
|
||||||
|
<script src="/admin/main.js" defer></script>
|
||||||
<style>
|
<style>
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -17,13 +20,16 @@
|
||||||
<article class="grid">
|
<article class="grid">
|
||||||
<div>
|
<div>
|
||||||
<hgroup>
|
<hgroup>
|
||||||
<h1 style='--color: var(--h1-color); font-weight: var(--font-weight); font-size: var(--font-size); font-family: var(--font-family);'>Multi Factor Authentication</h1>
|
<h1
|
||||||
|
style='--color: var(--h1-color); font-weight: var(--font-weight); font-size: var(--font-size); font-family: var(--font-family);'>
|
||||||
|
Multi Factor Authentication</h1>
|
||||||
|
|
||||||
</hgroup>
|
</hgroup>
|
||||||
<form method="post" action="/admin/login/multifactor" style="margin-bottom: 0;" autocomplete="off">
|
<form action="javascript:HandleMFALoginTokenPost()" style="margin-bottom: 0;" autocomplete="off">
|
||||||
<label for="token">Authentication Code (Format: 123456 or a1b2c3d4 for recovery codes)</label>
|
<label for="token">Authentication Code (Format: 123456 or a1b2c3d4 for recovery codes)</label>
|
||||||
<input type="text" name="token" id="token" autocomplete="one-time-code">
|
<input type="text" name="token" id="token" autocomplete="one-time-code">
|
||||||
|
<p id="error-message" class="hidden" style="color: red;">Error Message</p>
|
||||||
<input type="submit" value="Submit" class="contrast">
|
<input id="submit" type="submit" value="Submit" class="contrast">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
53
web/main.js
53
web/main.js
|
@ -1,6 +1,6 @@
|
||||||
// Link overview
|
// Link overview
|
||||||
|
|
||||||
async function HandleLinkIndexDelete(id){
|
async function HandleLinkIndexDelete(id) {
|
||||||
let response = await fetch("/api/v1/links/" + id, {
|
let response = await fetch("/api/v1/links/" + id, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
mode: "same-origin",
|
mode: "same-origin",
|
||||||
|
@ -30,7 +30,7 @@ async function HandleLinkEditSubmit() {
|
||||||
await LinkAction("edit")
|
await LinkAction("edit")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function LinkAction(action){
|
async function LinkAction(action) {
|
||||||
document.getElementById("submit").active = false
|
document.getElementById("submit").active = false
|
||||||
let slug = document.getElementById("linkname").value
|
let slug = document.getElementById("linkname").value
|
||||||
let url = document.getElementById("link").value
|
let url = document.getElementById("link").value
|
||||||
|
@ -38,22 +38,22 @@ async function LinkAction(action){
|
||||||
|
|
||||||
let method, endpoint = ""
|
let method, endpoint = ""
|
||||||
let body;
|
let body;
|
||||||
switch(action) {
|
switch (action) {
|
||||||
case "add":
|
case "add":
|
||||||
method = "POST"
|
method = "POST"
|
||||||
endpoint = "/api/v1/links/"
|
endpoint = "/api/v1/links/"
|
||||||
body = {
|
body = {
|
||||||
"id" : slug,
|
"id": slug,
|
||||||
"url" : url,
|
"url": url,
|
||||||
"description" : description
|
"description": description
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "edit":
|
case "edit":
|
||||||
method = "PUT"
|
method = "PUT"
|
||||||
endpoint = "/api/v1/links/" + slug
|
endpoint = "/api/v1/links/" + slug
|
||||||
body = {
|
body = {
|
||||||
"url" : url,
|
"url": url,
|
||||||
"description" : description
|
"description": description
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -82,8 +82,7 @@ async function LinkAction(action){
|
||||||
async function HandleLinkFieldChange() {
|
async function HandleLinkFieldChange() {
|
||||||
console.log("HandleChange")
|
console.log("HandleChange")
|
||||||
let buttonactive = true
|
let buttonactive = true
|
||||||
if (document.getElementById("link").value === "")
|
if (document.getElementById("link").value === "") {
|
||||||
{
|
|
||||||
buttonactive = false
|
buttonactive = false
|
||||||
}
|
}
|
||||||
document.getElementById("submit").active = buttonactive
|
document.getElementById("submit").active = buttonactive
|
||||||
|
@ -98,7 +97,7 @@ async function HandleApiKeyNewSubmit() {
|
||||||
button.setAttribute("aria-busy", "true")
|
button.setAttribute("aria-busy", "true")
|
||||||
|
|
||||||
let body = {
|
let body = {
|
||||||
"description" : description
|
"description": description
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = await fetch("/api/v1/apikeys", {
|
let response = await fetch("/api/v1/apikeys", {
|
||||||
|
@ -121,12 +120,13 @@ async function HandleMFASetupTokenSubmit() {
|
||||||
let token = document.getElementById("token").value
|
let token = document.getElementById("token").value
|
||||||
|
|
||||||
let body = {
|
let body = {
|
||||||
"token" : token
|
"token": token
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = await fetch("/admin/account/mfasetup", {
|
let response = await fetch("/admin/account/mfasetup", {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
|
headers: { "Content-Type": "application/json", },
|
||||||
mode: "same-origin",
|
mode: "same-origin",
|
||||||
method: "POST"
|
method: "POST"
|
||||||
});
|
});
|
||||||
|
@ -145,6 +145,35 @@ async function HandleMFASetupTokenSubmit() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function HandleMFALoginTokenPost() {
|
||||||
|
document.getElementById("submit").disabled = true;
|
||||||
|
document.getElementById("token").disabled = true;
|
||||||
|
let token = document.getElementById("token").value
|
||||||
|
let body = {
|
||||||
|
"token": token
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = await fetch("/admin/login/multifactor", {
|
||||||
|
credentials: "include",
|
||||||
|
headers: { "Content-Type": "application/json", },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
mode: "same-origin",
|
||||||
|
method: "POST"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
document.location = "/admin/"
|
||||||
|
} else {
|
||||||
|
document.getElementById("submit").disabled = false;
|
||||||
|
document.getElementById("token").disabled = false;
|
||||||
|
document.getElementById('error-message').innerHTML = "Two Factor Authentication failed. Please try again."
|
||||||
|
document.getElementById('error-message').setAttribute("class", "")
|
||||||
|
document.getElementById('token').value = ""
|
||||||
|
document.getElementById('token').setAttribute("aria-invalid", "true")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function HandleModalClose(redir) {
|
function HandleModalClose(redir) {
|
||||||
document.getElementById('dialog-success').close();
|
document.getElementById('dialog-success').close();
|
||||||
document.getElementById('dialog-error').close();
|
document.getElementById('dialog-error').close();
|
||||||
|
|
Loading…
Reference in a new issue