List and delete accounts with last-admin and Administrators-group guards, and expose editable network settings via the configuration UI.
146 lines
3.9 KiB
Go
146 lines
3.9 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"codeberg.org/SquidSE/ClusterCanvas/service/internal/settings"
|
|
)
|
|
|
|
type userPublic struct {
|
|
ID string `json:"id"`
|
|
Username string `json:"username"`
|
|
Enabled bool `json:"enabled"`
|
|
TOTPConfirmed bool `json:"totp_confirmed"`
|
|
GroupNames []string `json:"group_names"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
PasswordChangedAt time.Time `json:"password_changed_at"`
|
|
IsAdmin bool `json:"is_admin"`
|
|
CanDelete bool `json:"can_delete"`
|
|
}
|
|
|
|
type usersResponse struct {
|
|
Users []userPublic `json:"users"`
|
|
}
|
|
|
|
func (app *App) usersGetHandler(writer http.ResponseWriter, request *http.Request) {
|
|
if err := app.requireKey(); err != nil {
|
|
writeJSON(writer, http.StatusServiceUnavailable, apiErrorResponse{Error: err.Error()})
|
|
return
|
|
}
|
|
|
|
store, err := settings.LoadPasswords(app.ConfigDir, app.Key)
|
|
if err != nil {
|
|
writeJSON(writer, http.StatusInternalServerError, apiErrorResponse{Error: err.Error()})
|
|
return
|
|
}
|
|
|
|
writeJSON(writer, http.StatusOK, usersResponse{Users: toPublicUsers(store.Users)})
|
|
}
|
|
|
|
func (app *App) usersDeleteHandler(writer http.ResponseWriter, request *http.Request) {
|
|
if err := authorizeUsersWrite(request); err != nil {
|
|
writeJSON(writer, http.StatusForbidden, apiErrorResponse{Error: err.Error()})
|
|
return
|
|
}
|
|
if err := app.requireKey(); err != nil {
|
|
writeJSON(writer, http.StatusServiceUnavailable, apiErrorResponse{Error: err.Error()})
|
|
return
|
|
}
|
|
|
|
userID := strings.TrimSpace(request.URL.Query().Get("id"))
|
|
if userID == "" {
|
|
writeJSON(writer, http.StatusBadRequest, apiErrorResponse{Error: "missing user id"})
|
|
return
|
|
}
|
|
|
|
store, err := settings.LoadPasswords(app.ConfigDir, app.Key)
|
|
if err != nil {
|
|
writeJSON(writer, http.StatusInternalServerError, apiErrorResponse{Error: err.Error()})
|
|
return
|
|
}
|
|
|
|
adminCount := countAdministrators(store.Users)
|
|
remaining := make([]settings.UserCredential, 0, len(store.Users))
|
|
found := false
|
|
for _, user := range store.Users {
|
|
if user.ID != userID {
|
|
remaining = append(remaining, user)
|
|
continue
|
|
}
|
|
found = true
|
|
if userIsAdministrator(user) && adminCount <= 1 {
|
|
writeJSON(writer, http.StatusConflict, apiErrorResponse{
|
|
Error: "cannot delete the last administrator",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
writeJSON(writer, http.StatusNotFound, apiErrorResponse{Error: "user not found"})
|
|
return
|
|
}
|
|
|
|
store.Users = remaining
|
|
if err := settings.SavePasswords(app.ConfigDir, store, app.Key); err != nil {
|
|
writeJSON(writer, http.StatusInternalServerError, apiErrorResponse{Error: err.Error()})
|
|
return
|
|
}
|
|
|
|
if app.Sessions != nil {
|
|
_ = app.Sessions.InvalidateUserSessions(userID)
|
|
}
|
|
|
|
writeJSON(writer, http.StatusOK, usersResponse{Users: toPublicUsers(store.Users)})
|
|
}
|
|
|
|
func authorizeUsersWrite(_ *http.Request) error {
|
|
// TODO: integrate authz middleware.
|
|
return nil
|
|
}
|
|
|
|
func toPublicUsers(users []settings.UserCredential) []userPublic {
|
|
adminCount := countAdministrators(users)
|
|
result := make([]userPublic, 0, len(users))
|
|
for _, user := range users {
|
|
isAdmin := userIsAdministrator(user)
|
|
groupNames := user.GroupNames
|
|
if groupNames == nil {
|
|
groupNames = []string{}
|
|
}
|
|
result = append(result, userPublic{
|
|
ID: user.ID,
|
|
Username: user.Username,
|
|
Enabled: user.Enabled,
|
|
TOTPConfirmed: user.TOTPConfirmed,
|
|
GroupNames: groupNames,
|
|
CreatedAt: user.CreatedAt,
|
|
PasswordChangedAt: user.PasswordChangedAt,
|
|
IsAdmin: isAdmin,
|
|
CanDelete: !(isAdmin && adminCount <= 1),
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func userIsAdministrator(user settings.UserCredential) bool {
|
|
for _, groupName := range user.GroupNames {
|
|
if groupName == settings.AdministratorsGroupName {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func countAdministrators(users []settings.UserCredential) int {
|
|
count := 0
|
|
for _, user := range users {
|
|
if userIsAdministrator(user) {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|