Compare commits
No commits in common. "b4707e200fab8e8bf1de2cb7504fb9473015d5f5" and "a019e55c74a984251c4004dbeafc6cc97de66339" have entirely different histories.
b4707e200f
...
a019e55c74
24 changed files with 30 additions and 161 deletions
|
@ -12,7 +12,6 @@ func addWebRoutes(f *fiber.App) {
|
||||||
f.Get("/admin/account/", web.HandleAdminAccountGet)
|
f.Get("/admin/account/", web.HandleAdminAccountGet)
|
||||||
f.Get("/admin/account/mfasetup", web.HandleAdminAccountMFASetupGet)
|
f.Get("/admin/account/mfasetup", web.HandleAdminAccountMFASetupGet)
|
||||||
f.Post("/admin/account/mfasetup", web.HandleAdminAccountMFASetupPost)
|
f.Post("/admin/account/mfasetup", web.HandleAdminAccountMFASetupPost)
|
||||||
f.Delete("/admin/account/mfa", web.HandleAdminAccountMFARemove)
|
|
||||||
|
|
||||||
f.Get("/admin/login", web.HandleAdminLoginGet)
|
f.Get("/admin/login", web.HandleAdminLoginGet)
|
||||||
f.Post("/admin/login", web.HandleAdminLoginPost)
|
f.Post("/admin/login", web.HandleAdminLoginPost)
|
||||||
|
|
|
@ -3,14 +3,10 @@ package db
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.lila.network/adoralaura/go-urlsh/models"
|
"code.lila.network/adoralaura/go-urlsh/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserHasMFA checks the DB if given models.User has MFA enabled.
|
|
||||||
// Returns (true, nil) if User has MFA enabled, (false, nil) if not.
|
|
||||||
// (false, error) if a DB error happened
|
|
||||||
func UserHasMFA(user models.User) (bool, error) {
|
func UserHasMFA(user models.User) (bool, error) {
|
||||||
numrows, err := models.DB.NewSelect().Model((*models.MFAConfig)(nil)).Where("username = ?", user.UserName).Where("active = ?", true).Count(context.Background())
|
numrows, err := models.DB.NewSelect().Model((*models.MFAConfig)(nil)).Where("username = ?", user.UserName).Where("active = ?", true).Count(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,30 +33,3 @@ func ScratchCodeIsUnique(scratchcode string) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveMFAFromDB removes MFA entries for given models.User from the database.
|
|
||||||
// Returns nil on success, error otherwise.
|
|
||||||
func RemoveMFAFromDB(user models.User) error {
|
|
||||||
hasMfa, err := UserHasMFA(user)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("[RemoveMFAFromDB] Error removing MFA from DB for user %v: %w", user.UserName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasMfa {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = models.DB.NewDelete().Model((*models.MFAConfig)(nil)).Where("username = ?", user.UserName).Exec(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return fmt.Errorf("[RemoveMFAFromDB] Error removing MFA Config from DB for user %v: %w", user.UserName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = models.DB.NewDelete().Model((*models.MFAScratchCode)(nil)).Where("username = ?", user.UserName).Exec(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return fmt.Errorf("[RemoveMFAFromDB] Error removing MFA scratch codes from DB for user %v: %w", user.UserName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"code.lila.network/adoralaura/go-urlsh/internal/constants"
|
|
||||||
"code.lila.network/adoralaura/go-urlsh/internal/db"
|
|
||||||
"code.lila.network/adoralaura/go-urlsh/internal/misc"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleAdminAccountMFARemove is a DELETE endpoint that handles the deletion
|
|
||||||
// of the logged in users MFA configuration.
|
|
||||||
//
|
|
||||||
// Returns HTTP 401 if no valid user cookie, HTTP 400 if no MFA is configured for the user,
|
|
||||||
// HTTP 500 if a DB error happened or HTTP 204 if the deletion request succeeded.
|
|
||||||
func HandleAdminAccountMFARemove(c *fiber.Ctx) error {
|
|
||||||
|
|
||||||
if !db.IsCookieValid(c.Cookies(constants.LoginCookieName, "")) {
|
|
||||||
c.Status(http.StatusUnauthorized)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := db.GetUserFromCookie(c.Cookies(constants.LoginCookieName))
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
|
||||||
}
|
|
||||||
|
|
||||||
hasMfa, err := db.UserHasMFA(user)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasMfa {
|
|
||||||
return misc.New400Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.RemoveMFAFromDB(user)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Status(fiber.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -7,19 +7,12 @@
|
||||||
<title>Account - go-urlsh</title>
|
<title>Account - 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/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,
|
h1, h2, h3, h4, h5, h6 {
|
||||||
h2,
|
margin-bottom: 30px;
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a[role=button] {
|
a[role=button] {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +35,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>
|
||||||
|
@ -58,22 +51,16 @@
|
||||||
|
|
||||||
<h2>Security</h2>
|
<h2>Security</h2>
|
||||||
{{if .MFAEnabled}}
|
{{if .MFAEnabled}}
|
||||||
<a href="#" onclick="HandleMFARemovalRequest()" role="button">disable Two-Factor Authentication</a>
|
<a role="button" class="secondary">2fa already enabled</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a href="/admin/account/mfasetup" role="button">Setup Two-Factor Authentication</a>
|
<a href="/admin/account/mfasetup" role="button">Setup 2fa</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<dialog id="mfa-disable-dialog" style="flex-direction: column;">
|
<dialog id="dialog-info">
|
||||||
<h2 id="dialog-heading">⚠️ Are you sure you want to disable two-factor authentication?</h2>
|
<h2 id="dialog-heading"></h2>
|
||||||
<p id="dialog-text">Two-factor authentication adds an additional layer of security to your account by requiring
|
<p id="dialog-text"></p>
|
||||||
more than just a password to sign in.<br />If you need to change your configuration, you can delete and re-add
|
<form method="dialog">
|
||||||
Two-Factor authentication again.<br /><br />Do you really want to remove your Two-Factor authentication?</p>
|
<button>Close</button>
|
||||||
<div class="container grid">
|
</form>
|
||||||
|
|
||||||
<!-- change those two buttons and rework the functions to close and initiate deletion-->
|
|
||||||
<button id="button-keep" class="pico-background-green-450" onclick="HandleModalClose('mfa-disable-dialog')">No, keep Two-Factor authentication</button>
|
|
||||||
<button id="button-delete" class="pico-color-red-450 pico-background-red-450" onclick="HandleMFARemoval()">Yes,
|
|
||||||
remove Two-Factor authentication</button>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
</dialog>
|
||||||
</main>
|
</main>
|
||||||
{{template "partials/footer" .}}
|
{{template "partials/footer" .}}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<title>Add new Shortlink - go-urlsh</title>
|
<title>Add new Shortlink - 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/custom.css">
|
<link rel="stylesheet" href="/admin/custom.css">
|
||||||
<script src="/admin/main.js" defer></script>
|
<script src="/admin/main.js" defer></script>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<title>Edit Shortlink - go-urlsh</title>
|
<title>Edit Shortlink - 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/custom.css">
|
<link rel="stylesheet" href="/admin/custom.css">
|
||||||
<script src="/admin/main.js" defer></script>
|
<script src="/admin/main.js" defer></script>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<title>Shortlinks - go-urlsh</title>
|
<title>Shortlinks - 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/custom.css">
|
<link rel="stylesheet" href="/admin/custom.css">
|
||||||
<script src="/admin/main.js" defer></script>
|
<script src="/admin/main.js" defer></script>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<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">
|
||||||
<link rel="stylesheet" href="/admin/pico.colors.min.css">
|
|
||||||
<script src="/admin/main.js" defer></script>
|
<script src="/admin/main.js" defer></script>
|
||||||
<style>
|
<style>
|
||||||
.hidden {
|
.hidden {
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<title>Login - go-urlsh</title>
|
<title>Login - 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">
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<title>Setup MFA - go-urlsh</title>
|
<title>Setup MFA - 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/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>
|
||||||
|
@ -72,19 +71,19 @@
|
||||||
<input type="submit" value="Submit" class="contrast" style="width: fit-content;">
|
<input type="submit" value="Submit" class="contrast" style="width: fit-content;">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<dialog id="user-dialog" style="flex-direction: column;">
|
<dialog id="dialog-success" style="flex-direction: column;">
|
||||||
<h2 id="dialog-heading">2FA successfully set up!</h2>
|
<h2 id="dialog-heading">2FA successfully set up!</h2>
|
||||||
<p id="dialog-text">These are your recovery codes, keep them somewhere safe, you'll only see them once:</p>
|
<p id="dialog-text">These are your recovery codes, keep them somewhere safe, you'll only see them once:</p>
|
||||||
<p id="dialog-tokens" class="monospace"></p>
|
<p id="dialog-tokens" class="monospace"></p>
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
<button onclick="HandleModalClose('user-dialog', '/admin/account')">Close</button>
|
<button onclick="HandleModalClose('/admin/account')">Close</button>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
<dialog id="dialog-error" style="flex-direction: column;">
|
<dialog id="dialog-error" style="flex-direction: column;">
|
||||||
<h2 id="dialog-heading"></h2>
|
<h2 id="dialog-heading"></h2>
|
||||||
<p id="dialog-text"></p>
|
<p id="dialog-text"></p>
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
<button onclick="HandleModalClose('dialog-error')">Close</button>
|
<button onclick="HandleModalClose()">Close</button>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
<title>Add new Shortlink - go-urlsh</title>
|
<title>Add new Shortlink - 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/custom.css">
|
<link rel="stylesheet" href="/admin/custom.css">
|
||||||
|
<script src="/admin/link_add.js" defer></script>
|
||||||
<script src="/admin/misc.js" defer></script>
|
<script src="/admin/misc.js" defer></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<title>Edit Shortlink - go-urlsh</title>
|
<title>Edit Shortlink - 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/custom.css">
|
<link rel="stylesheet" href="/admin/custom.css">
|
||||||
<script src="/admin/main.js" defer></script>
|
<script src="/admin/main.js" defer></script>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<title>Shortlinks - go-urlsh</title>
|
<title>Shortlinks - 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/custom.css">
|
<link rel="stylesheet" href="/admin/custom.css">
|
||||||
<script src="/admin/main.js" defer></script>
|
<script src="/admin/main.js" defer></script>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<title>Login - go-urlsh</title>
|
<title>Login - 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">
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
38
web/main.js
38
web/main.js
|
@ -145,39 +145,6 @@ async function HandleMFASetupTokenSubmit() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function HandleMFARemovalRequest() {
|
|
||||||
document.getElementById('mfa-disable-dialog').showModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function HandleMFARemoval() {
|
|
||||||
let keepButton = document.getElementById('button-keep')
|
|
||||||
let deleteButton = document.getElementById('button-delete')
|
|
||||||
|
|
||||||
keepButton.disabled = true
|
|
||||||
deleteButton.disabled = true
|
|
||||||
deleteButton.value = 'Please wait...'
|
|
||||||
deleteButton.setAttribute('aria-busy', 'true')
|
|
||||||
|
|
||||||
endpoint = "/admin/account/mfa"
|
|
||||||
let response = await fetch(endpoint, {
|
|
||||||
credentials: "include",
|
|
||||||
mode: "same-origin",
|
|
||||||
method: "DELETE"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
document.location = "/admin/account"
|
|
||||||
} //else {
|
|
||||||
// TODO
|
|
||||||
// Handle error condition
|
|
||||||
//document.cookie = 'gourlsh_mfa_setup=; Max-Age=-1; path=/; domain=' + location.hostname;
|
|
||||||
|
|
||||||
//document.getElementById("dialog-heading").textContent = "Error!"
|
|
||||||
//document.getElementById("dialog-text").textContent = "Something didnt work!"
|
|
||||||
//document.getElementById('dialog-error').showModal()
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function HandleMFALoginTokenPost() {
|
async function HandleMFALoginTokenPost() {
|
||||||
document.getElementById("submit").disabled = true;
|
document.getElementById("submit").disabled = true;
|
||||||
document.getElementById("token").disabled = true;
|
document.getElementById("token").disabled = true;
|
||||||
|
@ -207,8 +174,9 @@ async function HandleMFALoginTokenPost() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function HandleModalClose(id, redir) {
|
function HandleModalClose(redir) {
|
||||||
document.getElementById(id).close();
|
document.getElementById('dialog-success').close();
|
||||||
|
document.getElementById('dialog-error').close();
|
||||||
|
|
||||||
if (redir) {
|
if (redir) {
|
||||||
document.location = redir
|
document.location = redir
|
||||||
|
|
5
web/pico.classless.min.css
vendored
Normal file
5
web/pico.classless.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
web/pico.classless.min.css.gz
Normal file
BIN
web/pico.classless.min.css.gz
Normal file
Binary file not shown.
1
web/pico.classless.min.css.map
Normal file
1
web/pico.classless.min.css.map
Normal file
File diff suppressed because one or more lines are too long
BIN
web/pico.classless.min.css.map.gz
Normal file
BIN
web/pico.classless.min.css.map.gz
Normal file
Binary file not shown.
4
web/pico.colors.min.css
vendored
4
web/pico.colors.min.css
vendored
File diff suppressed because one or more lines are too long
7
web/pico.min.css
vendored
7
web/pico.min.css
vendored
File diff suppressed because one or more lines are too long
1
web/pico.min.css.map
Normal file
1
web/pico.min.css.map
Normal file
File diff suppressed because one or more lines are too long
BIN
web/pico.min.css.map.gz
Normal file
BIN
web/pico.min.css.map.gz
Normal file
Binary file not shown.
Loading…
Reference in a new issue