feat(webui): add accounts username and isAdmin flag edition for admins
Closes #43
This commit is contained in:
parent
4f68621bad
commit
373f567773
6 changed files with 111 additions and 50 deletions
|
@ -234,48 +234,74 @@ func (db *DB) LoadAccountByUsername(username string) (*model.Account, error) {
|
||||||
return &account, nil
|
return &account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) SaveAccount(account *model.Account) error {
|
func (db *DB) SaveAccount(account *model.Account) (bool, error) {
|
||||||
_, err := db.Exec(
|
ret := false
|
||||||
`UPDATE accounts
|
return ret, db.WithTransaction(func(tx *sql.Tx) error {
|
||||||
SET username = ?,
|
_, err := tx.ExecContext(db.ctx,
|
||||||
salt = ?,
|
`UPDATE accounts
|
||||||
password_hash = ?,
|
SET username = ?,
|
||||||
is_admin = ?,
|
salt = ?,
|
||||||
password_reset = ?
|
password_hash = ?,
|
||||||
WHERE id = ?`,
|
is_admin = ?,
|
||||||
account.Username,
|
password_reset = ?
|
||||||
account.Salt,
|
WHERE id = ?`,
|
||||||
account.PasswordHash,
|
account.Username,
|
||||||
account.IsAdmin,
|
account.Salt,
|
||||||
account.PasswordReset,
|
account.PasswordHash,
|
||||||
account.Id)
|
account.IsAdmin,
|
||||||
if err != nil {
|
account.PasswordReset,
|
||||||
return fmt.Errorf("failed to update account id %s: %w", account.Id, err)
|
account.Id)
|
||||||
}
|
if err != nil {
|
||||||
return nil
|
var sqliteErr sqlite3.Error
|
||||||
|
if errors.As(err, &sqliteErr) {
|
||||||
|
if sqliteErr.Code == sqlite3.ErrNo(sqlite3.ErrConstraint) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to update account id %s: %w", account.Id, err)
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(account)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal account %s: %w", account.Username, err)
|
||||||
|
}
|
||||||
|
_, err = tx.ExecContext(db.ctx,
|
||||||
|
`UPDATE sessions
|
||||||
|
SET data = jsonb_replace(data,
|
||||||
|
'$.account', jsonb(:data))
|
||||||
|
WHERE data->'account'->>'id' = :id`,
|
||||||
|
sql.Named("data", data),
|
||||||
|
sql.Named("id", account.Id))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update account settings for user account %s: %w", account.Username, err)
|
||||||
|
}
|
||||||
|
ret = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) SaveAccountSettings(account *model.Account, settings *model.Settings) error {
|
func (db *DB) SaveAccountSettings(account *model.Account, settings *model.Settings) error {
|
||||||
data, err := json.Marshal(settings)
|
return db.WithTransaction(func(tx *sql.Tx) error {
|
||||||
if err != nil {
|
data, err := json.Marshal(settings)
|
||||||
return fmt.Errorf("failed to marshal settings for user accont %s: %w", account.Username, err)
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("failed to marshal settings for user account %s: %w", account.Username, err)
|
||||||
_, err = db.Exec(`UPDATE accounts SET settings = ? WHERE id = ?`, data, account.Id)
|
}
|
||||||
if err != nil {
|
_, err = tx.ExecContext(db.ctx, `UPDATE accounts SET settings = ? WHERE id = ?`, data, account.Id)
|
||||||
return fmt.Errorf("failed to update account settings for user account %s: %w", account.Username, err)
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("failed to update account settings for user account %s: %w", account.Username, err)
|
||||||
_, err = db.Exec(
|
}
|
||||||
`UPDATE sessions
|
_, err = tx.ExecContext(db.ctx,
|
||||||
SET data = jsonb_replace(data,
|
`UPDATE sessions
|
||||||
'$.settings', jsonb(:data),
|
SET data = jsonb_replace(data,
|
||||||
'$.account.settings', jsonb(:data))
|
'$.settings', jsonb(:data),
|
||||||
WHERE data->'account'->>'id' = :id`,
|
'$.account.settings', jsonb(:data))
|
||||||
sql.Named("data", data),
|
WHERE data->'account'->>'id' = :id`,
|
||||||
sql.Named("id", account.Id))
|
sql.Named("data", data),
|
||||||
if err != nil {
|
sql.Named("id", account.Id))
|
||||||
return fmt.Errorf("failed to update account settings for user account %s: %w", account.Username, err)
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("failed to update account settings for user account %s: %w", account.Username, err)
|
||||||
return nil
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) TouchAccount(account *model.Account) error {
|
func (db *DB) TouchAccount(account *model.Account) error {
|
||||||
|
|
|
@ -51,7 +51,7 @@ func handleAccountsPOST(db *database.DB) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
accountUsername := r.FormValue("username")
|
accountUsername := r.FormValue("username")
|
||||||
isAdmin := r.FormValue("isAdmin")
|
isAdmin := r.FormValue("is-admin")
|
||||||
page := AccountsPage{
|
page := AccountsPage{
|
||||||
Page: makePage(r, &Page{Title: "New Account", Section: "accounts"}),
|
Page: makePage(r, &Page{Title: "New Account", Section: "accounts"}),
|
||||||
Accounts: accounts,
|
Accounts: accounts,
|
||||||
|
|
|
@ -87,25 +87,54 @@ func handleAccountsIdPOST(db *database.DB) http.Handler {
|
||||||
if page == nil {
|
if page == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
session := r.Context().Value(model.SessionContextKey{}).(*model.Session)
|
||||||
action := r.FormValue("action")
|
action := r.FormValue("action")
|
||||||
switch action {
|
switch action {
|
||||||
case "delete":
|
case "delete":
|
||||||
errorResponse(w, r, http.StatusNotImplemented, nil)
|
errorResponse(w, r, http.StatusNotImplemented, nil)
|
||||||
return
|
return
|
||||||
case "edit":
|
case "edit":
|
||||||
errorResponse(w, r, http.StatusNotImplemented, nil)
|
page.Username = r.FormValue("username")
|
||||||
return
|
isAdmin := r.FormValue("is-admin")
|
||||||
|
if ok := validUsername.MatchString(page.Username); !ok {
|
||||||
|
page.UsernameInvalid = true
|
||||||
|
render(w, accountsIdTemplates, http.StatusBadRequest, page)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if page.Account.Id != session.Data.Account.Id {
|
||||||
|
page.Account.IsAdmin = isAdmin == "1"
|
||||||
|
}
|
||||||
|
prev := page.Account.Username
|
||||||
|
page.Account.Username = page.Username
|
||||||
|
success, err := db.SaveAccount(page.Account)
|
||||||
|
if err != nil {
|
||||||
|
errorResponse(w, r, http.StatusInternalServerError,
|
||||||
|
fmt.Errorf("failed to save account: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !success {
|
||||||
|
page.Account.Username = prev
|
||||||
|
page.UsernameDuplicate = true
|
||||||
|
render(w, accountsIdTemplates, http.StatusBadRequest, page)
|
||||||
|
return
|
||||||
|
}
|
||||||
case "reset-password":
|
case "reset-password":
|
||||||
if err := page.Account.ResetPassword(); err != nil {
|
if err := page.Account.ResetPassword(); err != nil {
|
||||||
errorResponse(w, r, http.StatusNotImplemented,
|
errorResponse(w, r, http.StatusNotImplemented,
|
||||||
fmt.Errorf("failed to reset password: %w", err))
|
fmt.Errorf("failed to reset password: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := db.SaveAccount(page.Account); err != nil {
|
success, err := db.SaveAccount(page.Account)
|
||||||
|
if err != nil {
|
||||||
errorResponse(w, r, http.StatusInternalServerError,
|
errorResponse(w, r, http.StatusInternalServerError,
|
||||||
fmt.Errorf("failed to save account: %w", err))
|
fmt.Errorf("failed to save account: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !success {
|
||||||
|
errorResponse(w, r, http.StatusInternalServerError,
|
||||||
|
fmt.Errorf("failed to save account: table constraint error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
if err := db.DeleteSessions(page.Account); err != nil {
|
if err := db.DeleteSessions(page.Account); err != nil {
|
||||||
errorResponse(w, r, http.StatusInternalServerError,
|
errorResponse(w, r, http.StatusInternalServerError,
|
||||||
fmt.Errorf("failed to save account: %w", err))
|
fmt.Errorf("failed to save account: %w", err))
|
||||||
|
|
|
@ -81,11 +81,17 @@ func handleAccountsIdResetPasswordPOST(db *database.DB) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
account.SetPassword(password)
|
account.SetPassword(password)
|
||||||
if err := db.SaveAccount(account); err != nil {
|
success, err := db.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
errorResponse(w, r, http.StatusInternalServerError,
|
errorResponse(w, r, http.StatusInternalServerError,
|
||||||
fmt.Errorf("failed to save account: %w", err))
|
fmt.Errorf("failed to save account: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !success {
|
||||||
|
errorResponse(w, r, http.StatusInternalServerError,
|
||||||
|
fmt.Errorf("failed to save account: table constraint error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
render(w, accountsIdResetPasswordTemplates, http.StatusOK,
|
render(w, accountsIdResetPasswordTemplates, http.StatusOK,
|
||||||
AccountsIdResetPasswordPage{
|
AccountsIdResetPasswordPage{
|
||||||
Account: account,
|
Account: account,
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{ if .Page.Session.Data.Account.IsAdmin }}
|
{{ if .Page.Session.Data.Account.IsAdmin }}
|
||||||
<form action="/accounts" enctype="multipart/form-data" method="post">
|
<form action="/accounts" method="post">
|
||||||
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>New User Account</legend>
|
<legend>New User Account</legend>
|
||||||
|
@ -21,11 +21,11 @@
|
||||||
type="text"
|
type="text"
|
||||||
value="{{ .Username }}">
|
value="{{ .Username }}">
|
||||||
<label for="is-admin">Is Admin</label>
|
<label for="is-admin">Is Admin</label>
|
||||||
<input {{ if .IsAdmin }} checked{{ end }}
|
<input {{ if .IsAdmin }}checked{{ end }}
|
||||||
id="is-admin"
|
id="is-admin"
|
||||||
name="is-admin"
|
name="is-admin"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value="{{ .IsAdmin }}" />
|
value="1" />
|
||||||
</div>
|
</div>
|
||||||
{{ if .UsernameDuplicate }}
|
{{ if .UsernameDuplicate }}
|
||||||
<span class="error">This username already exist.</span>
|
<span class="error">This username already exist.</span>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{ define "main" }}
|
{{ define "main" }}
|
||||||
<h1>User Account</h1>
|
<h1>{{ .Account.Username }}</h1>
|
||||||
{{ if ne .Account.PasswordReset nil }}
|
{{ if ne .Account.PasswordReset nil }}
|
||||||
<h2>Password Reset</h2>
|
<h2>Password Reset</h2>
|
||||||
<article>
|
<article>
|
||||||
|
@ -37,14 +37,14 @@
|
||||||
id="username"
|
id="username"
|
||||||
name="username"
|
name="username"
|
||||||
type="text"
|
type="text"
|
||||||
value="{{ .Username }}">
|
value="{{ if eq .Username "" }}{{ .Account.Username }}{{ else }}{{ .Username }}{{ end }}">
|
||||||
<label for="is-admin">Is Admin</label>
|
<label for="is-admin">Is Admin</label>
|
||||||
<input {{ if .Account.IsAdmin }}checked{{ end }}
|
<input {{ if .Account.IsAdmin }}checked{{ end }}
|
||||||
{{ if eq .Page.Session.Data.Account.Id.String .Account.Id.String }}disabled{{ end }}
|
{{ if eq .Page.Session.Data.Account.Id.String .Account.Id.String }}disabled{{ end }}
|
||||||
id="is-admin"
|
id="is-admin"
|
||||||
name="is-admin"
|
name="is-admin"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value="{{ .IsAdmin }}" />
|
value="1" />
|
||||||
</div>
|
</div>
|
||||||
{{ if .UsernameDuplicate }}
|
{{ if .UsernameDuplicate }}
|
||||||
<span class="error">This username already exist.</span>
|
<span class="error">This username already exist.</span>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue