parent
20bc9fe17a
commit
4f68621bad
9 changed files with 126 additions and 42 deletions
|
@ -28,8 +28,9 @@ func (db *DB) CreateAccount(username string, isAdmin bool) (*model.Account, erro
|
|||
if err := passwordReset.Generate(uuid.V4); err != nil {
|
||||
return nil, fmt.Errorf("failed to generate password reset uuid: %w", err)
|
||||
}
|
||||
_, err := db.Exec(`INSERT INTO accounts(id, username, is_Admin, settings, password_reset)
|
||||
VALUES (?, ?, ?, jsonb(?), ?);`,
|
||||
_, err := db.Exec(
|
||||
`INSERT INTO accounts(id, username, is_Admin, settings, password_reset)
|
||||
VALUES (?, ?, ?, jsonb(?), ?);`,
|
||||
accountId,
|
||||
username,
|
||||
isAdmin,
|
||||
|
|
|
@ -54,7 +54,17 @@ func (db *DB) DeleteExpiredSessions() error {
|
|||
func (db *DB) DeleteSession(session *model.Session) error {
|
||||
_, err := db.Exec(`DELETE FROM sessions WHERE id = ?`, session.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete session %s: %w", session.Id, err)
|
||||
return fmt.Errorf("failed to delete session: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) DeleteSessions(account *model.Account) error {
|
||||
_, err := db.Exec(
|
||||
`DELETE FROM sessions WHERE data->'account'->>'id' = ?`,
|
||||
account.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete sessions: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package model
|
|||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.adyxax.org/adyxax/tfstated/pkg/helpers"
|
||||
|
@ -27,6 +28,17 @@ func (account *Account) CheckPassword(password string) bool {
|
|||
return subtle.ConstantTimeCompare(hash, account.PasswordHash) == 1
|
||||
}
|
||||
|
||||
func (account *Account) ResetPassword() error {
|
||||
var passwordReset uuid.UUID
|
||||
if err := passwordReset.Generate(uuid.V4); err != nil {
|
||||
return fmt.Errorf("failed to generate password reset uuid: %w", err)
|
||||
}
|
||||
account.Salt = nil
|
||||
account.PasswordHash = nil
|
||||
account.PasswordReset = &passwordReset
|
||||
return nil
|
||||
}
|
||||
|
||||
func (account *Account) SetPassword(password string) {
|
||||
account.Salt = helpers.GenerateSalt()
|
||||
account.PasswordHash = helpers.HashPassword(password, account.Salt)
|
||||
|
|
|
@ -23,45 +23,98 @@ type AccountsIdPage struct {
|
|||
|
||||
var accountsIdTemplates = template.Must(template.ParseFS(htmlFS, "html/base.html", "html/accountsId.html"))
|
||||
|
||||
func prepareAccountsIdPage(db *database.DB, w http.ResponseWriter, r *http.Request) *AccountsIdPage {
|
||||
var accountId uuid.UUID
|
||||
if err := accountId.Parse(r.PathValue("id")); err != nil {
|
||||
errorResponse(w, r, http.StatusBadRequest, err)
|
||||
return nil
|
||||
}
|
||||
account, err := db.LoadAccountById(&accountId)
|
||||
if err != nil {
|
||||
errorResponse(w, r, http.StatusInternalServerError, err)
|
||||
return nil
|
||||
}
|
||||
if account == nil {
|
||||
errorResponse(w, r, http.StatusNotFound, fmt.Errorf("The account Id could not be found."))
|
||||
return nil
|
||||
}
|
||||
statePaths, err := db.LoadStatePaths()
|
||||
if err != nil {
|
||||
errorResponse(w, r, http.StatusInternalServerError, err)
|
||||
return nil
|
||||
}
|
||||
versions, err := db.LoadVersionsByAccount(account)
|
||||
if err != nil {
|
||||
errorResponse(w, r, http.StatusInternalServerError, err)
|
||||
return nil
|
||||
}
|
||||
isAdmin := ""
|
||||
if account.IsAdmin {
|
||||
isAdmin = "1"
|
||||
}
|
||||
return &AccountsIdPage{
|
||||
Account: account,
|
||||
IsAdmin: isAdmin,
|
||||
Page: makePage(r, &Page{
|
||||
Section: "accounts",
|
||||
Title: account.Username,
|
||||
}),
|
||||
StatePaths: statePaths,
|
||||
Versions: versions,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleAccountsIdGET(db *database.DB) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var accountId uuid.UUID
|
||||
if err := accountId.Parse(r.PathValue("id")); err != nil {
|
||||
page := prepareAccountsIdPage(db, w, r)
|
||||
if page != nil {
|
||||
render(w, accountsIdTemplates, http.StatusOK, page)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func handleAccountsIdPOST(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, err)
|
||||
return
|
||||
}
|
||||
account, err := db.LoadAccountById(&accountId)
|
||||
if err != nil {
|
||||
errorResponse(w, r, http.StatusInternalServerError, err)
|
||||
if !verifyCSRFToken(w, r) {
|
||||
return
|
||||
}
|
||||
if account == nil {
|
||||
errorResponse(w, r, http.StatusNotFound, fmt.Errorf("The account Id could not be found."))
|
||||
page := prepareAccountsIdPage(db, w, r)
|
||||
if page == nil {
|
||||
return
|
||||
}
|
||||
statePaths, err := db.LoadStatePaths()
|
||||
if err != nil {
|
||||
errorResponse(w, r, http.StatusInternalServerError, err)
|
||||
action := r.FormValue("action")
|
||||
switch action {
|
||||
case "delete":
|
||||
errorResponse(w, r, http.StatusNotImplemented, nil)
|
||||
return
|
||||
case "edit":
|
||||
errorResponse(w, r, http.StatusNotImplemented, nil)
|
||||
return
|
||||
case "reset-password":
|
||||
if err := page.Account.ResetPassword(); err != nil {
|
||||
errorResponse(w, r, http.StatusNotImplemented,
|
||||
fmt.Errorf("failed to reset password: %w", err))
|
||||
return
|
||||
}
|
||||
if err := db.SaveAccount(page.Account); err != nil {
|
||||
errorResponse(w, r, http.StatusInternalServerError,
|
||||
fmt.Errorf("failed to save account: %w", err))
|
||||
return
|
||||
}
|
||||
if err := db.DeleteSessions(page.Account); err != nil {
|
||||
errorResponse(w, r, http.StatusInternalServerError,
|
||||
fmt.Errorf("failed to save account: %w", err))
|
||||
return
|
||||
}
|
||||
default:
|
||||
errorResponse(w, r, http.StatusBadRequest, nil)
|
||||
return
|
||||
}
|
||||
versions, err := db.LoadVersionsByAccount(account)
|
||||
if err != nil {
|
||||
errorResponse(w, r, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
isAdmin := ""
|
||||
if account.IsAdmin {
|
||||
isAdmin = "1"
|
||||
}
|
||||
render(w, accountsIdTemplates, http.StatusOK, AccountsIdPage{
|
||||
Account: account,
|
||||
IsAdmin: isAdmin,
|
||||
Page: makePage(r, &Page{
|
||||
Section: "accounts",
|
||||
Title: account.Username,
|
||||
}),
|
||||
StatePaths: statePaths,
|
||||
Versions: versions,
|
||||
})
|
||||
render(w, accountsIdTemplates, http.StatusOK, page)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -82,7 +82,8 @@ func handleAccountsIdResetPasswordPOST(db *database.DB) http.Handler {
|
|||
}
|
||||
account.SetPassword(password)
|
||||
if err := db.SaveAccount(account); err != nil {
|
||||
errorResponse(w, r, http.StatusInternalServerError, err)
|
||||
errorResponse(w, r, http.StatusInternalServerError,
|
||||
fmt.Errorf("failed to save account: %w", err))
|
||||
return
|
||||
}
|
||||
render(w, accountsIdResetPasswordTemplates, http.StatusOK,
|
||||
|
|
|
@ -26,9 +26,9 @@
|
|||
{{ end }}
|
||||
{{ 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">
|
||||
<div class="flex-row">
|
||||
<form action="/accounts/{{ .Account.Id }}" method="post">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<fieldset>
|
||||
<legend>Edit User Account</legend>
|
||||
<div class="grid-2">
|
||||
|
@ -59,25 +59,30 @@
|
|||
</span>
|
||||
{{ end }}
|
||||
<div style="align-self:stretch; display:flex; justify-content:flex-end;">
|
||||
<button type="submit" value="edit">Edit User Account</button>
|
||||
<button name="action" type="submit" value="edit">Edit User Account</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<form action="/accounts/{{ .Account.Id }}" method="post">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<fieldset>
|
||||
<legend>Danger Zone</legend>
|
||||
<button {{ if eq .Page.Session.Data.Account.Id.String .Account.Id.String }}disabled{{ end }}
|
||||
name="action"
|
||||
type="submit"
|
||||
value="delete">
|
||||
Delete User Account
|
||||
</button>
|
||||
<!--<button type="submit" value="lock">Lock User Account</button>-->
|
||||
<button {{ if or (ne .Account.PasswordReset nil) (eq .Page.Session.Data.Account.Id.String .Account.Id.String) }}disabled{{ end }}
|
||||
name="action"
|
||||
type="submit"
|
||||
value="reset-password">
|
||||
Reset Password
|
||||
</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
{{ end }}
|
||||
<h2>Activity</h2>
|
||||
{{ if gt (len .Versions) 0 }}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
{{ else }}
|
||||
<h1>User Account</h1>
|
||||
<h2>Password Reset</h2>
|
||||
<form action="/accounts/{{ .Account.Id }}/reset/{{ .Token }}" enctype="multipart/form-data" method="post">
|
||||
<form action="/accounts/{{ .Account.Id }}/reset/{{ .Token }}" method="post">
|
||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||
<fieldset>
|
||||
<legend>Set Password</legend>
|
||||
|
|
|
@ -15,6 +15,7 @@ func addRoutes(
|
|||
requireAdmin := adminMiddleware(requireLogin)
|
||||
mux.Handle("GET /accounts", requireLogin(handleAccountsGET(db)))
|
||||
mux.Handle("GET /accounts/{id}", requireLogin(handleAccountsIdGET(db)))
|
||||
mux.Handle("POST /accounts/{id}", requireAdmin(handleAccountsIdPOST(db)))
|
||||
mux.Handle("GET /accounts/{id}/reset/{token}", requireSession(handleAccountsIdResetPasswordGET(db)))
|
||||
mux.Handle("POST /accounts/{id}/reset/{token}", requireSession(handleAccountsIdResetPasswordPOST(db)))
|
||||
mux.Handle("POST /accounts", requireAdmin(handleAccountsPOST(db)))
|
||||
|
|
|
@ -89,7 +89,8 @@ func handleStatesIdPOST(db *database.DB) http.Handler {
|
|||
action := r.FormValue("action")
|
||||
switch action {
|
||||
case "delete":
|
||||
errorResponse(w, r, http.StatusNotImplemented, err)
|
||||
errorResponse(w, r, http.StatusNotImplemented, nil)
|
||||
return
|
||||
case "edit":
|
||||
statePath := r.FormValue("path")
|
||||
parsedStatePath, err := url.Parse(statePath)
|
||||
|
@ -128,7 +129,7 @@ func handleStatesIdPOST(db *database.DB) http.Handler {
|
|||
}
|
||||
state.Lock = nil
|
||||
default:
|
||||
errorResponse(w, r, http.StatusBadRequest, err)
|
||||
errorResponse(w, r, http.StatusBadRequest, nil)
|
||||
return
|
||||
}
|
||||
render(w, statesIdTemplate, http.StatusOK, StatesIdPage{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue