Skip to content

Commit 20cbfd3

Browse files
zaigieclaude
andcommitted
fix: security and cross-platform improvements for player APIs
- Fix OptionalJWTMiddleware panic on non-standard Authorization headers - Hide sensitive fields (ip, steamId, userId) in /api/online_player for unauthenticated users - Unify userId hiding logic between listPlayers and getPlayer (keep platform prefix) - Support cross-platform userId for kick/ban/unban operations - Use safe delimiter "|" in frontend RCON player value to prevent split issues Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6d63d74 commit 20cbfd3

File tree

4 files changed

+45
-23
lines changed

4 files changed

+45
-23
lines changed

api/player.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ const (
1919
OrderByLevel PlayerOrderBy = "level"
2020
)
2121

22+
// getPlayerActionUserId 获取用于 kick/ban/unban 操作的 userId
23+
// 优先使用完整的 UserId(支持跨平台),兜底使用 steam_ + SteamId
24+
func getPlayerActionUserId(player database.Player) string {
25+
if player.UserId != "" {
26+
return player.UserId
27+
}
28+
if player.SteamId != "" {
29+
return fmt.Sprintf("steam_%s", player.SteamId)
30+
}
31+
return ""
32+
}
33+
2234
// listOnlinePlayers godoc
2335
//
2436
// @Summary List Online Players
@@ -37,6 +49,16 @@ func listOnlinePlayers(c *gin.Context) {
3749
return
3850
}
3951
service.PutPlayersOnline(database.GetDB(), onlinePLayers)
52+
// 未登录隐藏敏感字段
53+
if !c.GetBool("loggedIn") {
54+
for i := range onlinePLayers {
55+
onlinePLayers[i].Ip = ""
56+
if onlinePLayers[i].UserId != "" {
57+
onlinePLayers[i].UserId = strings.Split(onlinePLayers[i].UserId, "_")[0] + "_"
58+
}
59+
onlinePLayers[i].SteamId = ""
60+
}
61+
}
4062
c.JSON(http.StatusOK, onlinePLayers)
4163
}
4264

@@ -148,7 +170,9 @@ func getPlayer(c *gin.Context) {
148170
//未登录隐藏字段
149171
if !c.GetBool("loggedIn") {
150172
player.Ip = ""
151-
player.UserId = ""
173+
if player.UserId != "" {
174+
player.UserId = strings.Split(player.UserId, "_")[0] + "_"
175+
}
152176
player.SteamId = ""
153177
}
154178
c.JSON(http.StatusOK, player)
@@ -180,7 +204,7 @@ func kickPlayer(c *gin.Context) {
180204
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
181205
return
182206
}
183-
err = tool.KickPlayer(fmt.Sprintf("steam_%s", player.SteamId))
207+
err = tool.KickPlayer(getPlayerActionUserId(player))
184208
if err != nil {
185209
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
186210
return
@@ -214,7 +238,7 @@ func banPlayer(c *gin.Context) {
214238
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
215239
return
216240
}
217-
err = tool.BanPlayer(fmt.Sprintf("steam_%s", player.SteamId))
241+
err = tool.BanPlayer(getPlayerActionUserId(player))
218242
if err != nil {
219243
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
220244
return
@@ -248,7 +272,7 @@ func unbanPlayer(c *gin.Context) {
248272
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
249273
return
250274
}
251-
err = tool.UnBanPlayer(fmt.Sprintf("steam_%s", player.SteamId))
275+
err = tool.UnBanPlayer(getPlayerActionUserId(player))
252276
if err != nil {
253277
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
254278
return

api/router.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ func RegisterRouter(r *gin.Engine) {
6767
anonymousGroup.GET("/server", getServer)
6868
anonymousGroup.GET("/server/tool", getServerTool)
6969
anonymousGroup.GET("/server/metrics", getServerMetrics)
70-
anonymousGroup.GET("/online_player", listOnlinePlayers)
7170
anonymousGroup.GET("/guild", listGuilds)
7271
anonymousGroup.GET("/guild/:admin_player_uid", getGuild)
7372
}
7473
// 根据登录状态返回不同结果
7574
OptionalGroup := apiGroup.Group("")
7675
OptionalGroup.Use(auth.OptionalJWTMiddleware())
7776
{
77+
OptionalGroup.GET("/online_player", listOnlinePlayers)
7878
OptionalGroup.GET("/player", listPlayers)
7979
OptionalGroup.GET("/player/:player_uid", getPlayer)
8080
}

internal/auth/jwt.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,6 @@ func OptionalJWTMiddleware() gin.HandlerFunc {
5656
return func(c *gin.Context) {
5757
// 默认未登录
5858
c.Set("loggedIn", false)
59-
// 捕获可能的 panic,防止中间件崩掉
60-
defer func() {
61-
if r := recover(); r != nil {
62-
// panic 也当未登录
63-
c.Set("loggedIn", false)
64-
}
65-
}()
6659
authHeader := c.GetHeader("Authorization")
6760
if authHeader != "" {
6861
var tokenString string
@@ -71,12 +64,16 @@ func OptionalJWTMiddleware() gin.HandlerFunc {
7164
} else if strings.HasPrefix(authHeader, prefixJWT) {
7265
tokenString = strings.TrimPrefix(authHeader, prefixJWT)
7366
}
74-
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
75-
return SecretKey, nil
76-
})
77-
if claims, ok := token.Claims.(jwt.MapClaims); ok && err == nil && token.Valid {
78-
c.Set("claims", claims)
79-
c.Set("loggedIn", true)
67+
if tokenString != "" {
68+
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
69+
return SecretKey, nil
70+
})
71+
if err == nil && token != nil {
72+
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
73+
c.Set("claims", claims)
74+
c.Set("loggedIn", true)
75+
}
76+
}
8077
}
8178
}
8279
c.Next()

web/src/views/PcHome/PcHome.vue

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ const getPlayerList = async () => {
162162
rconPlayerOptions.value = data.value.map((item) => {
163163
return {
164164
label: `${item.nickname}(${item.player_uid})`,
165-
value: `${item.player_uid}-${item.user_id}-${item.steam_id}`,
165+
value: `${item.player_uid}|${item.user_id}|${item.steam_id}`,
166166
};
167167
});
168168
};
@@ -316,21 +316,22 @@ const removeRconCommand = async (uuid) => {
316316
};
317317
const fillRconCommand = (rconCommand) => {
318318
let cmd = rconCommand.placeholder;
319+
const playerParts = rconSelectedPlayer.value ? rconSelectedPlayer.value.split("|") : [];
319320
// {steamUserID},大小写不敏感
320321
if (/{steamUserID}/i.test(cmd)) {
321322
if (!rconSelectedPlayer.value) {
322323
message.warning(t("message.selectPlayerFirst"));
323324
return;
324325
}
325-
cmd = cmd.replace(/{steamUserID}/gi, "steam_" + rconSelectedPlayer.value.split("-")[2]);
326+
cmd = cmd.replace(/{steamUserID}/gi, "steam_" + playerParts[2]);
326327
}
327328
// {userID},大小写不敏感
328329
if (/{userID}/i.test(cmd)) {
329330
if (!rconSelectedPlayer.value) {
330331
message.warning(t("message.selectPlayerFirst"));
331332
return;
332333
}
333-
cmd = cmd.replace(/{userID}/gi, rconSelectedPlayer.value.split("-")[1]);
334+
cmd = cmd.replace(/{userID}/gi, playerParts[1]);
334335
}
335336
// {itemID},大小写不敏感
336337
if (/{itemID}/i.test(cmd)) {
@@ -1298,9 +1299,9 @@ onMounted(async () => {
12981299
</div>
12991300
<div class="flex w-full items-center mt-3 justify-between">
13001301
<n-text>
1301-
PlayerID: {{ rconSelectedPlayer?.split("-")[0] || "-" }}
1302+
PlayerID: {{ rconSelectedPlayer?.split("|")[0] || "-" }}
13021303
</n-text>
1303-
<n-text>UserID: {{ rconSelectedPlayer?.split("-")[1] || "-" }}</n-text>
1304+
<n-text>UserID: {{ rconSelectedPlayer?.split("|")[1] || "-" }}</n-text>
13041305
</div>
13051306
13061307
<div class="flex w-full items-center mt-3">

0 commit comments

Comments
 (0)