Skip to content

Commit

Permalink
Merge pull request #253 from peppy/log-logins-to-table
Browse files Browse the repository at this point in the history
Start logging logins to `osu_logins` table
  • Loading branch information
bdach authored Jan 8, 2025
2 parents 63bb290 + e8de2c0 commit 2d57213
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 3 deletions.
12 changes: 11 additions & 1 deletion osu.Server.Spectator.Tests/MetadataHubTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ public class MetadataHubTest
private readonly Mock<IMetadataClient> mockCaller;
private readonly Mock<IMetadataClient> mockWatchersGroup;
private readonly Mock<IGroupManager> mockGroupManager;
private readonly Mock<IDatabaseAccess> mockDatabase;

public MetadataHubTest()
{
userStates = new EntityStore<MetadataClientState>();

var mockDatabase = new Mock<IDatabaseAccess>();
mockDatabase = new Mock<IDatabaseAccess>();
var databaseFactory = new Mock<IDatabaseFactory>();
databaseFactory.Setup(factory => factory.GetInstance()).Returns(mockDatabase.Object);
var loggerFactoryMock = new Mock<ILoggerFactory>();
Expand Down Expand Up @@ -134,6 +135,15 @@ public async Task OfflineUserUpdatesAreNotBroadcast()
mockWatchersGroup.Verify(client => client.UserPresenceUpdated(user_id, null), Times.Exactly(2));
}

[Fact]
public async Task UserLoginLogging()
{
await hub.OnConnectedAsync();

// verify that the caller got the initial data update.
mockDatabase.Verify(caller => caller.AddLoginForUserAsync(user_id, It.IsAny<string>()), Times.Once);
}

[Fact]
public async Task UserWatchingHandling()
{
Expand Down
28 changes: 28 additions & 0 deletions osu.Server.Spectator/Database/DatabaseAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.JsonWebTokens;
using MySqlConnector;
using osu.Game.Online.Metadata;
Expand All @@ -19,6 +20,12 @@ namespace osu.Server.Spectator.Database
public class DatabaseAccess : IDatabaseAccess
{
private MySqlConnection? openConnection;
private readonly ILogger<DatabaseAccess> logger;

public DatabaseAccess(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<DatabaseAccess>();
}

public async Task<int?> GetUserIdFromTokenAsync(JsonWebToken jwtToken)
{
Expand Down Expand Up @@ -165,6 +172,27 @@ public async Task AddRoomParticipantAsync(MultiplayerRoom room, MultiplayerRoomU
}
}

public async Task AddLoginForUserAsync(int userId, string? userIp)
{
if (string.IsNullOrEmpty(userIp))
return;

var connection = await getConnectionAsync();

try
{
await connection.ExecuteAsync("INSERT INTO osu_logins (user_id, ip) VALUES (@UserID, @IP)", new
{
UserID = userId,
IP = userIp
});
}
catch (MySqlException ex)
{
logger.LogWarning(ex, "Could not log login for user {UserId}", userId);
}
}

public async Task RemoveRoomParticipantAsync(MultiplayerRoom room, MultiplayerRoomUser user)
{
var connection = await getConnectionAsync();
Expand Down
11 changes: 10 additions & 1 deletion osu.Server.Spectator/Database/DatabaseFactory.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using Microsoft.Extensions.Logging;

namespace osu.Server.Spectator.Database
{
public class DatabaseFactory : IDatabaseFactory
{
public IDatabaseAccess GetInstance() => new DatabaseAccess();
private readonly ILoggerFactory loggerFactory;

public DatabaseFactory(ILoggerFactory loggerFactory)
{
this.loggerFactory = loggerFactory;
}

public IDatabaseAccess GetInstance() => new DatabaseAccess(loggerFactory);
}
}
5 changes: 5 additions & 0 deletions osu.Server.Spectator/Database/IDatabaseAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ public interface IDatabaseAccess : IDisposable
/// </summary>
Task AddRoomParticipantAsync(MultiplayerRoom room, MultiplayerRoomUser user);

/// <summary>
/// Adds a login entry for the specified user.
/// </summary>
Task AddLoginForUserAsync(int userId, string? userIp);

/// <summary>
/// Remove a new participant for the specified <paramref name="room"/> in the database.
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions osu.Server.Spectator/Hubs/Metadata/MetadataHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,25 @@ public override async Task OnConnectedAsync()
}

usage.Item = new MetadataClientState(Context.ConnectionId, Context.GetUserId(), versionHash);

await logLogin(usage);
await broadcastUserPresenceUpdate(usage.Item.UserId, usage.Item.ToUserPresence());
await Clients.Caller.DailyChallengeUpdated(dailyChallengeUpdater.Current);
}
}

private async Task logLogin(ItemUsage<MetadataClientState> usage)
{
string? userIp = Context.GetHttpContext()?.Request.Headers.TryGetValue("X-Forwarded-For", out StringValues forwardedForIp) == true
// header may contain multiple IPs by spec, first is usually what we care for.
? forwardedForIp.ToString().Split(',').First()
// fallback to getting the raw IP.
: Context.GetHttpContext()?.Connection.RemoteIpAddress?.ToString();

using (var db = databaseFactory.GetInstance())
await db.AddLoginForUserAsync(usage.Item!.UserId, userIp);
}

public async Task<BeatmapUpdates> GetChangesSince(int queueId)
{
using (var db = databaseFactory.GetInstance())
Expand Down
2 changes: 1 addition & 1 deletion osu.Server.Spectator/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void ConfigureServices(IServiceCollection services)
logging.ClearProviders();
logging.AddConsole();
#if !DEBUG
logging.AddSentry();
logging.AddSentry(options => options.MinimumEventLevel = LogLevel.Warning);
#endif

// IdentityModelEventSource.ShowPII = true;
Expand Down

0 comments on commit 2d57213

Please sign in to comment.