[Server/Client/Protocol] Add developer staff role (#7211)

* [Server/Client/Protocol] Add developer staff role

Introduce a Developer staff level (proto flag 32, DB admin bit 8) that
sits between admin and moderator: no kick/ban/warn/report/admin powers,
but gets server log access via a new developer command container family
(GET_SERVER_STATS, VIEWLOG_HISTORY) and an idle-timeout exemption.

- Protocol: IsDeveloper flag, developer_commands.proto envelope,
  Command_GetServerStats/Command_GetLogHistory, Response_GetServerStats,
  Command_AdjustMod.should_be_developer
- Servatrice: fail-closed developer dispatcher, uptime snapshot handler,
  shared log history handler reuse, bit-8 DB mapping
- Client: burgundy pawn/badge/labels/sort order, prepareDeveloperCommand,
  minimal Developer stats tab, log tab access, promote/demote actions

Took 24 minutes

Took 18 seconds

* [Server/Client/Protocol] Address developer role review feedback

Address ZeizaZach's review of the developer staff role:

- Nudge the developer log query to exclude private chat and sender IPs
  (the ModeratorCommand path still sees everything).
- Deduplicate Command_GetLogHistory into Command_ViewLogHistory, which now
  extends both ModeratorCommand (ext) and DeveloperCommand (dev_ext); the
  client picks the DeveloperCommand-scoped extension by extendee, and the
  server reads it via the extension number.
- Pull the uptime snapshot SQL into Servatrice_DatabaseInterface as
  getLatestUptimeSnapshot() and widen the reported counters to 64-bit.
- Document the admin bitfield (1 admin, 2 moderator, 4 judge, 8 developer)
  and add a server-side test for the developer command path.

* Add missing trailing newline to user_context_menu.cpp

* Remove stale includes of deleted command_get_log_history proto

The Command_GetLogHistory message was folded into Command_ViewLogHistory,
which deleted command_get_log_history.proto, but serversocketinterface
still #included its generated header. Fresh CI builds fail on the missing
file; local builds masked it by reusing a previously generated header.

* [Server] Exclude chat rows when private-chat filter is bypassable

A developer who omits log_location entirely — or sends only "chat" —
leaves chatType, gameType, roomType all false, so getMessageLogHistory
skips the target_type clause and returns every row, private messages
included. When !allowPrivateChat the server now forces game+room when
no surviving location was requested, guaranteeing the query always
carries a target_type restriction.

[Client] Demote mod+dev to moderator path in log-tab dispatch

The developer command family is strictly weaker than the moderator one
(no private chat, no sender_ip, ip filter ignored), so granting the
developer bit to an existing moderator must not silently strip their
capabilities. useDeveloperCommands is now true only when the user holds
the developer bit and not the moderator bit.

[Client] Hide the IP-address filter for developer log tab users

The developer path ignores the ip_address query field server-side.
Showing the field lets a developer type an IP and get results that are
silently unfiltered by it rather than an empty result set — reads as a
broken filter. Hide labelFindIPAddress/findIPAddress alongside the
privateChat checkbox.

* Developer pawn is silver.

* [Client] Fix indentation of merged Card Art Rules / Developer tabs

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-09-11 17:18:56 +02:00 committed by GitHub
parent 7d867b9745
commit d5d99e4dfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 708 additions and 27 deletions

View file

@ -25,6 +25,7 @@ set(PROTO_FILES
command_dump_zone.proto
command_flip_card.proto
command_game_say.proto
command_get_server_stats.proto
command_inc_card_counter.proto
command_inc_counter.proto
command_kick_from_game.proto
@ -71,6 +72,7 @@ set(PROTO_FILES
context_ready_start.proto
context_set_sideboard_lock.proto
context_undo_draw.proto
developer_commands.proto
event_add_to_list.proto
event_attach_card.proto
event_change_zone_properties.proto
@ -140,6 +142,7 @@ set(PROTO_FILES
response_forgotpasswordrequest.proto
response_get_admin_notes.proto
response_get_games_of_user.proto
response_get_server_stats.proto
response_get_user_info.proto
response_join_room.proto
response_list_users.proto

View file

@ -37,6 +37,7 @@ message Command_AdjustMod {
required string user_name = 1;
optional bool should_be_mod = 2;
optional bool should_be_judge = 3;
optional bool should_be_developer = 4;
}
message Command_ResetUserPassword {

View file

@ -0,0 +1,8 @@
syntax = "proto2";
import "developer_commands.proto";
message Command_GetServerStats {
extend DeveloperCommand {
optional Command_GetServerStats ext = 1000;
}
}

View file

@ -4,6 +4,7 @@ import "game_commands.proto";
import "room_commands.proto";
import "moderator_commands.proto";
import "admin_commands.proto";
import "developer_commands.proto";
message CommandContainer {
optional uint64 cmd_id = 1;
@ -16,4 +17,5 @@ message CommandContainer {
repeated RoomCommand room_command = 102;
repeated ModeratorCommand moderator_command = 103;
repeated AdminCommand admin_command = 104;
repeated DeveloperCommand developer_command = 105;
}

View file

@ -0,0 +1,8 @@
syntax = "proto2";
message DeveloperCommand {
enum DeveloperCommandType {
GET_SERVER_STATS = 1000;
VIEWLOG_HISTORY = 1001;
}
extensions 100 to max;
}

View file

@ -1,4 +1,5 @@
syntax = "proto2";
import "developer_commands.proto";
message ModeratorCommand {
enum ModeratorCommandType {
BAN_FROM_SERVER = 1000;
@ -80,6 +81,9 @@ message Command_ViewLogHistory {
extend ModeratorCommand {
optional Command_ViewLogHistory ext = 1005;
}
extend DeveloperCommand {
optional Command_ViewLogHistory dev_ext = 1001;
}
optional string user_name = 1; // user that created message
optional string ip_address = 2; // ip address of user that created message
optional string game_name = 3; // client id of user that created the message

View file

@ -77,6 +77,7 @@ message Response {
FORGOT_PASSWORD_REQUEST = 1016; // Response to password reset request
PASSWORD_SALT = 1017; // Response containing password salt
GET_ADMIN_NOTES = 1018; // Response with admin notes
GET_SERVER_STATS = 1019; // Response with server status statistics
REPLAY_LIST = 1100; // Response listing replays
REPLAY_DOWNLOAD = 1101; // Response for replay download
REPLAY_GET_CODE = 1102; // Response containing replay code

View file

@ -0,0 +1,19 @@
syntax = "proto2";
import "response.proto";
message Response_GetServerStats {
extend Response {
optional Response_GetServerStats ext = 1220;
}
optional uint64 users_count = 1;
optional uint64 mods_count = 2;
optional uint64 games_count = 3;
// Traffic recorded during the last status update tick
optional uint64 tx_bytes = 4;
optional uint64 rx_bytes = 5;
optional uint64 uptime_secs = 6;
optional uint64 timest = 7; // unix timestamp of the snapshot
}

View file

@ -8,6 +8,7 @@ message ServerInfo_User {
IsModerator = 4;
IsAdmin = 8;
IsJudge = 16;
IsDeveloper = 32;
};
message PawnColorsOverride {
optional string left_side = 1;