parent
895615ad6e
commit
5d7b540718
15 changed files with 71 additions and 2 deletions
|
@ -1,6 +1,7 @@
|
|||
package webui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path"
|
||||
|
@ -36,6 +37,14 @@ func handleAccountsGET(db *database.DB) http.Handler {
|
|||
|
||||
func handleAccountsPOST(db *database.DB) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
errorResponse(w, r, http.StatusBadRequest,
|
||||
fmt.Errorf("failed to parse form: %w", err))
|
||||
return
|
||||
}
|
||||
if !verifyCSRFToken(w, r) {
|
||||
return
|
||||
}
|
||||
accounts, err := db.LoadAccounts()
|
||||
if err != nil {
|
||||
errorResponse(w, r, http.StatusInternalServerError, err)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package webui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
|
@ -66,6 +67,14 @@ func handleAccountsIdResetPasswordPOST(db *database.DB) http.Handler {
|
|||
if account == nil {
|
||||
return
|
||||
}
|
||||
if err := r.ParseForm(); err != nil {
|
||||
errorResponse(w, r, http.StatusBadRequest,
|
||||
fmt.Errorf("failed to parse form: %w", err))
|
||||
return
|
||||
}
|
||||
if !verifyCSRFToken(w, r) {
|
||||
return
|
||||
}
|
||||
password := r.FormValue("password")
|
||||
if len(password) < 8 {
|
||||
errorResponse(w, r, http.StatusBadRequest, nil)
|
||||
|
|
30
pkg/webui/csrf.go
Normal file
30
pkg/webui/csrf.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package webui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.adyxax.org/adyxax/tfstated/pkg/model"
|
||||
"go.n16f.net/uuid"
|
||||
)
|
||||
|
||||
func verifyCSRFToken(w http.ResponseWriter, r *http.Request) bool {
|
||||
session := r.Context().Value(model.SessionContextKey{}).(*model.Session)
|
||||
tokenStr := r.FormValue("csrf_token")
|
||||
if tokenStr == "" {
|
||||
tokenStr = r.Header.Get("X-XSRF-Token")
|
||||
}
|
||||
var token uuid.UUID
|
||||
if err := token.Parse(tokenStr); err != nil {
|
||||
errorResponse(w, r, http.StatusBadRequest,
|
||||
fmt.Errorf("failed to parse csrf token: %w", err))
|
||||
return false
|
||||
}
|
||||
|
||||
if !token.Equal(session.Data.CsrfToken) {
|
||||
errorResponse(w, r, http.StatusForbidden,
|
||||
fmt.Errorf("invalid csrf token"))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
</div>
|
||||
{{ if .Page.Session.Data.Account.IsAdmin }}
|
||||
<form action="/accounts" enctype="multipart/form-data" method="post">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<fieldset>
|
||||
<legend>New User Account</legend>
|
||||
<div class="grid-2">
|
||||
|
@ -40,6 +41,7 @@
|
|||
{{ end }}
|
||||
<div style="align-self:stretch; display:flex; justify-content:flex-end;">
|
||||
<button class="primary" type="submit" value="submit">Create User Account</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{{ end }}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
{{ if .Page.Session.Data.Account.IsAdmin }}
|
||||
<h2>Operations</h2>
|
||||
<form action="/accounts/{{ .Account.Id }}" enctype="multipart/form-data" method="post">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<div class="flex-row">
|
||||
<fieldset>
|
||||
<legend>Edit User Account</legend>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<h1>User Account</h1>
|
||||
<h2>Password Reset</h2>
|
||||
<form action="/accounts/{{ .Account.Id }}/reset/{{ .Token }}" enctype="multipart/form-data" method="post">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<fieldset>
|
||||
<legend>Set Password</legend>
|
||||
<p>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{{ define "main" }}
|
||||
<div style="display: grid;">
|
||||
<form action="/login" method="post" style="align-self:center; justify-self: center;">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<fieldset style="align-items:center; display:flex; flex-direction:column; gap:8px;">
|
||||
<legend>Login</legend>
|
||||
<div style="align-items:center; display:flex; flex-direction:row; gap:8px;">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{{ define "main" }}
|
||||
<h1>Settings</h1>
|
||||
<form action="/settings" method="post">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<fieldset>
|
||||
<legend>Account Settings</legend>
|
||||
<div style="align-items:center; display:grid; grid-template-columns:1fr 1fr;">
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<p>You also have the option to upload a JSON state file in order to create a new state in TfStated. This is equivalent to using the <code>state push</code> command of OpenTofu/Terraform on a brand new state.</p>
|
||||
</div>
|
||||
<form action="/states" enctype="multipart/form-data" method="post">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<fieldset>
|
||||
<legend>New State</legend>
|
||||
<div class="grid-2">
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<h2>Operations</h2>
|
||||
<div class="flex-row">
|
||||
<form action="/states/{{ .State.Id }}" method="post">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<fieldset>
|
||||
<legend>Edit State</legend>
|
||||
<div class="flex-row">
|
||||
|
@ -60,6 +61,7 @@
|
|||
</fieldset>
|
||||
</form>
|
||||
<form action="/states/{{ .State.Id }}" method="post">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<fieldset>
|
||||
<legend>Danger Zone</legend>
|
||||
<button action="delete" type="submit" value="delete">Delete State</button>
|
||||
|
|
|
@ -52,6 +52,9 @@ func handleLoginPOST(db *database.DB) http.Handler {
|
|||
fmt.Errorf("failed to parse form: %w", err))
|
||||
return
|
||||
}
|
||||
if !verifyCSRFToken(w, r) {
|
||||
return
|
||||
}
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ func addRoutes(
|
|||
mux.Handle("GET /login", requireSession(handleLoginGET()))
|
||||
mux.Handle("POST /login", requireSession(handleLoginPOST(db)))
|
||||
mux.Handle("GET /logout", requireLogin(handleLogoutGET(db)))
|
||||
mux.Handle("GET /settings", requireLogin(handleSettingsGET(db)))
|
||||
mux.Handle("GET /settings", requireLogin(handleSettingsGET()))
|
||||
mux.Handle("POST /settings", requireLogin(handleSettingsPOST(db)))
|
||||
mux.Handle("GET /states", requireLogin(handleStatesGET(db)))
|
||||
mux.Handle("POST /states", requireLogin(handleStatesPOST(db)))
|
||||
|
|
|
@ -16,7 +16,7 @@ type SettingsPage struct {
|
|||
|
||||
var settingsTemplates = template.Must(template.ParseFS(htmlFS, "html/base.html", "html/settings.html"))
|
||||
|
||||
func handleSettingsGET(db *database.DB) http.Handler {
|
||||
func handleSettingsGET() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
render(w, settingsTemplates, http.StatusOK, SettingsPage{
|
||||
Page: makePage(r, &Page{Title: "Settings", Section: "settings"}),
|
||||
|
@ -30,6 +30,9 @@ func handleSettingsPOST(db *database.DB) http.Handler {
|
|||
errorResponse(w, r, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if !verifyCSRFToken(w, r) {
|
||||
return
|
||||
}
|
||||
darkMode := r.FormValue("dark-mode")
|
||||
settings := model.Settings{
|
||||
LightMode: darkMode != "1",
|
||||
|
|
|
@ -44,6 +44,9 @@ func handleStatesPOST(db *database.DB) http.Handler {
|
|||
errorResponse(w, r, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if !verifyCSRFToken(w, r) {
|
||||
return
|
||||
}
|
||||
file, _, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
errorResponse(w, r, http.StatusBadRequest, err)
|
||||
|
|
|
@ -63,6 +63,9 @@ func handleStatesIdPOST(db *database.DB) http.Handler {
|
|||
errorResponse(w, r, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if !verifyCSRFToken(w, r) {
|
||||
return
|
||||
}
|
||||
action := r.FormValue("action")
|
||||
var stateId uuid.UUID
|
||||
if err := stateId.Parse(r.PathValue("id")); err != nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue