Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 105 additions & 6 deletions GenOnlineService/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,30 @@ public static List<UserSession> GetAllDataFromUser(Int64 userID)
return lstRet;
}

public static async Task DisconnectUser(Int64 userID, byte[] finalMessage)
{
List<UserSession> userSessions = GetAllDataFromUser(userID);

foreach (UserSession userSession in userSessions)
{
try
{
UserWebSocketInstance? oldWS = GetWebSocketForSession(userSession);
if (oldWS != null)
{
await oldWS.SendAsync(finalMessage, WebSocketMessageType.Text);
}

await DeleteSession(userID, userSession.GetSessionType(), oldWS, true);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] DisconnectUser failed for user {userID}, session {userSession.GetSessionType()}: {ex.Message}");
SentrySdk.CaptureException(ex);
}
}
}

public static async Task<bool> ClearDataFromUser(Int64 userID, EUserSessionType sessionType)
{
// NOTE: This is when a player is truly disconnected and we can destroy session, remove form lobby etc, websocket disconnect doesnt mean that because the clietn reconnects
Expand Down Expand Up @@ -819,9 +843,11 @@ public static async Task MarkRoomMemberListAsDirty(int roomID)
}

// NOTE: only one instance for ALL websockets/sessions, and is destroyed when the last one of the former is destroyed
public class SharedUserData
{
private int m_RefCount = 0;
public class SharedUserData
{
private int m_RefCount = 0;
private readonly Queue<Int64> m_chatMessageTimestamps = new();
private Int64? m_lastChatRateLimitNoticeTimestamp;

public void IncrementRefCount()
{
Expand Down Expand Up @@ -849,7 +875,49 @@ public bool NeedsGC()

public UserSocialContainer GetSocialContainer() { return m_socialContainer; }

public bool IsAdmin() { return m_bIsAdmin; }
public bool IsAdmin() { return m_bIsAdmin; }

public bool TryConsumeChatMessage()
{
const int maxMessages = 3;
const Int64 windowMilliseconds = 9000;
Int64 now = Environment.TickCount64;

lock (m_chatMessageTimestamps)
{
while (m_chatMessageTimestamps.TryPeek(out Int64 timestamp)
&& now - timestamp >= windowMilliseconds)
{
m_chatMessageTimestamps.Dequeue();
}

if (m_chatMessageTimestamps.Count >= maxMessages)
{
return false;
}

m_chatMessageTimestamps.Enqueue(now);
return true;
}
}

public bool TryConsumeChatRateLimitNotice()
{
const Int64 intervalMilliseconds = 9000;
Int64 now = Environment.TickCount64;

lock (m_chatMessageTimestamps)
{
if (m_lastChatRateLimitNoticeTimestamp.HasValue
&& now - m_lastChatRateLimitNoticeTimestamp.Value < intervalMilliseconds)
{
return false;
}

m_lastChatRateLimitNoticeTimestamp = now;
return true;
}
}

public SharedUserData(Int64 ownerID, UserSocialContainer socialContainer, string strDisplayName, bool bIsAdmin, PlayerStats userStats)
{
Expand Down Expand Up @@ -2545,7 +2613,10 @@ public enum EWebSocketMessageID
AC_REGISTER_PLAYER = 40,
AC_DEREGISTER_PLAYER = 41,
WS_KEEPALIVE = 42,
WS_KEEPALIVE_CLIENT = 43
WS_KEEPALIVE_CLIENT = 43,
MODERATION_NOTICE = 46,
MODERATION_COMMAND = 47,
MODERATION_COMMAND_RESULT = 48
};

public static class UserPresence
Expand Down Expand Up @@ -2643,6 +2714,34 @@ public class WebSocketMessage_StartMatch : WebSocketMessage
public string screenshot_url { get; set; } = String.Empty;
}

public class WebSocketMessage_ModerationNotice : WebSocketMessage
{
public string action_type { get; set; } = String.Empty;
public string reason { get; set; } = String.Empty;

[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string? scope_type { get; set; }
}

public sealed class WebSocketMessage_ModerationCommand : WebSocketMessage
{
public UInt64 request_id { get; set; }
public string action_type { get; set; } = String.Empty;
public Int64? target_user_id { get; set; }
public string reason { get; set; } = String.Empty;
}

public sealed class WebSocketMessage_ModerationCommandResult : WebSocketMessage
{
public UInt64 request_id { get; set; }
public bool success { get; set; }

[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string? error_code { get; set; }

public string message { get; set; } = String.Empty;
}

public abstract class WebSocketMessage
{
public int msg_id { get; set; }
Expand Down Expand Up @@ -2829,4 +2928,4 @@ public class WebSocketMessage_MatchmakerStartGame : WebSocketMessage

}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public override Type GetReturnType()
public string refresh_token { get; set; } = "";
public Int64 user_id { get; set; } = -1;
public string display_name { get; set; } = "";
public string ban_reason { get; set; } = "";

public string ws_uri { get; set; } = "";
}
Expand Down Expand Up @@ -176,12 +177,14 @@ public async Task<APIResult> Post_InternalHandler(string jsonData, string ipAddr
Int64 user_id = loginEntry.user_id;

// ban check
bool bIsBanned = await Database.Users.IsUserBanned(db, user_id);
if (bIsBanned)
UserBanStatus? banStatus = await Database.Users.GetUserBanStatus(db, user_id);
if (banStatus?.IsBanned == true)
{
await TokenRevocationManager.RevokeAllTokensForUser(user_id, "user is banned");
await ModerationManager.DisconnectUser(user_id, EModerationAction.Ban, banStatus.BanReason);

result.result = EPendingLoginState.LoginFailed;
result.ban_reason = banStatus.BanReason;
Response.StatusCode = (int)HttpStatusCode.Locked;
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public override Type GetReturnType()
public string refresh_token { get; set; } = "";
public Int64 user_id { get; set; } = -1;
public string display_name { get; set; } = "";
public string ban_reason { get; set; } = "";

public string ws_uri { get; set; } = "";
}
Expand Down Expand Up @@ -129,13 +130,15 @@ public async Task<APIResult> Post_InternalHandler(string jsonData, string ipAddr
}

// ban check
bool bIsBanned = await Database.Users.IsUserBanned(db, user_id);
if (bIsBanned)
UserBanStatus? banStatus = await Database.Users.GetUserBanStatus(db, user_id);
if (banStatus?.IsBanned == true)
{
// kill every token they hold, not just this request
await TokenRevocationManager.RevokeAllTokensForUser(user_id, "user is banned");
await ModerationManager.DisconnectUser(user_id, EModerationAction.Ban, banStatus.BanReason);

result.result = EPendingLoginState.LoginFailed;
result.ban_reason = banStatus.BanReason;
Response.StatusCode = (int)HttpStatusCode.Locked;
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public override Type GetReturnType()
public string refresh_token { get; set; } = "";
public Int64 user_id { get; set; } = -1;
public string display_name { get; set; } = "";
public string ban_reason { get; set; } = "";
}

// Pure token rotation. Unlike LoginWithToken this does NOT establish a session - the caller's
Expand Down Expand Up @@ -97,13 +98,15 @@ public async Task<APIResult> Post_InternalHandler(string ipAddr)

// re-check the ban on every rotation so a ban applied since the last refresh takes
// effect immediately rather than waiting for the periodic reconcile
bool bIsBanned = await Database.Users.IsUserBanned(db, user_id);
if (bIsBanned)
UserBanStatus? banStatus = await Database.Users.GetUserBanStatus(db, user_id);
if (banStatus?.IsBanned == true)
{
// kill every token they hold, not just this request
await TokenRevocationManager.RevokeAllTokensForUser(user_id, "user is banned");
await ModerationManager.DisconnectUser(user_id, EModerationAction.Ban, banStatus.BanReason);

result.result = EPendingLoginState.LoginFailed;
result.ban_reason = banStatus.BanReason;
Response.StatusCode = (int)HttpStatusCode.Locked;
return result;
}
Expand Down
5 changes: 3 additions & 2 deletions GenOnlineService/Controllers/Rooms/RoomsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ public override Type GetReturnType()
return this.GetType();
}

public List<RoomData>? rooms { get; set; } = null;
}
public List<RoomData>? rooms { get; set; } = null;
public bool supports_moderation_commands { get; set; } = true;
}

[ApiController]
[Route("env/{environment}/contract/{contract_version}/[controller]")]
Expand Down
Loading
Loading