commit 391ef20c129638b3e67594f8182633120dea67de
parent 6016072301ff0702511e518cfaa3546f2e76054a
Author: cblgh <cblgh@cblgh.org>
Date: Fri, 15 Dec 2023 09:41:34 +0100
introduce admin demotion functionality
Diffstat:
5 files changed, 91 insertions(+), 10 deletions(-)
diff --git a/constants/constants.go b/constants/constants.go
@@ -4,10 +4,9 @@ const (
MODLOG_RESETPW = iota
MODLOG_ADMIN_VETO
MODLOG_ADMIN_MAKE
- // MODLOG_ADMIN_PROPOSE
- // MODLOG_ADMIN_CONFIRM
MODLOG_REMOVE_USER
MODLOG_ADMIN_ADD_USER
+ MODLOG_ADMIN_DEMOTE
/* NOTE: when adding new values, only add them after already existing values! otherwise the existing variables will
* receive new values */
// MODLOG_DELETE_VETO
diff --git a/database/database.go b/database/database.go
@@ -632,6 +632,33 @@ func (d DB) AddAdmin(userid int) error {
return nil
}
+func (d DB) DemoteAdmin(userid int) error {
+ ed := util.Describe("demote admin")
+ // make sure the id exists
+ exists, err := d.CheckUserExists(userid)
+ if !exists {
+ return errors.New(fmt.Sprintf("demote admin: userid %d did not exist", userid))
+ }
+ if err != nil {
+ return ed.Eout(err, "CheckUserExists had an error")
+ }
+ isAdmin, err := d.IsUserAdmin(userid)
+ if !isAdmin {
+ return errors.New(fmt.Sprintf("demote admin: userid %d was not an admin", userid))
+ }
+ if err != nil {
+ // some kind of error, let's bubble it up
+ return ed.Eout(err, "IsUserAdmin")
+ }
+ // all checks are done: perform the removal
+ stmt := `DELETE FROM admins WHERE id = ?`
+ _, err = d.db.Exec(stmt, userid)
+ if err != nil {
+ return ed.Eout(err, "inserting new admin")
+ }
+ return nil
+}
+
func (d DB) IsUserAdmin (userid int) (bool, error) {
stmt := `SELECT 1 FROM admins WHERE id = ?`
return d.existsQuery(stmt, userid)
diff --git a/html/admin.html b/html/admin.html
@@ -4,10 +4,14 @@
<section>
<form method="GET" id="add-user" action="/add-user"></form>
<p>
- Does someone wish admittance? You can <button form="add-user" type="submit" href="/add-user">Add new user</button>.
+ <form method="POST" id="demote-self" action="/demote-admin">
+ <input type="hidden" name="userid" value="{{ .LoggedInID }}">
+ </form>
+ <p>
+ Does someone wish admittance? You can <button form="add-user" type="submit">Add new user</button>.
</p>
<p>
- If you want to stop being an admin, you can <button>Step down</button>.
+ If you want to stop being an admin, you can <button form="demote-self" type="submit">Step down</button>.
</p>
<p>
View past actions in the <a href="/moderations">moderation log</a>.
@@ -19,9 +23,15 @@
{{ if len .Data.Admins | eq 0 }}
<p> there are no admins; chaos reigns </p>
{{ else }}
- {{ $userID := .LoggedInID }} <!-- do some kind of extra styling to indicate "hey this is you!" -->
+ {{ $userID := .LoggedInID }}
{{ range $index, $user := .Data.Admins }}
- <p> {{ $user.Name }} ({{ $user.ID }}) {{ if eq $userID $user.ID }} <i>(this is you!)</i>{{ end }}</p>
+ <form method="POST" id="demote-admin-{{$user.ID}}" action="/demote-admin">
+ <input type="hidden" name="userid" value="{{ $user.ID }}">
+ </form>
+ <p> {{ $user.Name }} ({{ $user.ID }})
+ {{ if eq $userID $user.ID }} <i>(this is you!)</i>
+ {{ else }}<button type="submit" form="demote-admin-{{$user.ID}}">Demote</button>{{ end }}
+ </p>
{{ end }}
{{ end }}
</section>
diff --git a/i18n/i18n.go b/i18n/i18n.go
@@ -31,6 +31,9 @@ var English = map[string]string{
"modlogResetPasswordAdmin": `<code>{{ .Data.Time }}</code> <b>{{ .Data.ActingUsername }}</b> reset <b> {{ .Data.RecipientUsername}}</b>'s password`,
"modlogMakeAdmin": `<code>{{ .Data.Time }}</code> <b>{{ .Data.ActingUsername }}</b> made <b> {{ .Data.RecipientUsername}}</b> an admin`,
"modlogAddUser": `<code>{{ .Data.Time }}</code> <b>{{ .Data.ActingUsername }}</b> manually registered an account for <b> {{ .Data.RecipientUsername }}</b>`,
+ "modlogDemoteAdmin": `<code>{{ .Data.Time }}</code> <b>{{ .Data.ActingUsername }}</b> demoted <b>
+ {{ if eq .Data.ActingUsername .Data.RecipientUsername }} themselves
+ {{ else }} {{ .Data.RecipientUsername}} {{ end }}</b> from admin back to normal user`,
// "modlogProposeAdmin": `<code>{{ .Data.Time }}</code> <b>{{ .Data.ActingUsername }}</b> made <b> {{ .Data.RecipientUsername}} an admin`,
// "modlogVetoAdmin": `<code>{{ .Data.Time }}</code> <b>{{ .Data.ActingUsername }}</b> vetoed making {{ .Data.RecipientUsername }} an admin`,
// "modlogConfirmAdmin": `<code>{{ .Data.Time }}</code> <b>{{ .Data.ActingUsername }}</b> confirmed making {{ .Data.RecipientUsername }} a new admin`,
diff --git a/server/server.go b/server/server.go
@@ -327,7 +327,6 @@ func (h *RequestHandler) AdminRemoveUser(res http.ResponseWriter, req *http.Requ
loggedIn, _ := h.IsLoggedIn(req)
isAdmin, adminUserid := h.IsAdmin(req)
if req.Method == "GET" || !loggedIn || !isAdmin {
- // redirect to index
IndexRedirect(res, req)
return
}
@@ -359,7 +358,6 @@ func (h *RequestHandler) AdminMakeUserAdmin(res http.ResponseWriter, req *http.R
loggedIn, _ := h.IsLoggedIn(req)
isAdmin, adminUserid := h.IsAdmin(req)
if req.Method == "GET" || !loggedIn || !isAdmin {
- // redirect to index
IndexRedirect(res, req)
return
}
@@ -396,13 +394,55 @@ func (h *RequestHandler) AdminMakeUserAdmin(res http.ResponseWriter, req *http.R
h.renderGenericMessage(res, req, data)
}
+func (h *RequestHandler) AdminDemoteAdmin(res http.ResponseWriter, req *http.Request) {
+ ed := util.Describe("demote admin route")
+ loggedIn, _ := h.IsLoggedIn(req)
+ isAdmin, adminUserid := h.IsAdmin(req)
+ if req.Method == "GET" || !loggedIn || !isAdmin {
+ IndexRedirect(res, req)
+ return
+ }
+ useridString := req.PostFormValue("userid")
+ targetUserid, err := strconv.Atoi(useridString)
+ util.Check(err, "convert user id string to a plain userid")
+
+ // TODO (2023-12-10): introduce 2-quorom
+ err = h.db.DemoteAdmin(targetUserid)
+
+ if err != nil {
+ errMsg := ed.Eout(err, "demote admin failed")
+ fmt.Println(errMsg)
+ data := GenericMessageData{
+ Title: "Demote admin",
+ Message: errMsg.Error(),
+ }
+ h.renderGenericMessage(res, req, data)
+ return
+ }
+
+ username, _ := h.db.GetUsername(targetUserid)
+ err = h.db.AddModerationLog(adminUserid, targetUserid, constants.MODLOG_ADMIN_DEMOTE)
+ if err != nil {
+ fmt.Println(ed.Eout(err, "error adding moderation log"))
+ }
+
+ // output copy-pastable credentials page for admin to send to the user
+ data := GenericMessageData{
+ Title: "Demote admin success",
+ Message: fmt.Sprintf("User %s is now a regular user", username),
+ LinkMessage: "Go back to the",
+ LinkText: "admin view",
+ Link: "/admin",
+ }
+ h.renderGenericMessage(res, req, data)
+}
+
func (h *RequestHandler) AdminManualAddUserRoute(res http.ResponseWriter, req *http.Request) {
ed := util.Describe("admin manually add user")
loggedIn, _ := h.IsLoggedIn(req)
isAdmin, adminUserid := h.IsAdmin(req)
if !isAdmin {
- // redirect to index
IndexRedirect(res, req)
return
}
@@ -474,7 +514,6 @@ func (h *RequestHandler) AdminResetUserPassword(res http.ResponseWriter, req *ht
loggedIn, _ := h.IsLoggedIn(req)
isAdmin, adminUserid := h.IsAdmin(req)
if req.Method == "GET" || !loggedIn || !isAdmin {
- // redirect to index
IndexRedirect(res, req)
return
}
@@ -546,6 +585,8 @@ func (h *RequestHandler) ModerationLogRoute(res http.ResponseWriter, req *http.R
translationString = "modlogRemoveUser"
case constants.MODLOG_ADMIN_ADD_USER:
translationString = "modlogAddUser"
+ case constants.MODLOG_ADMIN_DEMOTE:
+ translationString = "modlogDemoteAdmin"
}
str := h.translator.TranslateWithData(translationString, i18n.TranslationData{Data: tdata})
viewData.Log = append(viewData.Log, str)
@@ -1196,6 +1237,7 @@ func NewServer(allowlist []string, sessionKey, dir string, config types.Config)
// TODO (2022-01-10): introduce middleware to make sure there is never an issue with trailing slashes
s.ServeMux.HandleFunc("/reset/", handler.ResetPasswordRoute)
s.ServeMux.HandleFunc("/admin", handler.AdminRoute)
+ s.ServeMux.HandleFunc("/demote-admin", handler.AdminDemoteAdmin)
s.ServeMux.HandleFunc("/add-user", handler.AdminManualAddUserRoute)
s.ServeMux.HandleFunc("/moderations", handler.ModerationLogRoute)
s.ServeMux.HandleFunc("/about", handler.AboutRoute)