Skopka.Chat.Client.Http
0.7.0
See the version list below for details.
dotnet add package Skopka.Chat.Client.Http --version 0.7.0
NuGet\Install-Package Skopka.Chat.Client.Http -Version 0.7.0
<PackageReference Include="Skopka.Chat.Client.Http" Version="0.7.0" />
<PackageVersion Include="Skopka.Chat.Client.Http" Version="0.7.0" />
<PackageReference Include="Skopka.Chat.Client.Http" />
paket add Skopka.Chat.Client.Http --version 0.7.0
#r "nuget: Skopka.Chat.Client.Http, 0.7.0"
#:package Skopka.Chat.Client.Http@0.7.0
#addin nuget:?package=Skopka.Chat.Client.Http&version=0.7.0
#tool nuget:?package=Skopka.Chat.Client.Http&version=0.7.0
Skopka.Chat
Skopka.Chat — переиспользуемый транспорт-независимый движок личного чата для .NET 10. Сервер регистрирует публичные данные устройств, хранит и доставляет зашифрованные конверты, но не получает закрытые ключи и не имеет API расшифровки.
Security status: это ограниченный E2EE MVP, а не реализация Signal и не прошедший аудит production-протокол. В v1 нет Double Ratchet, forward secrecy относительно компрометации долгосрочного ключа получателя и post-compromise security. Перед использованием прочитайте threat model, ADR по криптографии и ограничения MVP.
Пакеты
Skopka.Chat.Protocol— идентификаторы, лимиты, публичные контракты и каноническое бинарное представление v1; без ASP.NET Core, EF Core и криптографии клиента.Skopka.Chat.Client— идентичность устройства,IDeviceKeyStore, X25519/HKDF/XChaCha20-Poly1305/Ed25519 через NSec, fingerprints/security codes,IChatTransportи локальная дедупликация.Skopka.Chat.Transport.Http— общие HTTP routes, JSON DTO, protocol mappings, лимиты и строгий source-generatedSystem.Text.Jsonпрофиль; зависит только от Protocol.Skopka.Chat.Client.Http— typedHttpClient,IAccessTokenProvider, HTTPS-by-default, bounded responses и ограниченные retries идемпотентных операций; без ссылки на Server.Skopka.Chat.Server— личные диалоги, жизненный цикл устройств, идемпотентный приём, очередь доставки, acknowledgements и repository-интерфейсы; без ссылки на Client.Skopka.Chat.Server.AspNetCore— необязательные Minimal API endpoints с обязательной авторизацией и строгой привязкой user/device claims к серверным операциям; без выбора формата токена или identity provider.Skopka.Chat.Persistence.PostgreSql— EF Core 10/Npgsql, PostgreSQL migration, ограниченияbytea, внешние ключи, индексы доставки и TTL cleanup.
Версия пакетов 0.7.0 по-прежнему реализует protocol v1; minor-релиз добавляет coverage-guided JSON fuzz harness, regression corpus, проверки реального Kestrel edge и согласованную публикацию семи NuGet-пакетов с symbol packages. Маршруты, DTO и канонический wire format конверта не изменились. Правила совместимости описаны в protocol-compatibility.md.
Документация
- Индекс документации
- Руководство разработчика
- Руководство по выпуску
- Инструкции для coding-агентов
- Threat model и security self-review
Быстрый старт клиента
Реальное приложение должно реализовать IDeviceKeyStore поверх защищённого хранилища платформы. InMemoryDeviceKeyStore предназначен только для тестов и sample.
var keyStore = new MyPlatformDeviceKeyStore();
var identityService = new DeviceIdentityService(keyStore);
var alice = await identityService.CreateAsync(userId, DeviceId.New(), DateTimeOffset.UtcNow);
// PublicDevice получателя приходит из аутентифицированного каталога сервера.
var crypto = new ChatCryptoService(keyStore);
EncryptedEnvelope envelope = await crypto.EncryptTextAsync(
"hello",
conversationId,
MessageId.New(),
alice.DeviceId,
bobPublicDevice,
DateTimeOffset.UtcNow);
await transport.SendAsync(envelope); // transport реализует IChatTransport
Получатель обязан получить текущий PublicDevice отправителя, проверить подпись при расшифровке и сравнить security code по доверенному внешнему каналу при первом контакте или смене ключа:
var receiver = new ChatReceiver(
new ChatCryptoService(keyStore),
new MyTransactionalReceivedMessageStore());
ReceiveResult result = await receiver.ReceiveAsync(delivery.Envelope, senderPublicDevice);
string code = SecurityCodes.Between(myPublicDevice, senderPublicDevice);
HTTP-клиент
Host реализует получение access token и регистрирует отдельный typed client для текущей пары user/device:
builder.Services.AddScoped<IAccessTokenProvider, MyAccessTokenProvider>();
builder.Services.AddSkopkaChatHttpClient(
new Uri("https://chat.example.com/"),
options =>
{
options.AuthenticatedUserId = myPublicDevice.UserId.Value;
options.AuthenticatedDeviceId = myPublicDevice.DeviceId.Value;
});
var api = serviceProvider.GetRequiredService<SkopkaChatHttpClient>();
await api.RegisterDeviceAsync(myPublicDevice);
await api.CreateConversationAsync(peerUserId, conversationId);
PublicDevice peer = await api.GetDeviceAsync(peerDeviceId)
?? throw new InvalidOperationException("Peer device was not found.");
EncryptedEnvelope envelope = await crypto.EncryptTextAsync(
"hello",
conversationId,
MessageId.New(),
myPublicDevice.DeviceId,
peer,
DateTimeOffset.UtcNow);
await api.SendAsync(envelope);
SkopkaChatHttpClient также регистрируется как IChatTransport. Он предназначен для transient/scoped использования, требует HTTPS, отключает redirects в предоставленном DI handler, получает новый токен перед каждой попыткой и не копирует error body или детали JSON parser в исключения. Успешный ответ сначала проходит byte limit, проверку JSON Content-Type, строгий разбор и protocol validation. RequireHttps = false допустим только для доверенного локального TestServer. Подробнее: ADR 0003 и ADR 0006.
Подключение transport-neutral сервера
Ядро не навязывает HTTP, WebSocket или SignalR. Host проверяет пользователя/access token, затем вызывает движок:
var options = new DbContextOptionsBuilder<ChatDbContext>()
.UseNpgsql(connectionString)
.Options;
await using var db = new ChatDbContext(options);
await db.Database.MigrateAsync();
var store = new PostgreSqlChatStore(db);
var engine = new ChatServerEngine(store, store, store);
await engine.RegisterDeviceAsync(publicDevice);
await engine.CreateConversationAsync(aliceUserId, bobUserId, conversationId, now);
await engine.SubmitAsync(encryptedEnvelope, now);
IReadOnlyList<StoredEnvelope> pending = await engine.ReceiveAsync(recipientDeviceId, 50, now);
ChatServerEngine проверяет структуру, лимиты, участников, актуальность key ID, revocation и идемпотентность. Он намеренно не проверяет криптографическую подпись: получатель делает это в Client; транспортный host обязан аутентифицировать право устройства отправлять от своего имени.
Подключение ASP.NET Core API
Необязательный пакет Skopka.Chat.Server.AspNetCore не выпускает и не валидирует токены. Сначала host настраивает доверенный authentication handler, затем регистрирует движок/repositories и transport:
builder.Services
.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", jwt =>
{
jwt.Authority = identityAuthority;
jwt.Audience = chatAudience;
});
builder.Services.AddAuthorization();
builder.Services.AddDbContext<ChatDbContext>(options => options.UseNpgsql(connectionString));
builder.Services.AddScoped<PostgreSqlChatStore>();
builder.Services.AddScoped<IDeviceRepository>(sp => sp.GetRequiredService<PostgreSqlChatStore>());
builder.Services.AddScoped<IConversationRepository>(sp => sp.GetRequiredService<PostgreSqlChatStore>());
builder.Services.AddScoped<IEnvelopeRepository>(sp => sp.GetRequiredService<PostgreSqlChatStore>());
builder.Services.AddScoped<ChatServerEngine>();
builder.Services.AddSkopkaChatAspNetCore(options =>
{
options.UserIdClaimType = ClaimTypes.NameIdentifier;
options.DeviceIdClaimType = "skopka_chat_device_id";
});
app.UseAuthentication();
app.UseAuthorization();
app.MapSkopkaChatApi();
По умолчанию обе claims должны встречаться ровно один раз и содержать GUID в формате D. Регистрация получает user ID из principal, отправка сверяет claim устройства с SenderDeviceId, а polling/acknowledgement вообще не принимают recipient device ID от клиента. Общие DTO находятся в Skopka.Chat.Transport.Http, поэтому Client.Http не ссылается на серверную сборку. AddSkopkaChatAspNetCore применяет строгий профиль к общим ASP.NET Core HttpJsonOptions; host с другими Minimal API должен проверить их совместимость с case-sensitive DTO без неизвестных или дублированных полей. Для cookie-authentication host дополнительно обязан настроить CSRF-защиту; для любой схемы обязательны TLS, rate limits и внешний request-size limit. Полное решение зафиксировано в ADR 0002 и ADR 0006.
Сборка и проверка
dotnet restore
dotnet build --no-restore
dotnet test --solution Skopka.Chat.sln --no-build --no-restore
dotnet pack Skopka.Chat.sln --no-restore
dotnet run --project samples/Skopka.Chat.Sample
NuGet-пакеты создаются в artifacts/packages.
PostgreSQL integration tests в двух проектах запускаются только против явно предоставленной одноразовой базы:
$env:SKOPKA_CHAT_POSTGRES = 'Host=localhost;Database=skopka_chat_tests;Username=postgres;Password=...'
dotnet test --project tests/Skopka.Chat.Persistence.PostgreSql.Tests
dotnet test --project tests/Skopka.Chat.Http.IntegrationTests
Без переменной DB-тесты корректно пропускаются; остальные unit и in-memory integration tests не требуют инфраструктуры. Для release-like проверки установите также $env:SKOPKA_CHAT_POSTGRES_REQUIRED = 'true': тогда отсутствие connection string завершит тест ошибкой.
Workflow .github/workflows/ci.yml поднимает одноразовый PostgreSQL 18 service, последовательно требует hostile-input unit suite и оба DB-проекта, выполняет Release build/test/pack и сохраняет все семь .nupkg как artifact. Используемые GitHub Actions закреплены полными commit SHA; workflow имеет только contents: read.
Каждый CI build также воспроизводит сохранённый fuzz corpus, запускает короткую coverage-guided AFL++/SharpFuzz сессию, проверяет real-Kestrel request limits/cancellation и загружает семь .nupkg вместе с семью .snupkg. Tag v<SemVer> запускает отдельный coordinated release: tag обязан принадлежать main, версия должна совпасть с VersionPrefix, вся версия должна быть свободна на NuGet.org, а после публикации создаётся GitHub Release. Настройка environment и ключа описана в releasing.md.
PostgreSQL delivery остаётся at-least-once: конкурентные poller'ы до acknowledgement могут получить один и тот же конверт. Хранилище держит одну строку на messageId, первый ack атомарно побеждает, а клиент обязан сохранять локальную дедупликацию через транзакционную реализацию IReceivedMessageStore. При одинаковом acceptedAt порядок стабилен по messageId.
Что не входит в v1
UI, production-инфраструктура, интеграция со SkopiClub, группы, вложения, push, backup/recovery ключей и автоматический multi-device fan-out не входят в этот репозиторий. Контракты различают user и device, поэтому один пользователь может иметь несколько устройств, а отправитель создаёт отдельный конверт для каждого устройства-получателя.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Http (>= 10.0.11)
- NSec.Cryptography (>= 26.4.0)
- Skopka.Chat.Client (>= 0.7.0)
- Skopka.Chat.Transport.Http (>= 0.7.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Skopka.Chat.Client.Http:
| Package | Downloads |
|---|---|
|
Skopka.Chat.Client.Browser
Browser-only E2EE cryptography, encrypted IndexedDB identity/history/outbox and same-origin BFF adapters. |
GitHub repositories
This package is not used by any popular GitHub repositories.