diff --git a/backend/character/handlers.go b/backend/character/handlers.go
index bf3b6dd..66855fb 100644
--- a/backend/character/handlers.go
+++ b/backend/character/handlers.go
@@ -30,6 +30,17 @@ func respondWithError(c *gin.Context, status int, message string) {
c.JSON(status, gin.H{"error": message})
}
+// checkCharacterOwnership verifies that the logged-in user owns the character
+func checkCharacterOwnership(c *gin.Context, character *models.Char) bool {
+ userID := c.GetUint("userID")
+ if character.UserID != userID {
+ logger.Warn("Unauthorized access attempt: user %d tried to modify character %d owned by user %d", userID, character.ID, character.UserID)
+ respondWithError(c, http.StatusForbidden, "You are not authorized to modify this character")
+ return false
+ }
+ return true
+}
+
func ListCharacters(c *gin.Context) {
logger.Debug("ListCharacters aufgerufen")
@@ -109,6 +120,11 @@ func UpdateCharacter(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkCharacterOwnership(c, &character) {
+ return
+ }
+
// Store the original ID to preserve it
originalID := character.ID
originalGameSystem := character.GameSystem
@@ -141,6 +157,12 @@ func DeleteCharacter(c *gin.Context) {
respondWithError(c, http.StatusNotFound, "Character not found")
return
}
+
+ // Check ownership
+ if !checkCharacterOwnership(c, &character) {
+ return
+ }
+
err = character.Delete()
if err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to delete character")
@@ -275,6 +297,11 @@ func UpdateCharacterExperience(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkCharacterOwnership(c, &character) {
+ return
+ }
+
// Parse Request
var req UpdateExperienceRequest
if err := c.ShouldBindJSON(&req); err != nil {
@@ -365,6 +392,11 @@ func UpdateCharacterWealth(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkCharacterOwnership(c, &character) {
+ return
+ }
+
// Parse Request
var req UpdateWealthRequest
if err := c.ShouldBindJSON(&req); err != nil {
@@ -575,6 +607,11 @@ func LearnSkill(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkCharacterOwnership(c, &character) {
+ return
+ }
+
// Verwende gsmaster.LernCostRequest direkt
var request gsmaster.LernCostRequest
if err := c.ShouldBindJSON(&request); err != nil {
@@ -1060,6 +1097,11 @@ func ImproveSkill(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkCharacterOwnership(c, char) {
+ return
+ }
+
// 2. Skill validieren und Level ermitteln
characterClass, skillInfo, currentLevel, err := validateSkillForImprovement(char, &request)
if err != nil {
@@ -1233,6 +1275,18 @@ func LearnSpell(c *gin.Context) {
}
charID := uint(charIDInt)
+ // Load character to check ownership
+ var character models.Char
+ if err := character.FirstID(char_ID); err != nil {
+ respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden")
+ return
+ }
+
+ // Check ownership
+ if !checkCharacterOwnership(c, &character) {
+ return
+ }
+
var lernRequest gsmaster.LernCostRequest
if err := c.ShouldBindJSON(&lernRequest); err != nil {
respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error())
diff --git a/backend/character/image_handler.go b/backend/character/image_handler.go
index b88b6da..f79bc86 100644
--- a/backend/character/image_handler.go
+++ b/backend/character/image_handler.go
@@ -25,6 +25,11 @@ func UpdateCharacterImage(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkCharacterOwnership(c, &character) {
+ return
+ }
+
var request ImageUpdateRequest
if err := c.ShouldBindJSON(&request); err != nil {
logger.Error("Invalid request data: %s", err.Error())
diff --git a/backend/character/practice_points_handler.go b/backend/character/practice_points_handler.go
index 5c3d41a..714dca0 100644
--- a/backend/character/practice_points_handler.go
+++ b/backend/character/practice_points_handler.go
@@ -63,6 +63,11 @@ func UpdatePracticePoints(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkCharacterOwnership(c, &character) {
+ return
+ }
+
// Request-Parameter abrufen
var practicePoints []PracticePointResponse
if err := c.ShouldBindJSON(&practicePoints); err != nil {
@@ -120,6 +125,11 @@ func AddPracticePoint(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkCharacterOwnership(c, &character) {
+ return
+ }
+
// Request-Parameter abrufen
type AddPPRequest struct {
SkillName string `json:"skill_name" binding:"required"`
@@ -218,6 +228,11 @@ func UsePracticePoint(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkCharacterOwnership(c, &character) {
+ return
+ }
+
// Request-Parameter abrufen
type UsePPRequest struct {
SkillName string `json:"skill_name" binding:"required"`
diff --git a/backend/equipment/handlers.go b/backend/equipment/handlers.go
index be68e2f..abe4e16 100644
--- a/backend/equipment/handlers.go
+++ b/backend/equipment/handlers.go
@@ -19,6 +19,21 @@ func respondWithError(c *gin.Context, status int, message string) {
c.JSON(status, gin.H{"error": message})
}
+// checkEquipmentOwnership verifies that the logged-in user owns the equipment's character
+func checkEquipmentOwnership(c *gin.Context, characterID uint) bool {
+ userID := c.GetUint("userID")
+ var character models.Char
+ if err := database.DB.Select("id", "user_id").First(&character, characterID).Error; err != nil {
+ respondWithError(c, http.StatusNotFound, "Character not found")
+ return false
+ }
+ if character.UserID != userID {
+ respondWithError(c, http.StatusForbidden, "You are not authorized to modify this character's equipment")
+ return false
+ }
+ return true
+}
+
func CreateAusruestung(c *gin.Context) {
var ausruestung models.EqAusruestung
if err := c.ShouldBindJSON(&ausruestung); err != nil {
@@ -26,6 +41,11 @@ func CreateAusruestung(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkEquipmentOwnership(c, ausruestung.CharacterID) {
+ return
+ }
+
if err := database.DB.Create(&ausruestung).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to create Ausruestung")
return
@@ -55,6 +75,11 @@ func UpdateAusruestung(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkEquipmentOwnership(c, ausruestung.CharacterID) {
+ return
+ }
+
if err := c.ShouldBindJSON(&ausruestung); err != nil {
respondWithError(c, http.StatusBadRequest, err.Error())
return
@@ -70,7 +95,19 @@ func UpdateAusruestung(c *gin.Context) {
func DeleteAusruestung(c *gin.Context) {
ausruestungID := c.Param("ausruestung_id")
- if err := database.DB.Delete(&models.EqAusruestung{}, ausruestungID).Error; err != nil {
+
+ var ausruestung models.EqAusruestung
+ if err := database.DB.First(&ausruestung, ausruestungID).Error; err != nil {
+ respondWithError(c, http.StatusNotFound, "Ausruestung not found")
+ return
+ }
+
+ // Check ownership
+ if !checkEquipmentOwnership(c, ausruestung.CharacterID) {
+ return
+ }
+
+ if err := database.DB.Delete(&ausruestung).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to delete Ausruestung")
return
}
@@ -89,6 +126,11 @@ func CreateWaffe(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkEquipmentOwnership(c, waffe.CharacterID) {
+ return
+ }
+
if err := database.DB.Create(&waffe).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to create Waffe")
return
@@ -118,6 +160,11 @@ func UpdateWaffe(c *gin.Context) {
return
}
+ // Check ownership
+ if !checkEquipmentOwnership(c, waffe.CharacterID) {
+ return
+ }
+
if err := c.ShouldBindJSON(&waffe); err != nil {
respondWithError(c, http.StatusBadRequest, err.Error())
return
@@ -133,7 +180,19 @@ func UpdateWaffe(c *gin.Context) {
func DeleteWaffe(c *gin.Context) {
waffeID := c.Param("waffe_id")
- if err := database.DB.Delete(&models.EqWaffe{}, waffeID).Error; err != nil {
+
+ var waffe models.EqWaffe
+ if err := database.DB.First(&waffe, waffeID).Error; err != nil {
+ respondWithError(c, http.StatusNotFound, "Waffe not found")
+ return
+ }
+
+ // Check ownership
+ if !checkEquipmentOwnership(c, waffe.CharacterID) {
+ return
+ }
+
+ if err := database.DB.Delete(&waffe).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to delete Waffe")
return
}
diff --git a/backend/gsmaster/routes.go b/backend/gsmaster/routes.go
index 02d8e5a..be9c905 100644
--- a/backend/gsmaster/routes.go
+++ b/backend/gsmaster/routes.go
@@ -8,50 +8,52 @@ import (
func RegisterRoutes(r *gin.RouterGroup) {
maintGrp := r.Group("/maintenance")
+
+ maintGrp.GET("", GetMasterData)
+ maintGrp.GET("/skills", GetMDSkills)
+ maintGrp.GET("/skills-enhanced", GetEnhancedMDSkills) // New enhanced endpoint
+ maintGrp.GET("/skills/:id", GetMDSkill)
+ maintGrp.GET("/skills-enhanced/:id", GetEnhancedMDSkill) // New enhanced endpoint
+ maintGrp.GET("/weaponskills", GetMDWeaponSkills)
+ maintGrp.GET("/weaponskills-enhanced", GetEnhancedMDWeaponSkills) // New enhanced endpoint
+ maintGrp.GET("/weaponskills/:id", GetMDWeaponSkill)
+ maintGrp.GET("/weaponskills-enhanced/:id", GetEnhancedMDWeaponSkill) // New enhanced endpoint
+ maintGrp.GET("/spells", GetMDSpells)
+ maintGrp.GET("/spells-enhanced", GetEnhancedMDSpells) // New enhanced endpoint
+ maintGrp.GET("/spells/:id", GetMDSpell)
+ maintGrp.GET("/spells-enhanced/:id", GetEnhancedMDSpell) // New enhanced endpoint
+ maintGrp.GET("/equipment", GetMDEquipments)
+ maintGrp.GET("/equipment-enhanced", GetEnhancedMDEquipment) // New enhanced endpoint
+ maintGrp.GET("/equipment/:id", GetMDEquipment)
+ maintGrp.GET("/equipment-enhanced/:id", GetEnhancedMDEquipmentItem) // New enhanced endpoint
+ maintGrp.GET("/weapons", GetMDWeapons)
+ maintGrp.GET("/weapons-enhanced", GetEnhancedMDWeapons) // New enhanced endpoint
+ maintGrp.GET("/weapons/:id", GetMDWeapon)
+ maintGrp.GET("/weapons-enhanced/:id", GetEnhancedMDWeapon) // New enhanced endpoint
+
maintGrp.Use(user.RequireMaintainer())
{
- maintGrp.GET("", GetMasterData)
- maintGrp.GET("/skills", GetMDSkills)
- maintGrp.GET("/skills-enhanced", GetEnhancedMDSkills) // New enhanced endpoint
maintGrp.POST("/skills-enhanced", CreateEnhancedMDSkill) // Create new skill
- maintGrp.GET("/skills/:id", GetMDSkill)
- maintGrp.GET("/skills-enhanced/:id", GetEnhancedMDSkill) // New enhanced endpoint
maintGrp.PUT("/skills/:id", UpdateMDSkill)
maintGrp.PUT("/skills-enhanced/:id", UpdateEnhancedMDSkill) // New enhanced endpoint
maintGrp.POST("/skills", AddSkill)
maintGrp.DELETE("/skills/:id", DeleteMDSkill)
- maintGrp.GET("/weaponskills", GetMDWeaponSkills)
- maintGrp.GET("/weaponskills-enhanced", GetEnhancedMDWeaponSkills) // New enhanced endpoint
- maintGrp.GET("/weaponskills/:id", GetMDWeaponSkill)
- maintGrp.GET("/weaponskills-enhanced/:id", GetEnhancedMDWeaponSkill) // New enhanced endpoint
maintGrp.PUT("/weaponskills/:id", UpdateMDWeaponSkill)
maintGrp.PUT("/weaponskills-enhanced/:id", UpdateEnhancedMDWeaponSkill) // New enhanced endpoint
maintGrp.POST("/weaponskills", AddWeaponSkill)
maintGrp.DELETE("/weaponskills/:id", DeleteMDWeaponSkill)
- maintGrp.GET("/spells", GetMDSpells)
- maintGrp.GET("/spells-enhanced", GetEnhancedMDSpells) // New enhanced endpoint
- maintGrp.GET("/spells/:id", GetMDSpell)
- maintGrp.GET("/spells-enhanced/:id", GetEnhancedMDSpell) // New enhanced endpoint
maintGrp.PUT("/spells/:id", UpdateMDSpell)
maintGrp.PUT("/spells-enhanced/:id", UpdateEnhancedMDSpell) // New enhanced endpoint
maintGrp.POST("/spells", AddSpell)
maintGrp.DELETE("/spells/:id", DeleteMDSpell)
- maintGrp.GET("/equipment", GetMDEquipments)
- maintGrp.GET("/equipment-enhanced", GetEnhancedMDEquipment) // New enhanced endpoint
- maintGrp.GET("/equipment/:id", GetMDEquipment)
- maintGrp.GET("/equipment-enhanced/:id", GetEnhancedMDEquipmentItem) // New enhanced endpoint
maintGrp.PUT("/equipment/:id", UpdateMDEquipment)
maintGrp.PUT("/equipment-enhanced/:id", UpdateEnhancedMDEquipmentItem) // New enhanced endpoint
maintGrp.POST("/equipment", AddEquipment)
maintGrp.DELETE("/equipment/:id", DeleteMDEquipment)
- maintGrp.GET("/weapons", GetMDWeapons)
- maintGrp.GET("/weapons-enhanced", GetEnhancedMDWeapons) // New enhanced endpoint
- maintGrp.GET("/weapons/:id", GetMDWeapon)
- maintGrp.GET("/weapons-enhanced/:id", GetEnhancedMDWeapon) // New enhanced endpoint
maintGrp.PUT("/weapons/:id", UpdateMDWeapon)
maintGrp.PUT("/weapons-enhanced/:id", UpdateEnhancedMDWeapon) // New enhanced endpoint
maintGrp.POST("/weapons", AddWeapon)
diff --git a/frontend/src/components/CharacterDetails.vue b/frontend/src/components/CharacterDetails.vue
index 3ae6682..275f1b0 100644
--- a/frontend/src/components/CharacterDetails.vue
+++ b/frontend/src/components/CharacterDetails.vue
@@ -6,7 +6,7 @@
-