feat(infra): add SessionManager with full session tracking and API key validation
This commit is contained in:
@@ -6,6 +6,12 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="tests\**\*" />
|
||||||
|
<Content Remove="tests\**\*" />
|
||||||
|
<None Remove="tests\**\*" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Protobuf Include="Protos/scada.proto" GrpcServices="Server" />
|
<Protobuf Include="Protos/scada.proto" GrpcServices="Server" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
51
infra/lmxfakeproxy/Sessions/SessionManager.cs
Normal file
51
infra/lmxfakeproxy/Sessions/SessionManager.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace LmxFakeProxy.Sessions;
|
||||||
|
|
||||||
|
public record SessionInfo(string ClientId, long ConnectedSinceUtcTicks);
|
||||||
|
|
||||||
|
public class SessionManager
|
||||||
|
{
|
||||||
|
private readonly string? _requiredApiKey;
|
||||||
|
private readonly ConcurrentDictionary<string, SessionInfo> _sessions = new();
|
||||||
|
|
||||||
|
public SessionManager(string? requiredApiKey)
|
||||||
|
{
|
||||||
|
_requiredApiKey = requiredApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (bool Success, string Message, string SessionId) Connect(string clientId, string apiKey)
|
||||||
|
{
|
||||||
|
if (!CheckApiKey(apiKey))
|
||||||
|
return (false, "Invalid API key", string.Empty);
|
||||||
|
|
||||||
|
var sessionId = Guid.NewGuid().ToString("N");
|
||||||
|
var info = new SessionInfo(clientId, DateTime.UtcNow.Ticks);
|
||||||
|
_sessions[sessionId] = info;
|
||||||
|
return (true, "Connected", sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Disconnect(string sessionId)
|
||||||
|
{
|
||||||
|
return _sessions.TryRemove(sessionId, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ValidateSession(string sessionId)
|
||||||
|
{
|
||||||
|
return _sessions.ContainsKey(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (bool Found, string ClientId, long ConnectedSinceUtcTicks) GetConnectionState(string sessionId)
|
||||||
|
{
|
||||||
|
if (_sessions.TryGetValue(sessionId, out var info))
|
||||||
|
return (true, info.ClientId, info.ConnectedSinceUtcTicks);
|
||||||
|
return (false, string.Empty, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CheckApiKey(string apiKey)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_requiredApiKey))
|
||||||
|
return true;
|
||||||
|
return apiKey == _requiredApiKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
global using Xunit;
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
namespace LmxFakeProxy.Tests;
|
||||||
|
|
||||||
|
using LmxFakeProxy.Sessions;
|
||||||
|
|
||||||
|
public class SessionManagerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Connect_ReturnsUniqueSessionId()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager(null);
|
||||||
|
var (ok1, _, id1) = mgr.Connect("client1", "");
|
||||||
|
var (ok2, _, id2) = mgr.Connect("client2", "");
|
||||||
|
Assert.True(ok1);
|
||||||
|
Assert.True(ok2);
|
||||||
|
Assert.NotEqual(id1, id2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Connect_WithValidApiKey_Succeeds()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager("secret");
|
||||||
|
var (ok, _, _) = mgr.Connect("client1", "secret");
|
||||||
|
Assert.True(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Connect_WithInvalidApiKey_Fails()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager("secret");
|
||||||
|
var (ok, msg, id) = mgr.Connect("client1", "wrong");
|
||||||
|
Assert.False(ok);
|
||||||
|
Assert.Empty(id);
|
||||||
|
Assert.Contains("Invalid API key", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Connect_WithNoKeyConfigured_AcceptsAnyKey()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager(null);
|
||||||
|
var (ok1, _, _) = mgr.Connect("c1", "anykey");
|
||||||
|
var (ok2, _, _) = mgr.Connect("c2", "");
|
||||||
|
Assert.True(ok1);
|
||||||
|
Assert.True(ok2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Disconnect_RemovesSession()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager(null);
|
||||||
|
var (_, _, id) = mgr.Connect("client1", "");
|
||||||
|
Assert.True(mgr.ValidateSession(id));
|
||||||
|
var ok = mgr.Disconnect(id);
|
||||||
|
Assert.True(ok);
|
||||||
|
Assert.False(mgr.ValidateSession(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Disconnect_UnknownSession_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager(null);
|
||||||
|
Assert.False(mgr.Disconnect("nonexistent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateSession_ValidId_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager(null);
|
||||||
|
var (_, _, id) = mgr.Connect("client1", "");
|
||||||
|
Assert.True(mgr.ValidateSession(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateSession_InvalidId_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager(null);
|
||||||
|
Assert.False(mgr.ValidateSession("bogus"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetConnectionState_ReturnsCorrectInfo()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager(null);
|
||||||
|
var (_, _, id) = mgr.Connect("myClient", "");
|
||||||
|
var (found, clientId, ticks) = mgr.GetConnectionState(id);
|
||||||
|
Assert.True(found);
|
||||||
|
Assert.Equal("myClient", clientId);
|
||||||
|
Assert.True(ticks > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetConnectionState_UnknownSession_ReturnsNotConnected()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager(null);
|
||||||
|
var (found, clientId, ticks) = mgr.GetConnectionState("unknown");
|
||||||
|
Assert.False(found);
|
||||||
|
Assert.Empty(clientId);
|
||||||
|
Assert.Equal(0, ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CheckApiKey_NoKeyConfigured_AlwaysValid()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager(null);
|
||||||
|
Assert.True(mgr.CheckApiKey("anything"));
|
||||||
|
Assert.True(mgr.CheckApiKey(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CheckApiKey_WithKeyConfigured_ValidatesCorrectly()
|
||||||
|
{
|
||||||
|
var mgr = new SessionManager("mykey");
|
||||||
|
Assert.True(mgr.CheckApiKey("mykey"));
|
||||||
|
Assert.False(mgr.CheckApiKey("wrong"));
|
||||||
|
Assert.False(mgr.CheckApiKey(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user