feat: Implement message encryption and decryption support

- Added IMessageEncryptionService and its implementation MessageEncryptionService for handling message encryption.
- Updated ChannelsController and ChatService to encrypt messages before storing and sending.
- Introduced encryption key retrieval endpoint in ServerController.
- Modified EchoHubDbContext to accommodate increased message content and embed JSON lengths for encrypted data.
- Created migrations to support encryption-related database changes.
- Enhanced FirstRunSetup to ensure encryption key is generated if not present.
- Updated appsettings.example.json to include encryption configuration.
- Added comprehensive unit tests for encryption service and compatibility tests between client and server encryption.
This commit is contained in:
HueByte
2026-02-20 17:16:32 +01:00
parent dfa1b21d32
commit 2efe54e417
26 changed files with 1517 additions and 31 deletions
@@ -8,6 +8,7 @@ namespace EchoHub.Client.Services;
public sealed class EchoHubConnection : IAsyncDisposable
{
private readonly HubConnection _connection;
private readonly ClientEncryptionService _encryption;
public event Action<MessageDto>? OnMessageReceived;
public event Action<string, string>? OnUserJoined;
@@ -25,8 +26,9 @@ public sealed class EchoHubConnection : IAsyncDisposable
public bool IsConnected => _connection.State == HubConnectionState.Connected;
public EchoHubConnection(string serverUrl, ApiClient apiClient)
public EchoHubConnection(string serverUrl, ApiClient apiClient, ClientEncryptionService encryption)
{
_encryption = encryption;
var hubUrl = serverUrl.TrimEnd('/') + HubConstants.ChatHubPath;
_connection = new HubConnectionBuilder()
@@ -63,7 +65,9 @@ public sealed class EchoHubConnection : IAsyncDisposable
{
_connection.On<MessageDto>(nameof(Core.Contracts.IEchoHubClient.ReceiveMessage), message =>
{
OnMessageReceived?.Invoke(message);
// Decrypt message content received from server
var decrypted = message with { Content = _encryption.Decrypt(message.Content) };
OnMessageReceived?.Invoke(decrypted);
});
_connection.On<string, string>(nameof(Core.Contracts.IEchoHubClient.UserJoined), (channelName, username) =>
@@ -132,7 +136,8 @@ public sealed class EchoHubConnection : IAsyncDisposable
public async Task<List<MessageDto>> JoinChannelAsync(string channelName)
{
return await _connection.InvokeAsync<List<MessageDto>>("JoinChannel", channelName);
var messages = await _connection.InvokeAsync<List<MessageDto>>("JoinChannel", channelName);
return DecryptMessages(messages);
}
public async Task LeaveChannelAsync(string channelName)
@@ -142,12 +147,15 @@ public sealed class EchoHubConnection : IAsyncDisposable
public async Task SendMessageAsync(string channelName, string content)
{
await _connection.InvokeAsync("SendMessage", channelName, content);
// Encrypt content before sending to server
var encrypted = _encryption.Encrypt(content);
await _connection.InvokeAsync("SendMessage", channelName, encrypted);
}
public async Task<List<MessageDto>> GetHistoryAsync(string channelName, int count = HubConstants.DefaultHistoryCount)
{
return await _connection.InvokeAsync<List<MessageDto>>("GetChannelHistory", channelName, count);
var messages = await _connection.InvokeAsync<List<MessageDto>>("GetChannelHistory", channelName, count);
return DecryptMessages(messages);
}
public async Task UpdateStatusAsync(UserStatus status, string? statusMessage = null)
@@ -160,6 +168,11 @@ public sealed class EchoHubConnection : IAsyncDisposable
return await _connection.InvokeAsync<List<UserPresenceDto>>("GetOnlineUsers", channelName);
}
private List<MessageDto> DecryptMessages(List<MessageDto> messages)
{
return messages.Select(m => m with { Content = _encryption.Decrypt(m.Content) }).ToList();
}
public async ValueTask DisposeAsync()
{
await _connection.DisposeAsync();