diff --git a/docs/changelog/index.md b/docs/changelog/index.md index f35822f..d589a66 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -4,6 +4,9 @@ Release history for EchoHub. ## Releases +- [v0.2.8](v0.2.8.md) - Docker Support, IRC Account Creation & BOM Fix +- [v0.2.7](v0.2.7.md) - User List Fix & Terminal.Gui NuGet Migration +- [v0.2.6](v0.2.6.md) - Major Refactoring & Code Organization - [v0.2.5](v0.2.5.md) - Session Persistence, Auto-Updates, Audio & Transparent Theme - [v0.2.4](v0.2.4.md) - E2E Message Encryption - [v0.2.3](v0.2.3.md) - Moderation, Embeds & UI Overhaul diff --git a/docs/changelog/toc.yml b/docs/changelog/toc.yml index 7f71eca..7bc9ec0 100644 --- a/docs/changelog/toc.yml +++ b/docs/changelog/toc.yml @@ -1,5 +1,7 @@ - name: Overview href: index.md +- name: v0.2.8 + href: v0.2.8.md - name: v0.2.7 href: v0.2.7.md - name: v0.2.6 diff --git a/docs/changelog/v0.2.7.md b/docs/changelog/v0.2.7.md index 198ccfe..929c70c 100644 --- a/docs/changelog/v0.2.7.md +++ b/docs/changelog/v0.2.7.md @@ -4,14 +4,6 @@ - Fix user list empty on initial connect — `FetchAndUpdateOnlineUsers` was called before `InvokeUI` set the current channel, causing an early return -## New Features - -- Add Docker support for EchoHub.Server — `docker compose up -d` for easy self-hosting with persistent volume for database, uploads, and logs - -## CI - -- Add Docker workflow — builds and pushes multi-arch (`amd64`/`arm64`) server image to GHCR on release - ## Infrastructure - Switch Terminal.Gui from local fork submodule back to NuGet package (`2.0.0-develop.5039`) — transparent color PR merged upstream diff --git a/docs/changelog/v0.2.8.md b/docs/changelog/v0.2.8.md new file mode 100644 index 0000000..7ffff4b --- /dev/null +++ b/docs/changelog/v0.2.8.md @@ -0,0 +1,15 @@ +# v0.2.8 + +## Bug Fixes + +- Fix IRC gateway sending UTF-8 BOM on first message, breaking CAP negotiation and SASL auth for all clients +- Handle `AUTHENTICATE *` (SASL abort) instead of crashing on invalid base64 + +## New Features + +- Add Docker support for EchoHub.Server — `docker compose up -d` for easy self-hosting with persistent volume for database, uploads, and logs +- IRC account creation — connecting with a new username auto-registers the account (PASS and SASL PLAIN) + +## CI + +- Add Docker workflow — builds and pushes multi-arch (`amd64`/`arm64`) server image to GHCR on release diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 31d2558..744d01d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.7 + 0.2.8 true $(NoWarn);CS1591 diff --git a/src/EchoHub.Core/Contracts/IChatService.cs b/src/EchoHub.Core/Contracts/IChatService.cs index 03ea1c2..97d880a 100644 --- a/src/EchoHub.Core/Contracts/IChatService.cs +++ b/src/EchoHub.Core/Contracts/IChatService.cs @@ -29,4 +29,5 @@ public interface IChatService Task GetUserProfileAsync(string username); Task> GetChannelsForUserAsync(string username); Task<(Guid UserId, string Username)?> AuthenticateUserAsync(string username, string password); + Task<(Guid UserId, string Username)?> RegisterUserAsync(string username, string password); } diff --git a/src/EchoHub.Server.Irc/IrcCommandHandler.cs b/src/EchoHub.Server.Irc/IrcCommandHandler.cs index f2f6a0f..00b4370 100644 --- a/src/EchoHub.Server.Irc/IrcCommandHandler.cs +++ b/src/EchoHub.Server.Irc/IrcCommandHandler.cs @@ -170,9 +170,13 @@ public sealed class IrcCommandHandler var result = await _chatService.AuthenticateUserAsync(username, password); + // Auth failed — try registering a new account + if (result is null) + result = await _chatService.RegisterUserAsync(username, password); + if (result is null) { - _logger.LogWarning("SASL auth failed for user '{Username}' (connection {Id})", + _logger.LogWarning("SASL auth/register failed for user '{Username}' (connection {Id})", username, _conn.ConnectionId); await _conn.SendNumericAsync(ServerName, IrcNumericReply.ERR_SASLFAIL, ":SASL authentication failed"); @@ -286,10 +290,14 @@ public sealed class IrcCommandHandler var result = await _chatService.AuthenticateUserAsync(_conn.Nickname!, _conn.Password); + // Auth failed — try registering a new account + if (result is null) + result = await _chatService.RegisterUserAsync(_conn.Nickname!, _conn.Password); + if (result is null) { await _conn.SendNumericAsync(ServerName, IrcNumericReply.ERR_PASSWDMISMATCH, - ":Password incorrect or account not found. Register via the EchoHub client first."); + ":Password incorrect."); await _conn.SendAsync("ERROR :Authentication failed"); return; } diff --git a/src/EchoHub.Server/Services/ChatService.cs b/src/EchoHub.Server/Services/ChatService.cs index b5010fd..63954c1 100644 --- a/src/EchoHub.Server/Services/ChatService.cs +++ b/src/EchoHub.Server/Services/ChatService.cs @@ -339,6 +339,38 @@ public class ChatService : IChatService return (user.Id, user.Username); } + public async Task<(Guid UserId, string Username)?> RegisterUserAsync(string username, string password) + { + username = username.ToLowerInvariant().Trim(); + + if (!ValidationConstants.UsernameRegex().IsMatch(username)) + return null; + + if (password.Length < 6 || password.Length > ValidationConstants.MaxPasswordLength) + return null; + + using var scope = _scopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + if (await db.Users.AnyAsync(u => u.Username == username)) + return null; + + var isFirstUser = !await db.Users.AnyAsync(); + + var user = new User + { + Id = Guid.NewGuid(), + Username = username, + PasswordHash = BCrypt.Net.BCrypt.HashPassword(password), + Role = isFirstUser ? ServerRole.Owner : ServerRole.Member, + }; + + db.Users.Add(user); + await db.SaveChangesAsync(); + + return (user.Id, user.Username); + } + /// /// Collapse consecutive newlines and cap total line count to prevent newline spam. ///