feat: Implement end-to-end encryption for channels

- Added EncryptionSalt and WrappedRoomKey properties to Channel model.
- Introduced RoomCrypto class for client-side encryption and decryption.
- Updated ChannelService to handle encrypted channels, including creation and rekeying.
- Modified ChannelsController to expose crypto metadata and rekey functionality.
- Enhanced IrcCommandHandler to block joining encrypted channels over IRC.
- Updated database schema with migration for new encryption fields.
- Refactored file validation and image processing services to accommodate encrypted channels.
- Added unit tests for RoomCrypto functionality and updated existing tests for channel services.
This commit is contained in:
HueByte
2026-07-16 03:50:23 +02:00
parent ea8e583ee5
commit e05b420ce9
36 changed files with 1400 additions and 67 deletions
@@ -24,6 +24,7 @@ internal sealed class ConnectionManager : IAsyncDisposable
private EchoHubConnection? _connection;
private ApiClient? _apiClient;
private readonly ClientEncryptionService _encryption = new();
private readonly RoomKeyStore _roomKeys = new();
private readonly HashSet<string> _joinedChannels = [];
// ── Properties ────────────────────────────────────────────────────────
@@ -31,6 +32,7 @@ internal sealed class ConnectionManager : IAsyncDisposable
public bool IsConnected => _connection?.IsConnected == true;
public bool IsAuthenticated => _apiClient is not null;
public ApiClient? Api => _apiClient;
public RoomKeyStore RoomKeys => _roomKeys;
// ── Events (forwarded from SignalR) ───────────────────────────────────
@@ -101,7 +103,8 @@ internal sealed class ConnectionManager : IAsyncDisposable
if (_connection is not null)
await _connection.DisposeAsync();
_connection = new EchoHubConnection(info.ServerUrl, _apiClient, _encryption);
_roomKeys.LoadForServer(info.ServerUrl);
_connection = new EchoHubConnection(info.ServerUrl, _apiClient, _encryption, _roomKeys);
WireConnectionEvents(_connection);
await _connection.ConnectAsync();
@@ -156,6 +159,7 @@ internal sealed class ConnectionManager : IAsyncDisposable
_apiClient?.Dispose();
_apiClient = null;
_joinedChannels.Clear();
_roomKeys.Clear();
}
/// <summary>
@@ -169,14 +173,14 @@ internal sealed class ConnectionManager : IAsyncDisposable
// ── Channel Operations ────────────────────────────────────────────────
public async Task<List<MessageDto>> JoinChannelAsync(string channelName, string? password = null)
public async Task<JoinOutcome> JoinChannelAsync(string channelName, string? password = null)
{
if (_connection is null) throw new InvalidOperationException("Not connected");
try
{
var history = await _connection.JoinChannelAsync(channelName, password);
var outcome = await _connection.JoinChannelAsync(channelName, password);
_joinedChannels.Add(channelName);
return history;
return outcome;
}
catch (ChannelPasswordRequiredException)
{