Files
bamort/backend/user/admin_handlers.go
Bardioc26 95f0fc0b7a editing and maintenance and user suggestions
* every user has a right of a username and a display name and has the right to change it
* System information page now shows information about database user count, char count, and database schema version
* more maintenance lists
* show the right values in columns and fields
* move similar from inside of frontend component functions to utility js when used multiple times
* display help on mouse over
* add more than one believe to character
* make char name editable with better char info in headline
* GiT Gifttoleranz value not calculated correctly
* Bump backend to 0.2.3, frontend to 0.2.2
2026-02-03 17:21:43 +01:00

249 lines
7.3 KiB
Go

package user
import (
"bamort/database"
"bamort/logger"
"crypto/md5"
"encoding/hex"
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
// ListUsers returns all users (admin only)
func ListUsers(c *gin.Context) {
logger.Debug("Listing all users...")
var users []User
if err := database.DB.Find(&users).Error; err != nil {
logger.Error("Failed to fetch users: %s", err.Error())
respondWithError(c, http.StatusInternalServerError, "Failed to fetch users")
return
}
// Remove password hashes from response
for i := range users {
users[i].PasswordHash = ""
users[i].ResetPwHash = nil
users[i].DisplayName = users[i].DisplayNameOrUsername()
}
logger.Info("Successfully fetched %d users", len(users))
c.JSON(http.StatusOK, users)
}
// GetUser returns a specific user by ID (admin only, or own profile)
func GetUser(c *gin.Context) {
logger.Debug("Fetching user by ID...")
userIDParam := c.Param("id")
targetUserID, err := strconv.ParseUint(userIDParam, 10, 32)
if err != nil {
logger.Error("Invalid user ID: %s", userIDParam)
respondWithError(c, http.StatusBadRequest, "Invalid user ID")
return
}
// Get requesting user from context
requestingUserInterface, exists := c.Get("user")
if !exists {
logger.Error("User not found in context")
respondWithError(c, http.StatusUnauthorized, "Unauthorized")
return
}
requestingUser, ok := requestingUserInterface.(*User)
if !ok {
logger.Error("Invalid user context")
respondWithError(c, http.StatusInternalServerError, "Invalid user context")
return
}
// Allow users to view their own profile, or admins to view any profile
if requestingUser.UserID != uint(targetUserID) && !requestingUser.IsAdmin() {
logger.Warn("User %s attempted to access user %d without permission", requestingUser.Username, targetUserID)
respondWithError(c, http.StatusForbidden, "Forbidden")
return
}
var user User
if err := user.FirstId(uint(targetUserID)); err != nil {
logger.Error("User not found: %d", targetUserID)
respondWithError(c, http.StatusNotFound, "User not found")
return
}
// Remove sensitive data
user.PasswordHash = ""
user.ResetPwHash = nil
user.DisplayName = user.DisplayNameOrUsername()
logger.Info("Successfully fetched user: %s (ID: %d)", user.Username, user.UserID)
c.JSON(http.StatusOK, user)
}
// UpdateUserRole updates a user's role (admin only)
func UpdateUserRole(c *gin.Context) {
logger.Debug("Updating user role...")
userIDParam := c.Param("id")
targetUserID, err := strconv.ParseUint(userIDParam, 10, 32)
if err != nil {
logger.Error("Invalid user ID: %s", userIDParam)
respondWithError(c, http.StatusBadRequest, "Invalid user ID")
return
}
var input struct {
Role string `json:"role" binding:"required"`
}
if err := c.ShouldBindJSON(&input); err != nil {
logger.Error("Failed to parse role update data: %s", err.Error())
respondWithError(c, http.StatusBadRequest, "Role is required")
return
}
// Validate role
if !ValidateRole(input.Role) {
logger.Error("Invalid role: %s", input.Role)
respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Invalid role. Must be one of: %s, %s, %s", RoleStandardUser, RoleMaintainer, RoleAdmin))
return
}
var user User
if err := user.FirstId(uint(targetUserID)); err != nil {
logger.Error("User not found: %d", targetUserID)
respondWithError(c, http.StatusNotFound, "User not found")
return
}
// Get requesting user for logging
requestingUserInterface, _ := c.Get("user")
requestingUser, _ := requestingUserInterface.(*User)
oldRole := user.Role
user.Role = input.Role
if err := user.Save(); err != nil {
logger.Error("Failed to update user role for user %s: %s", user.Username, err.Error())
respondWithError(c, http.StatusInternalServerError, "Failed to update user role")
return
}
logger.Info("User role updated: %s (ID: %d) from %s to %s by %s", user.Username, user.UserID, oldRole, user.Role, requestingUser.Username)
c.JSON(http.StatusOK, gin.H{
"message": "User role updated successfully",
"user": gin.H{
"id": user.UserID,
"username": user.Username,
"display_name": user.DisplayNameOrUsername(),
"role": user.Role,
},
})
}
// DeleteUser deletes a user (admin only)
func DeleteUser(c *gin.Context) {
logger.Debug("Deleting user...")
userIDParam := c.Param("id")
targetUserID, err := strconv.ParseUint(userIDParam, 10, 32)
if err != nil {
logger.Error("Invalid user ID: %s", userIDParam)
respondWithError(c, http.StatusBadRequest, "Invalid user ID")
return
}
// Get requesting user
requestingUserInterface, exists := c.Get("user")
if !exists {
logger.Error("User not found in context")
respondWithError(c, http.StatusUnauthorized, "Unauthorized")
return
}
requestingUser, ok := requestingUserInterface.(*User)
if !ok {
logger.Error("Invalid user context")
respondWithError(c, http.StatusInternalServerError, "Invalid user context")
return
}
// Prevent self-deletion
if requestingUser.UserID == uint(targetUserID) {
logger.Warn("User %s attempted to delete themselves", requestingUser.Username)
respondWithError(c, http.StatusBadRequest, "Cannot delete your own account")
return
}
var user User
if err := user.FirstId(uint(targetUserID)); err != nil {
logger.Error("User not found: %d", targetUserID)
respondWithError(c, http.StatusNotFound, "User not found")
return
}
if err := database.DB.Delete(&user).Error; err != nil {
logger.Error("Failed to delete user %s: %s", user.Username, err.Error())
respondWithError(c, http.StatusInternalServerError, "Failed to delete user")
return
}
logger.Info("User deleted: %s (ID: %d) by %s", user.Username, user.UserID, requestingUser.Username)
c.JSON(http.StatusOK, gin.H{
"message": "User deleted successfully",
})
}
// ChangeUserPassword allows admin to change a user's password (admin only)
func ChangeUserPassword(c *gin.Context) {
logger.Debug("Admin changing user password...")
userIDParam := c.Param("id")
targetUserID, err := strconv.ParseUint(userIDParam, 10, 32)
if err != nil {
logger.Error("Invalid user ID: %s", userIDParam)
respondWithError(c, http.StatusBadRequest, "Invalid user ID")
return
}
var input struct {
NewPassword string `json:"new_password" binding:"required,min=6"`
}
if err := c.ShouldBindJSON(&input); err != nil {
logger.Error("Failed to parse password data: %s", err.Error())
respondWithError(c, http.StatusBadRequest, "New password (min 6 characters) is required")
return
}
var user User
if err := user.FirstId(uint(targetUserID)); err != nil {
logger.Error("User not found: %d", targetUserID)
respondWithError(c, http.StatusNotFound, "User not found")
return
}
// Get requesting user for logging
requestingUserInterface, _ := c.Get("user")
requestingUser, _ := requestingUserInterface.(*User)
// Hash new password using MD5 (same as registration)
hashedPassword := md5.Sum([]byte(input.NewPassword))
user.PasswordHash = hex.EncodeToString(hashedPassword[:])
if err := user.Save(); err != nil {
logger.Error("Failed to update password for user %s: %s", user.Username, err.Error())
respondWithError(c, http.StatusInternalServerError, "Failed to update password")
return
}
logger.Info("Password changed for user %s (ID: %d) by admin %s", user.Username, user.UserID, requestingUser.Username)
c.JSON(http.StatusOK, gin.H{
"message": "Password updated successfully",
})
}