Add user identity system with channel pairing and web /link command

Introduce Luna.Identity project with user identity service, pairing code
generation, and channel linking. Channels (Telegram, CLI) now verify link
status before routing messages and prompt users to pair via the web
interface. WebChannel handles /link <channel-type> <code> commands to
complete the pairing flow.

Fix inverted expiration check in IsPairingCodeExpired that caused codes
to be treated as expired immediately after creation.
This commit is contained in:
2026-04-04 16:09:10 +02:00
parent 3138436965
commit 2a07e07180
26 changed files with 779 additions and 465 deletions
@@ -1,5 +1,6 @@
using Luna.Channels.Abstractions;
using Luna.Configuration;
using Luna.Identity.Abstractions;
using Luna.Shared;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -11,6 +12,7 @@ namespace Luna.Channels.Telegram;
public class TelegramAdapter(
IChannelManager channelManager,
IUserIdentityService identityService,
ITelegramBotClient botClient,
TelegramOptions options,
ILogger<TelegramAdapter> adapterLogger,
@@ -60,6 +62,9 @@ public class TelegramAdapter(
return;
}
if (await channel.VerifyChannelLinkAsync() == false)
return;
var channelMessage = new ChannelMessage(
MessageId: message.Id.ToString(),
ConversationId: chat.Id.ToString(),
@@ -86,6 +91,7 @@ public class TelegramAdapter(
var channel = new TelegramChannel(
chatId.ToString(),
botClient,
identityService,
channelLogger,
options);
+22
View File
@@ -1,6 +1,7 @@
using System.Text;
using Luna.Channels.Abstractions;
using Luna.Configuration;
using Luna.Identity.Abstractions;
using Luna.Shared;
using Microsoft.Extensions.Logging;
using Telegram.Bot;
@@ -11,6 +12,7 @@ namespace Luna.Channels.Telegram;
public class TelegramChannel(
string chatId,
ITelegramBotClient botClient,
IUserIdentityService identityService,
ILogger<TelegramChannel> logger,
TelegramOptions options) : IChannel
{
@@ -24,6 +26,26 @@ public class TelegramChannel(
public event ChannelMessageReceivedEventHandler? MessageReceived;
public event ChannelConnectionChangedEventHandler? ConnectionStateChanged;
public async Task<bool> VerifyChannelLinkAsync()
{
var currentUser = identityService.GetCurrentUser();
if (identityService.IsChannelLinked(currentUser.UserId, ChannelType))
return true;
var pairingCode = identityService.StartChannelLink(currentUser.UserId, ChannelType);
await SendMessageAsync(new ChannelMessage(
MessageId: Guid.NewGuid().ToString(),
ConversationId: Guid.NewGuid().ToString(),
SenderId: Guid.NewGuid().ToString(),
SenderName: "luna",
Content:
$"{ChannelType} channel is not linked to current user.\nGo to web interface and execute command \"/link [pairing code]\".\nYou will have to provide following PairingCode: {pairingCode}",
Timestamp: DateTimeOffset.UtcNow));
return false;
}
public async Task SendMessageAsync(ChannelMessage message, CancellationToken cancellationToken = default)
{
try